Files
psxrecomp/tools/_parse_crash.py
T
Matthew Stanley cf87fd8fc6 crash_trace: structured post-mortem dump (mirrors SMW recomp pattern)
New runtime/src/crash_trace.c writes psx_last_run_report.json with:
  reason / timestamp / frame / last_func_addr / last_store_pc
  full CPU register state (all 32 GPRs + hi/lo/sr/cause/epc)
  dispatch_ring tail (last 256 dispatched func entries)
  unknown_dispatch tail (last 50 entries with addr/phys/ra/a0/a1)
  dirty_block_log tail (last 100 entries)
  Windows SEH info (when triggered from SEH filter)

Triggered from:
  - SIGSEGV / SIGABRT signal handlers
  - Windows SetUnhandledExceptionFilter (catches access violations)
  - atexit
  - psx_unknown_dispatch fail-fast path (set PSX_FAIL_FAST_UNKNOWN_DISPATCH=1)
  - trap_crash (existing fatal trap path)

File overwrites per dump (last-write-wins). Per CLAUDE.md §3 this is not
a log — it's a one-shot final state snapshot for crashes the running
TCP server cannot intercept.

Pattern mirrors F:/Projects/SuperMarioWorldRecomp/src/post_mortem.c.

Used the new infrastructure to capture a controlled crash with
0xBFC0989C re-seeded. Crash dump revealed the chain:
  - JAL at 0xBFC09788 is jalr $s3, NOT jalr $a0
  - $s3 was set in func_1FC09598 prologue: $s3 = $a2 (callee saves arg)
  - Caller at 0xBFC093C4 in func_1FC092EC: $a2 = $s3 - 128 (delay slot)
  - func_1FC092EC at 0xBFC09314: $s3 = $a3 (callee saves arg)
  So real bug is upstream-of-upstream caller passing $a3 = 131 (0x83)
  to func_1FC092EC, propagating down to a misaligned function pointer.
  The 0xA000E1E8 we suspected as a function-pointer table was a
  red herring — $a0 in the loop body is loaded from $fp+s1*4 (the
  ARG to pass to the called fn), not the call target.

0xBFC0989C reverted out of seed list (was diagnostic-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:45:33 -07:00

47 lines
1.9 KiB
Python

"""Parse psx_last_run_report.json — show: reason, cpu state, last 30
dispatch_ring entries, last 5 unknown_dispatch entries, last 30
dirty_block_log entries. Highlight the relevant ones for the
0xBFC09790 caller investigation."""
import json
with open(r'F:\Projects\psxrecomp-v4\psx_last_run_report.json') as f:
r = json.load(f)
print(f"reason: {r.get('reason')}")
print(f"timestamp: {r.get('timestamp')}")
print(f"frame: {r.get('frame')}")
print(f"last_func_addr: {r.get('last_func_addr')}")
print(f"last_store_pc: {r.get('last_store_pc')}")
cpu = r.get('cpu')
if cpu:
print(f"\nCPU state:")
print(f" pc={cpu.get('pc')} epc={cpu.get('epc')} sr={cpu.get('sr')} cause={cpu.get('cause')}")
print(f" hi={cpu.get('hi')} lo={cpu.get('lo')}")
gpr = cpu.get('gpr', [])
NAMES = ['$0','at','v0','v1','a0','a1','a2','a3','t0','t1','t2','t3','t4','t5','t6','t7',
's0','s1','s2','s3','s4','s5','s6','s7','t8','t9','k0','k1','gp','sp','fp','ra']
for i in range(0, 32, 4):
line = ' '
for j in range(4):
line += f"{NAMES[i+j]:>3}={gpr[i+j]} "
print(line)
# Dispatch tail — last 30
dt = r.get('dispatch_tail', {})
addrs = dt.get('addrs', [])
print(f"\nDispatch tail (last 30 of {dt.get('count')} returned, total seq {dt.get('total')}):")
for a in addrs[-30:]:
print(f" {a}")
# Unknown dispatch tail
ud = r.get('unknown_dispatch_tail', {})
print(f"\nUnknown-dispatch tail (last 5 of {ud.get('count')} returned, total seq {ud.get('total')}):")
for e in ud.get('entries', [])[-5:]:
print(f" seq={e['seq']} phys={e['phys']} ra={e['ra']} a0={e['a0']} a1={e['a1']} frame={e['frame']}")
# Dirty block tail (last 20)
db = r.get('dirty_block_tail', {})
print(f"\nDirty-block tail (last 20 of {db.get('count')} returned, total seq {db.get('total')}):")
for e in db.get('entries', [])[-20:]:
print(f" seq={e['seq']} target={e['target']} ra={e['ra']} a0={e['a0']} frame={e['frame']}")