From 7daa89e0e7a8a9f672ee74a9199d8c97308c7f9e Mon Sep 17 00:00:00 2001 From: jpolo1224 Date: Sat, 8 Aug 2026 20:12:08 -0400 Subject: [PATCH] Give the SIGSEGV handler a stack to report from Arkham City dies of SIGSEGV about 140ms after the game writes PS3Progress_Frame_1, on both the Qualcomm driver and Turnip, with the database on or off, Multithreaded RSX on or off, and the RSX profiler on or off. A table of ten runs showed no setting correlates with it. The reason it took so long to even establish that it WAS a segfault: nothing records it. No tombstone (/data/tombstones is root-only, so its emptiness proves nothing), no logcat crash-buffer entry, and no line from our own handler. The single witness anywhere is Zygote: I Zygote : Process exited due to signal 11 (Segmentation fault) That combination is what a stack overflow looks like here. The handler is installed with SA_SIGINFO alone, so it runs on the faulting thread's own stack; if that stack is what overflowed there is nowhere to run, it faults again immediately, and the kernel applies the default action having written nothing. So: an alternate signal stack per thread in thread_base::initialize, and SA_ONSTACK on the handler. Thread-local rather than shared, because two threads can fault at once and a shared stack would corrupt whichever report lost the race. This does not fix the fault. It makes the fault reportable, which is the thing that has been missing all along: the next occurrence should log where it came from instead of vanishing. Android only. --- Utilities/Thread.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 1e045c456..86705ef7b 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -2690,7 +2690,21 @@ void sigpipe_signaling_handler(int) const bool s_exception_handler_set = []() -> bool { struct ::sigaction sa; +#ifdef __ANDROID__ + // Run the handler on the alternate stack installed per thread in + // thread_base::initialize. Without this the handler runs on the faulting thread's own + // stack, so a stack overflow has nowhere to report itself from: the handler faults + // again immediately and the kernel applies the default action, killing the process + // having written nothing. + // + // Arkham City does exactly that. The only record anywhere of the crash was a single + // Zygote line, "exited due to signal 11 (Segmentation fault)", with no tombstone, no + // crash-buffer entry and no line from this handler, which cost hours of diagnosing it + // as an external kill. + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; +#else sa.sa_flags = SA_SIGINFO; +#endif sigemptyset(&sa.sa_mask); sa.sa_sigaction = signal_handler; @@ -2794,6 +2808,33 @@ void thread_base::start() void thread_base::initialize(void (*error_cb)()) { +#ifdef __ANDROID__ + // Somewhere for the SIGSEGV handler to run, per thread. See SA_ONSTACK above. + // + // Deliberately a thread_local rather than a shared buffer: the handler can fire on any + // thread, two threads can fault at once, and a shared stack would corrupt whichever + // report lost the race. It is released with the thread, after which no handler can run + // on it. + // + // 128KB because the handler formats and logs rather than just setting a flag. That is + // real memory across the emulator's thread count, and it buys turning a silent death + // into a reported one. + static thread_local std::array s_signal_stack; + + stack_t alt{}; + alt.ss_sp = s_signal_stack.data(); + alt.ss_size = s_signal_stack.size(); + alt.ss_flags = 0; + + if (::sigaltstack(&alt, nullptr) == -1) + { + // Not fatal: the handler simply falls back to the faulting stack, which is the + // behaviour everywhere else. Worth knowing about, because it means a stack + // overflow will go unreported again. + sig_log.error("sigaltstack failed (%d); stack overflows will not be reported", errno); + } +#endif + #ifndef _WIN32 #ifdef __APPLE__ while (!m_thread)