Merge pull request #4693 from EagleBearerYS/fix/libc-windows-undeliverable-exception

fix(libc): report host C++ exceptions that Windows cannot deliver
This commit is contained in:
Dean authored and GitHub committed 2026-10-14 18:04:38 +13:00
commit 871f4ce8a0
3 files changed
+86

No files matched your search

+8
View File
@@ -240,6 +240,14 @@ if(BUILD_TESTING)
set_tests_properties(windows_write_watch PROPERTIES TIMEOUT 20)
endif()
if(WIN32)
add_test_executable(undeliverable_host_exception_tests tests/UndeliverableHostException.cpp)
add_dependencies(undeliverable_host_exception_tests libc)
add_test(NAME undeliverable_host_exception COMMAND undeliverable_host_exception_tests $<TARGET_FILE:libc>)
set_tests_properties(undeliverable_host_exception PROPERTIES
PASS_REGULAR_EXPRESSION "C\\+\\+ exception St9bad_alloc raised on thread [0-9]+ cannot be delivered: 0x[0-9a-f]+ undeliverable_host_exception_tests\\.exe\\+0x[0-9a-f]+ has no unwind data")
endif()
add_test_executable(guest_offline_imports_tests tests/GuestOfflineImports.cpp)
target_include_directories(guest_offline_imports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libc)
@@ -3,6 +3,7 @@
#include <tlhelp32.h>
#include "prx/libc/include/CallTrace.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <csignal>
@@ -226,6 +227,50 @@ void TraceSse4a(std::uint64_t rip, const sse4a::Instruction& instruction, const
Report("[sse4a] #%llu %s %s (length %u, index %u, %u bytes) at %s on thread %lu\n", count, mnemonic, operands, field.length, field.index, static_cast<unsigned>(instruction.size), line, GetCurrentThreadId());
}
constexpr DWORD GccExceptionMask = 0x20ffffffu;
constexpr DWORD GccException = 0x20474343u;
constexpr std::uint64_t GccExceptionClass = 0x474e5543432b2b00ull;
constexpr std::uint64_t CxaExceptionTypeOffset = 80;
std::uint64_t FrameWithoutUnwindData(const CONTEXT& start) {
const HMODULE runtime[] = {GetModuleHandleA("KERNELBASE.dll"), GetModuleHandleA("libgcc_s_seh-1.dll"), GetModuleHandleA("libstdc++-6.dll")};
CONTEXT context = start;
for (int depth = 0; depth < 64 && context.Rip != 0; ++depth) {
HMODULE module = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCSTR>(context.Rip), &module);
DWORD64 imageBase = 0;
const auto entry = RtlLookupFunctionEntry(context.Rip, &imageBase, nullptr);
if (module == nullptr || std::find(std::begin(runtime), std::end(runtime), module) == std::end(runtime)) return entry == nullptr ? context.Rip : 0;
if (entry == nullptr) return 0;
PVOID handlerData = nullptr;
DWORD64 establisher = 0;
RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, context.Rip, entry, &context, &handlerData, &establisher, nullptr);
}
return 0;
}
bool ReadWord(std::uint64_t address, std::uint64_t* value) {
return ReadCode(address, reinterpret_cast<std::uint8_t*>(value), sizeof(*value)) == sizeof(*value);
}
void ReportUndeliverableException(const EXCEPTION_POINTERS* info, std::uint64_t frame) {
const auto* record = info->ExceptionRecord;
char type[256] = "of unknown type";
const auto header = record->NumberParameters >= 1 ? static_cast<std::uint64_t>(record->ExceptionInformation[0]) : 0;
std::uint64_t exceptionClass = 0;
std::uint64_t typeInfo = 0;
std::uint64_t namePointer = 0;
if (ReadWord(header, &exceptionClass) && exceptionClass == GccExceptionClass && ReadWord(header - CxaExceptionTypeOffset, &typeInfo) && ReadWord(typeInfo + 8, &namePointer)) {
std::uint8_t name[sizeof(type)];
const auto copied = ReadCode(namePointer, name, sizeof(name));
if (const auto* end = static_cast<const std::uint8_t*>(std::memchr(name, 0, copied))) std::memcpy(type, name, static_cast<std::size_t>(end - name) + 1);
}
char line[MAX_PATH + 64];
DescribeAddress(frame, line, sizeof(line));
Report("\nFATAL: C++ exception %s raised on thread %lu cannot be delivered: %s has no unwind data\n", type, GetCurrentThreadId(), line);
ReportStackCandidates(info->ContextRecord->Rsp, 32, 0x4000);
}
bool HandleSse4a(EXCEPTION_POINTERS* info) {
if (!g_sse4aEmulation) return false;
const auto* record = info->ExceptionRecord;
@@ -250,6 +295,10 @@ LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) {
if (HandleWatch(info)) return EXCEPTION_CONTINUE_EXECUTION;
if (HandleSse4a(info)) return EXCEPTION_CONTINUE_EXECUTION;
const auto* record = info->ExceptionRecord;
if ((record->ExceptionCode & GccExceptionMask) == GccException) {
if (const auto frame = FrameWithoutUnwindData(*info->ContextRecord)) ReportUndeliverableException(info, frame);
return EXCEPTION_CONTINUE_SEARCH;
}
if (!IsFatal(record->ExceptionCode)) return EXCEPTION_CONTINUE_SEARCH;
if (reported.exchange(true)) {
// Another thread is already reporting; let it finish before this fault ends the process.
@@ -0,0 +1,29 @@
#include <cstddef>
#include <cstdio>
#include <new>
#include <windows.h>
namespace {
[[gnu::noinline]] void* AllocateWithoutUnwindData(std::size_t size) {
void* const block = ::operator new(size);
std::printf("allocated %p\n", block);
return block;
}
}
int main(int argc, char** argv) {
if (argc != 2) {
std::fprintf(stderr, "usage: %s <libc.prx>\n", argv[0]);
return 2;
}
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
if (LoadLibraryExA(argv[1], nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) == nullptr) {
std::fprintf(stderr, "%s: LoadLibraryEx failed with error %lu\n", argv[1], GetLastError());
return 1;
}
AllocateWithoutUnwindData(std::size_t{1} << 46);
std::fputs("operator new returned instead of throwing\n", stderr);
return 1;
}