From 20e8bb09ba038268cc28624f9b76abecbfae4d19 Mon Sep 17 00:00:00 2001 From: Morten Holm Date: Thu, 8 Oct 2026 17:53:56 +0200 Subject: [PATCH] fix(libc): run guest exit handlers before the host exit handlers LibcExit called std::exit, which ran the static destructors of the libraries before the guest exit handlers. A guest handler that freed memory then locked a destroyed std::mutex, which throws, and a noexcept guest destructor ended in std::terminate. The guest handlers now run first, while the library objects are alive. Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libc/CMakeLists.txt | 4 +++ core/libs/prx/libc/src/Process.cpp | 1 + core/libs/tests/GuestExitOrder.cpp | 53 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 core/libs/tests/GuestExitOrder.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 30aaf71ba..4f3680eb2 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -296,6 +296,10 @@ if(BUILD_TESTING) target_include_directories(exception_personality_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(exception_personality_tests PRIVATE libc) add_test(NAME exception_personality COMMAND exception_personality_tests) + add_test_executable(guest_exit_order_tests ${LIBS_INCLUDE_DIR}/tests/GuestExitOrder.cpp) + target_include_directories(guest_exit_order_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_exit_order_tests PRIVATE libc) + add_test(NAME guest_exit_order COMMAND guest_exit_order_tests) if(NOT APPLE) enable_language(C) add_test_executable(exception_c_cleanup_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionCCleanup.cpp ${LIBS_INCLUDE_DIR}/tests/ExceptionCCleanup.c) diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index d8ce47284..47140b365 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -113,6 +113,7 @@ extern "C" { exitRequested.store(true); if (exitStarted.exchange(true)) LibcAwaitExit_nid_postfix(); LibcRunShutdown_nid_postfix(); + CxaFinalize_nid_no_patch(nullptr); std::exit(code); } diff --git a/core/libs/tests/GuestExitOrder.cpp b/core/libs/tests/GuestExitOrder.cpp new file mode 100644 index 000000000..067bea90f --- /dev/null +++ b/core/libs/tests/GuestExitOrder.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); + +namespace { + +int events[2]; + +void Record(char value) { + static_cast(!write(events[1], &value, 1)); +} + +void APS5_VABI GuestHandler(void*) { Record('G'); } + +void HostHandler() { Record('H'); } + +bool GuestHandlersRunFirst() { + if (pipe(events) != 0) return false; + const pid_t child = fork(); + if (child < 0) return false; + if (child == 0) { + close(events[0]); + __cxa_atexit_nid_postfix(GuestHandler, nullptr, nullptr); + std::atexit(HostHandler); + LibcExit_nid_no_patch(0); + } + close(events[1]); + char order[3] = {}; + ssize_t length = 0; + while (length < 2) { + const ssize_t count = read(events[0], order + length, 2 - length); + if (count <= 0) break; + length += count; + } + close(events[0]); + int status = 0; + return waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0 && length == 2 && order[0] == 'G' && order[1] == 'H'; +} + +} + +int main() { + if (!GuestHandlersRunFirst()) { + std::fputs("guest exit handlers must run before the host exit handlers\n", stderr); + return 1; + } + return 0; +}