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.
This commit is contained in:
Hussein Kadhim 2026-10-13 18:26:04 +03:00 committed by MarcZX100
parent 0214069e33
commit 37ea3b651a
3 changed files with 44 additions and 7 deletions

View File

@ -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:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_system_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemService.cpp)

View File

@ -33,6 +33,7 @@ std::stop_source shutdownSource;
std::atomic<bool> exitRequested{false};
std::atomic<bool> exitStarted{false};
using GuestExitCallback = void (APS5_VABI*)();
thread_local bool runningExit = false;
void APS5_VABI RunGuestExitCallback(void* callback) {
reinterpret_cast<GuestExitCallback>(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);
}

View File

@ -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<char*>(nullptr));
execl(self.c_str(), self.c_str(), mode, static_cast<char*>(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<char>(file), std::istreambuf_iterator<char>());
}
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();
}