Files
ps5debug-NG/PROTOCOL.md
T
OpenSourcereR-dev 9c649a44dc Initial public release: ps5debug-NG v1.2.2
PS5 debugger payload, inspired by Ctn's ps5debug 1.0b5 and
wire-compatible with it. Runs the wire-protocol server inside
SceShellCore via an umtx-loaded installer that injects the embedded
debugger ELF.

Features: process scan/RPC/ELF-inject, on-console scan, full
debugger (breakpoints, watchpoints, single-step, regs/FP/DR access),
kernel R/W, server-side Zydis disassembly, on-console Keystone
assembler (0xBDAA0024), foreground-app metadata (0xBDDD0006),
server-side RBP stack walk (CMD_PROC_READ_STACK), klog forwarder
(TCP 3232), rest-mode survival and a lot more.

Supports FW families 3.x / 4.0 / 4.x (incl. 4.51) / 5.x / 6.x /
7.x / 7.5x / 8.x via the kpatch routine in installer/source/main.c.

GPL-3.0-only.
2026-05-14 13:44:35 +07:00

838 lines
46 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ps5debug-NG - Wire Protocol Reference
Developer-facing reference for every command exposed by the debugger payload.
All field offsets, sizes, opcodes, and behaviors below are taken directly from
the sources under `debugger/`, `installer/`, and `common/` in this tree, with
file:line citations. Nothing is summarized from external documentation.
---
## 1. Wire protocol fundamentals
### 1.1 Core constants
| Symbol | Value | Source |
|---------------------|--------------------------------------|----------------------------|
| `PACKET_VERSION` | `"1.3"` | protocol.h:11 |
| `PACKET_BRANDING` | `"ps5debug-NG by OSR v1.2.2"` | meta.c (`handle_branding`) |
| `PACKET_MAGIC` | `0xFFAABBCC` | protocol.h:13 |
| `BROADCAST_MAGIC` | `0xFFFFAAAA` | server.h:19 |
### 1.2 Ports
| Port | Protocol | Role | Source |
|------|----------|------------------------------------------------------|-------------------|
| 744 | TCP | Main command server | main.c |
| 755 | TCP | Asynchronous debug-interrupt channel (server→client) | debug.h |
| 3232 | TCP | Kernel-log forwarder (server→client, read-only) | main.c |
| 1010 | UDP | Discovery broadcast | main.c |
### 1.3 Byte order
All multi-byte integers are **little-endian** (native x86-64 layout). `__attribute__((packed))`
is used on every wire struct, so there is no implicit padding between fields.
### 1.4 Packet framing
Every request on port 744 begins with a fixed 12-byte header. `void *data` in the
C struct is NOT on the wire - it is a client-side pointer filled in by the server
after the variable-length body is received (protocol.h:104-110, server.c:167-185):
```
struct cmd_packet { // 12 bytes on the wire
uint32_t magic; // must equal PACKET_MAGIC (0xFFAABBCC)
uint32_t cmd; // command ID (see §4)
uint32_t datalen; // length of request body that follows the header
void *data; // NOT transmitted; server-side pointer only
};
#define CMD_PACKET_SIZE 12
```
Framing loop (server.c:102-203):
1. Server blocking-reads 12 bytes → `cmd_packet` header.
2. Validates `magic == PACKET_MAGIC`; on mismatch the packet is silently dropped
and the loop continues (no error response).
3. If `datalen > 0`, server allocates a buffer via `net_alloc_buffer(datalen)`
and reads exactly `datalen` bytes into it.
4. Dispatches to the appropriate handler; the handler reads any *additional*
trailing data (beyond the fixed request struct) directly from the socket.
5. Handler replies with `CMD_SUCCESS` (or an error code from §1.6) via
`net_send_int32`, optionally followed by response payload.
### 1.5 Dispatch routing (meta.c, `cmd_handler`)
`cmd_handler` inlines five infrequent commands, then routes everything else by
testing the command against `VALID_*_CMD` macros (full-opcode match, no
middle-byte shortcut):
| Namespace | Predicate | Dispatch target | File |
|----------------|---------------------|------------------|---------------------|
| Info/ping | n/a (inlined) | `cmd_handler` | meta.c (5 cases) |
| Process | `VALID_PROC_CMD` | `proc_handle` | proc.c |
| Debug | `VALID_DEBUG_CMD` | `debug_handle` | debug.c |
| Kernel R/W | `VALID_KERN_CMD` | `kern_handle` | kern.c |
| Console | `VALID_CONSOLE_CMD` | `console_handle` | console.c |
`VALID_CMD(cmd)` additionally requires the top byte to be `0xBD`
(common/include/protocol.h).
**Special case:** if `cmd == CMD_DEBUG_ATTACH`, the per-connection handler is
invoked with the active client slot so it can attach debug state to the
specific TCP connection.
### 1.6 Status codes (common/include/protocol.h)
| Code | Value | Meaning |
|-----------------------|--------------|---------------------------------------------------|
| `CMD_SUCCESS` | `0x80000000` | Command succeeded; response payload may follow. |
| `CMD_ERROR` | `0xF0000001` | Generic failure. |
| `CMD_TOO_MUCH_DATA` | `0xF0000002` | Client requested more than the server will send. |
| `CMD_DATA_NULL` | `0xF0000003` | NULL/missing packet body or allocation failure. |
| `CMD_ALREADY_DEBUG` | `0xF0000004` | A debug session is already active. |
| `CMD_INVALID_INDEX` | `0xF0000005` | Breakpoint/watchpoint index out of range. |
| `CMD_FATAL_STATUS(s)` | macro | `((s >> 28) == 15)` - any `0xFxxxxxxx` is fatal. |
**PS5-specific wire quirk: bit-pair-swapped status words.** The server's
`net_send_int32` helper (used to transmit `uint32_t` status codes back to the
client) writes the value with its even/odd bit positions swapped. Clients must
un-swap incoming status `uint32_t`s before comparing to the table above:
```c
static inline uint32_t bitswap32(uint32_t x) {
return ((x << 1) & 0xAAAAAAAAu) | ((x >> 1) & 0x55555555u);
}
```
The swap is an involution (its own inverse). Subsequent response payload bytes
(structs, strings, raw memory) are sent **without** the swap - only the
`uint32_t` status word is affected. Payload-length prefixes inside response
bodies (e.g. the `uint32_t length` before a branding/version string) are also
sent raw; the swap is gated specifically to `net_send_int32`.
### 1.7 Discovery (server.c:235-306)
A UDP server on port 1010 accepts a single 4-byte datagram. If the payload equals
`BROADCAST_MAGIC` (`0xFFFFAAAA`), the server echoes the same 4 bytes back to the
sender. No other payload is accepted; there is no authentication on this channel.
### 1.8 Authentication (proc.c:1862-1900)
A single connection-scoped flag word `g_proc_auth_bits` gates the scan family.
Bit 1 (`0x02`) is the *scan-enabled* bit; all `CMD_PROC_SCAN*` handlers check
it on entry (proc.c:1904-1905, 1691, 1860, etc.).
`CMD_PROC_AUTH` protocol:
1. Client sends a `struct cmd_proc_auth_packet`:
- `uint32_t magic` - must equal `CMD_PROC_AUTH_MAGIC` (`0xBB40E64D`).
- `uint32_t flags` - requested bits to OR into `g_proc_auth_bits` on success.
2. Server generates a 64-byte challenge and sends: `uint16_t challenge_length = 64`
followed by the 64 bytes of challenge.
3. Client XORs the challenge against a keystream (derived client-side; mechanism
intentionally undocumented) and sends 64 response bytes back.
4. On match, server OR-s the requested flags into `g_proc_auth_bits` and replies
`CMD_SUCCESS`; otherwise `CMD_DATA_NULL`.
State is per-connection: disconnecting resets it. There is *no* server-wide
authentication on other command families - reads/writes/debug/kernel RW are
unauthenticated.
### 1.9 Asynchronous interrupt channel (port 755)
When a client issues `CMD_DEBUG_ATTACH`, the server also opens a TCP connection
*outbound* to that client's IP on port 755 (debug.c:25). The client must be
listening on 755 before sending `CMD_DEBUG_ATTACH`.
Interrupts (hit breakpoints/watchpoints, single-step completions) are delivered
asynchronously as fixed-size packets of `DEBUG_INTERRUPT_PACKET_SIZE = 0x4A0`
bytes (1184) on this channel (debug.h:105-113):
```
struct debug_interrupt_packet { // 0x4A0 bytes
uint32_t lwpid;
uint32_t status;
char tdname[40];
struct __reg64 reg64; // GP registers; 184 bytes
struct savefpu_ymm savefpu; // FPU+YMM; 512 bytes
struct __dbreg64 dbreg64; // 16× uint64_t = 128 bytes
};
```
Note: the struct is `packed`, so `savefpu` is not cache-line aligned - the
compiler emits warnings for this (observed during build) but the layout is
stable.
---
## 2. Command reference
58 total commands. Every `CMD_*` macro defined in `common/include/protocol.h`
has a handler, plus the raw-literal opcodes `0xBDAA0024` (assemble) and
`0xBDDD0006` (foreground app).
### 2.1 Info & ping (`0xBD000xxx`, `0xBDAACC06`)
All five are handled inline in `cmd_handler` (server.c:52-71).
#### `CMD_VERSION = 0xBD000001`
- **Request body:** none.
- **Response:** `uint32_t length`, then `length` bytes of `PACKET_VERSION` ("1.3").
#### `CMD_FW_VERSION = 0xBD000500`
- **Request body:** none.
- **Response:** `uint16_t` firmware version (BCD, e.g. `0x505` = 5.05).
#### `CMD_BRANDING = 0xBD000501`
- **Request body:** none.
- **Response:** `uint32_t length`, then `length` bytes of `PACKET_BRANDING`.
#### `CMD_PLATFORM_ID = 0xBD000502`
- **Request body:** none.
- **Response:** `uint16_t` platform tag - hardcoded `5` (PS5). Clients display
this as the console version (e.g. logged as "Console PS5"). The wire-protocol
version reported by `CMD_VERSION` is the server protocol revision (currently
`1.3`); this command is the *platform* discriminator.
#### `CMD_PROC_NOP = 0xBDAACC06`
- **Request body:** none.
- **Response:** `CMD_SUCCESS`. Keepalive / liveness probe.
---
### 2.2 Process commands (`0xBDAAxxxx`) - dispatched by `proc_handle` (proc.c:2268-2295)
#### `CMD_PROC_LIST = 0xBDAA0001` (proc.c:330-358)
- **Request body:** none.
- **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num × struct proc_list_entry`:
```
struct proc_list_entry { char p_comm[32]; int pid; }; // 36 bytes each
```
#### `CMD_PROC_READ = 0xBDAA0002` (proc.c:360-397)
- **Request body:** `struct cmd_proc_read_packet` (16 bytes).
- **Response:** `CMD_SUCCESS`, then up to `length` bytes in 64 KiB chunks.
#### `CMD_PROC_WRITE = 0xBDAA0003` (proc.c:399-432)
- **Request body:** `struct cmd_proc_write_packet` (16 bytes).
- **Trailing data:** `length` bytes to write.
- **Response:** `CMD_SUCCESS`.
#### `CMD_PROC_MAPS = 0xBDAA0004` (proc.c:434-477)
- **Request body:** `struct cmd_proc_maps_packet` (4 bytes, just `pid`).
- **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num × struct proc_vm_map_entry`
(`{uint64_t start; uint64_t end; uint64_t offset; uint64_t prot; char name[32];}` = 64 bytes each).
#### `CMD_PROC_INTALL = 0xBDAA0005` (proc.c:479-505)
Note the literal spelling `INTALL` (typo in source, preserved for compat).
- **Request body:** `struct cmd_proc_install_packet` (4 bytes, just `pid`).
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_install_response` (`uint64_t rpcstub`, 8 bytes).
- **Effect:** injects the RPC stub code + per-process thread into the target. The
returned `rpcstub` is the stub header address and must be supplied back on
every subsequent `CMD_PROC_CALL`.
#### `CMD_PROC_CALL = 0xBDAA0006` (proc.c:507-540)
- **Request body:** `struct cmd_proc_call_packet` (68 bytes):
```
uint32_t pid;
uint64_t rpcstub; // from CMD_PROC_INTALL response
uint64_t rpc_rip; // target function address
uint64_t rpc_rdi, rpc_rsi, rpc_rdx, rpc_rcx, rpc_r8, rpc_r9; // SysV args
```
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_call_response` (`{uint32_t pid; uint64_t rpc_rax;}`, 12 bytes).
#### `CMD_PROC_ELF = 0xBDAA0007` (proc.c:542-579)
- **Request body:** `struct cmd_proc_elf_packet` (`{uint32_t pid; uint32_t length;}`, 8 bytes).
- **Trailing data:** `length` bytes of ELF image.
- **Response:** `CMD_SUCCESS` or error.
#### `CMD_PROC_PROTECT = 0xBDAA0008` (proc.c:618-640)
- **Request body:** `struct cmd_proc_protect_packet` (20 bytes):
`{uint32_t pid; uint64_t address; uint32_t length; uint32_t newprot;}`.
- **Response:** `CMD_SUCCESS`.
#### `CMD_PROC_SCAN = 0xBDAA0009` (proc.c:1401-1485) - **requires auth bit 1**
Legacy single-pass scan; superseded by the `SCAN_START/COUNT/GET` trio.
- **Request body:** `struct cmd_proc_scan_packet` (10 bytes).
- **Trailing data:** `lenData` bytes of comparison value(s).
- **Response:** `uint32_t num`, then `num × uint64_t` addresses.
#### `CMD_PROC_INFO = 0xBDAA000A` (proc.c:1487-1511)
- **Request body:** `struct cmd_proc_info_packet` (4 bytes, `pid`).
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_info_response` (188 bytes):
`{uint32_t pid; char name[40]; char path[64]; char titleid[16]; char contentid[64];}`.
#### `CMD_PROC_ALLOC = 0xBDAA000B` (proc.c:1513-1534)
- **Request body:** `struct cmd_proc_alloc_packet` (8 bytes, `{pid, length}`).
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_alloc_response` (`uint64_t address`, 8 bytes).
#### `CMD_PROC_FREE = 0xBDAA000C` (proc.c:1536-1554)
- **Request body:** `struct cmd_proc_free_packet` (16 bytes, `{pid, address, length}`).
- **Response:** `CMD_SUCCESS`.
#### `CMD_PROC_ALLOC_HINTED = 0xBDAA000E` (proc.c:1557-1580)
- **Request body:** `struct cmd_proc_alloc_hinted_packet` (16 bytes, `{pid, hint, length}`).
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_alloc_response` (`uint64_t address`).
#### `CMD_PROC_ELF_RPC = 0xBDAA0010` (proc.c:581-616)
Like `CMD_PROC_ELF` but returns the entry address instead of jumping into it.
- **Request body:** `struct cmd_proc_elf_rpc_packet` (8 bytes, `{pid, length}`).
- **Trailing data:** `length` bytes of ELF image.
- **Response:** `CMD_SUCCESS`, `struct cmd_proc_elf_rpc_response` (`uint64_t entry`, 8 bytes).
#### `CMD_PROC_DISASM_REGION = 0xBDAA0020` (proc.c:145-184)
- **Request body:** `struct cmd_proc_disasm_packet` (20 bytes, `{pid, address, length, max_entries}`).
- **Response:** `CMD_SUCCESS`, a stream of `struct disasm_instr_entry` (32 bytes each),
terminated by a sentinel entry with all fields `0xFF`.
- **Backend:** Zydis amalgamation, compiled with `ZYAN_NO_LIBC`.
`disasm_instr_entry` layout (protocol.h:143-154):
```
struct disasm_instr_entry { // 32 bytes
uint64_t addr; // instruction start
uint64_t rip_rel_target; // resolved absolute addr if MEM_IS_RIP_REL; else 0
int64_t mem_disp; // [base + index*scale + disp]
uint8_t length;
uint8_t kind; // bitmask below
uint8_t mem_base_reg; // ZydisRegister low byte; 0 if none
uint8_t mem_index_reg;
uint8_t mem_scale; // 1/2/4/8; 0 if none
uint8_t mnemonic_lo; // ZydisMnemonic low byte (category hint)
uint16_t pad;
};
```
Kind bitmask (protocol.h:139-142):
| Bit | Meaning |
|------|------------------------------------|
| 0x01 | `CALL` |
| 0x02 | `RET` |
| 0x04 | Unconditional `JMP` |
| 0x08 | Conditional branch |
| 0x10 | Instruction has a memory operand |
| 0x20 | Memory operand is RIP-relative |
| 0x40 | Memory read |
| 0x80 | Memory write |
#### `CMD_PROC_EXTRACT_CODE_XREFS = 0xBDAA0021` (proc.c:220-294)
- **Request body:** `struct cmd_proc_disasm_packet` (20 bytes).
- **Response:** `CMD_SUCCESS`, `uint32_t num_targets`, `num_targets × uint64_t`
(deduplicated RIP-relative branch/call targets inside the scanned range).
#### `CMD_PROC_FIND_XREFS_TO = 0xBDAA0022` (proc.c:296-328)
- **Request body:** `struct cmd_proc_xrefs_to_packet` (24 bytes):
`{uint32_t pid; uint64_t scan_address; uint32_t scan_length; uint64_t target_address;}`.
- **Response:** `CMD_SUCCESS`, `uint32_t num`, then `num × (uint64_t address, uint8_t kind)`
- the `kind` byte reuses the `disasm_instr_entry.kind` bitmask.
#### `CMD_PROC_READ_STACK = 0xBDAA0023` (proc.c)
Server-side RBP-chain walk. The server walks the frame chain itself and bundles
every frame's data (saved RBP, return address, frame-local bytes, and a code
window around the return address) into one response, so clients avoid paying
~4 TCP round-trips per stack frame. Clients can feature-detect this command via
the `CMD_BRANDING` version string (added in v1.2.2).
- **Request body:** `struct cmd_proc_read_stack_packet` (24 bytes):
`{uint32_t pid; uint64_t rbp; uint64_t rsp; uint32_t depth;}`
- `rbp` / `rsp` describe the local (top) frame; `depth` caps frames returned (incl. local).
- **Response:** `CMD_SUCCESS`, `uint32_t bundle_len`, then `bundle_len` bytes:
```
u32 n_frames
per frame {
u64 rbp; u64 rsp; u64 saved_rbp; u64 ret_addr;
u32 flags; // bit0: frame-locals omitted (oversized/invalid) - read rsp..rbp yourself
u32 frame_locals_len;
u32 code_len; // bytes of code at (ret_addr - 10); 0 if unavailable
u8 frame_locals[frame_locals_len];
u8 code[code_len];
}
```
- Caps: `CMD_PROC_READ_STACK_MAX_DEPTH = 64`, `CMD_PROC_READ_STACK_LOCALS_CAP = 0x1000` per frame,
`CMD_PROC_READ_STACK_CODE_OFF = 10`, `CMD_PROC_READ_STACK_CODE_LEN = 200`.
#### `0xBDAA0024` - assemble x86-64 (proc.c, `proc_assemble_handle`)
Assemble x86-64 text into machine bytes using the Keystone (LLVM-MC) assembler
embedded in the payload. Pure userspace - needs no attached process, no
elevation. The on-console equivalent of what Reaper Studio does with its
client-side `keystone.dll` when applying `VarType.ASM` patches.
**The bare opcode is intentionally NOT given a `CMD_*` macro** - it's a raw
literal in `proc_handle`'s switch - so the published `CMD_*` set (which some
clients enumerate) stays unchanged. Clients that need it should send the literal.
- **Request body:** `struct cmd_proc_assemble_packet` (12 bytes):
`{uint64_t base_addr; uint32_t ks_opt_syntax;}`
- `base_addr` is what Keystone resolves PC-relative operands against.
- `ks_opt_syntax` = 0 keeps the engine default (Intel); pass a Keystone
`KS_OPT_SYNTAX_*` value (1=Intel, 2=ATT, 3=NASM, 4=MASM, 5=GAS) to switch.
- **Trailing data:** `(datalen - 12)` bytes of asm text. NUL-termination not required.
- **Response on success:** `CMD_SUCCESS`, `struct cmd_proc_assemble_ok` (8 bytes,
`{uint32_t byte_len; uint32_t insn_count;}`), then `byte_len` bytes of machine code.
- **Response on assembler error:** `CMD_ERROR`, `struct cmd_proc_assemble_err`
(8 bytes, `{uint32_t ks_errno; uint32_t msg_len;}`), then `msg_len` bytes of
the human-readable Keystone error (`ks_strerror`).
- **Caveats:** Keystone here was built with `LLVM_ENABLE_THREADS=OFF`; the
payload's command dispatcher serialises clients so this is fine for the
current usage, but concurrent assembles would need an external mutex.
#### `CMD_PROC_SCAN_AOB = 0xBDAA0501` (proc.c:1582-1691) - **requires auth bit 1**
- **Request body:** `struct cmd_proc_scan_aob_packet` (22 bytes):
`{pid, address, length, max_matches(u8), stop_flag(u8), pattern_length(u32)}`.
- **Trailing data:** `pattern_length` bytes. Each byte is either an exact value
or the wildcard marker `0x??` (see handler at proc.c:1638).
- **Response:** `uint32_t num`, then `num × uint64_t` addresses.
#### `CMD_PROC_SCAN_AOB_MULTI = 0xBDAA0502` (proc.c:1693-1860) - **requires auth bit 1**
- **Request body:** `struct cmd_proc_scan_aob_multi_packet` (21 bytes):
`{pid, address, length, stop_flag(u8), patterns_length(u32)}`.
- **Trailing data:** `patterns_length` bytes of concatenated patterns; each
pattern is prefixed by its own `uint8_t length` byte.
- **Response:** `uint32_t num_patterns`, then for each pattern: `uint32_t num`,
`num × uint64_t`.
#### `CMD_PROC_AUTH = 0xBDAACCFF` (proc.c:1862-1900)
See §1.8.
#### `CMD_PROC_SCAN_START = 0xBDAACC01` (proc.c:1922-2044) - **requires auth bit 1**
- **Request body:** `struct cmd_proc_scan_start_packet` (23 bytes):
`{pid, address, length, valueType(u8), compareType(u8), alignment(u8), lenData(u32)}`.
- **Trailing data:** `lenData` bytes of seed value(s).
- **Response:** `CMD_SUCCESS`. Server now holds a scan session keyed on `pid`.
#### `CMD_PROC_SCAN_COUNT = 0xBDAACC02` (proc.c:2046-2221) - **requires auth bit 1**
Narrows the current scan set by applying a second comparison.
- **Request body:** `struct cmd_proc_scan_count_packet` (18 bytes):
`{pid, base_address, valueType(u8), compareType(u8), lenData(u32)}`.
- **Trailing data:** `lenData` bytes.
- **Response:** `uint32_t num_results` (the new cardinality).
#### `CMD_PROC_SCAN_GET = 0xBDAACC03` (proc.c:2223-2265) - **requires auth bit 1**
- **Request body:** `struct cmd_proc_scan_get_packet` (8 bytes, `{pid, count}`).
- **Response:** `CMD_SUCCESS`, `uint32_t actual`, then `actual × uint64_t`.
For the enum values used by `valueType` and `compareType`, see §7.
---
### 2.3 Debug commands (`0xBDBBxxxx`) - dispatched by `debug_handle` (kern.c:779-803)
All debug commands (except `ATTACH`, see §1.5) require an already-attached
debug session. The dispatcher returns early with no response if none exists.
#### `CMD_DEBUG_ATTACH = 0xBDBB0001` (kern.c:98-166, via server.c:188)
- **Request body:** `struct cmd_debug_attach_packet` (8 bytes, `{pid, client_ip}`).
- **Response:** `CMD_SUCCESS`, or `CMD_ALREADY_DEBUG` / `CMD_ERROR`.
- **Side effect:** server opens outbound TCP to client on port 755 (see §1.9)
and `ptrace(PT_ATTACH)`-es the target.
`debug_attach_handle` at kern.c:92-96 is a stub that always replies `CMD_ERROR`
and returns `1`; it exists so the switch at kern.c:784 is complete, but is
unreachable in normal flow because `server.c:188` intercepts `CMD_DEBUG_ATTACH`
before `cmd_handler` runs and routes to `debug_attach_handle_svc` instead.
#### `CMD_DEBUG_DETACH = 0xBDBB0002` (kern.c:216-461)
- **Request body:** none.
- **Response:** `CMD_SUCCESS`. Clears all breakpoints/watchpoints, resumes target,
closes port 755 connection.
#### `CMD_DEBUG_SET_BREAKPOINT = 0xBDBB0003` (kern.c:463-501)
- **Request body:** `struct cmd_debug_breakpt_packet` (16 bytes, `{index, enabled, address}`).
- **Response:** `CMD_SUCCESS` or `CMD_INVALID_INDEX`.
- **Backend:** software breakpoint - writes `0xCC` at `address`, saves original byte.
`MAX_BREAKPOINTS = 30` (protocol.h:438).
#### `CMD_DEBUG_SET_WATCHPOINT = 0xBDBB0004` (kern.c:504-568)
- **Request body:** `struct cmd_debug_watchpt_packet` (24 bytes):
`{index, enabled, length, breaktype, address}`.
- **Response:** `CMD_SUCCESS` or `CMD_INVALID_INDEX`.
- **Backend:** hardware breakpoint via DR0-DR3 / DR7. `MAX_WATCHPOINTS = 4`
(protocol.h:439). `breaktype` uses `DBREG_DR7_*` encoding (debug.h:119-126):
`EXEC=0`, `WRONLY=1`, `RDWR=3`; length `1/2/4/8` via `DBREG_DR7_LEN_*`.
#### `CMD_DEBUG_GET_THREAD_LIST = 0xBDBB0005` (kern.c:570-604)
- **Request body:** none.
- **Response:** `CMD_SUCCESS`, `uint32_t num`, `num × uint32_t lwpid`.
#### `CMD_DEBUG_SUSPEND_THREAD = 0xBDBB0006` (kern.c:606-614)
- **Request body:** `struct cmd_debug_stopthr_packet` (4 bytes, `lwpid`).
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_RESUME_THREAD = 0xBDBB0007` (kern.c:616-624)
- **Request body:** `struct cmd_debug_resumethr_packet` (4 bytes, `lwpid`).
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_GETREGS = 0xBDBB0008` (kern.c:626-639)
- **Request body:** `struct cmd_debug_getregs_packet` (4 bytes, `lwpid`).
- **Response:** `CMD_SUCCESS`, `struct __reg64` (184 bytes; debug.h:11-38).
#### `CMD_DEBUG_SETREGS = 0xBDBB0009` (kern.c:641-655)
- **Request body:** `struct cmd_debug_setregs_packet` (8 bytes, `{lwpid, length}`).
- **Trailing data:** `length` bytes - must be `sizeof(struct __reg64)`.
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_GETFPREGS = 0xBDBB000A` (kern.c:657-670)
- **Request body:** `struct cmd_debug_getregs_packet` (4 bytes, `lwpid`).
- **Response:** `CMD_SUCCESS`, `struct savefpu_ymm` (512 bytes; debug.h:88-97).
#### `CMD_DEBUG_SETFPREGS = 0xBDBB000B` (kern.c:672-686)
- **Request body:** `struct cmd_debug_setregs_packet` (8 bytes).
- **Trailing data:** `length` bytes - `sizeof(struct savefpu_ymm)`.
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_GETDBREGS = 0xBDBB000C` (kern.c:688-701)
- **Request body:** `struct cmd_debug_getregs_packet` (4 bytes).
- **Response:** `CMD_SUCCESS`, `struct __dbreg64` (128 bytes; 16 × uint64_t).
#### `CMD_DEBUG_SETDBREGS = 0xBDBB000D` (kern.c:703-717)
- **Request body:** `struct cmd_debug_setregs_packet` (8 bytes).
- **Trailing data:** `length` bytes (`sizeof(struct __dbreg64)`).
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_CONTINUE = 0xBDBB0010` (kern.c:719-729)
- **Request body:** `struct cmd_debug_stopgo_packet` (4 bytes, `stop`).
- `stop == 0` → resume all threads. `stop == 1` → enter single-step / halted mode.
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_THREAD_INFO = 0xBDBB0011` (kern.c:731-744)
- **Request body:** `struct cmd_debug_thrinfo_packet` (4 bytes, `lwpid`).
- **Response:** `CMD_SUCCESS`, `struct cmd_debug_thrinfo_response` (40 bytes,
`{lwpid, priority, name[32]}`).
#### `CMD_DEBUG_STEP = 0xBDBB0012` (kern.c:746-756)
- **Request body:** none.
- **Response:** `CMD_SUCCESS`. Single-step all threads of the debugged process.
#### `CMD_DEBUG_STEP_THREAD = 0xBDBB0013` (kern.c:758-776)
- **Request body:** `struct cmd_debug_stopthr_packet` (4 bytes, `lwpid`).
- **Response:** `CMD_SUCCESS`.
#### `CMD_DEBUG_PROCESS_STOP = 0xBDBB0500` (kern.c:54-89)
- **Request body:** 5 bytes of raw data (no named struct):
`u32 pid; u8 state;` - `state ∈ {0,1,2}` maps to `{resume, stop, kill}`
(kern.c:61-77). Returns `CMD_ERROR` if `state > 2` or `pid == 0`.
- **Response:** `CMD_SUCCESS`, or `CMD_DATA_NULL`, or `CMD_ERROR`.
- **Behavior:** If a debug session is active (`g_debugging != 0`), stashes a
pending signal into `g_pending_signal` for the next event dispatch
(`0 → 0`, `1 → SIGSTOP(17)`, `2 → SIGKILL(9)`). Otherwise calls `kill(pid, sig)`
directly with `SIGSTOP(19 - FreeBSD SIGSTOP)`, `SIGTSTP(17)`, or `SIGKILL(9)`.
---
### 2.4 Kernel R/W commands (`0xBDCCxxxx`) - dispatched by `kern_handle` (console.c:60-67)
#### `CMD_KERN_BASE = 0xBDCC0001` (console.c:7-13)
- **Request body:** none.
- **Response:** `CMD_SUCCESS`, `uint64_t` kernel base.
#### `CMD_KERN_READ = 0xBDCC0002` (console.c:16-35)
- **Request body:** `struct cmd_kern_read_packet` (12 bytes, `{address, length}`).
- **Response:** `CMD_SUCCESS`, then `length` bytes of kernel memory.
#### `CMD_KERN_WRITE = 0xBDCC0003` (console.c:38-57)
- **Request body:** `struct cmd_kern_write_packet` (12 bytes, `{address, length}`).
- **Trailing data:** `length` bytes to write.
- **Response pattern** (unique - note the **two** status messages):
1. `CMD_SUCCESS` sent *before* the server receives the write payload (console.c:51).
2. `CMD_SUCCESS` sent *after* the write completes (console.c:54).
---
### 2.5 Console commands (`0xBDDDxxxx`) - dispatched by `console_handle` (debug.c:121-140)
#### `CMD_CONSOLE_REBOOT = 0xBDDD0001` (debug.c:110-118)
- **Request body:** none.
- **Response:** none - connection is closed; system reboots via `syscall(112, 1, 0)`.
#### `CMD_CONSOLE_END = 0xBDDD0002` (debug.c:133-136)
- **Request body:** none.
- **Response:** none - handler returns `1`, which tears down the connection.
#### `CMD_CONSOLE_PRINT = 0xBDDD0003` (debug.c:58-82)
- **Request body:** `uint32_t length`, then `length` bytes of UTF-8 text.
- **Response:** `CMD_SUCCESS`.
#### `CMD_CONSOLE_NOTIFY = 0xBDDD0004` (debug.c:85-107)
- **Request body:** `struct cmd_console_notify_packet` (8 bytes, `{messageType, length}`).
- **Trailing data:** `length` bytes of UTF-8 text (must include trailing `\0`).
- **Response:** `CMD_SUCCESS`.
- **Effect:** calls `sceSysUtilSendSystemNotificationWithText(messageType, text)`;
`messageType = 222` is the generic icon used by main.c:39.
#### `CMD_CONSOLE_INFO = 0xBDDD0005` (debug.c:129-132)
- **Request body:** none.
- **Response:** `CMD_SUCCESS`. No side effect (ping variant).
#### `0xBDDD0006` - foreground-app metadata (console.c, `console_foreground_app_handle`)
Identify the currently-foregrounded game and return its metadata (pid, titleid,
contentid, process name, and version). Resolves the version from the title's
`param.sfo`. **Raw literal in `console_handle`'s switch - no `CMD_*` macro**
(same rationale as `0xBDAA0024`).
- **Request body:** none.
- **Response:** `CMD_SUCCESS`, `struct cmd_console_foreground_app_response` (140 bytes):
```
u32 pid;
char titleid[16];
char contentid[64];
char name[40];
char app_ver[16]; // "XX.YY" version string from param.sfo
```
- **Behaviour:** if there is no foreground game, all fields are zeroed (`pid==0`).
- **Version selection:** when both `APP_VER` and `VERSION` are present in
`param.sfo`, the server returns the lexically larger of the two so that
packer-mistakes (where the bundled `VERSION` overtakes `APP_VER`) still
yield the higher-precedence version string.
---
## 3. In-process kernel/target primitives
Unlike PS4-class debuggers that ship a separate kernel module, ps5debug-NG
runs the entire wire-protocol server inside SceShellCore (after the umtx-loaded
installer injects the embedded debugger ELF as a SceShellCore-internal thread).
All "kernel-side" operations are therefore performed from userland using
primitives supplied by [ps5-payload-sdk](https://github.com/ps5-payload-dev/sdk)
and short kernel patches applied at installer time:
| Concern | How ps5debug-NG implements it |
|--------------------------------------|----------------------------------------------------------------------------|
| Process enumeration (`CMD_PROC_LIST`)| `sysctl(KERN_PROC, KERN_PROC_PROC)` from inside SceShellCore. |
| Target memory R/W | `mdbg_call` syscalls + the SDK's `kernel_copyin` / `kernel_copyout`. |
| Target `mprotect` / alloc / free | `mdbg_call` PT_TRACE_ME-tainted variant + KEX-state shim from installer. |
| Kernel base / kernel R/W | `kernel_get_root` and `kernel_copyin/out` (toggles `CR0.WP` internally). |
| RPC stub install + call (`CMD_PROC_INSTALL` / `CMD_PROC_CALL`) | In-target thread creation through `scePthreadCreate` after the installer caches the libkernel.sprx symbol offsets. |
| ELF inject (`CMD_PROC_ELF`, `CMD_PROC_ELF_RPC`) | Self-relocating loader inside the debugger; mmap target pages via remote-mmap RPC stub; jump or return entry. |
| Console reboot / notify / print | `sceSysUtilSendSystemNotificationWithText`, `kern_reboot`, `klog_printf`. |
| Jailbreak (uid=0, escape prison) | Performed by the installer before injecting the debugger; not reachable via the wire protocol. |
The installer's role is bootstrap-only: find SceShellCore, apply the FW-specific
kpatch (see [installer/source/main.c](installer/source/main.c)), set up the
target's KEX state and syscall-origin filter, then call the SCE-side
`inject_remote_thread_create` primitive to hand control off to the embedded
debugger. After that the installer thread exits and the debugger services TCP.
---
## 4. Full command ID table
| Hex | Name | Handler | Auth? |
|--------------|----------------------------------|---------------------------------|-------|
| `0xBD000001` | `CMD_VERSION` | inline (server.c:53) | |
| `0xBD000500` | `CMD_FW_VERSION` | inline (server.c:55) | |
| `0xBD000501` | `CMD_BRANDING` | inline (server.c:59) | |
| `0xBD000502` | `CMD_PLATFORM_ID` | inline (server.c:64) | |
| `0xBDAA0001` | `CMD_PROC_LIST` | `proc_list_handle` | |
| `0xBDAA0002` | `CMD_PROC_READ` | `proc_read_handle` | |
| `0xBDAA0003` | `CMD_PROC_WRITE` | `proc_write_handle` | |
| `0xBDAA0004` | `CMD_PROC_MAPS` | `proc_maps_handle` | |
| `0xBDAA0005` | `CMD_PROC_INTALL` | `proc_install_handle` | |
| `0xBDAA0006` | `CMD_PROC_CALL` | `proc_call_handle` | |
| `0xBDAA0007` | `CMD_PROC_ELF` | `proc_elf_handle` | |
| `0xBDAA0008` | `CMD_PROC_PROTECT` | `proc_protect_handle` | |
| `0xBDAA0009` | `CMD_PROC_SCAN` | `proc_scan_handle` | bit 1 |
| `0xBDAA000A` | `CMD_PROC_INFO` | `proc_info_handle` | |
| `0xBDAA000B` | `CMD_PROC_ALLOC` | `proc_alloc_handle` | |
| `0xBDAA000C` | `CMD_PROC_FREE` | `proc_free_handle` | |
| `0xBDAA000E` | `CMD_PROC_ALLOC_HINTED` | `proc_alloc_hinted_handle` | |
| `0xBDAA0010` | `CMD_PROC_ELF_RPC` | `proc_elf_rpc_handle` | |
| `0xBDAA0020` | `CMD_PROC_DISASM_REGION` | `proc_disasm_region_handle` | |
| `0xBDAA0021` | `CMD_PROC_EXTRACT_CODE_XREFS` | `proc_extract_code_xrefs_handle`| |
| `0xBDAA0022` | `CMD_PROC_FIND_XREFS_TO` | `proc_find_xrefs_to_handle` | |
| `0xBDAA0023` | `CMD_PROC_READ_STACK` | `proc_read_stack_handle` | |
| `0xBDAA0024` | _(raw literal, no macro)_ | `proc_assemble_handle` | |
| `0xBDAA0501` | `CMD_PROC_SCAN_AOB` | `proc_scan_aob_handle` | bit 1 |
| `0xBDAA0502` | `CMD_PROC_SCAN_AOB_MULTI` | `proc_scan_aob_multi_handle` | bit 1 |
| `0xBDAACCFF` | `CMD_PROC_AUTH` | `proc_auth_handle` | |
| `0xBDAACC01` | `CMD_PROC_SCAN_START` | `proc_scan_start_handle` | bit 1 |
| `0xBDAACC02` | `CMD_PROC_SCAN_COUNT` | `proc_scan_count_handle` | bit 1 |
| `0xBDAACC03` | `CMD_PROC_SCAN_GET` | `proc_scan_get_handle` | bit 1 |
| `0xBDAACC06` | `CMD_PROC_NOP` | inline (server.c:68) | |
| `0xBDBB0001` | `CMD_DEBUG_ATTACH` | `debug_attach_handle_svc` | |
| `0xBDBB0002` | `CMD_DEBUG_DETACH` | `debug_detach_handle` | |
| `0xBDBB0003` | `CMD_DEBUG_SET_BREAKPOINT` | `debug_set_breakpoint_handle` | |
| `0xBDBB0004` | `CMD_DEBUG_SET_WATCHPOINT` | `debug_set_watchpoint_handle` | |
| `0xBDBB0005` | `CMD_DEBUG_GET_THREAD_LIST` | `debug_get_thread_list_handle` | |
| `0xBDBB0006` | `CMD_DEBUG_SUSPEND_THREAD` | `debug_suspend_thread_handle` | |
| `0xBDBB0007` | `CMD_DEBUG_RESUME_THREAD` | `debug_resume_thread_handle` | |
| `0xBDBB0008` | `CMD_DEBUG_GETREGS` | `debug_getregs_handle` | |
| `0xBDBB0009` | `CMD_DEBUG_SETREGS` | `debug_setregs_handle` | |
| `0xBDBB000A` | `CMD_DEBUG_GETFPREGS` | `debug_getfpregs_handle` | |
| `0xBDBB000B` | `CMD_DEBUG_SETFPREGS` | `debug_setfpregs_handle` | |
| `0xBDBB000C` | `CMD_DEBUG_GETDBREGS` | `debug_getdbregs_handle` | |
| `0xBDBB000D` | `CMD_DEBUG_SETDBREGS` | `debug_setdbregs_handle` | |
| `0xBDBB0010` | `CMD_DEBUG_CONTINUE` | `debug_continue_handle` | |
| `0xBDBB0011` | `CMD_DEBUG_THREAD_INFO` | `debug_thread_info_handle` | |
| `0xBDBB0012` | `CMD_DEBUG_STEP` | `debug_step_handle` | |
| `0xBDBB0013` | `CMD_DEBUG_STEP_THREAD` | `debug_step_thread_handle` | |
| `0xBDBB0500` | `CMD_DEBUG_PROCESS_STOP` | `debug_process_stop_handle` | |
| `0xBDCC0001` | `CMD_KERN_BASE` | `kern_base_handle` | |
| `0xBDCC0002` | `CMD_KERN_READ` | `kern_read_handle` | |
| `0xBDCC0003` | `CMD_KERN_WRITE` | `kern_write_handle` | |
| `0xBDDD0001` | `CMD_CONSOLE_REBOOT` | `console_reboot_handle` | |
| `0xBDDD0002` | `CMD_CONSOLE_END` | inline (debug.c:133) | |
| `0xBDDD0003` | `CMD_CONSOLE_PRINT` | `console_print_handle` | |
| `0xBDDD0004` | `CMD_CONSOLE_NOTIFY` | `console_notify_handle` | |
| `0xBDDD0005` | `CMD_CONSOLE_INFO` | inline (debug.c:129) | |
| `0xBDDD0006` | _(raw literal, no macro)_ | `console_foreground_app_handle` | |
---
## 5. Request packet appendix
All structs are `__attribute__((packed))`. Sizes match the `CMD_*_PACKET_SIZE` macros.
| Struct | Size | Fields |
|-----------------------------------------|------|---------------------------------------------------------------------------|
| `cmd_packet` (wire portion) | 12 | `u32 magic; u32 cmd; u32 datalen;` |
| `cmd_proc_read_packet` | 16 | `u32 pid; u64 address; u32 length;` |
| `cmd_proc_write_packet` | 16 | `u32 pid; u64 address; u32 length;` |
| `cmd_proc_maps_packet` | 4 | `u32 pid;` |
| `cmd_proc_install_packet` | 4 | `u32 pid;` |
| `cmd_proc_call_packet` | 68 | `u32 pid; u64 rpcstub,rpc_rip,rpc_rdi,rpc_rsi,rpc_rdx,rpc_rcx,rpc_r8,rpc_r9;` |
| `cmd_proc_elf_packet` | 8 | `u32 pid; u32 length;` |
| `cmd_proc_elf_rpc_packet` | 8 | `u32 pid; u32 length;` |
| `cmd_proc_protect_packet` | 20 | `u32 pid; u64 address; u32 length; u32 newprot;` |
| `cmd_proc_scan_packet` | 10 | `u32 pid; u8 valueType, compareType; u32 lenData;` |
| `cmd_proc_scan_start_packet` | 23 | `u32 pid; u64 address; u32 length; u8 valueType, compareType, alignment; u32 lenData;` |
| `cmd_proc_scan_count_packet` | 18 | `u32 pid; u64 base_address; u8 valueType, compareType; u32 lenData;` |
| `cmd_proc_scan_aob_packet` | 22 | `u32 pid; u64 address; u32 length; u8 max_matches, stop_flag; u32 pattern_length;` |
| `cmd_proc_scan_aob_multi_packet` | 21 | `u32 pid; u64 address; u32 length; u8 stop_flag; u32 patterns_length;` |
| `cmd_proc_auth_packet` | 8 | `u32 magic (0xBB40E64D); u32 flags;` |
| `cmd_proc_scan_get_packet` | 8 | `u32 pid; u32 count;` |
| `cmd_proc_info_packet` | 4 | `u32 pid;` |
| `cmd_proc_alloc_packet` | 8 | `u32 pid; u32 length;` |
| `cmd_proc_alloc_hinted_packet` | 16 | `u32 pid; u64 hint; u32 length;` |
| `cmd_proc_free_packet` | 16 | `u32 pid; u64 address; u32 length;` |
| `cmd_proc_disasm_packet` | 20 | `u32 pid; u64 address; u32 length; u32 max_entries;` |
| `cmd_proc_xrefs_to_packet` | 24 | `u32 pid; u64 scan_address; u32 scan_length; u64 target_address;` |
| `cmd_proc_read_stack_packet` | 24 | `u32 pid; u64 rbp; u64 rsp; u32 depth;` |
| `cmd_proc_assemble_packet` | 12 | `u64 base_addr; u32 ks_opt_syntax;` (followed by asm text on the wire) |
| `cmd_debug_attach_packet` | 8 | `u32 pid; u32 client_ip;` |
| `cmd_debug_breakpt_packet` | 16 | `u32 index, enabled; u64 address;` |
| `cmd_debug_watchpt_packet` | 24 | `u32 index, enabled, length, breaktype; u64 address;` |
| `cmd_debug_stopthr_packet` | 4 | `u32 lwpid;` |
| `cmd_debug_resumethr_packet` | 4 | `u32 lwpid;` |
| `cmd_debug_getregs_packet` | 4 | `u32 lwpid;` |
| `cmd_debug_setregs_packet` | 8 | `u32 lwpid; u32 length;` |
| `cmd_debug_stopgo_packet` | 4 | `u32 stop;` |
| `cmd_debug_thrinfo_packet` | 4 | `u32 lwpid;` |
| `cmd_kern_read_packet` | 12 | `u64 address; u32 length;` |
| `cmd_kern_write_packet` | 12 | `u64 address; u32 length;` |
| `cmd_console_notify_packet` | 8 | `u32 messageType; u32 length;` |
## 6. Response packet appendix
| Struct | Size | Fields |
|-----------------------------------------|------|---------------------------------------------------------------------------|
| `cmd_proc_install_response` | 8 | `u64 rpcstub;` |
| `cmd_proc_call_response` | 12 | `u32 pid; u64 rpc_rax;` |
| `cmd_proc_elf_rpc_response` | 8 | `u64 entry;` |
| `cmd_proc_alloc_response` | 8 | `u64 address;` |
| `cmd_proc_info_response` | 188 | `u32 pid; char name[40], path[64], titleid[16], contentid[64];` |
| `cmd_proc_assemble_ok` | 8 | `u32 byte_len; u32 insn_count;` (followed by `byte_len` machine bytes) |
| `cmd_proc_assemble_err` | 8 | `u32 ks_errno; u32 msg_len;` (followed by `msg_len` chars of `ks_strerror`) |
| `cmd_console_foreground_app_response` | 132 | `u32 pid; char titleid[16], contentid[64], name[40], app_ver[8];` |
| `cmd_debug_thrinfo_response` | 40 | `u32 lwpid, priority; char name[32];` |
| `disasm_instr_entry` (streamed) | 32 | see §2.2 |
---
## 7. Enum reference
### 7.1 `cmd_proc_scan_valuetype` (protocol.h:221-234)
| Value | Name |
|-------|--------------------|
| 0 | `valTypeUInt8` |
| 1 | `valTypeInt8` |
| 2 | `valTypeUInt16` |
| 3 | `valTypeInt16` |
| 4 | `valTypeUInt32` |
| 5 | `valTypeInt32` |
| 6 | `valTypeUInt64` |
| 7 | `valTypeInt64` |
| 8 | `valTypeFloat` |
| 9 | `valTypeDouble` |
| 10 | `valTypeArrBytes` |
| 11 | `valTypeString` |
### 7.2 `cmd_proc_scan_comparetype` (protocol.h:236-252)
| Value | Name |
|-------|------------------------------------|
| 0 | `cmpTypeExactValue` |
| 1 | `cmpTypeFuzzyValue` |
| 2 | `cmpTypeBiggerThan` |
| 3 | `cmpTypeSmallerThan` |
| 4 | `cmpTypeValueBetween` |
| 5 | `cmpTypeIncreasedValue` |
| 6 | `cmpTypeIncreasedValueBy` |
| 7 | `cmpTypeDecreasedValue` |
| 8 | `cmpTypeDecreasedValueBy` |
| 9 | `cmpTypeChangedValue` |
| 10 | `cmpTypeUnchangedValue` |
| 11 | `cmpTypeUnknownInitialValue` |
| 12 | `cmpTypeUnknownInitialLowValue` |
### 7.3 DR7 encoding for hardware watchpoints (debug.h:119-126)
`breaktype` in `cmd_debug_watchpt_packet`:
| Value | Name |
|-------|-----------------------|
| 0 | `DBREG_DR7_EXEC` |
| 1 | `DBREG_DR7_WRONLY` |
| 3 | `DBREG_DR7_RDWR` |
`length` encoding (reused as DR7 length field):
| Value | Bytes |
|-------|-------|
| 0 | 1 |
| 1 | 2 |
| 2 | 8 |
| 3 | 4 |
---
## 8. Response framing patterns
Four reply shapes cover every handler:
1. **Fixed status**: `uint32_t status` (usually `CMD_SUCCESS`). Used by writes,
breakpoint/watchpoint management, thread suspend/resume, etc.
2. **Status + fixed struct**: `uint32_t status` then a `struct cmd_*_response`
of known size. Used by info/install/call/alloc.
3. **Status + count + array**: `uint32_t status`, `uint32_t count`,
`count × element`. Used by list, thread list, maps, scan results.
4. **Streamed with sentinel**: `uint32_t status`, then a sequence of fixed-size
records terminated by an all-`0xFF` sentinel. Only `CMD_PROC_DISASM_REGION`.
The `CMD_KERN_WRITE` handler is the only exception: it sends `CMD_SUCCESS`
*twice* (before and after the data phase). Clients must consume both `u32`s.
---
## 9. Known source-code oddities
Documented here so a developer doesn't mistake them for bugs:
- `CMD_PROC_INTALL` is misspelled (missing `S`). Preserved on the wire.
- `debug_attach_handle` (kern.c:92-96) is a stub that always returns `CMD_ERROR`;
the real attach path is `debug_attach_handle_svc`, which `server.c:188`
intercepts `CMD_DEBUG_ATTACH` to call directly. Both declarations remain in
`include/kern.h`.
- `struct debug_interrupt_packet` is `packed` - the compiler warns about
`savefpu` being offset 224 instead of a 64-byte boundary. The layout is
intentional; clients rely on `DEBUG_INTERRUPT_PACKET_SIZE = 0x4A0` exactly.
- `SYS_CONSOLE_CMD_JAILBREAK` has no corresponding `CMD_*` opcode: it is
invoked locally by the payload at startup (main.c:22) and is not reachable
over the network.
- The disassembler amalgamation (`third_party/zydis/Zydis.c`) is compiled with
`-O3 -DNDEBUG -w -DZYAN_NO_LIBC`; the rest of the debugger is `-O2`. This is
a deliberate Makefile override (debugger/Makefile:45-46).
---
*This document reflects the `ps5debug-NG v1.2.2` payload (see
`debugger/source/meta.c:27`). Citations elsewhere may point at line numbers
that drift as the source evolves - when in doubt, the source under
`common/include/protocol.h`, `debugger/source/`, and `installer/source/` is
the authoritative reference.*