Gary Riches e225e02256 PS5 DevTools: crash reports and memory graphs for PS5 homebrew
A resident payload that attaches to homebrew titles as their debugger,
writes a crash report (registers, every thread's backtrace, stack,
modules, code at the fault, memory before the crash) and shows a TV
notification, then hands the signal back. It samples every title's
memory (direct/GPU, heap, other), CPU and threads, and serves a
dashboard on port 9113. The host tool symbolicates reports against the
matching build's ELF, including reports a player sends as JSON.

Tested on firmware 13.60 with Super Mario Sunshine and SwitchWaker.

Claude-Session: https://claude.ai/code/session_016xMYBaR9GfxtETyVf6er1P
2026-10-14 15:50:36 +01:00

PS5 DevTools

stars release licence

Crash reports and memory graphs for homebrew on a jailbroken PS5, with no change to the titles and no internet.

When a port crashes on the console, all you normally get is "Something went wrong with this game or app", or the title simply vanishes. DevTools is one small payload that stays resident and watches every homebrew title you start:

  • Crash reports. It attaches to each title as its debugger. When the title crashes it records the signal, the faulting address, every thread's registers and backtrace, a copy of the faulting stack, the loaded modules, the code at the fault and the last two minutes of memory use. Then it hands the crash back to the system. A notification on the TV says the report is saved.
  • Function names. On the machine that built the title, the host tool finds the build's ELF (matched by a hash of the code on the console) and turns addresses into functions and source lines.
  • Memory and CPU graphs. Every title is sampled once a second, for the last hour: resident memory, split into direct memory (what the GPU uses) and heap, with CPU and threads.
  • The console's own error history. Every error dialog the system has shown, with its code, title and time.

The dashboard: Super Mario Sunshine with its HD texture pack, 5.5 GiB resident, nearly all of it GPU memory

Everything is kept on the console and served on the local network: a browser at http://<console>:9113/ shows the dashboard. Nothing is sent anywhere.

tools/ps5devtools.py load        # once per console boot (the ELF loader must be up, port 9021)
tools/ps5devtools.py serve       # the dashboard at http://localhost:8113/, with function names
tools/ps5devtools.py crashes     # or from the terminal
tools/ps5devtools.py report      # the newest report, symbolicated

For homebrew development on a console you own. Tested on firmware 13.60 only. The calls it relies on are undocumented and may differ elsewhere.

A report

A real one, from test-crash on Super Mario Sunshine (its main thread sent to address 0x10, as a call through a bad function pointer would):

Super Mario Sunshine (PPSA99312 00.009.000) SIGSEGV at libkernel.sprx+0x8a0b (called from)
  2026-10-14T13:20:11Z, pid 127, up 25 s, outcome: crashed
  fault address 0x10, signal 11 code 1, thread eboot.bin (101396)
  symbols: PS5_Sunshine/build/last-title.elf (matches)
  in the title's code: eboot.bin+0xed287c  __wrap_pthread_join

backtrace:
  #0            0x10  ?+0x0
  #1     0x800008a0b  libkernel.sprx+0x8a0b
  #2       0x12d287c  eboot.bin+0xed287c  __wrap_pthread_join
  #3        0x402861  eboot.bin+0x2861  main  (PS5_Sunshine/vendor/sms-pc-port/src/port_main.cpp:80)
  #4        0x400174  eboot.bin+0x174  _start

The thread was in a system call when the test sent it to 0x10, so its caller is libkernel's system-call stub. port_main.cpp:80 is the main thread's pthread_join on the game thread, which is where it was waiting. "In the title's code" is the first frame in the title's own executable, and the TV's notification names it too. report also saves the full JSON and the text under captures/.

--all-threads shows what every other thread was doing. In the same report, the game thread was waiting for the next frame (its first two frames, in libkernel, left out):

thread 102283
  #2        0x447c2c  eboot.bin+0x47c2c  (anonymous namespace)::reschedule(bool)  (PS5_Sunshine/vendor/sms-pc-port/platform/os/os_thread.cpp:374)
  #3        0x464976  eboot.bin+0x64976  VIWaitForRetrace  (PS5_Sunshine/vendor/sms-pc-port/platform/vi/vi.cpp:282)
  #4        0x4b2fd3  eboot.bin+0xb2fd3  JDrama::TVideo::waitForRetrace(unsigned short)  (cp932/libs/JSystem/src/JDrama/JDRVideo.cpp:43)
  #5        0x4af31e  eboot.bin+0xaf31e  JDrama::TDisplay::endRendering()  (cp932/libs/JSystem/src/JDrama/JDRDisplay.cpp:46)
  #6        0x708e10  eboot.bin+0x308e10  TApplication::gameLoop()  (cp932/src/System/Application.cpp:1375)

Sending a report to a developer

Players do not need the source or the build to help. When a title crashes, open http://<console>:9113/ in any browser on the same network, click the crash and use Download JSON, or copy the file from /data/devtools/crashes/ over FTP. Send that file to the title's developer, who reads it with function names on the machine that built the title:

tools/ps5devtools.py report 20261014-132011-PPSA99312-127.json

The report says (matches) when the developer's ELF is the build the player was running. The JSON holds the title's registers, a copy of each thread's stack, its modules and its memory use. The stack is the game's own data at the moment of the crash.

What you need

  • A jailbroken PS5 with an ELF loader on port 9021 (the usual payload chains start one).
  • Python 3 on the host. For function names, llvm-symbolizer, llvm-dwarfdump and llvm-nm on the PATH, from Homebrew's llvm, or in the Lima VM that builds the titles, which the tool uses by itself.
  • To build: the PS5 payload SDK. tools/build.sh finds it through PS5_PAYLOAD_SDK, or builds in the Lima VM.
  • The console's address in PS5_HOST, or PS5_HOST= in .env.

Commands

Command What it does
load [ELF] send the payload to the ELF loader
status the titles it has seen, and whether each is attached
crashes the reports on the console
report [ID or FILE] [--all-threads] [--elf PATH] one report, newest by default or a .json someone sent, with function names
errors [--last N] the console's own error history
serve [--port 8113] [--lan] the dashboard on this machine, with function names
test-crash [PID] [--untraced] make an attached title take a real page fault (it closes)
quit detach from every title and stop the payload

Which titles

Every title is sampled. A title is attached to for crashes when it has a folder in /data/homebrew. To choose yourself, put title ids in /data/devtools/watch.txt, one a line, or all, or none.

Function names need the build's ELF with its debug information. The tool looks in every folder beside PS5_DevTools whose sce_sys/param.json names the title, at its build/*.elf, and uses the one whose code matches the report. devtools.json here can list more:

{"elfs": {"PPSA99312": ["/path/to/eboot.elf"]}}

If no ELF matches, the report says so and names the newest one it used: rebuild the title from the source that is on the console, or pass --elf.

The API

One HTTP request per connection on port 9113:

Request Answer
GET / the dashboard
GET /api/status the payload and the titles it has seen
GET /api/memory?pid=P&since=T a title's samples after unix time T
GET /api/crashes report summaries, newest first
GET /api/crash/<id> one report
GET /api/errors the system's error history
GET /api/log the payload's log
DELETE /api/crash/<id> remove a report
POST /api/test/fault?pid=P[&untraced=1] the test fault
POST /api/quit detach from everything and stop

Reports are files in /data/devtools/crashes/, readable over FTP too.

How it works, and what the console showed (13.60)

  • Finding titles. The process list (kern.proc), with sceKernelGetAppInfo for each process's title id. System processes (NPXS…) are skipped. A launch first appears as a process named SceSysCore.elf, which becomes eboot.bin; attaching waits for that and three seconds more, because attaching earlier failed or caught a stop from the launch.
  • Attaching. ptrace(PT_ATTACH), refused with a payload's own credentials. With the authid 0x4800000000010003 and full capabilities it is allowed. The SDK's ptrace is the bare system call, which returns an error as a positive number without setting errno; the payload wraps it.
  • Never taking a title down. A debugger that exits takes the processes it is tracing with it: the first quit closed Sunshine. So before the payload leaves, by quit or by crashing, it stops each title and detaches. Verified: quit while attached leaves Sunshine running at the same CPU use.
  • At a crash. The faulting thread's registers and siginfo, every thread, 16 KiB of stack, and a backtrace by frame pointers, which our ports keep (push rbp; mov rbp, rsp). Then PT_DETACH with the signal, so the title dies of it untraced. A title still running 15 seconds later is marked handled; one that is gone, crashed, with the notification.
  • Module names. The memory map has no paths on the PS5, so modules come from the kernel's list of the process's loaded objects (/app0/eboot.bin, libkernel.sprx, libSceLibcInternal.sprx, and so on).
  • Code is execute-only. A title's code cannot be read even by its debugger, so the code at a fault, and the hash that identifies the build, come from its eboot.bin on disk. That file is a fake-signed SELF (FTP servers hand it out as a plain ELF); the payload finds the code segment inside it. The hash is FNV-1a 64 of the first 64 KiB of the segment loaded at address 0, as the host computes it from each candidate ELF.
  • Names you can trust. The linker leaves the debug information of functions it removed at address 0, where it overlaps the start of the code (the SDK's _start, which has no debug information of its own). A lookup there finds an inlined piece of a removed function. So for each address the host checks with llvm-dwarfdump that the debug information puts it in a function the link kept, and otherwise uses the symbol table's name with no source line.
  • Memory. ki_rssize, ki_size, ki_runtime and ki_numthreads once a second, and the map totalled by object type (KERN_PROC_VMMAP) every five. Direct memory is mapped as device memory and counts in the resident total. For Sunshine: 1.7 GiB resident, of which 1.46 GiB direct memory and 175 MiB heap, inside a 16 GiB anonymous reservation. The console answers none of the vm.stats sysctls, so there is no system-wide free figure.
  • The system's own crash handling. A test fault left no CE-108255-1 record in the error history whether DevTools was attached or not (--untraced), so attaching does not change what the system records. kstuff showed its own crash notice beside DevTools'.

Limits

  • System modules show as module and offset only.
  • A crash in a leaf function of a system library can lose its caller.
  • The payload listens on the local network with no authentication. Run it on a network you trust.
  • Three reports at most per process, so a title that faults on purpose many times cannot fill the disk.
  • Only one debugger can attach to a title: do not run gdbsrv or ps5debug on a title DevTools is watching.

PS5 DevLoop drives the console from a script: a virtual controller and screenshots.

Licence

GPL-3.0-or-later. See LICENSE.

2026-10-14 17:01:56 +02:00
Languages
C 63.5%
Python 18.1%
HTML 16%
Shell 1.4%
Makefile 1%