Don't set up a signal alt stack in the memory fault handler

sigaltstack() is per thread. InstallExceptionHandler() runs in CPU_Init()
on the loading thread, which exits after boot, while the faults come from
the CPU thread and UninstallExceptionHandler() runs on the emu thread. So
our stack was never used, and the uninstall "restored" the loading
thread's previous alt stack onto the emu thread. On Android that's the
one bionic gives each thread and frees when the thread exits, so every
later fault on the emu thread ran its handler on freed memory. A Play
crash report shows scudo finding a corrupted chunk header when freeing our
stack in UninstallExceptionHandler().

The alt stack came with the Dolphin code, which needs it to handle its own
stack overflows. Ours only handles guest memory faults, so let the handler
run on the faulting thread's stack, or the alt stack that thread already
has. This also leaves a libretro frontend's alt stack alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-25 19:37:23 +01:00
1 parent 42f9af1744
commit daf46b1dbc
1 file changed
+4 -45
+4 -45
View File
@@ -24,7 +24,6 @@
#endif
static BadAccessHandler g_badAccessHandler;
static void *altStack = nullptr;
void SetupCRT(bool suppressDialogs) {
#if defined(_MSC_VER)
@@ -190,8 +189,6 @@ void UninstallExceptionHandler() {
static struct sigaction old_sa_segv;
static struct sigaction old_sa_bus;
static stack_t old_signal_stack{};
static bool old_signal_stack_valid = false;
// Hand the signal on to whatever was installed before us. Returning from a fault handler just
// re-runs the faulting instruction, so for anything we can't deal with this is the only exit
@@ -265,33 +262,11 @@ void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTra
}
g_badAccessHandler = badAccessHandler;
#if PPSSPP_PLATFORM(TVOS)
// tvOS has no sigaltstack, so the handler runs on the faulting thread's stack.
INFO_LOG(Log::System, "Installed exception handler (no alt stack).");
#else
size_t altStackSize = SIGSTKSZ;
// No sigaltstack of our own: it's per thread, and this runs on the loading thread, while the faults
// come from the CPU thread. The handler runs on the faulting thread's stack, or on its alternate
// stack if it has one (bionic gives every thread one).
INFO_LOG(Log::System, "Installed exception handler");
// Add some extra room.
altStackSize += 65536;
INFO_LOG(Log::System, "Installed exception handler. stack size: %d", (int)altStackSize);
stack_t signal_stack{};
if (sigaltstack(nullptr, &old_signal_stack) == 0) {
old_signal_stack_valid = true;
}
altStack = malloc(altStackSize);
#ifdef __FreeBSD__
signal_stack.ss_sp = (char*)altStack;
#else
signal_stack.ss_sp = altStack;
#endif
signal_stack.ss_size = altStackSize;
signal_stack.ss_flags = 0;
if (sigaltstack(&signal_stack, nullptr)) {
_assert_msg_(false, "sigaltstack failed");
}
#endif
struct sigaction sa{};
sa.sa_handler = nullptr;
sa.sa_sigaction = &sigsegv_handler;
@@ -307,22 +282,6 @@ void UninstallExceptionHandler() {
if (!g_badAccessHandler) {
return;
}
#if !PPSSPP_PLATFORM(TVOS)
if (old_signal_stack_valid) {
// Darwin fails with ENOMEM on a size below MINSIGSTKSZ, even with SS_DISABLE.
if ((old_signal_stack.ss_flags & SS_DISABLE) && old_signal_stack.ss_size < MINSIGSTKSZ) {
old_signal_stack.ss_size = MINSIGSTKSZ;
}
if (0 != sigaltstack(&old_signal_stack, nullptr)) {
ERROR_LOG(Log::System, "Could not restore previous signal altstack");
}
old_signal_stack_valid = false;
}
#endif
if (altStack) {
free(altStack);
altStack = nullptr;
}
sigaction(SIGSEGV, &old_sa_segv, nullptr);
#ifdef __APPLE__
sigaction(SIGBUS, &old_sa_bus, nullptr);