From da6948ea182ecbfa8b05c56f88b39f1b3f44773b Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+yunusshaikh7@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:41:31 -0700 Subject: [PATCH 1/2] fix(libc): report host C++ exceptions that Windows cannot deliver libstdc++ and libgcc raise C++ exceptions as SEH exceptions (STATUS_GCC_THROW, 0x20474343), which the system dispatcher delivers by unwinding frame by frame with each function's unwind data. The libraries are built with -fno-asynchronous-unwind-tables and unwind with DWARF, so their functions have no SEH unwind data. When a host runtime call such as operator new throws straight into one of them, the dispatcher cannot unwind it: no handler, the unhandled exception filter or std::terminate runs, and the process ends without output and with exit code 0x20474343. The vectored crash handler now follows a GCC exception through the runtime frames that raise it (KERNELBASE, libgcc_s_seh-1, libstdc++-6). When the first frame outside them has no unwind data, it reports the exception's type, the thread and that frame, and lists the stack's return address candidates, before the dispatcher continues. The test loads libc.prx, then lets operator new throw std::bad_alloc into a function built with the libraries' flags. Without the handler change it exits silently with 0x20474343. --- core/libs/CMakeLists.txt | 8 ++++ .../src/specifics/windows/CrashReport.cpp | 47 +++++++++++++++++++ .../libs/tests/UndeliverableHostException.cpp | 29 ++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 core/libs/tests/UndeliverableHostException.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index d094f39cfc..d6568cebd1 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -186,6 +186,14 @@ if(BUILD_TESTING) add_subdirectory(tests/write_watch) 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 $) + 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) diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 53d651430f..104e208d2c 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -2,6 +2,7 @@ #include #include #include "prx/libc/include/GuestArena.hpp" +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include #include "Sse4aEmulation.hpp" @@ -223,6 +225,47 @@ 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(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(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; +} + +void ReportUndeliverableException(const EXCEPTION_POINTERS* info, std::uint64_t frame) { + const auto* record = info->ExceptionRecord; + char type[256] = "of unknown type"; + if (record->NumberParameters >= 1) { + const auto header = static_cast(record->ExceptionInformation[0]); + if (IsReadable(header) && IsReadable(header - CxaExceptionTypeOffset) && *reinterpret_cast(header) == GccExceptionClass) { + const auto typeInfo = *reinterpret_cast(header - CxaExceptionTypeOffset); + if (IsReadable(typeInfo) && IsReadable(typeInfo + 8)) { + const auto name = *reinterpret_cast(typeInfo + 8); + if (IsReadable(name)) std::snprintf(type, sizeof(type), "%s", reinterpret_cast(name)); + } + } + } + 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; @@ -246,6 +289,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. diff --git a/core/libs/tests/UndeliverableHostException.cpp b/core/libs/tests/UndeliverableHostException.cpp new file mode 100644 index 0000000000..6356af4e7b --- /dev/null +++ b/core/libs/tests/UndeliverableHostException.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +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 \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; +} From 6a9781cdc6c41731c0db36478d5f0cebab80f748 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+yunusshaikh7@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:29:46 -0700 Subject: [PATCH 2/2] fix(libc): read the C++ exception metadata with bounded reads The undeliverable-exception report checked only that the first byte of each value it read was in readable memory: the exception class, the type_info pointer, the name pointer and the RTTI name, which it then printed with %s, scanning to the terminator without a bound. A name that runs into an unreadable page, or damaged exception metadata, could fault inside the crash handler. Every read now goes through ReadCode, which stops at the first unreadable page and at the requested size. The name is copied into a 256-byte buffer and used only if the copy holds its terminator; otherwise the report keeps "of unknown type". --- .../src/specifics/windows/CrashReport.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 104e208d2c..80c742647c 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -247,18 +247,21 @@ std::uint64_t FrameWithoutUnwindData(const CONTEXT& start) { return 0; } +bool ReadWord(std::uint64_t address, std::uint64_t* value) { + return ReadCode(address, reinterpret_cast(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"; - if (record->NumberParameters >= 1) { - const auto header = static_cast(record->ExceptionInformation[0]); - if (IsReadable(header) && IsReadable(header - CxaExceptionTypeOffset) && *reinterpret_cast(header) == GccExceptionClass) { - const auto typeInfo = *reinterpret_cast(header - CxaExceptionTypeOffset); - if (IsReadable(typeInfo) && IsReadable(typeInfo + 8)) { - const auto name = *reinterpret_cast(typeInfo + 8); - if (IsReadable(name)) std::snprintf(type, sizeof(type), "%s", reinterpret_cast(name)); - } - } + const auto header = record->NumberParameters >= 1 ? static_cast(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(std::memchr(name, 0, copied))) std::memcpy(type, name, static_cast(end - name) + 1); } char line[MAX_PATH + 64]; DescribeAddress(frame, line, sizeof(line));