The installer kpatch address is security_flags+0x24, so 13.40 and 13.42 join
the existing 13.0x case while 13.60 gets its own at 0xD9C088. Folding 13.60
into the shared case would have OR-ed the patch bits into the wrong kernel
address, the same failure mode as the ps4 10.50/10.70 dispatch mis-route.
Also add the three firmwares to fw_uses_kernel_dbreg_path(), otherwise
hardware breakpoints and watchpoints silently do nothing on them, and list
them in the README firmware table.
The remaining firmware-dependent code is threshold-based and already selects
the right values for 13.4x/13.6x, and the page-table walker discovers pm_pml4
at runtime, so nothing else needed changing.
The event dispatcher's internal DBREG accesses clear the live DR6, hiding which watchpoint fired from the client. Snapshot the event DR6 and re-apply it to the outgoing packet, except on single-step events. Also clear the watchpoint/debug-register shadow on every teardown path, matching the breakpoint-slot clearing, so a reattaching connection cannot inherit stale watchpoint state.
Branding advertises capability level 1.1 (content-verified writes) and now carries a 16-hex build fingerprint injected by build.sh and the debugger/installer Makefiles, so a staged ELF is identifiable from CMD_BRANDING.
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.
Reject arming at address 0; make re-arming the same slot at the same address idempotent so the already-patched 0xCC is never saved as the restore byte; require an explicit disarm before moving an enabled slot to a new address; enforce one INT3 owner per address across slots. Clear every breakpoint slot on all teardown paths, including the dead-target liveness-failure exit, so a same-connection re-attach cannot match stale slot state.
The increased-by (cmpType 6) and decreased-by (cmpType 8) comparators
read the user-supplied double delta as a 4-byte float instead of the
full 8-byte double, so round deltas like 1.0 (whose low 32 bits are 0)
collapsed to 0.0 and the scan behaved like "unchanged".
Read the delta as a full double in:
- scan_compare_simd.c _SPC_BY case 9 (Turbo Scan comparator)
- scan_compare.c _PER_VALTYPE_BY case 9, which backs both
proc_scan_legacy_compareValues and proc_scan_compareValues, so this
repairs the legacy and regular/async scan+rescan paths as well.
Client wire format is unchanged (already sends an 8-byte double delta).
The default-ON alloc arena reserved a full 16 MB RWX segment on the first plain alloc, then handed out the small request. On a game whose executable/JIT memory pool is nearly full (measured ceiling ~2 MB RWX), the 16 MB reservation fails and proc_alloc returns CMD_ERROR, so a client's attach-time AllocateMemory throws (libdbg status F0000001). 1.2.7 was unaffected because it asked the kernel for exactly the requested size.
Make the arena grow on demand: the first segment for a pid is exactly the request size (a single small hijack, same exposure as the pre-arena path), then segments grow geometrically up to the 16 MB cap as a pid keeps allocating, so heavy clients still amortize. A single-shot miss retreats to the request size and freezes growth for that pid - never a speculative failing probe (each such probe is a target hijack that can catch a lock-holding thread and crash the game).
HW-validated FW 9.00: the previously-failing trainer attach now succeeds.
Abort an in-flight TURBOSCAN START/COUNT for a pid via a pid-scoped flag the scan loops poll (all parallel workers observe it) and stop early, emitting their normal terminator. Sent from a second connection since the scanning connection is busy streaming. A cancelled snapshot create frees the half-built snapshot and reports snapshot_ok=0; known-value START and COUNT rescan return partial results (session left indeterminate - END or restart before trusting). START/COUNT clear a stale same-pid flag on entry so a scan is never pre-cancelled. Auth bit 1 required.
Bumps version 1.3.0 -> 1.3.1 and documents the opcode in PROTOCOL.md/README. Raw-literal opcode (no CMD_* macro), per the enumerated-set rule.
New per-thread segment-base access, HW-validated on FW 9.00 (read and write):
- 0xBDBB000E GET_FSGS_BASE: 16-byte {fs_base, gs_base} read from the pcb
- 0xBDBB000F SET_FSGS_BASE: writes fs/gs base into the pcb
(pcb_fsbase @ pcb+0x40, pcb_gsbase @ pcb+0x48; kernel-path only, no ptrace)
GETFPREGS/SETFPREGS now read and write the full 832-byte xsave image via the
kernel pcb_save area (pcb+0x148) so the YMM (AVX) upper halves are populated
and applied, falling back to PT_GET/SETFPREGS for the 512-byte FXSAVE path.
SET handlers memset their buffer and bail on a short net_recv_all before
writing, so a malformed SET cannot push garbage into the pcb/xsave.
Docs: PROTOCOL.md (two command sections, table rows, YMM notes on GET/SETFPREGS,
corrected debug.c line refs, opcode count to 69) and README.md (register
capability line, namespace command counts).
proc_alloc hijacks an arbitrary game thread and single-steps it through mmap;
if that thread holds a non-sleeping kernel lock, the step's copyin trips the
kernel WITNESS ("copyin: eboot.bin has nonsleeping lock") and crashes the game.
Concurrent hammering accumulates and makes it near-certain.
Serve plain allocations from a per-pid 16MB arena: one hijacking mmap per
segment, then zero-hijack sub-allocation with free-list recycling. This cuts
hijacks from thousands to about one. Default on and transparent to clients
(same wire, valid target addresses); a freed sub-block is recycled rather than
munmap'd and reclaimed when the game exits. Hinted allocations (which need a
specific address) pass straight through. Cap the real-alloc retry storm from
100 to 5. Runtime toggle via raw literal 0xBDAACC24.
HW-validated on FW 4.51: procalloc_stress 3 conns x 2000 iters (6000 cycles)
completes with the game alive (previously a reliable crash at 6-120 hijacks);
smoke 18/18 green.
Additive, capability-gated turbo memory-scan path (CC10-CC16); the legacy and
async scan paths stay byte-for-byte unchanged.
- SIMD typed comparator (scan_compare_simd.c) + CC10 caps + CC11 first scan.
- CC12 local-cluster rescan; server-resident survivor sets (CC13 GET / CC14
END); unknown-value bitmap + NVMe snapshot with since-last narrowing and a
materialize-when-sparse handoff to a compact record list.
- Aliasing read engine (scan_alias.c): maps target physical pages into our pmap
via guarded not-present leaf-PTE writes, scanning in place at DRAM bandwidth.
Unified per-2MB-span derivation (proc_ptwalk_span_resolve) makes it page-size
agnostic; verify-before-trust + restore + verify-before-munmap reclaim keep it
safe-to-fail. Opt-in (TSE_ALIASING + TS_USE_ALIASING, default off); mdbg is the
floor. Verifies 1-in-16 maps (derivation is byte-proven, guards prevent faults
regardless) - ~30% faster than verify-every-map.
- Opt-in server-side parallel compare for the aliased exact-match streaming scan
(TSE_PARALLEL_COMPARE + TS_PARALLEL_COMPARE, default off) - for single-
connection clients; multi-connection clients fan out across connections instead.
- Region-parallel multi-pattern AOB scan (CMD_PROC_SCAN_AOB_MULTI, aob_scan.h):
splits the range into per-worker sub-ranges, each scanned once for all patterns
via a shared first-byte dispatch table. Wire and results unchanged.
- Region-classify query (0xBDAACC16): per-region cache attribute (uncached PCD
bit) + measured read throughput, so a client can opt out of slow/uncached
regions. Read-only; the server drops nothing.
HW-validated on FW 9.00.
The variadic __crt_syscall reads each argument as a 64-bit long; a bare int
(e.g. the -1 mmap fd) leaves the upper 32 bits undefined and can fail
call-site-dependently. Route the remaining bare sites through the typed
ps5debug_syscall wrapper (behavior-preserving): net.c select(93), debug.c
wait4(7), proc_remote.c ptrace(26) + wait4(7), installer find_pid.c
sysctl(202), installer proc_remote.c ptrace(26).
- New 0xBDAACC04 bulk-write command (proc.c proc_write_multi_handle), dispatched
raw (no name macro). Collapses N CMD_PROC_WRITE round-trips into one: header
{pid,count,flags} then count streamed {u64 addr,u32 len,bytes} entries; two
CMD_SUCCESS words. Interleaved streaming keeps server memory bounded to one
64KiB scratch. Not auth-gated (mirrors single write). Caps: count<=0xFFFF,
entry<=0x100000 -> CMD_ERROR + abort. Opt-in per-entry status byte array via
flags bit 0 (PROC_WRITE_MULTI_F_STATUS), using a returning proc_write_mem
variant (ptwalk ret + mdbg bytes-written); single write + breakpoint callers
untouched.
- CMD_BRANDING (meta.c) now returns brand + NUL + capability level "1.0" so
capability-aware clients gate features without probing. Split literal
"\0" "1.0" (not octal \01).
- version.h 1.2.7 -> 1.2.8. PROTOCOL.md documents both.
HW-validated (wire test). Sources not yet comment-stripped (pre-push step).
Rescanning with a "between (A to B)" comparison crashed the console
(SceShellCore SIGSEGV -> kernel panic): proc_scan_count_handle passed
prevValue = NULL, but the async "between" comparator reads its upper bound
from prevValue. Long-standing since v1.2.2; only the async rescan + between
combo triggers it.
- scan.c: for "between", point prev at the upper bound in the scan-value
buffer (pattern + value_length), as proc_scan_start_handle already does.
- scan_compare.c: harden both comparators - cmpType 4-10 return false instead
of dereferencing a NULL prev/extra operand.
- Strip stray comments re-introduced this session (proc.c/scan.c/scan_compare.c)
to match the public-release convention.
Bump version 1.2.6 to 1.2.7.
Pin LF endings for our C/H/S/Makefile/Markdown/shell/Python files so edits no longer produce spurious whole-file CRLF/LF diffs across machines or editors. Converts the 6 previously-CRLF tracked files to LF (PROTOCOL.md, README.md, debugger/Makefile, debugger/source/proc_remote.c, installer/Makefile, installer/source/main.c). Vendored trees (ps5-payload-sdk, third_party, debugger/third_party) are marked -text and left byte-for-byte unchanged. EOL-only change; no code changes.
Makes the "initial" 8-13.x support from v1.2.5 actually functional.
HW-validated on FW 8.xx, 9.xx, 10.xx and 12.xx (all watchpoint types,
breakpoints, title id, content id, version etc.). FW-agnostic by design; 11/13.x
are lower-risk but not tested.
Memory write (poke) on FW >= 8.40:
- mdbg write op (0x13) is kernel-gated with EPERM on FW >= 8.40, so pokes
failed. Route writes through a DMAP page-table walk (kernel_copyin on the
physical alias) on FW >= 8.40; mdbg stays the path on < 8.40 and as a
fallback.
- PROC_ALLOC pre-faults its pages so writes land on freshly-allocated
demand-zero memory.
Breakpoints / watchpoints on FW 9.x-13.x:
- Extend the kernel-dbreg allowlist to 9.00-13.20; HW BP/WP were silently
no-oping on 9.x+.
- SW (INT3) breakpoints write the 0xCC via the DMAP path (mdbg rejected it
on FW >= 8.40).
- Clear DR6 after each debug exception: FW 11/12 stops re-delivering a
HW BP/WP trap after a few hits when the sticky DR6 status is left set;
plus re-arm the debug regs for data watchpoints.
Game ID / Content ID / Version - firmware-agnostic:
- Resolve title id and content id by pattern-scanning the proc struct
instead of per-FW offsets (which shifted at 8.x and 12.x).
- Foreground-app command resolves the title id robustly so the param.sfo
game version reads correctly on 12.x.
Console crash on client disconnect (SIGPIPE):
- Closing the client mid-operation raised SIGPIPE in the server thread and
killed the host SceShellCore. Suppress it: MSG_NOSIGNAL on sends +
SO_NOSIGPIPE on the sockets.
Load notification now shows the running firmware version, and the credits
line was updated.
Documentation:
- Rewrote PROTOCOL.md against the v1.2.6 sources (commands, packet structs,
status/bit-swap semantics, scan/xref wire formats, enums).
- Corrected and expanded README: per-release firmware support table with
verification status, and accuracy fixes throughout.
Bump version 1.2.5 to 1.2.6.
vm_map nentries/name adjustments were gated to explicit 6/7.0x/8.x cases, so
7.5x (7.20-7.61) and 9.x+ got no adjustment. That broke locating the inject
target on install and left PROC_INFO/PROC_LIST/PROC_MAPS names empty/truncated.
Switch all three sites (installer proc_elf, debugger proc_elf, scan) to a
>= 6.x threshold.
- find_pid: locate SceShellCore via sysctl KERN_PROC_PID/ki_comm
(firmware-agnostic) instead of a per-FW struct proc name offset.
- install_kernel_patch: skip the write when the jailbreak already set the
bits, preventing a kernel panic on 7.61.
- proc_field_offsets: switch to threshold offsets (corrects 7.5x and adds
10/11/12.x).
The struct proc name/path/titleid/contentid offsets were hardcoded to the
4.x layout with no firmware gating, so on 6.x and newer they read empty or
garbage: PROC_INFO/PROC_LIST returned blank names and the installer failed
with "SceShellCore not found" on 6.x.
Restore the original's per-firmware selection (three offset sets across
FW 3.x-8.x) behind a shared proc_get_field_offsets() helper that switches on
kernel_get_fw_version(); unsupported firmwares report unknown rather than
emitting wrong offsets. Wired into find_pid, proc_info_handle/proc_list, and
console_foreground_app_handle.
Features
- Option E: CMD_PROC_MAPS now augments the vm_map list with Sony-aux regions
(SceGnm/SceAgc/JIT/DMEM) that bypass vm_map, discovered via a kernel-side
x86-64 page-table walk over the DMAP. FW-agnostic (runtime pmap-offset
discovery + ground-truth validation); falls back to the plain vm_map result
on any failure.
- Hybrid memory-read path: reads route DMAP-direct for vm_map-absent (aux)
regions and mdbg otherwise (and as fallback), making aux scans ~4x faster on
both the CMD_PROC_READ and server-side scan paths. No protocol change;
byte-identical results.
Fixes
- SW breakpoint wait4-race: eliminate the multi-threaded epilogue-BP SIGSEGV
storm (stale TRAP_TRACE/TRAP_BRKPT absorbers, PT_LWPINFO-gated sibling
rewind, RIP-only fast caches, authid bump around rearm, SIGSEGV-storm guard).
- AOB-multi scan: unique-mode now exits only on invalidated >= pat_count
(matches the original), fixing wrongly-reported-unique / truncated scans.
Performance / robustness
- BP-event latency: select timeout 1000->100 us, poll 100->50 us.
- Per-command debug-state locking (0xBDBB) to prevent BP-slot-table corruption
under concurrent clients.
- Installer: double-load guard (loopback 744 probe), relocation pre-apply plus
batched ptrace_io for faster injection, wait4 instead of a fixed sleep.
Refactor
- Firmware detection rewritten to a direct switch(fw & 0xffff0000) with
per-case version annotations; behavior-preserving (verified equivalent).
Version 1.2.3 -> 1.2.4.
- dispatch_debug_events: drop the function-wide elev_save_and_set
wrapping; macro DDE_RETURN simplified to return (v).
- debug_attach_handle: release elevation before wait4 + resume_app,
re-acquire only for PT_CONTINUE.
- debug_full_teardown / debug_set_watchpoint_handle: use new
resume_app_unelev helper so the libk LNC notification fires
outside the elevated cred window.
- common/include/version.h: single source of truth for the
brand/version strings used in meta.c (handle_branding) and
main.c (loaded_msg).
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.