Skip to content

Debugging Tips

Italo Dell Areti edited this page May 18, 2026 · 1 revision

Debugging Tips

This page is the practical catalogue of runtime environment variables, build modes and tooling. For new-game bring-up patterns see Adding a New Game.

Environment variables

All flags are opt-in (defaults are quiet). Combine freely.

Tracing

Var What it does
PS1_HLE_TRACE=1 Logs every psyq_dispatch call to stderr: function name, first three argument registers (a0/a1/a2), and the return address (RA). Output volume can be high — pipe to a file and post-process with tools/trace_replay.py.
PS1_INDIRECT_TRACE=1 Logs every CALL_INDIRECT / JUMP_INDIRECT dispatch with the target address. Catches dispatch tables that route to address 0 (silently dropped).
PS1_INDIRECT_TRACE_RA=0x80123456 Filter PS1_INDIRECT_TRACE output to indirect dispatches whose return address matches. Useful when the global trace is too noisy.
PS1_BIOS_DEBUG=1 Verbose A0/B0/C0 syscall logging.
PS1_DRAIN_TRACE=N Log every N-th call to Bios::drainPendingCallbacks with the per-call delta. A high call rate with delta=0 means the game thread is spinning, not deadlocked.

Typical first-day-of-bring-up combo:

PS1_HLE_TRACE=1 PS1_HLE_PERMISSIVE=1 SDL_AUDIODRIVER=dummy \
  ./build/ps1Runtime/ps1Runtime --config configs/<game>.toml \
  2> /tmp/<game>_trace.log

tools/trace_replay.py /tmp/<game>_trace.log --tail 20

Permissive modes

Var What it does
PS1_HLE_PERMISSIVE=1 When a PsyQ function is identified by hash but has no native HLE implementation registered, log [PSYQ] WARN: function 'X' missing HLE — NOP once per name and continue, instead of abort().

Per-game workarounds

These are temporary bypasses that let the game proceed past a known blocker. None of them are correct — they buy time while a permanent fix lands.

Var Title Effect Permanent fix
PS1_SKIP_31BF8=1 Crash Bandicoot 1 NOPs func_80031BF8 (display-mode setup) to avoid the unpopulated hash-table walk at 0x8005C530. Roadmap Phase 2 — port the NS chunk subsystem.

Headless

Var What it does
SDL_AUDIODRIVER=dummy Skip real audio backend (no PulseAudio / ALSA / CoreAudio dependency). Essential for CI and MCP runs.

Build modes

Stub mode (no ROM)

The full pipeline builds and 557/557 tests pass without any game files. This is the CI baseline.

cmake -B build -DCMAKE_BUILD_TYPE=Release \
               -DPS1RECOMP_USE_STUB=ON \
               -DPS1RECOMP_BUILD_TESTS=ON
cmake --build build -j$(nproc)
ctest --test-dir build --output-on-failure

AddressSanitizer + UndefinedBehaviorSanitizer

cmake -B build-asan -DPS1RECOMP_ASAN=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build-asan -j$(nproc)
./build-asan/ps1Runtime/ps1Runtime --config configs/<game>.toml

Runs are slower but catch:

  • Use-after-free across shutdown destructors (the Crash UAF in Bios::~Bios() was caught this way).
  • Signed-integer-overflow UB in emitted ADD/ADDU/SUB/SUBU/ADDI/ADDIU (now fixed by emitting through uint32_t).
  • Misaligned 4-byte loads (e.g. iso9660.cpp reading uint32_t from a byte buffer that is not 4-aligned — open).

GUI studio

cmake -B build -DPS1RECOMP_BUILD_INTERFACE=ON
cmake --build build -j$(nproc)
./build/ps1Interface/ps1Interface

The studio exposes an ImGui explorer of functions, an inspector for changing the per-function recompilation strategy, and a config-editor pane.

Tooling

tools/run_and_report.py

Runs the game for N seconds and emits a JSON report with frame rate, VRAM pixel analysis, GPU command log and CD-ROM callback counts.

python3 tools/run_and_report.py --duration 15 --output /tmp/report.json

tools/trace_replay.py

Tails or summarises a PS1_HLE_TRACE log into a per-function call count and the last N call sites.

tools/smoke_test.py

Pixel-diffs a captured frame against a reference VRAM PPM. Reference for Crash is captured via PCSX-Redux — see audit_notes/crash/HOW_TO_CAPTURE_TITLE_REF.md in the working tree.

tools/smoke_test.py --config configs/crash.toml \
                    --ref audit_notes/crash/title_ref.ppm

MCP tools

The repository ships an MCP (Model Context Protocol) server at tools/ps1_mcp_server.py. When run, exposes typed tools for the project:

Tool Purpose
mcp__ps1-recomp__build cmake --build build
mcp__ps1-recomp__run_game Runs the game and captures JSON metrics
mcp__ps1-recomp__run_tests ctest
mcp__ps1-recomp__decompile_function View C body of a function by address
mcp__ps1-recomp__find_callers / find_callees Call graph queries
mcp__ps1-recomp__get_vram_image Capture current VRAM state
mcp__ps1-recomp__search_functions / search_code Code search
mcp__ps1-recomp__read_log Read /tmp/*.log

Configure via .mcp.json at the repo root.

Ghidra integration

Ghidra scripts under tools/ghidra/ export function boundaries to CSV and launch a GhidraMCP bridge for AI-assisted analysis:

tools/ghidra/start_ghidra_mcp.sh   # MCP bridge on port 8080

Install the ghidra_psx_ldr plugin first — it adds the PSX loader with GTE and PsyQ awareness. See Reference-Projects-Misc for notes on what ghidra_psx_ldr ships.

Captured logs

progress/*.log in the working tree contains historical trace captures for Crash bring-up sessions:

  • 2026-05-15_crash_indirect_trace.log
  • 2026-05-15_crash_trace_70calls.log
  • 2026-05-16_crash_asan_use_after_free.log
  • 2026-05-16_crash_first_frames_loop.log
  • 2026-05-16_crash_hash_walk_isolated.log

These are session-private (gitignored) but the patterns they document are generic and worth reading if you are doing similar diagnostic work.

Clone this wiki locally