Files
Pharaoh2k 951e54a3c4 Add content-verified process writes
CMD_PROC_WRITE and write-multi now read writes back before reporting success: a proven DMAP mismatch no longer falls through to mdbg, and failures surface as CMD_ERROR (single write) or typed per-entry codes (invalid / DMAP / verify / mdbg) for write-multi. A transfer whose start address ends in 0xFF backs up one byte to keep the write aligned; that adjacent-byte read-modify-write needs a stable or suspended target.
2026-07-28 12:31:56 +00:00

495 lines
19 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
#ifndef _PS5DEBUG_PROTOCOL_H
#define _PS5DEBUG_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#define CMD_SUCCESS 0x40000000u
#define CMD_DATA_NULL 0xF0000003u
#define CMD_ERROR 0xF0000002u
#define CMD_ALREADY_DEBUG 0xF0000008u
#define CMD_VERSION 0xBD000001u
#define CMD_FW_VERSION 0xBD000500u
#define CMD_BRANDING 0xBD000501u
#define CMD_PLATFORM_ID 0xBD000502u
#define CMD_PROC_NOP 0xBDAACC06u
/* TURBOSCAN family (additive; legacy 0xBDAA0009 + async 0xBDAACC01/02/03 unchanged).
* Opcodes 0xBDAACC10..0xBDAACC15 are RAW LITERALS with NO CMD_* macro - the published
* CMD_* set that some clients enumerate must stay unchanged (see README), so new commands
* are dispatched by hex literal in the per-namespace switch:
* 0xBDAACC10 CAPS 0xBDAACC11 START 0xBDAACC12 COUNT
* 0xBDAACC13 GET 0xBDAACC14 END 0xBDAACC15 CONFIG (RAM threshold + spill dir) */
/* TURBOSCAN engine bits (CAPS response) */
#define TSE_SIMD_COMPARE 0x00000001u
#define TSE_ALIASING 0x00000002u /* Phase 3; advertised only when built+enabled */
#define TSE_SERVER_RESIDENT 0x00000004u /* Phase 2b: server-resident survivor sets */
#define TSE_SNAPSHOT 0x00000008u /* Phase 2c: unknown-value NVMe snapshot scans */
#define TSE_SNAPSHOT_SEGMENTS 0x00000010u /* server understands a trailing segment list (disjoint regions in one session): snapshot + resident START */
#define TSE_SNAPSHOT_CONFIG 0x00000020u /* CMD_PROC_TURBOSCAN_CONFIG (RAM threshold + spill dir) supported */
#define TSE_SNAPSHOT_FIRST 0x00000040u /* TS_SNAPSHOT_KEEP_FIRST + CC13 GET first-scan value (3-value records) supported */
#define TSE_SNAPSHOT_PREVIOUS 0x00000080u /* TS_SNAPSHOT_KEEP_PREVIOUS: CC13 GET "previous" = prior-scan value (not last-scan) */
#define TSE_PARALLEL_COMPARE 0x00000100u /* TS_PARALLEL_COMPARE supported (server-side multi-thread for the aliased SIMD exact-match streaming scan) */
#define TSE_RESCAN_ALIASING 0x00000200u /* TS_RESCAN_ALIASING supported (CC12 rescan reads full-size survivor windows via the aliasing engine) */
#define TSE_FLOAT_POLICY 0x00000400u /* TS_FLOAT_SIMPLE / TS_FLOAT_EXACT + packed exponent threshold supported */
#define TSE_COMPACT_SIMPLE_SNAPSHOT 0x00000800u /* TS_FLOAT_SIMPLE snapshots store survivor records instead of raw-slot-dense backing */
/* TURBOSCAN START/COUNT flags (per-request; opt-in). Phase 1 honored none. */
#define TS_USE_ALIASING 0x00000001u /* Phase 3 read engine (client opt-in) */
#define TS_SERVER_RESIDENT 0x00000002u /* Phase 2b: keep/rescan survivors server-side */
#define TS_SNAPSHOT 0x00000004u /* Phase 2c: unknown-initial-value snapshot scan */
#define TS_SNAPSHOT_INCLUDE_ZEROS 0x00000008u /* 2c snapshot: seed all-zero slots too; DEFAULT drops them */
#define TS_SNAPSHOT_SEGMENTS 0x00000010u /* START: cover a trailing segment list of disjoint regions (with TS_SNAPSHOT or TS_SERVER_RESIDENT; see below) */
#define TS_SNAPSHOT_KEEP_FIRST 0x00000020u /* START+TS_SNAPSHOT: retain an immutable first-scan value store so CC13 GET can return first (3-value records) */
#define TS_SNAPSHOT_KEEP_PREVIOUS 0x00000040u /* START+TS_SNAPSHOT: retain a prior-scan value store so CC13 GET "previous" = value at the previous scan (not the just-matched value) */
#define TS_PARALLEL_COMPARE 0x00000080u /* START+TS_USE_ALIASING, exact-match streaming: split the scan across worker threads server-side. For SINGLE-connection clients - multi-connection clients should fan out across connections instead (do NOT combine; over-subscribes) */
#define TS_RESCAN_ALIASING 0x00000100u /* CC12 (client opt-in): read full-size (bridging-dense) survivor windows via the aliasing engine instead of mdbg; tiny scattered windows + any alias failure fall back to mdbg (the floor). ~2-3x on dense/moderate rescans; single-connection clients (the survivor set is per-connection) - do NOT enable on many connections at once (over-subscribes the aliasing setup like TS_PARALLEL_COMPARE) */
#define TS_FLOAT_SIMPLE 0x00000200u /* float/double: retain only positive zero or values within the packed exponent distance of 1.0 */
#define TS_FLOAT_EXACT 0x00000400u /* exact-value float/double: numeric IEEE equality instead of the legacy fuzzy comparator */
#define TS_FLOAT_EXPONENT_SHIFT 16u /* TS_FLOAT_SIMPLE threshold: 7-bit unsigned exponent distance (1..127; 0 => 11) */
#define TS_FLOAT_EXPONENT_MASK 0x007F0000u
#define VALID_CMD(cmd) (((cmd) >> 24) == 0xBDu)
#define VALID_PROC_CMD(cmd) ((((cmd) >> 16) & 0xFFu) == 0xAAu)
#define VALID_DEBUG_CMD(cmd) ((((cmd) >> 16) & 0xFFu) == 0xBBu)
#define VALID_KERN_CMD(cmd) ((((cmd) >> 16) & 0xFFu) == 0xCCu)
#define VALID_CONSOLE_CMD(cmd) ((((cmd) >> 16) & 0xFFu) == 0xDDu)
struct cmd_packet {
uint32_t magic;
uint32_t cmd;
uint32_t datalen;
void *data;
} __attribute__((packed));
struct cmd_kern_read_packet {
uint64_t address;
uint32_t length;
} __attribute__((packed));
struct cmd_kern_write_packet {
uint64_t address;
uint32_t length;
} __attribute__((packed));
struct proc_list_entry {
char name[32];
int32_t pid;
} __attribute__((packed));
struct cmd_proc_alloc_packet {
uint32_t pid;
uint32_t length;
} __attribute__((packed));
struct cmd_proc_alloc_response {
uint64_t address;
} __attribute__((packed));
#define CMD_PROC_ALLOC_PACKET_SIZE 8
struct cmd_proc_alloc_hinted_packet {
uint32_t pid;
uint64_t hint;
uint32_t length;
} __attribute__((packed));
struct cmd_proc_free_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
} __attribute__((packed));
struct cmd_proc_install_packet {
uint32_t pid;
} __attribute__((packed));
struct cmd_proc_unknown_d_packet {
uint32_t pid;
} __attribute__((packed));
struct cmd_proc_read_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
} __attribute__((packed));
struct cmd_proc_read_stack_packet {
uint32_t pid;
uint64_t rbp;
uint64_t rsp;
uint32_t depth;
} __attribute__((packed));
#define CMD_PROC_READ_STACK_PACKET_SIZE 24
#define CMD_PROC_READ_STACK_CODE_OFF 10u
#define CMD_PROC_READ_STACK_CODE_LEN 200u
#define CMD_PROC_READ_STACK_LOCALS_CAP 0x1000u
#define CMD_PROC_READ_STACK_MAX_DEPTH 64u
#define CMD_PROC_ASSEMBLE_HDR_SIZE 12u
struct cmd_proc_assemble_packet {
uint64_t base_addr;
uint32_t ks_opt_syntax;
} __attribute__((packed));
struct cmd_proc_assemble_ok {
uint32_t byte_len;
uint32_t insn_count;
} __attribute__((packed));
struct cmd_proc_assemble_err {
uint32_t ks_errno;
uint32_t msg_len;
} __attribute__((packed));
struct cmd_proc_write_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
} __attribute__((packed));
/* Bulk write / freeze batching (opcode 0xBDAACC04). Header body below is
followed by `count` streamed entries, each { uint64 address; uint32 length;
<length> bytes }. flags bit 0 asks the server for a per-entry status byte
array sent just before the trailing CMD_SUCCESS; any nonzero byte is a
failure code below. Without that flag, any failed entry makes the trailing
status CMD_ERROR. */
struct cmd_proc_write_multi_packet {
uint32_t pid;
uint32_t count;
uint32_t flags;
} __attribute__((packed));
#define PROC_WRITE_MULTI_F_STATUS 0x1u
#define PROC_WRITE_MULTI_MAX_COUNT 0xFFFFu
#define PROC_WRITE_MULTI_MAX_ENTRY 0x100000u
#define PROC_WRITE_STATUS_OK 0u
#define PROC_WRITE_STATUS_INVALID 1u
#define PROC_WRITE_STATUS_DMAP_FAILED 2u
#define PROC_WRITE_STATUS_VERIFY_FAILED 3u
#define PROC_WRITE_STATUS_MDBG_FAILED 4u
struct cmd_proc_maps_packet {
uint32_t pid;
} __attribute__((packed));
struct cmd_proc_call_packet {
uint32_t pid;
uint64_t rpcstub;
uint64_t rpc_rip;
uint64_t rpc_rdi;
uint64_t rpc_rsi;
uint64_t rpc_rdx;
uint64_t rpc_rcx;
uint64_t rpc_r8;
uint64_t rpc_r9;
} __attribute__((packed));
struct sys_proc_call_args {
uint32_t pid;
uint64_t rpcstub;
uint64_t rax;
uint64_t rip;
uint64_t rdi;
uint64_t rsi;
uint64_t rdx;
uint64_t rcx;
uint64_t r8;
uint64_t r9;
} __attribute__((packed));
struct cmd_proc_call_response {
uint32_t pid;
uint64_t rpc_rax;
} __attribute__((packed));
struct cmd_proc_elf_packet {
uint32_t pid;
uint32_t length;
} __attribute__((packed));
struct proc_vm_map_entry {
char name[32];
uint64_t start;
uint64_t end;
uint64_t offset;
uint16_t prot;
} __attribute__((packed));
struct cmd_proc_scan_start_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
uint8_t valueType;
uint8_t compareType;
uint8_t alignment;
uint32_t lenData;
} __attribute__((packed));
struct cmd_proc_scan_count_packet {
uint32_t pid;
uint64_t base_address;
uint8_t valueType;
uint8_t compareType;
uint32_t lenData;
} __attribute__((packed));
/* TURBOSCAN START request: mirrors cmd_proc_scan_start_packet + a flags field.
* Result wire format is identical to the async START (batched (offset,value)
* runs, 8-byte 0xFFFF... sentinel) so clients reuse their result parser. */
struct cmd_proc_turboscan_start_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
uint8_t valueType;
uint8_t compareType;
uint8_t alignment;
uint32_t lenData;
uint32_t flags; /* TS_* */
} __attribute__((packed));
/* TURBOSCAN config (CC15): set the snapshot value-store RAM threshold + spill directory
* (global; affects the next snapshot create). Body = this header + spill_path_len path
* bytes (no NUL). ram_thresh_mb 0 => reset to the 512MB default; spill_path_len 0 =>
* reset spill dir to /data. Reply: CMD_SUCCESS. Advertised via TSE_SNAPSHOT_CONFIG.
* ram_thresh_mb is the amount FULLY COMMITTED to RAM: a store <= it is pure anon RAM;
* a larger store is HYBRID - the first ram_thresh_mb (whole slots) is cached in anon
* RAM and only the overflow spills to the file (the kernel page cache may keep extra
* spill pages warm on top). So raising it commits more RAM up front but a too-large
* scan no longer goes 100% to disk. */
struct cmd_proc_turboscan_config_packet {
uint32_t ram_thresh_mb; /* RAM committed to the value store; overflow spills to a file */
uint32_t spill_path_len; /* length of the trailing spill-directory string (<=63) */
} __attribute__((packed));
struct cmd_proc_turboscan_caps_response {
uint32_t version; /* turboscan protocol version (1) */
uint32_t engines; /* TSE_* bitmask of supported engines */
uint32_t max_threads; /* worker threads the compare stage will use */
uint32_t reserved;
} __attribute__((packed));
/* Region-classify query (opcode 0xBDAACC16, raw literal). Returns every readable
region of the target with its cache attribute + a measured read throughput, so the
client can let the user exclude uncached/slow regions (e.g. the GPU/Garlic blob).
The server does NOT drop anything - exclusion is the client's choice. */
struct cmd_proc_turboscan_regions_packet {
uint32_t pid;
uint32_t max; /* cap on regions returned (0 => server default) */
uint32_t probe_bytes; /* bytes probe-read per region to measure MB/s (0 => 64 KiB) */
uint32_t reserved;
} __attribute__((packed));
struct cmd_proc_turboscan_region_info {
uint64_t start;
uint64_t end;
uint32_t prot; /* protection bits (low 3: read/write/exec) */
uint32_t flags; /* bit0 = leaf PTE PCD=1 (uncached / slow) */
uint32_t mbps; /* measured read throughput, whole MB/s */
uint32_t reserved;
} __attribute__((packed)); /* 32 bytes */
/* TURBOSCAN rescan (COUNT) request: mirrors cmd_proc_scan_count_packet + a flags
* field (forward-compat for a Phase-3 aliasing rescan). The streaming wire format
* (per-chunk candidate offsets in, batched (offset,value) runs + 0xFFFF... per-chunk
* sentinel out, final CMD_SUCCESS) is identical to the async COUNT so clients reuse
* one parser. Phase 2 echoes `flags` and honors none. */
struct cmd_proc_turboscan_count_packet {
uint32_t pid;
uint64_t base_address;
uint8_t valueType;
uint8_t compareType;
uint32_t lenData;
uint32_t flags; /* TS_* */
} __attribute__((packed));
/* TURBOSCAN server-resident replies/requests (Phase 2b). When a START/COUNT carries
* TS_SERVER_RESIDENT, the survivor set lives in a per-connection mmap buffer instead
* of being streamed. START replies this summary (and streams NO result runs) unless
* resident_stored==0 (overflow / mmap-fail / unsupported) in which case the full
* (offset,value) result runs follow exactly as the non-resident path.
*
* Multi-segment resident (START with TS_SERVER_RESIDENT|TS_SNAPSHOT_SEGMENTS, TS_SNAPSHOT
* clear): after the 2nd ack the client streams a segment list (u32 segment_count, then
* segment_count x cmd_proc_turboscan_snap_segment) IDENTICAL to the snapshot sub-protocol;
* the packet address/length are ignored. The server scans each disjoint segment, appending
* matches into the one session buffer; survivors are stored as absolute [u64 addr][value]
* so CC12 narrow / CC13 GET are geometry-independent. On resident_stored==1 the reply is
* the same 12-byte summary (count = total across segments). On resident_stored==0 (overflow
* / malformed segment_count / OOM) there is NO transparent stream fallback (a u32 offset
* cannot carry >4GB-scattered absolute addresses): the framing stays byte-identical - the
* summary{0,0} is followed by an EMPTY run stream (an immediate 0xFFFF.. sentinel) and the
* final CMD_SUCCESS - and the client falls back to its own per-section streaming. */
struct cmd_proc_turboscan_resident_summary {
uint32_t resident_stored; /* 1 = kept server-side; 0 = runs streamed next */
uint64_t count; /* survivors stored (valid iff resident_stored==1) */
} __attribute__((packed));
/* TURBOSCAN snapshot (CC11 + TS_SNAPSHOT) reply, after the two leading CMD_SUCCESS:
* plan { slot_count, total_bytes } -- up front so the UI can show progress
* progress: repeated uint64 bytes_done records, terminated by an 8-byte 0xFFFF..
* summary { snapshot_ok, survivor_count } -- snapshot_ok=0 => ENOSPC/fail, client
* falls back to its client-driven unknown-value path
* final CMD_SUCCESS.
* Subsequent narrowing uses CC12 + TS_SERVER_RESIDENT (rescans the snapshot in place,
* since-last-scan), and CC13 GET fetches survivors. */
/* TURBOSCAN multi-segment snapshot (CC11 + TS_SNAPSHOT + TS_SNAPSHOT_SEGMENTS).
* After the two leading CMD_SUCCESS acks and the value/mask trailing data, the client
* streams: uint32 segment_count, then segment_count x this struct. The snapshot then
* covers exactly those (disjoint) regions as one resident session - the slot space is
* the concatenation of the segments, so unmapped gaps between them are never read and
* storage scales with selected bytes, not with the bounding span. The packet's
* `address`/`length` are ignored in this mode. survivor addresses are absolute, so
* CC12 narrow / CC13 GET / materialize are unaffected. Without the flag the snapshot
* covers the single [address, address+length) range exactly as before. */
struct cmd_proc_turboscan_snap_segment {
uint64_t address;
uint32_t length;
} __attribute__((packed));
struct cmd_proc_turboscan_snap_plan {
uint64_t slot_count;
uint64_t total_bytes;
} __attribute__((packed));
struct cmd_proc_turboscan_snap_summary {
uint32_t snapshot_ok;
uint64_t survivor_count;
} __attribute__((packed));
/* TURBOSCAN_GET (CC13): fetch resident survivors [start_index, start_index+count).
* Reply: uint32 hdr, then (hdr & 0x7FFFFFFF) records, then CMD_SUCCESS.
* hdr low 31 bits = actual_count (<= requested); hdr bit 31 = records carry the
* first-scan value (3-value shape). Record:
* bit31 clear: { uint64 addr; value current; value previous } (8 + 2*value_length)
* bit31 set: { uint64 addr; value current; value previous; value first } (8 + 3*value_length)
* current = the matched (last-scan) value from the session value store, NOT a live re-read
* (matches every other scan path; the client refreshes to live itself); previous = the prior-scan value (the value at the scan
* BEFORE the most recent one) when the session was started with TS_SNAPSHOT_KEEP_PREVIOUS,
* else the last-scan value (the snapshot/list value store, ~= current); first = value at
* snapshot create, present only for a session started with TS_SNAPSHOT_KEEP_FIRST (else
* bit 31 is clear and there is no first field).
* valueType/value_length come from the session. */
struct cmd_proc_turboscan_get_packet {
uint32_t start_index;
uint32_t count;
uint32_t flags;
} __attribute__((packed));
struct cmd_proc_scan_get_packet {
uint32_t pid;
uint32_t count;
} __attribute__((packed));
struct cmd_proc_scan_aob_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
uint8_t max_matches;
uint8_t stop_flag;
uint32_t pattern_length;
} __attribute__((packed));
struct cmd_proc_scan_aob_multi_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
uint8_t stop_flag;
uint32_t patterns_length;
} __attribute__((packed));
struct cmd_proc_scan_packet {
uint32_t pid;
uint8_t valueType;
uint8_t compareType;
uint32_t lenData;
} __attribute__((packed));
struct cmd_proc_elf_rpc_packet {
uint32_t pid;
uint32_t length;
} __attribute__((packed));
#define CMD_PROC_ELF_RPC_PACKET_SIZE 8
struct cmd_proc_elf_rpc_response {
uint64_t entry;
} __attribute__((packed));
#define CMD_PROC_ELF_RPC_RESPONSE_SIZE 8
struct cmd_proc_disasm_packet {
uint32_t pid;
uint64_t address;
uint32_t length;
uint32_t max_entries;
} __attribute__((packed));
#define CMD_PROC_DISASM_PACKET_SIZE 20
struct cmd_proc_xrefs_to_packet {
uint32_t pid;
uint64_t scan_address;
uint32_t scan_length;
uint64_t target_address;
} __attribute__((packed));
#define CMD_PROC_XREFS_TO_PACKET_SIZE 24
struct disasm_instr_entry {
uint64_t addr;
uint64_t rip_rel_target;
int64_t mem_disp;
uint8_t length;
uint8_t kind;
uint8_t mem_base_reg;
uint8_t mem_index_reg;
uint8_t mem_scale;
uint8_t mnemonic_lo;
uint16_t pad;
} __attribute__((packed));
#define DISASM_INSTR_ENTRY_SIZE 32
#define CMD_PROC_AUTH_MAGIC 0xBB40E64Du
#define CMD_PROC_AUTH_MAGIC_BSWAP 0x7780D98Eu
struct cmd_proc_auth_packet {
uint32_t magic;
uint32_t flags;
} __attribute__((packed));
struct cmd_proc_info_packet {
uint32_t pid;
} __attribute__((packed));
struct cmd_proc_info_response {
uint32_t pid;
char name[40];
char path[64];
char titleid[16];
char contentid[64];
} __attribute__((packed));
#define CMD_DEBUG_ATTACH 0xBDBB0001u
#define CMD_DEBUG_SET_BREAKPOINT 0xBDBB0003u
#define CMD_DEBUG_SET_WATCHPOINT 0xBDBB0004u
#define CMD_INVALID_INDEX 0xF000000Au
#define MAX_BREAKPOINTS 30
#define MAX_WATCHPOINTS 4
struct cmd_debug_breakpt_packet {
uint32_t index;
uint32_t enabled;
uint64_t address;
} __attribute__((packed));
#define CMD_DEBUG_BREAKPT_PACKET_SIZE 16
struct cmd_debug_watchpt_packet {
uint32_t index;
uint32_t enabled;
uint32_t length;
uint32_t breaktype;
uint64_t address;
} __attribute__((packed));
#define CMD_DEBUG_WATCHPT_PACKET_SIZE 24
struct cmd_debug_attach_packet {
uint32_t pid;
} __attribute__((packed));
#define CMD_DEBUG_ATTACH_PACKET_SIZE 4
#endif