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
11 KiB
PS5 DevTools
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.
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-dwarfdumpandllvm-nmon the PATH, from Homebrew'sllvm, or in the Lima VM that builds the titles, which the tool uses by itself. - To build: the PS5 payload SDK.
tools/build.shfinds it throughPS5_PAYLOAD_SDK, or builds in the Lima VM. - The console's address in
PS5_HOST, orPS5_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), withsceKernelGetAppInfofor each process's title id. System processes (NPXS…) are skipped. A launch first appears as a process namedSceSysCore.elf, which becomeseboot.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 authid0x4800000000010003and full capabilities it is allowed. The SDK'sptraceis the bare system call, which returns an error as a positive number without settingerrno; the payload wraps it. - Never taking a title down. A debugger that exits takes the processes it
is tracing with it: the first
quitclosed Sunshine. So before the payload leaves, byquitor by crashing, it stops each title and detaches. Verified:quitwhile 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). ThenPT_DETACHwith the signal, so the title dies of it untraced. A title still running 15 seconds later is markedhandled; 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.binon 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 withllvm-dwarfdumpthat 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_runtimeandki_numthreadsonce 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 thevm.statssysctls, so there is no system-wide free figure. - The system's own crash handling. A test fault left no
CE-108255-1record 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.
Related
PS5 DevLoop drives the console from a script: a virtual controller and screenshots.
Licence
GPL-3.0-or-later. See LICENSE.
