Skip to content

libretro: report where the core was executing when it dies - #158

Open
WizzardSK wants to merge 1 commit into
libretro:libretroizationfrom
WizzardSK:crash-handler-upstream
Open

libretro: report where the core was executing when it dies#158
WizzardSK wants to merge 1 commit into
libretro:libretroizationfrom
WizzardSK:crash-handler-upstream

Conversation

@WizzardSK

@WizzardSK WizzardSK commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

A crash in the Android core currently leaves nothing behind. The report in #159 is a RetroArch log that stops mid-sentence in the middle of microVU1 compiling VU1 programs — RetroArch cannot log a signal that kills the process, and the core installs no reporter of its own. The only sigaction in the tree is the fastmem page fault filter.

So the question a crash in a recompiled core turns on goes unanswered: was the program counter in generated code, or in the core’s own text? A backtrace cannot say — JIT pages carry no unwind information, so the unwinder stops at the signal frame. /proc/self/maps can, because generated code lives in an anonymous mapping and compiled code lives in the .so.

This installs a handler for SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGABRT and SIGTRAP that prints the signal and its code, the faulting thread’s name, pc/lr/sp/fp and the fault address, and the /proc/self/maps line each of those falls in. Output goes to logcat as well as stderr, since logcat is where an Android crash is actually readable.

Ordering

It installs from retro_init(), before vtlb reaches HostSys::InstallPageFaultHandler(). That is the whole trick: fastmem takes SIGSEGV (and SIGBUS on aarch64) and chains to whatever it displaced when a fault is not one of its own, so going in first puts this on the end of that chain instead of out of it. Fastmem’s own handling is untouched and this only ever sees faults fastmem has already declined. Having reported, it chains onward to whatever was there before it, so the process still dies the way it would have.

Testing

Faulting on purpose, both ways round:

  • a store to 0x1234 reports SIGSEGV, with pc and lr resolved to the executable and the fault address unmapped;
  • jumping into an anonymous PROT_EXEC page holding an undefined encoding reports SIGILL, with pc in ... rwxp 00000000 00:00 0 and lr pointing back at the caller — the shape a codegen bug would take.

Both then die with the default action, 139 and 132. Compiles clean with -Wall on this base; non-POSIX targets get an empty CrashHandler_Install().

A crash in the Android core currently leaves nothing behind. The report in
pcee2-libretro#30 is a RetroArch log that simply stops mid-sentence, in the
middle of microVU1 compiling VU1 programs, because RetroArch's log cannot
capture a native crash and the core installs no reporter of its own - the
only sigaction in the tree is the fastmem page fault filter.

So the one question a crash in a recompiled core turns on goes unanswered:
was the program counter in generated code, or in the core's own text? A
backtrace cannot say, since JIT pages carry no unwind information and the
unwinder stops at the signal frame. /proc/self/maps can, because generated
code lives in an anonymous mapping and compiled code lives in the .so.

This installs a handler for SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGABRT and
SIGTRAP that prints the signal and its code, the faulting thread's name,
pc/lr/sp/fp and the fault address, and the /proc/self/maps line each of
those falls in. It goes to logcat as well as stderr, since logcat is where
an Android crash is actually readable.

It installs from retro_init(), before vtlb reaches
HostSys::InstallPageFaultHandler(). That ordering is the whole trick:
fastmem takes SIGSEGV (and SIGBUS on aarch64) and chains to whatever it
displaced when a fault is not one of its own, so going in first puts this on
the end of that chain instead of out of it. Fastmem's own handling is
untouched, and this reports only faults fastmem has already declined. Having
reported, it chains onward to whatever was there before it, so the process
still dies the way it would have.

Tested by faulting on purpose, both ways round. A store to 0x1234 reports
SIGSEGV with pc and lr resolved to the executable and the fault address
unmapped; jumping into an anonymous PROT_EXEC page holding an undefined
encoding reports SIGILL with "pc in ... rwxp 00000000 00:00 0" and lr
pointing back at the caller - which is exactly the shape a codegen bug would
take. Both then die with the default action, 139 and 132.
LibretroAdmin pushed a commit that referenced this pull request Aug 28, 2026
#159 reports God of War crashing shortly after boot on Android.
The reporter ran the triage that would have implicated the recompilers -
MTVU off, VU1 softfloat on, EE interpreter - and it still crashed, which is
what pointed away from CPU emulation entirely. Their log ends immediately
after this:

  SET_SYSTEM_AV_INFO: 640x448, Aspect: 1.3333, FPS: 59.9401, Sample rate: 48000
  ... video and audio drivers reinitialised ...
  SET_SYSTEM_AV_INFO: 640x448, Aspect: 1.3333, FPS: 59.9401, Sample rate: 48000
  ... video and audio drivers reinitialised again ...

Two announcements carrying identical values, each making the frontend tear
down and rebuild its whole video driver, which on the HW-render path means
context_destroy and a fresh negotiation.

update_av_info() sent the announcement unconditionally whenever
pending_update_av_info was set, without comparing against what had last gone
out, and without regard for what the GS thread was doing - so the rebuild
could land on top of a thread still submitting to the shared Vulkan queue.

Both guards are the ones pcee2 already carries for the same defect
(adf456e28d):

  - Announce only on a real timing change. The fps compare needs a
    tolerance, since NTSC reports 59.94005994 Hz against a 59.94 default and
    that 0.00006 Hz difference must not rebuild anything. Geometry needs no
    announcement on the HW-render path: SET_GEOMETRY carries it without a
    reinit, which is already how the widescreen hint does it, so it is
    forwarded that way instead.
  - Drain the GS thread before an announcement that does go out. The CPU
    thread is already parked at this point - retro_run has not resumed it.

Software rendering keeps the old behaviour for a geometry change, matching
pcee2 rather than widening the change.

This is a hypothesis the log supports rather than a confirmed fix: nothing
in that log says what the crash was, because the core has no crash reporter
yet (#158), and the reporter cannot get logcat without root. What can be
said is that the frontend was rebuilding its video driver for no reason at
the moment everything stopped, and that this is a defect either way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant