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 <pid> 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.
This commit is contained in:
jpolo1224
2026-08-08 20:12:08 -04:00
parent 9d85567743
commit 7daa89e0e7
+41
View File
@@ -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<u8, 128 * 1024> 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)