mirror of
https://github.com/Pharaoh2k/ps5debug-NG.git
synced 2026-10-22 03:42:51 -05:00
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.
838 lines
46 KiB
Markdown
838 lines
46 KiB
Markdown
# 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.*
|