From 37ea3b651a249487790e1e231c9ce80fa5e7d568 Mon Sep 17 00:00:00 2001 From: Hussein Kadhim Date: Tue, 13 Oct 2026 18:26:04 +0300 Subject: [PATCH] fix(libc): finish the exit handlers when one of them calls exit A guest handler that called exit threw out of the libc exit path because the exiting thread waited on itself. Like FreeBSD, the nested call now runs the handlers that are still registered and ends the process with the status it passed. --- .../prx/libSceSystemService/CMakeLists.txt | 5 ++- core/libs/prx/libc/src/Process.cpp | 9 ++++- core/libs/tests/GuestSystemExit.cpp | 37 +++++++++++++++++-- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 9b90ef588..099f5a190 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -13,14 +13,17 @@ if(BUILD_TESTING) add_test(NAME guest_immediate_exit COMMAND guest_system_exit_tests --immediate) add_test(NAME guest_return_from_main COMMAND guest_system_exit_tests --return-from-main) add_test(NAME guest_system_relaunch COMMAND guest_system_exit_tests --relaunch-driver) + add_test(NAME guest_nested_exit COMMAND guest_system_exit_tests --nested-exit-driver) set_tests_properties(guest_system_exit PROPERTIES PASS_REGULAR_EXPRESSION "Guest shutdown and atexit completed") set_tests_properties(guest_quick_exit PROPERTIES PASS_REGULAR_EXPRESSION "Guest quick_exit handlers completed") set_tests_properties(guest_system_relaunch PROPERTIES PASS_REGULAR_EXPRESSION "Guest relaunch completed" TIMEOUT 30) + set_tests_properties(guest_nested_exit PROPERTIES + PASS_REGULAR_EXPRESSION "Guest nested exit completed" TIMEOUT 30) if(WIN32) - set_tests_properties(guest_system_exit guest_system_exit_parameters guest_immediate_exit guest_return_from_main guest_quick_exit guest_system_relaunch PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_system_exit guest_system_exit_parameters guest_immediate_exit guest_return_from_main guest_quick_exit guest_system_relaunch guest_nested_exit PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_system_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemService.cpp) diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index fd810843e..76a702a2a 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -33,6 +33,7 @@ std::stop_source shutdownSource; std::atomic exitRequested{false}; std::atomic exitStarted{false}; using GuestExitCallback = void (APS5_VABI*)(); +thread_local bool runningExit = false; void APS5_VABI RunGuestExitCallback(void* callback) { reinterpret_cast(callback)(); @@ -115,8 +116,12 @@ extern "C" { [[noreturn]] void LibcExit_nid_no_patch(int code) { exitRequested.store(true); - if (exitStarted.exchange(true)) LibcAwaitExit_nid_postfix(); - LibcRunShutdown_nid_postfix(); + if (exitStarted.exchange(true)) { + if (!runningExit) LibcAwaitExit_nid_postfix(); + } else { + runningExit = true; + LibcRunShutdown_nid_postfix(); + } CxaFinalize_nid_no_patch(nullptr); std::exit(code); } diff --git a/core/libs/tests/GuestSystemExit.cpp b/core/libs/tests/GuestSystemExit.cpp index 56c414635..2f712816f 100644 --- a/core/libs/tests/GuestSystemExit.cpp +++ b/core/libs/tests/GuestSystemExit.cpp @@ -20,6 +20,7 @@ extern "C" int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle); extern "C" void APS5_VABI _Exit_nid_postfix(int); +extern "C" void APS5_VABI exit_nid_postfix(int); extern "C" void APS5_VABI catchReturnFromMain_nid_postfix(int); using GuestQuickExitCallback = void (APS5_VABI*)(); extern "C" int APS5_VABI at_quick_exit_nid_postfix(GuestQuickExitCallback); @@ -71,9 +72,11 @@ void AppendMarker(const std::string& line) { } void MainShutdown() { AppendMarker("shutdown"); } void SecondaryShutdown() { AppendMarker("secondary shutdown"); } -int RunRelaunchingChild(const std::filesystem::path& self) { +void APS5_VABI RemainingHandler(void*) { AppendMarker("remaining handler"); } +void APS5_VABI ExitingHandler(void*) { exit_nid_postfix(7); } +int RunChild(const std::filesystem::path& self, const char* mode) { #ifdef _WIN32 - std::wstring commandLine = L"\"" + self.wstring() + L"\" --relaunch"; + std::wstring commandLine = L"\"" + self.wstring() + L"\" " + std::wstring(mode, mode + std::strlen(mode)); STARTUPINFOW startup{}; startup.cb = sizeof(startup); PROCESS_INFORMATION process{}; @@ -88,7 +91,7 @@ int RunRelaunchingChild(const std::filesystem::path& self) { const pid_t child = fork(); Require(child >= 0); if (child == 0) { - execl(self.c_str(), self.c_str(), "--relaunch", static_cast(nullptr)); + execl(self.c_str(), self.c_str(), mode, static_cast(nullptr)); std::_Exit(127); } int status = 0; @@ -96,6 +99,23 @@ int RunRelaunchingChild(const std::filesystem::path& self) { return WIFEXITED(status) ? WEXITSTATUS(status) : 1; #endif } +int DriveNestedExit() { + const auto self = OwnExecutable(); + const auto marker = std::filesystem::temp_directory_path() / ("anyps5-nested-exit-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"); + SetVariable(RelaunchMarker, marker.string().c_str()); + const int code = RunChild(self, "--nested-exit"); + std::string text; + { + std::ifstream file(marker); + text.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); + } + std::error_code ignored; + std::filesystem::remove(marker, ignored); + Require(code == 7); + Require(text == "remaining handler\n"); + std::puts("Guest nested exit completed"); + return 0; +} int DriveRelaunch() { const auto self = OwnExecutable(); const auto secondary = self.parent_path() / ("picker.picker.bin" + self.extension().string()); @@ -103,7 +123,7 @@ int DriveRelaunch() { const auto marker = std::filesystem::temp_directory_path() / ("anyps5-relaunch-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"); SetVariable(RelaunchMarker, marker.string().c_str()); SetVariable(MainExecutable, self.string().c_str()); - Require(RunRelaunchingChild(self) == 0); + Require(RunChild(self, "--relaunch") == 0); std::string text; for (int attempt = 0; attempt < 200 && text.find("relaunched") == std::string::npos; ++attempt) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); @@ -140,6 +160,15 @@ int main(int argc, char** argv) { quick_exit_nid_postfix(0); return 2; } + if (argc > 1 && std::strcmp(argv[1], "--nested-exit-driver") == 0) { + return DriveNestedExit(); + } + if (argc > 1 && std::strcmp(argv[1], "--nested-exit") == 0) { + Require(__cxa_atexit_nid_postfix(RemainingHandler, nullptr, nullptr) == 0); + Require(__cxa_atexit_nid_postfix(ExitingHandler, nullptr, nullptr) == 0); + exit_nid_postfix(0); + return 2; + } if (argc > 1 && std::strcmp(argv[1], "--relaunch-driver") == 0) { return DriveRelaunch(); }