diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index a983b0ad7d..45d6c732b9 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -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 $) + 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 1c69a3d3a6..1081c6817e 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -3,6 +3,7 @@ #include #include "prx/libc/include/CallTrace.hpp" #include "prx/libc/include/GuestArena.hpp" +#include #include #include #include @@ -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(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; +} + +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"; + 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)); + 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. 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; +}