From e9736282f7dc6a7b63d4e2e453d26789670a20a0 Mon Sep 17 00:00:00 2001 From: ducloc080202 Date: Sun, 11 Oct 2026 19:59:13 +0700 Subject: [PATCH 1/2] fix(libc): name the throw site in unhandled exception reports --- core/libs/prx/libc/src/exception/Exports.cpp | 2 +- core/libs/prx/libc/src/exception/Runtime.cpp | 8 +- core/libs/tests/WindowsExceptions.cpp | 77 +++++++++++++++++++- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/src/exception/Exports.cpp b/core/libs/prx/libc/src/exception/Exports.cpp index 2bf7ef211..dbc0c7acb 100644 --- a/core/libs/prx/libc/src/exception/Exports.cpp +++ b/core/libs/prx/libc/src/exception/Exports.cpp @@ -52,7 +52,7 @@ void NativeFreeException(void* object) asm("__cxa_free_exception"); void NativeFreeException(void* object) { __cxa_free_exception_nid_postfix(object); } [[noreturn]] void NativeThrow(void* object, std::type_info* type, void (*destructor)(void*)) asm("__cxa_throw"); [[noreturn]] void NativeThrow(void* object, std::type_info* type, void (*destructor)(void*)) { - __cxa_throw_nid_postfix(object, type, destructor); + ThrowFromSite(object, type, destructor, __builtin_return_address(0)); } void* NativeBeginCatch(void* exception) asm("__cxa_begin_catch"); void* NativeBeginCatch(void* exception) { return __cxa_begin_catch_nid_postfix(exception); } diff --git a/core/libs/prx/libc/src/exception/Runtime.cpp b/core/libs/prx/libc/src/exception/Runtime.cpp index b9e26d77f..7b18999b7 100644 --- a/core/libs/prx/libc/src/exception/Runtime.cpp +++ b/core/libs/prx/libc/src/exception/Runtime.cpp @@ -229,7 +229,7 @@ void APS5_VABI __cxa_free_exception_nid_postfix(void* object) { std::free(allocation); } -[[noreturn]] void APS5_VABI __cxa_throw_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)) { +[[noreturn]] static void ThrowFromSite(void* object, std::type_info* type, void (*destructor)(void*), void* throwSite) { using namespace LibcException; auto* header = FromObject(object); header->type = type; @@ -240,11 +240,15 @@ void APS5_VABI __cxa_free_exception_nid_postfix(void* object) { header->unwind.exception_cleanup = Cleanup; ++globals.uncaught; _Unwind_RaiseException_nid_postfix(&header->unwind); - RawLog("[libc] unhandled exception '%s' thrown from %p\n", type ? type->name() : "?", __builtin_return_address(0)); + RawLog("[libc] unhandled exception '%s' thrown from %p\n", type ? type->name() : "?", throwSite); __cxa_begin_catch_nid_postfix(&header->unwind); InvokeTerminate(header->terminate); } +[[noreturn]] void APS5_VABI __cxa_throw_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)) { + ThrowFromSite(object, type, destructor, __builtin_return_address(0)); +} + LibcException::Header* __cxa_init_primary_exception_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)) { using namespace LibcException; auto* header = FromObject(object); diff --git a/core/libs/tests/WindowsExceptions.cpp b/core/libs/tests/WindowsExceptions.cpp index 3b5bc6116..63f8cb1d3 100644 --- a/core/libs/tests/WindowsExceptions.cpp +++ b/core/libs/tests/WindowsExceptions.cpp @@ -1,14 +1,18 @@ #include #include #include +#include #include #include +#include +#include #include #include #include #include #include #include +#include #include "../prx/libc/include/general/VabiMacros.hpp" extern "C" { @@ -55,6 +59,72 @@ static void TestTypeInfoVtables() { extern "C" void NotImplemented_nid_no_patch(const char*); +static void RequireHandle(bool value) { + if (!value) throw std::runtime_error("child process setup failed"); +} + +[[gnu::noinline, noreturn]] static void ThrowUncaughtProbe() { + throw std::runtime_error("unhandled site probe"); +} + +static int RunThrowChild() { + std::fprintf(stderr, "probe %p\n", reinterpret_cast(&ThrowUncaughtProbe)); + std::fflush(stderr); + ThrowUncaughtProbe(); + return 100; +} + +static void testUnhandledThrowSite() { + SECURITY_ATTRIBUTES attributes{}; + attributes.nLength = sizeof(attributes); + attributes.bInheritHandle = TRUE; + HANDLE readPipe = nullptr; + HANDLE writePipe = nullptr; + RequireHandle(CreatePipe(&readPipe, &writePipe, &attributes, 0) != 0); + RequireHandle(SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0) != 0); + std::array image{}; + RequireHandle(GetModuleFileNameW(nullptr, image.data(), static_cast(image.size())) != 0); + const wchar_t* argument = L" throw-uncaught"; + std::array command{}; + const std::size_t imageLength = std::wcslen(image.data()); + const std::size_t argumentLength = std::wcslen(argument); + RequireHandle(imageLength + argumentLength + 1 < command.size()); + std::memcpy(command.data(), image.data(), imageLength * sizeof(wchar_t)); + std::memcpy(command.data() + imageLength, argument, (argumentLength + 1) * sizeof(wchar_t)); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup.hStdOutput = writePipe; + startup.hStdError = writePipe; + PROCESS_INFORMATION child{}; + RequireHandle(CreateProcessW(image.data(), command.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &child) != 0); + CloseHandle(writePipe); + CloseHandle(child.hThread); + std::array output{}; + std::size_t outputLength = 0; + DWORD chunk = 0; + while (outputLength < output.size() && ReadFile(readPipe, output.data() + outputLength, static_cast(output.size() - outputLength), &chunk, nullptr) != 0 && chunk != 0) { + outputLength += chunk; + } + CloseHandle(readPipe); + RequireHandle(WaitForSingleObject(child.hProcess, 20000) == WAIT_OBJECT_0); + DWORD exitCode = 0; + RequireHandle(GetExitCodeProcess(child.hProcess, &exitCode) != 0); + CloseHandle(child.hProcess); + output[outputLength < output.size() ? outputLength : output.size() - 1] = 0; + RequireHandle(exitCode == 0xC0000409); + const char* probeMarker = std::strstr(output.data(), "probe "); + RequireHandle(probeMarker != nullptr); + const auto probe = static_cast(std::strtoull(probeMarker + 6, nullptr, 16)); + RequireHandle(probe != 0); + const char* marker = std::strstr(output.data(), "thrown from "); + RequireHandle(marker != nullptr); + const auto logged = static_cast(std::strtoull(marker + 13, nullptr, 16)); + RequireHandle(logged != 0); + if (logged <= probe || logged - probe >= 0x1000) throw std::runtime_error("unhandled exception named the wrapper instead of the throw site"); +} + static int destroyed; struct Guard { ~Guard() { ++destroyed; } }; static void ThrowNested() { @@ -178,8 +248,13 @@ static void testUnwindThroughRuntimeModules() { } } -int main() { +int main(int argc, char** argv) { + if (argc > 1) { + if (std::strcmp(argv[1], "throw-uncaught") == 0) return RunThrowChild(); + return 100; + } TestTypeInfoVtables(); + testUnhandledThrowSite(); testExceptionPointer(); testFutureException(); testMadeExceptionPointer(); From ae07d4a7a1ceb9c24f70777f09ff2dd2dee2a878 Mon Sep 17 00:00:00 2001 From: ducloc080202 Date: Sun, 11 Oct 2026 20:25:42 +0700 Subject: [PATCH 2/2] fix(libc): quote the executable path in the throw-site test --- core/libs/tests/WindowsExceptions.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/libs/tests/WindowsExceptions.cpp b/core/libs/tests/WindowsExceptions.cpp index 63f8cb1d3..17fefe88b 100644 --- a/core/libs/tests/WindowsExceptions.cpp +++ b/core/libs/tests/WindowsExceptions.cpp @@ -84,13 +84,14 @@ static void testUnhandledThrowSite() { RequireHandle(SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0) != 0); std::array image{}; RequireHandle(GetModuleFileNameW(nullptr, image.data(), static_cast(image.size())) != 0); - const wchar_t* argument = L" throw-uncaught"; + const wchar_t* argument = L"\" throw-uncaught"; std::array command{}; const std::size_t imageLength = std::wcslen(image.data()); const std::size_t argumentLength = std::wcslen(argument); - RequireHandle(imageLength + argumentLength + 1 < command.size()); - std::memcpy(command.data(), image.data(), imageLength * sizeof(wchar_t)); - std::memcpy(command.data() + imageLength, argument, (argumentLength + 1) * sizeof(wchar_t)); + RequireHandle(1 + imageLength + argumentLength + 1 < command.size()); + command[0] = L'"'; + std::memcpy(command.data() + 1, image.data(), imageLength * sizeof(wchar_t)); + std::memcpy(command.data() + 1 + imageLength, argument, (argumentLength + 1) * sizeof(wchar_t)); STARTUPINFOW startup{}; startup.cb = sizeof(startup); startup.dwFlags = STARTF_USESTDHANDLES;