34 Commits
Author SHA1 Message Date
Pharaoh2k 938d1a7350 Update README 2026-09-23 18:46:11 +07:00
Pharaoh2k 3d14b04755 Update README
Added a section for the official PlayStation 4 version and updated the Discord server heading.
2026-09-23 18:45:38 +07:00
Pharaoh2k fc9aef98f3 Add FW 13.40/13.42/13.60 kpatch addresses, dbreg support and docs
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.
2026-09-23 03:03:38 +00:00
Pharaoh2k bdc1ba7780 vendored SDK: add FW 13.40/13.42/13.60 kernel offsets
Additive and FW-gated, so every previously supported firmware is untouched.
13.40 and 13.42 share a case; 13.60 needs its own because its kernel data
layout shifted: the text/data delta grows to 0x0CC0000 and security_flags,
rootvnode and bus_data_devices all move.

Every value was derived from the decrypted kernel dumps by static analysis
rather than copied, using FW 12.00 as a control that the method had to
reproduce first: allproc via the intersection of the process-list-walking
syscalls (discriminated from allproc_lock by its insert/remove writes),
security_flags via the flag-bit test histogram, rootvnode via sys_is_in_sandbox,
and bus_data_devices via masked code signatures transferred from the control.
kdata_base is the read-only data segment base, which is what pins the text
delta. vmspace p_root stays 0x1d0, confirmed from the vm_map function cluster.
2026-09-23 03:03:24 +00:00
Pharaoh2k 2e9dff1442 Preserve watchpoint DR6 across internal debug-register writes
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.
2026-07-29 10:47:42 +00:00
Pharaoh2k 6badda5248 Track write-path policy header for content-verified writes
proc.c, sys_proc_rw.c, both kern_rw_fast.c and proc_ptwalk.c include write_policy.h, but it was left untracked when that feature was committed, so a fresh checkout did not build. Add it: it defines the auto/DMAP/mdbg write-path selection (PS5DEBUG_FORCE_WRITE_PATH), the 0xFF adjacent-byte prefix helper, and the diagnostics toggle.
2026-07-29 10:47:33 +00:00
Pharaoh2k 23f1471e62 Document content-verified writes and capability 1.1
Update PROTOCOL.md and README for the verified-write semantics, the per-entry write-multi status codes, the 0xFF adjacent-byte behavior, and the v1.3.2 / capability 1.1 / build-fingerprint branding.
2026-07-28 12:32:09 +00:00
Pharaoh2k 10c42774f1 Bump to v1.3.2 with capability 1.1 and embed a build fingerprint
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.
2026-07-28 12:32:02 +00:00
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
Pharaoh2k 5fc368f6fd Use RWX protection for ELF injection scratch mappings 2026-07-23 14:12:23 +00:00
Pharaoh2k 4bb24e4844 Guard PS5 release build provenance 2026-07-22 19:23:53 +00:00
Pharaoh2k 7f667b7775 Implement compact Simple float snapshots 2026-07-22 19:23:20 +00:00
Pharaoh2k 23b27a7780 Harden software breakpoint arming against 0xCC corruption
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.
2026-07-22 06:01:12 +00:00
Pharaoh2k 2fc77a2e25 Fix debugger RPC deadlock on stopped targets 2026-07-22 03:37:17 +00:00
Pharaoh2k 1f6db9df56 Fix double increased-by/decreased-by delta truncated to float
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).
2026-07-21 16:38:26 +00:00
Pharaoh2k 8ebdbf9ab8 Fix arena over-reservation crash on exec-tight games (grow-on-demand)
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.
2026-07-06 14:48:45 +00:00
Pharaoh2k 1ce0bdf627 Add Turbo Scan cancel command 0xBDAACC17 (v1.3.1)
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.
2026-07-06 14:48:31 +00:00
OpenSourcereR-dev d32d2d001d Merge pull request #19 from Pharaoh2k/master
Long due shit
2026-06-21 14:24:06 +07:00
Pharaoh2k 1a18f94eaf Add FS/GS base get/set commands and make GET/SETFPREGS YMM-aware
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).
2026-06-21 13:53:15 +07:00
Pharaoh2k 43f715975d Fix proc_alloc concurrency crash with a server-side alloc arena
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.
2026-06-20 19:22:45 +07:00
Pharaoh2k 5fbdd894ee Add TURBOSCAN scan command family + aliasing read engine (v1.3.0)
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.
2026-06-20 00:48:15 +07:00
Pharaoh2k 712fe0466b Harden raw syscalls: route bare __crt_syscall sites through ps5debug_syscall
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).
2026-06-16 03:10:39 +07:00
Pharaoh2k fedbd4f6d7 v1.2.8: bulk write CMD 0xBDAACC04 + branding capability level "1.0"
- 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).
2026-06-15 03:32:03 +07:00
OpenSourcereR-dev f8456241fc README: mark all firmware families Fully Verified (all rig-tested, no known issues) 2026-06-07 09:58:07 +00:00
OpenSourcereR-dev 14fc8c16b5 v1.2.7: fix console crash on "between" rescan (NULL deref in async scan)
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.
2026-06-03 13:07:18 +00:00
OpenSourcereR-dev 81b8e07fd9 Add .gitattributes; normalize our sources to LF
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.
2026-06-03 07:55:57 +00:00
OpenSourcereR-dev 72eb2104c7 v1.2.6: FW 8-13.x poke/breakpoints/watchpoints + FW-agnostic title/content/version + SIGPIPE crash fix
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.
2026-06-03 07:20:46 +00:00
OpenSourcereR-dev be05448527 Bump version to 1.2.5 2026-05-31 13:17:13 +00:00
OpenSourcereR-dev 19f0ce87ff README: document FW 8-13.x support (initial, testing required) 2026-05-31 13:04:12 +00:00
Echo Stretch 4c3c86913a Add FW 9-13.x kpatch addresses and 12.x path offset (#9)
9-13.x kernel-patch addresses in install_kernel_patch, plus the 12.x proc path offset (0x604). SDK externalization from the original PR was dropped to keep the repo self-contained.
2026-05-31 18:55:22 +07:00
OpenSourcereR-dev 36b858471f vendored SDK: add FW 13.x kernel offsets (additive, FW-gated)
Backport the FW 13.00/13.20 case from upstream ps5-payload-sdk into our
vendored crt/kernel.c: kernel base/allproc/security-flags/rootvnode/
bus-data offsets, plus the KERNEL_OFFSET_UCRED_CR_PRISON constant and the
kernel_get_ucred_prison() helper used to derive PRISON0 at runtime (upstream
dropped the per-FW PRISON0 offset).

Purely additive and gated to fw == 0x13xxxxxx, so 1.x-12.x are unaffected by
construction. 13.x itself is not yet hardware-validated.
2026-05-31 11:40:18 +00:00
OpenSourcereR-dev 8a81f37f9f Add EchoStretch and Sonic-Iso to credits banner 2026-05-31 11:40:17 +00:00
OpenSourcereR-dev 89d08fe135 Fix FW 7.5x (and 9-12.x) support: per-FW vm_map offsets + sysctl find + kpatch guard
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).
2026-05-31 05:20:19 +00:00
OpenSourcereR-dev 6b3cc3a216 Fix per-firmware proc appinfo offsets (name/path/titleid/contentid)
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.
2026-05-31 02:16:51 +00:00
40 changed files with 8143 additions and 2408 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# Line-ending policy.
# Our own sources and docs are normalized to LF (stored in-repo as LF and
# checked out as LF) so edits no longer produce spurious whole-file CRLF/LF
# diffs across machines/editors.
*.c text eol=lf
*.h text eol=lf
*.S text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.py text eol=lf
Makefile text eol=lf
# Vendored trees are left exactly as committed (no EOL normalization), to keep
# them byte-for-byte identical to upstream and protect byte-exact rebuilds.
ps5-payload-sdk/** -text
third_party/** -text
debugger/third_party/** -text
+1362 -837
View File
File diff suppressed because it is too large. Load diff
+493 -381
View File
@@ -1,381 +1,493 @@
# ps5debug-NG
A debugger payload for jailbroken PlayStation 5 consoles. Ships a userland
command server that runs inside SceShellCore, letting remote clients inspect
and manipulate running processes, the kernel itself, and the system UI over a
simple TCP protocol.
ps5debug-NG is inspired by Ctn's `ps5debug 1.0b5` and is wire-compatible with
it - existing clients should work without modification. It is licensed under
GPL-3.
Discord Server: [Team Reaper](https://discord.gg/7bjtgZf4PY)
---
## Supported firmwares
The kernel kpatch routine in [installer/source/main.c](installer/source/main.c)
recognises the following firmware families. Booting on an unsupported FW prints
`port_outer: kpatch SKIP - unsupported FW magic 0x...` to the kernel log and
aborts cleanly.
| Family | Label in code |
|--------------------|----------------------------|
| 3.xx | `FW 3.x` |
| 4.xx | `FW 4.x` |
| 5.xx | `FW 5.x` |
| 6.xx | `FW 6.x` |
| 7.xx | `FW 7.x` |
| 7.5x | `FW 7.5x` |
Each family covers several point releases - see the switch in
[installer/source/main.c](installer/source/main.c) for the exact set of FW
magic values recognised per family. Clients can read the running FW with
`CMD_FW_VERSION` (returns the kernel's raw FW magic word).
---
## Primary Features
### Process inspection and manipulation
- **Enumerate processes** (`p_comm` + pid list).
- **Read and write target memory** in streamed chunks.
- **List virtual memory maps** - ranges, protections, backing names.
- **Query process metadata** - name, path, titleId, contentId.
- **Identify the foreground app** (`0xBDDD0006`) - returns pid + titleid +
contentid + process name + the game's version, parsed server-side from the
title's `param.sfo`. Useful for clients that need to know what's currently
running without listing every process.
- **Server-side stack walk** (`CMD_PROC_READ_STACK`) - the server walks the
RBP chain itself (up to 64 frames) and bundles each frame's saved-RBP,
return address, frame-local bytes, and a 200-byte code window around the
return address into one response. Clients avoid paying many TCP round-trips
per stack frame.
- **Change memory protection** on arbitrary target regions.
- **Allocate / free / hint-allocate** memory inside any target process.
### In-target code execution
- **Install an RPC stub** (`CMD_PROC_INSTALL`) - injects a reusable trampoline
with its own thread into the target.
- **Call arbitrary functions** with up to six SysV ABI register arguments and
read back `rax` (`CMD_PROC_CALL`).
- **Load ELFs** into a target process - either jump to the entry point
immediately (`CMD_PROC_ELF`) or return the entry for later invocation
(`CMD_PROC_ELF_RPC`).
### Full userland debugger
- **Attach** to a single target with `CMD_DEBUG_ATTACH` (sets up an async
interrupt channel back to the client).
- **Software breakpoints** - up to **30** slots, transparent `0xCC` injection.
- **Hardware watchpoints** - up to **4** DR0-DR3 slots with read / write /
read-write and 1/2/4/8-byte granularity.
- **Thread control** - list, suspend, resume, single-step, per-thread step.
- **Full register access** - general-purpose, FPU + YMM, and debug registers.
- **Continue / stop / halt** the whole process from one command.
- **Asynchronous interrupt packets** delivered on a separate TCP connection so
the client never polls.
### Kernel access
- Get the **kernel base address**.
- **Read** arbitrary kernel memory.
- **Write** arbitrary kernel memory.
### Built-in Zydis disassembler
Large memory regions never leave the PS5. Three server-side decoder commands
keep bandwidth low:
- `CMD_PROC_DISASM_REGION` - packed 32-byte-per-instruction stream with
control-flow, memory-operand, and RIP-relative metadata.
- `CMD_PROC_EXTRACT_CODE_XREFS` - all resolved RIP-relative branch/call
targets in a region, deduplicated.
- `CMD_PROC_FIND_XREFS_TO` - only instructions that reference a specific
target address.
### Built-in Keystone assembler (x86-64)
A cross-compiled LLVM-MC Keystone (x86-only, no exceptions / no RTTI, static
~4 MB) is embedded in the payload, exposed via the raw-literal opcode
`0xBDAA0024`. Lets clients assemble asm text into machine code on the console
itself.
- Pure userspace - needs no attached process and no `CMD_PROC_AUTH` handshake.
- Request: `u64 base_addr; u32 ks_opt_syntax;` + asm text (NUL not required).
`ks_opt_syntax` defaults to Intel; pass 1/2/4/8/0x10 for Intel/ATT/NASM/MASM/GAS.
- Response: `CMD_SUCCESS` + `u32 byte_len; u32 insn_count;` + machine bytes,
or `CMD_ERROR` + `u32 ks_errno; u32 msg_len;` + Keystone's human-readable error.
- The opcode is deliberately a raw literal (no `CMD_*` macro) so the published
`CMD_*` set that some clients enumerate stays unchanged.
### Memory scanning
- **Value scan** (`CMD_PROC_SCAN`) - single-pass, 12 value types × 13 compare
modes (exact, fuzzy, bigger/smaller, between, increased, decreased, changed,
etc.).
- **Iterative scan session** (`SCAN_START` → `SCAN_COUNT` → `SCAN_GET`) - lets
clients narrow a result set server-side over many passes.
- **AOB scan** (`CMD_PROC_SCAN_AOB`) - byte patterns with `??` wildcards.
- **Multi-pattern AOB scan** (`CMD_PROC_SCAN_AOB_MULTI`) - many patterns in
one pass.
- **Auth-gated** - scan commands require a prior `CMD_PROC_AUTH` handshake.
### System UI integration
- **Push notifications** to the user's screen with arbitrary UTF-8 text.
- **Print** to the kernel console.
- **Reboot** the console.
### Klog forwarder
- TCP **3232** streams the kernel log to a connected client (host-side
`klog reader` style). Survives suspend/resume the same as the main server.
### Discovery
- A UDP broadcast responder on port `1010` echoes a handshake magic
(`0xFFFFAAAA`) so clients can find the PS5 on the LAN without hard-coding
an IP.
### Rest-mode support
- The payload **survives suspend / resume** without needing to be reloaded.
A supervisory loop polls the network periodically: when the console drops
into rest mode the server exits cleanly, and as soon as the network comes
back the server restarts and a fresh "online" notification fires.
- Clients see a clean disconnect on port 744 when rest mode begins and can
simply reconnect after wake.
### Performance-oriented design
- Non-blocking sockets with `TCP_NODELAY`, `SO_KEEPALIVE`, large transfer
chunks.
- Zydis amalgamation compiled at `-O3 -DNDEBUG` for maximum decode throughput.
- Link-time dead stripping (`-ffunction-sections -fdata-sections
-Wl,--gc-sections`).
- Interrupt packets streamed over a dedicated side channel to avoid blocking
the command loop.
---
## Architecture
The deployable artifact `ps5debug-NG.elf` is a two-component build:
```
┌──────────────────────────────────────────────────────────────┐
│ ps5debug-NG.elf │
│ │
│ ┌───────────────────┐ injects ┌───────────────────┐ │
│ │ installer ELF │─────────────▶│ debugger ELF │ │
│ │ (umtx-loaded PIE) │ │ (in SceShellCore) │ │
│ └───────────────────┘ └────────┬──────────┘ │
│ │ │
│ ┌────────────────▼─────────┐ │
│ │ - TCP server :744 │ │
│ │ - debug async :755 │ │
│ │ - klog forward :3232 │ │
│ │ - UDP bcast :1010 │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
- **installer** - umtx-loaded SDK PIE. Finds SceShellCore, sets up the
target's KEX state and syscall-origin filter, then calls the SCE-side
`inject_remote_thread_create` primitive to run the embedded debugger as
a SceShellCore-internal thread.
- **debugger** - runs inside SceShellCore once injected. Implements the
wire protocol, breakpoints / watchpoints / single-step, memory scan, RPC,
and ELF inject. Built into `debugger/build/debugger.elf`, then embedded as
a `.rodata` blob into the installer via `.incbin`.
Running the wire protocol from inside SceShellCore is what lets `PT_ATTACH`
on game pids look kernel-side like an SCE-originated debug attach, which
PS5's AppContext gating allows. A standalone process doing `PT_ATTACH` gets
the game flagged and stops progressing.
---
## Network protocol at a glance
| Port | Proto | Direction | Purpose |
|-------|-------|----------------|--------------------------------------|
| 744 | TCP | client → PS5 | Command server |
| 755 | TCP | PS5 → client | Async debug interrupts |
| 3232 | TCP | PS5 → client | Kernel log forwarder |
| 1010 | UDP | bidirectional | Discovery beacon (`0xFFFFAAAA`) |
Every command begins with a 12-byte header:
```c
struct cmd_packet {
uint32_t magic; // 0xFFAABBCC
uint32_t cmd; // 0xBDAA..., 0xBDBB..., 0xBDCC..., 0xBDDD...
uint32_t datalen; // length of request body that follows
};
```
Followed by the command's fixed request struct (if any), any trailing
variable-length payload, and a `uint32_t` status code reply.
**Note on status words.** The status `uint32_t` on PS5 is transmitted with
its bit pairs swapped (`net_send_int32` swaps even/odd-bit positions). Clients
must un-bitswap incoming status values before comparing to `CMD_SUCCESS` /
`CMD_ERROR` / `CMD_DATA_NULL` / `CMD_ALREADY_DEBUG`. Subsequent payload bytes
are sent raw.
**Full protocol specification:** [PROTOCOL.md](PROTOCOL.md) - every command,
every packet struct, every enum, every status code, with `file:line`
citations.
---
## Command coverage
| Namespace | Count | Examples |
|---------------|-------|------------------------------------------------------------|
| Info / ping | 5 | `VERSION`, `FW_VERSION`, `BRANDING`, `PLATFORM_ID`, `NOP` |
| Process | 26 | `READ`, `WRITE`, `MAPS`, `CALL`, `SCAN_*`, `DISASM_*` |
| Debug | 18 | `ATTACH`, `SET_BREAKPOINT`, `GETREGS`, `STEP`, `CONTINUE` |
| Kernel R/W | 3 | `KERN_BASE`, `KERN_READ`, `KERN_WRITE` |
| Console | 6 | `NOTIFY`, `PRINT`, `REBOOT`, `INFO`, `END`, `FOREGROUND_APP` |
| **Total** | **58**| |
---
## Building
Prerequisites (Ubuntu / Debian):
```sh
sudo apt install bash clang-18 lld-18
```
Build:
```sh
./build.sh
```
This builds the SDK first (one-time, cached), then the debugger, then the
installer (which embeds the debugger), then publishes `ps5debug-NG.elf` at
the top level. Subsequent runs only rebuild what changed.
Clean (including the SDK install):
```sh
./build.sh clean
```
---
## Deploying
`ps5debug-NG.elf` is loaded onto the PS5 via a umtx-based ELF loader (e.g.
elfldr from etaHEN-class loaders).
You should see a system notification confirming the payload is alive:
```
ps5debug-NG by OSR v1.2.3 loaded!
Coded by OpenSourcereR
Special thanks to
golden, Ctn & SiSTRo! ♥
```
---
## Writing your own client
The protocol is deliberately simple - a raw TCP client in any language can
drive it. Example: pinging the server and reading its branding string, in
Python (don't forget the bit-pair swap on the status word):
```python
import socket, struct
PACKET_MAGIC = 0xFFAABBCC
CMD_BRANDING = 0xBD000501
CMD_SUCCESS = 0x40000000
def bitswap32(x):
x &= 0xFFFFFFFF
return ((x << 1) & 0xAAAAAAAA) | ((x >> 1) & 0x55555555)
s = socket.create_connection(("<PS5_IP>", 744))
s.sendall(struct.pack("<III", PACKET_MAGIC, CMD_BRANDING, 0))
(status_raw,) = struct.unpack("<I", s.recv(4))
assert bitswap32(status_raw) == CMD_SUCCESS
(length,) = struct.unpack("<I", s.recv(4))
print("server branding:", s.recv(length).decode())
```
See [PROTOCOL.md](PROTOCOL.md) for the exact byte layout of every command,
response, and async interrupt packet.
---
## Source layout
```
.
├── build.sh # one-command full build
│
├── common/ # headers + sources shared by both components
│ ├── include/ # protocol.h, sdk_shim.h, net.h, proc.h, ...
│ └── source/
│
├── debugger/ # in-SceShellCore wire-protocol debugger
│ ├── Makefile source/ include/
│ └── third_party/ # Zydis (decoder) + Keystone (assembler)
│
├── installer/ # umtx-loaded SceShellCore installer
│ ├── Makefile source/
│ └── source/embedded_inner.S # embeds debugger.elf via .incbin
│
├── ps5-payload-sdk/ # vendored John Törnblom SDK
└── third_party/ # keystone-0.9.2 full source (for rebuilds)
```
Three source files (`kern_rw_fast.c`, `proc_elf.c`, `proc_remote.c`) and
`main.c` exist in **both** `debugger/source/` and `installer/source/` because
they genuinely diverge between the two builds - same code specialized for
each component's role.
---
## SDK pin
The vendored SDK is **ps5-payload-sdk v0.38** (commit
`6ae1470fd50c5791e8a8bb728627e657e36eb55a`, dated 2026-04-02). Upstream:
https://github.com/ps5-payload-dev/sdk
To upgrade the SDK:
```sh
./build.sh clean
rm -rf ps5-payload-sdk
curl -fsSL https://github.com/ps5-payload-dev/sdk/archive/refs/tags/<TAG>.tar.gz \
| tar xz -C /tmp
mv /tmp/sdk-<TAG_WITHOUT_v> ps5-payload-sdk
./build.sh
```
---
## Credits
- **jogolden** - original public `ps4debug` and the wire protocol this project
indirectly inherits.
- **Ctn & SiSTRo** - `ps5debug` authors; this project is wire-compatible with
their implementation.
- **DeathRGH** - Frame4 author. Inspiration.
- **John Törnblom** - `ps5-payload-sdk`, the vendored SDK / toolchain.
- **Zydis** - x86 disassembler used in decoder-only mode (`ZYAN_NO_LIBC`,
`-DNDEBUG`). Third-party, unmodified; MIT-licensed.
- **Keystone** - LLVM-MC-based assembler; cross-compiled here for the PS5
payload (x86-only, `-fno-exceptions -fno-rtti`, static).
- **OSR** (OpenSourcereR) - author.
---
## License
Licensed under the **GNU General Public License v3.0** - see [LICENSE.txt](LICENSE.txt)
for the full text.
In short:
- You may use, study, modify, and redistribute this software freely.
- If you distribute a modified binary, you **must** also make the complete
corresponding source code available under the same license.
- The software is provided **without warranty** of any kind.
# ps5debug-NG
A debugger payload for jailbroken PlayStation 5 consoles. Ships a userland
command server that runs inside SceShellCore, letting remote clients inspect
and manipulate running processes, the kernel itself, and the system UI over a
simple TCP protocol.
ps5debug-NG is inspired by Ctn's `ps5debug 1.0b5` and is wire-compatible with
it - existing clients should work without modification. It is licensed under
GPL-3.
## Official Discord Server: [Team Reaper](https://discord.gg/7bjtgZf4PY)
## Official PlayStation 4 version [ps4debug-NG](https://github.com/Pharaoh2k/ps4debug-NG)
---
## Supported firmwares
The kernel kpatch routine in [installer/source/main.c](installer/source/main.c)
recognises the following firmware families. Booting on an unsupported FW prints
`port_outer: kpatch SKIP - unsupported FW magic 0x...` to the kernel log and
aborts cleanly.
| Family | Point releases recognised | Status |
|--------|-------------------------------------------------|-------------------------------------------|
| 3.xx | 3.00, 3.10, 3.20, 3.21 | Fully Verified |
| 4.xx | 4.00, 4.02, 4.03, 4.50, 4.51 | Fully Verified |
| 5.xx | 5.00, 5.02, 5.10, 5.50 | Fully Verified |
| 6.xx | 6.00, 6.02, 6.50 | Fully Verified |
| 7.xx | 7.00, 7.01, 7.01.01, 7.20, 7.40, 7.60, 7.61 | Fully Verified |
| 8.xx | 8.00, 8.20, 8.40, 8.60 | Fully Verified |
| 9.xx | 9.00, 9.05, 9.20, 9.40, 9.60 | Fully Verified |
| 10.xx | 10.00, 10.01, 10.20, 10.40, 10.60 | Fully Verified |
| 11.xx | 11.00, 11.20, 11.40, 11.60 | Fully Verified |
| 12.xx | 12.00, 12.02, 12.20, 12.40, 12.60, 12.70 | Fully Verified |
| 13.xx | 13.00, 13.20, 13.40, 13.42, 13.60 | Fully Verified |
The point releases above are the exact FW magic values recognised by the switch
in [installer/source/main.c](installer/source/main.c); that file is the source
of truth. Clients can read the running FW with `CMD_FW_VERSION`, which returns
the firmware as a decimal `uint16_t` (e.g. `900` for 9.00, `1240` for 12.40).
---
## Primary Features
### Process inspection and manipulation
- **Enumerate processes** (process name + pid list).
- **Read and write target memory** in streamed chunks.
- **Truthful writes** - pipe setup and transfer results are checked, every
accepted DMAP or mdbg write is read back, and single/multi-write replies
expose failure. Low-byte-`0xFF` transfers use a preserve-and-shift workaround
for FW 10.01; its adjacent byte must remain stable during the operation.
- **List virtual memory maps** - ranges, protections, backing names.
- **Query process metadata** - name, path, titleId, contentId.
- **Identify the foreground app** (`0xBDDD0006`) - returns pid + titleid +
contentid + process name + the game's version, parsed server-side from the
title's `param.sfo`. Useful for clients that need to know what's currently
running without listing every process.
- **Server-side stack walk** (`CMD_PROC_READ_STACK`) - the server walks the
RBP chain itself (up to 64 frames) and bundles each frame's saved-RBP,
return address, frame-local bytes, and a 200-byte code window around the
return address into one response. Clients avoid paying many TCP round-trips
per stack frame.
- **Change memory protection** on arbitrary target regions.
- **Allocate / free / hint-allocate** memory inside any target process. Plain
allocations are served from a server-side **per-pid arena** (one hijack per 16 MB
segment, then zero-hijack sub-allocation) so heavy concurrent allocation no longer
trips the thread-hijack crash; transparent to clients, toggleable via `0xBDAACC24`.
### In-target code execution
- **Call arbitrary functions** with up to six SysV ABI register arguments and
read back `rax` (`CMD_PROC_CALL`) - serviced kernel-side via `sys_proc_cmd`.
- **Install an RPC stub** (`CMD_PROC_INTALL`, opcode `0xBDAA0005`; note the
source spelling). In the current build this handler is a stub that returns a
`0` handle and performs no injection.
- **Load ELFs** into a target process - either jump to the entry point
immediately (`CMD_PROC_ELF`) or return the entry for later invocation
(`CMD_PROC_ELF_RPC`).
### Full userland debugger
- **Attach** to a single target with `CMD_DEBUG_ATTACH` (sets up an async
interrupt channel back to the client).
- **Software breakpoints** - up to **30** slots, transparent `0xCC` injection.
- **Hardware watchpoints** - up to **4** DR0-DR3 slots with read / write /
read-write and 1/2/4/8-byte granularity.
- **Thread control** - list, suspend, resume, single-step, per-thread step.
- **Full register access** - general-purpose, FPU + YMM (AVX), debug registers,
and the FS/GS segment base addresses (TLS pointers).
- **Continue / stop / halt** the whole process from one command.
- **Asynchronous interrupt packets** delivered on a separate TCP connection so
the client never polls.
### Kernel access
- Get the **kernel base address**.
- **Read** arbitrary kernel memory.
- **Write** arbitrary kernel memory.
### Built-in Zydis disassembler
Large memory regions never leave the PS5. Three server-side decoder commands
keep bandwidth low:
- `CMD_PROC_DISASM_REGION` - packed 32-byte-per-instruction stream with
control-flow, memory-operand, and RIP-relative metadata.
- `CMD_PROC_EXTRACT_CODE_XREFS` - all resolved RIP-relative operand targets in
a region, streamed (not deduplicated server-side).
- `CMD_PROC_FIND_XREFS_TO` - only instructions whose RIP-relative target equals
a specific address.
### Built-in Keystone assembler (x86-64)
A cross-compiled LLVM-MC Keystone (x86-only, no exceptions / no RTTI, static
~4 MB) is embedded in the payload, exposed via the raw-literal opcode
`0xBDAA0024`. Lets clients assemble asm text into machine code on the console
itself.
- Pure userspace - needs no attached process and no `CMD_PROC_AUTH` handshake.
- Request: `u64 base_addr; u32 ks_opt_syntax;` + asm text (NUL not required).
`ks_opt_syntax` defaults to Intel; pass 1/2/4/8/0x10 for Intel/ATT/NASM/MASM/GAS.
- Response: `CMD_SUCCESS` + `u32 byte_len; u32 insn_count;` + machine bytes,
or `CMD_ERROR` + `u32 ks_errno; u32 msg_len;` + Keystone's human-readable error.
- The opcode is deliberately a raw literal (no `CMD_*` macro) so the published
`CMD_*` set that some clients enumerate stays unchanged.
### Memory scanning
- **Value scan** (`CMD_PROC_SCAN`) - single-pass, 12 value types × 13 compare
modes (exact, fuzzy, bigger/smaller, between, increased, decreased, changed,
etc.).
- **Iterative scan** (`SCAN_START` → `SCAN_COUNT` → `SCAN_GET`) - narrows a
result set over many passes. The client holds the candidate list and streams
it back each pass; the server re-reads memory and re-compares.
- **AOB scan** (`CMD_PROC_SCAN_AOB`) - byte patterns with a per-byte wildcard
mask.
- **Multi-pattern AOB scan** (`CMD_PROC_SCAN_AOB_MULTI`) - many patterns in
one pass.
- **Auth-gated** - only the iterative scan trio (`SCAN_START` / `SCAN_COUNT` /
`SCAN_GET`) requires a prior `CMD_PROC_AUTH` handshake; the value scan and the
AOB scans do not.
### Turbo scan family (v1.3.0, additive + capability-gated)
A faster, opt-in scan path (`CMD_PROC_TURBOSCAN_*`, `0xBDAACC10`-`0xBDAACC17`) that
runs alongside the legacy and iterative scanners, which are byte-for-byte
unchanged. A client detects it via `CMD_PROC_TURBOSCAN_CAPS` and falls back to the
iterative trio when it (or a specific engine) is absent. Result format mirrors the
iterative scan, so clients reuse one parser.
- **SIMD comparator** - typed AVX2 (256-bit) exact-match inner loop, ~25-32x the
per-element compare of the legacy path (`TSE_SIMD_COMPARE`).
- **Server-resident result sets** (`TSE_SERVER_RESIDENT`) - the survivor set can
live in a per-connection server buffer instead of being re-uploaded each pass;
rescans refresh each survivor's baseline so "since last scan" deltas work without
the client holding state. `CMD_PROC_TURBOSCAN_GET` fetches values on demand.
- **Unknown-initial-value scans** (`TSE_SNAPSHOT`) - a server-side value snapshot
(RAM, or an NVMe `/data` file for large regions) drives
increased/decreased/changed narrowing with no known starting value, materialising
to a compact record list once survivors get sparse. Dense snapshots use a bitmap;
Simple float snapshots use survivor records from the start. By default the seed **drops
all-zero slots** (most memory is zeroed and rarely useful); `TS_SNAPSHOT_INCLUDE_ZEROS`
keeps them.
- **Multi-segment scans** (`TSE_SNAPSHOT_SEGMENTS`) - a single session can cover a
list of disjoint regions (`TS_SNAPSHOT_SEGMENTS` + a trailing segment list) instead
of one contiguous range, for both an unknown-value snapshot (`TS_SNAPSHOT`) and a
known-value server-resident scan (`TS_SERVER_RESIDENT`). The slot/record space spans
only the mapped segments, so unmapped gaps between selected modules/sections are never
read and storage scales with the selected bytes - letting a scattered selection use
the turbo server-side path instead of falling back to per-section streaming. (When a
segmented resident scan overflows the server buffer it declines with an empty result
stream and the client streams per-section itself.)
- **Snapshot storage tuning** (`TSE_SNAPSHOT_CONFIG`, `CMD_PROC_TURBOSCAN_CONFIG`) - the
client can set the **RAM threshold** (how large the value store may be before it spills
to disk; default 512 MiB) and the **spill directory** (default `/data`; the encrypted
internal partition can be slow, so an extended NVMe `/mnt/ext1` or USB `/mnt/usb0` is
often much faster). Spill writes use 16 MiB chunks.
- **Aliasing read engine** (`TSE_ALIASING`, opt-in, default off) - maps the
target's physical pages into the server's address space via guarded page-table
writes so the scan reads in place at DRAM bandwidth instead of copying.
Layout-agnostic (4K / 2MB / 1GB pages); enabled per request with
`TS_USE_ALIASING`, and always falls back to the normal read path on any
guard/verify miss (mdbg is the floor).
- **Parallel compare** (`TSE_PARALLEL_COMPARE`, opt-in, default off) - with
`TS_PARALLEL_COMPARE` on an aliased exact-match streaming scan, the server splits
the scan across worker threads. This is for **single-connection** clients: a
multi-connection client parallelizes better by opening more connections (the server
threads per connection) and should leave this clear. Don't set both - they
over-subscribe. Same wire result either way.
- **Rescan aliasing** (`TSE_RESCAN_ALIASING`, opt-in, default off) - with
`TS_RESCAN_ALIASING` on a `COUNT` rescan, full-size (gap-bridged, contiguous)
survivor windows read via the aliasing engine instead of mdbg (~2-3x on
dense/moderate-density rescans; the win grows as survivors stay dense). Tiny
scattered windows and any alias miss stay on mdbg. The survivor set is
per-connection, so it is single-connection by nature; a multi-connection client
must not enable it across many connections at once. Same wire result either way.
- **Float policy offload** (`TSE_FLOAT_POLICY`, `TSE_COMPACT_SIMPLE_SNAPSHOT`) - clients may send
`TS_FLOAT_SIMPLE` plus the configured exponent-distance threshold so the server
excludes extreme/denormal float and double candidates from survivor membership,
later narrowing, and streaming/GET transfer. `TS_FLOAT_EXACT` makes exact-value
float/double scans use numeric IEEE equality instead of the legacy fuzzy
comparison. The policy is enforced consistently during streaming START,
snapshot creation, and every resident or client-driven COUNT rescan. When Simple
seeds a snapshot, filtering now happens before storage: only survivor records are
written, no raw-slot bitmap is allocated, optional First/Previous values are embedded
per record, and each later narrow compacts the stream again. The dedicated compact
capability bit lets clients distinguish this storage guarantee from older payloads
that advertised float-policy correctness but still used dense snapshot backing.
- **Region classify** (`0xBDAACC16`) - returns every readable region with its cache
attribute (uncached `PCD` leaf-PTE bit) and a measured read throughput, so the client
can offer the user a per-region "exclude uncached/slow" choice (e.g. the GPU/Garlic
blob, which reads ~40 MB/s and is rarely worth scanning). The server never drops
anything - exclusion is the client's opt-in, user-overridable decision, default scan
everything. The opcode is a raw literal (no `CMD_*` macro) so the enumerated `CMD_*`
set stays unchanged.
- **Cancel** (`0xBDAACC17`, v1.3.1) - abort a long scan mid-flight (e.g. an unknown-value
snapshot over a huge region). Sent from a second connection, since the scanning
connection is busy streaming; a cancelled snapshot create returns `snapshot_ok=0`.
- **Auth-gated** - `TURBOSCAN_START` / `_COUNT` / `_GET` / `_END` / `_CANCEL` require the
`CMD_PROC_AUTH` handshake (like the iterative trio); `TURBOSCAN_CAPS` does not.
### System UI integration
- **Push notifications** to the user's screen with arbitrary UTF-8 text.
- **Print** to the kernel console.
- **Reboot** the console.
### Klog forwarder
- TCP **3232** streams the kernel log to a connected client (host-side
`klog reader` style). Survives suspend/resume the same as the main server.
### Discovery
- A UDP broadcast responder on port `1010` echoes a handshake magic
(`0xFFFFAAAA`) so clients can find the PS5 on the LAN without hard-coding
an IP.
### Rest-mode support
- The payload **survives suspend / resume** without needing to be reloaded.
A supervisory loop polls the network periodically: when the console drops
into rest mode the server exits cleanly, and as soon as the network comes
back the server restarts and a fresh "online" notification fires.
- Clients see a clean disconnect on port 744 when rest mode begins and can
simply reconnect after wake.
### Performance-oriented design
- Non-blocking sockets with `TCP_NODELAY`, `SO_KEEPALIVE`, large transfer
chunks.
- Zydis amalgamation compiled at `-O3 -DNDEBUG` for maximum decode throughput.
- Link-time dead stripping (`-ffunction-sections -fdata-sections
-Wl,--gc-sections`).
- Interrupt packets streamed over a dedicated side channel to avoid blocking
the command loop.
---
## Architecture
The deployable artifact `ps5debug-NG.elf` is a two-component build:
```
┌──────────────────────────────────────────────────────────────┐
│ ps5debug-NG.elf │
│ │
│ ┌───────────────────┐ injects ┌───────────────────┐ │
│ │ installer ELF │─────────────▶│ debugger ELF │ │
│ │ (umtx-loaded PIE) │ │ (in SceShellCore) │ │
│ └───────────────────┘ └────────┬──────────┘ │
│ │ │
│ ┌────────────────▼─────────┐ │
│ │ - TCP server :744 │ │
│ │ - debug async :755 │ │
│ │ - klog forward :3232 │ │
│ │ - UDP bcast :1010 │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
- **installer** - umtx-loaded SDK PIE. Finds SceShellCore, sets up the
target's KEX state and syscall-origin filter, then calls the SCE-side
`inject_remote_thread_create` primitive to run the embedded debugger as
a SceShellCore-internal thread.
- **debugger** - runs inside SceShellCore once injected. Implements the
wire protocol, breakpoints / watchpoints / single-step, memory scan, RPC,
and ELF inject. Built into `debugger/build/debugger.elf`, then embedded as
a `.rodata` blob into the installer via `.incbin`.
Running the wire protocol from inside SceShellCore is what lets `PT_ATTACH`
on game pids look kernel-side like an SCE-originated debug attach, which
PS5's AppContext gating allows. A standalone process doing `PT_ATTACH` gets
the game flagged and stops progressing.
---
## Network protocol at a glance
| Port | Proto | Direction | Purpose |
|-------|-------|----------------|--------------------------------------|
| 744 | TCP | client → PS5 | Command server |
| 755 | TCP | PS5 → client | Async debug interrupts |
| 3232 | TCP | PS5 → client | Kernel log forwarder |
| 1010 | UDP | bidirectional | Discovery beacon (`0xFFFFAAAA`) |
Every command begins with a 12-byte header:
```c
struct cmd_packet {
uint32_t magic; // 0xFFAABBCC
uint32_t cmd; // 0xBDAA..., 0xBDBB..., 0xBDCC..., 0xBDDD...
uint32_t datalen; // length of request body that follows
};
```
Followed by the command's fixed request struct (if any) and any trailing
variable-length payload. The reply shape is per-command: most replies begin
with a `uint32_t` status word (some commands send two, and the info/version
commands send their data with no status word at all) - see
[PROTOCOL.md](PROTOCOL.md) for the exact sequence of each.
**Note on status words.** The status `uint32_t` on PS5 is transmitted with
its bit pairs swapped (`net_send_int32` swaps even/odd-bit positions). Clients
must account for the swap - either un-bitswap the incoming word and compare to
the server-side constants (`CMD_SUCCESS = 0x40000000`, etc., as the example
below does), or compare the raw word against the pre-swapped values
(`CMD_SUCCESS = 0x80000000`, etc.). Subsequent payload bytes are sent raw.
**Full protocol specification:** [PROTOCOL.md](PROTOCOL.md) - every command,
every packet struct, every enum, every status code, with `file:line`
citations.
---
## Command coverage
| Namespace | Count | Examples |
|---------------|-------|------------------------------------------------------------|
| Info / ping | 5 | `VERSION`, `FW_VERSION`, `BRANDING`, `PLATFORM_ID`, `NOP` |
| Process | 35 | `READ`, `WRITE`, `MAPS`, `CALL`, `SCAN_*`, `TURBOSCAN_*`, `DISASM_*` |
| Debug | 20 | `ATTACH`, `SET_BREAKPOINT`, `GETREGS`, `GET_FSGS_BASE`, `STEP`, `CONTINUE` |
| Kernel R/W | 3 | `KERN_BASE`, `KERN_READ`, `KERN_WRITE` |
| Console | 6 | `NOTIFY`, `PRINT`, `REBOOT`, `INFO`, `END`, `FOREGROUND_APP` |
| **Total** | **69**| |
---
## Building
Release builds require Ubuntu 22.04 and Clang/LLD 18.1.8. The build script
checks both before publishing an ELF.
```sh
sudo apt install bash clang-18 lld-18
```
Build:
```sh
./build.sh
```
This builds the SDK first (one-time, cached), then force-rebuilds the debugger
and installer, and publishes `ps5debug-NG.elf` at the top level. The branding
contains a 16-hex source/configuration fingerprint.
Forced-path diagnostic builds isolate either write engine:
```sh
WRITE_DIAG=1 WRITE_PATH=1 ./build.sh # DMAP only
WRITE_DIAG=1 WRITE_PATH=2 ./build.sh # mdbg only
```
Set `WRITE_DIAG=1` only when a full debugger write trace is required. The
bootstrap installer always stays quiet, and stock builds log failures only.
`WRITE_PATH=0` is the normal automatic policy. `./build.sh clean` is disabled;
clean `debugger/` or `installer/` with `make clean` when required.
---
## Deploying
`ps5debug-NG.elf` is loaded onto the PS5 via a umtx-based ELF loader (e.g.
elfldr from etaHEN-class loaders).
You should see a system notification confirming the payload is alive:
```
ps5debug-NG by OSR v1.3.2 [<build fingerprint>] loaded!
Firmware: 9.00
Coded by OpenSourcereR
Special thanks to
golden, Ctn, SiSTRo, EchoStretch,
Sonic-Iso & Pharaoh2k! ❤
```
(`Firmware:` shows the running console's firmware, e.g. `9.00` or `12.40`.)
---
## Writing your own client
The protocol is deliberately simple - a raw TCP client in any language can
drive it. Example: pinging the server and reading its branding string, in
Python (don't forget the bit-pair swap on the status word):
```python
import socket, struct
PACKET_MAGIC = 0xFFAABBCC
CMD_BRANDING = 0xBD000501
CMD_SUCCESS = 0x40000000
def bitswap32(x):
x &= 0xFFFFFFFF
return ((x << 1) & 0xAAAAAAAA) | ((x >> 1) & 0x55555555)
s = socket.create_connection(("<PS5_IP>", 744))
s.sendall(struct.pack("<III", PACKET_MAGIC, CMD_BRANDING, 0))
(status_raw,) = struct.unpack("<I", s.recv(4))
assert bitswap32(status_raw) == CMD_SUCCESS
(length,) = struct.unpack("<I", s.recv(4))
print("server branding:", s.recv(length).decode())
```
See [PROTOCOL.md](PROTOCOL.md) for the exact byte layout of every command,
response, and async interrupt packet.
---
## Source layout
```
.
├── build.sh # one-command full build
│
├── common/ # headers + sources shared by both components
│ ├── include/ # protocol.h, sdk_shim.h, net.h, proc.h, ...
│ └── source/
│
├── debugger/ # in-SceShellCore wire-protocol debugger
│ ├── Makefile source/ include/
│ └── third_party/ # Zydis (decoder) + Keystone (assembler)
│
├── installer/ # umtx-loaded SceShellCore installer
│ ├── Makefile source/
│ └── source/embedded_inner.S # embeds debugger.elf via .incbin
│
├── ps5-payload-sdk/ # vendored John Törnblom SDK
└── third_party/ # keystone-0.9.2 full source (for rebuilds)
```
Three source files (`kern_rw_fast.c`, `proc_elf.c`, `proc_remote.c`) and
`main.c` exist in **both** `debugger/source/` and `installer/source/` because
they genuinely diverge between the two builds - same code specialized for
each component's role.
---
## SDK pin
The vendored SDK is **ps5-payload-sdk v0.38** (commit
`6ae1470fd50c5791e8a8bb728627e657e36eb55a`, dated 2026-04-02). Upstream:
https://github.com/ps5-payload-dev/sdk
To upgrade the SDK, replace the vendored source while preserving its symlinks,
then remove only generated build products:
```sh
make -C debugger clean
make -C installer clean
rm -rf ps5-payload-sdk/install
./build.sh
```
---
## Credits
- **jogolden** - original public `ps4debug` and the wire protocol this project
indirectly inherits.
- **Ctn & SiSTRo** - `ps5debug` authors; this project is wire-compatible with
their implementation.
- **DeathRGH** - Frame4 author. Inspiration.
- **John Törnblom** - `ps5-payload-sdk`, the vendored SDK / toolchain.
- **Zydis** - x86 disassembler used in decoder-only mode (`ZYAN_NO_LIBC`,
`-DNDEBUG`). Third-party, unmodified; MIT-licensed.
- **Keystone** - LLVM-MC-based assembler; cross-compiled here for the PS5
payload (x86-only, `-fno-exceptions -fno-rtti`, static).
- **EchoStretch, Sonic-Iso, Pharaoh2k** - contributions and testing.
- **OSR** (OpenSourcereR) - author.
---
## License
Licensed under the **GNU General Public License v3.0** - see [LICENSE.txt](LICENSE.txt)
for the full text.
In short:
- You may use, study, modify, and redistribute this software freely.
- If you distribute a modified binary, you **must** also make the complete
corresponding source code available under the same license.
- The software is provided **without warranty** of any kind.
+122 -13
View File
@@ -9,13 +9,108 @@ cd "$SCRIPT_DIR"
SDK_SRC="$SCRIPT_DIR/ps5-payload-sdk"
SDK_INSTALL="$SDK_SRC/install"
ARTIFACT="$SCRIPT_DIR/ps5debug-NG.elf"
WRITE_DIAG_MODE="${WRITE_DIAG:-0}"
WRITE_PATH_MODE="${WRITE_PATH:-0}"
clean_build() {
echo "==> cleaning"
(cd debugger && make clean) || true
(cd installer && make clean) || true
(cd "$SDK_SRC" && make clean) || true
rm -rf "$SDK_INSTALL" "$ARTIFACT"
compute_build_fingerprint() {
local manifest source_fingerprint
manifest="$(mktemp)"
if git -C "$SCRIPT_DIR" rev-parse --is-inside-work-tree \
>/dev/null 2>&1; then
(
cd "$SCRIPT_DIR"
git ls-files -co --exclude-standard -z -- \
build.sh common debugger installer |
LC_ALL=C sort -z
) > "$manifest"
else
(
cd "$SCRIPT_DIR"
find build.sh common debugger installer -type f \
! -path '*/build/*' -print0 |
LC_ALL=C sort -z
) > "$manifest"
fi
if [ ! -s "$manifest" ]; then
rm -f "$manifest"
echo "ERROR: no payload sources found for build fingerprint." >&2
return 1
fi
source_fingerprint="$(
(cd "$SCRIPT_DIR"; xargs -0 sha256sum < "$manifest") |
sha256sum | cut -c1-64
)"
rm -f "$manifest"
printf '%s diag=%s path=%s\n' \
"$source_fingerprint" "$WRITE_DIAG_MODE" "$WRITE_PATH_MODE" |
sha256sum | cut -c1-16
}
validate_write_build_mode() {
case "$WRITE_DIAG_MODE" in
0|1) ;;
*) echo "ERROR: WRITE_DIAG must be 0 or 1." >&2; exit 1 ;;
esac
case "$WRITE_PATH_MODE" in
0|1|2) ;;
*) echo "ERROR: WRITE_PATH must be 0 (auto), 1 (DMAP), or 2 (mdbg)." >&2; exit 1 ;;
esac
}
require_build_host() {
# This payload's release bytes are toolchain-sensitive. Keep the supported
# build host explicit so invoking build.sh from the default WSL distro cannot
# silently publish an Ubuntu 24 / LLVM 19 artifact.
. /etc/os-release
if [ "${ID:-}" != "ubuntu" ] || [ "${VERSION_ID:-}" != "22.04" ]; then
echo "ERROR: ps5debug-NG must be built in WSL Ubuntu-22.04 (found ${ID:-unknown} ${VERSION_ID:-unknown})." >&2
exit 1
fi
}
verify_sdk_links() {
# Git tracks these FreeBSD forwarding headers as symlinks. A checkout made
# with core.symlinks=false turns them into one-line regular files that the C
# compiler then parses as source text.
local rel target
if ! git -C "$SCRIPT_DIR" rev-parse --is-inside-work-tree \
>/dev/null 2>&1; then
while IFS= read -r -d '' rel; do
if [ ! -e "$SCRIPT_DIR/$rel" ]; then
echo "ERROR: required SDK symlink is broken: $rel" >&2
exit 1
fi
done < <(
cd "$SCRIPT_DIR"
find ps5-payload-sdk/include/freebsd -type l -print0
)
return
fi
while IFS= read -r rel; do
[ -z "$rel" ] && continue
if [ ! -L "$SCRIPT_DIR/$rel" ]; then
echo "ERROR: required SDK symlink is not materialized: $rel" >&2
echo " Enable Git symlinks and restore the tracked link before building." >&2
exit 1
fi
target="$(git show ":$rel")"
if [ "$(readlink "$SCRIPT_DIR/$rel")" != "$target" ]; then
echo "ERROR: SDK symlink has the wrong target: $rel" >&2
exit 1
fi
done < <(git ls-files -s ps5-payload-sdk/include/freebsd | awk '$1 == 120000 { print $4 }')
}
verify_toolchain() {
local clang_line lld_line
clang_line="$($SDK_INSTALL/bin/prospero-clang --version | head -n 1)"
lld_line="$($SDK_INSTALL/bin/prospero-lld --version | head -n 1)"
if [[ "$clang_line" != *"version 18.1.8"* ]] || [[ "$lld_line" != *"LLD 18.1.8"* ]]; then
echo "ERROR: ps5debug-NG release builds require Clang/LLD 18.1.8." >&2
echo " clang: $clang_line" >&2
echo " lld: $lld_line" >&2
exit 1
fi
}
build_sdk() {
@@ -29,13 +124,21 @@ build_sdk() {
}
build_debugger() {
echo "==> building debugger"
make -C debugger
local fingerprint="$1"
echo "==> building debugger (fingerprint $fingerprint)"
make -B -C debugger \
BUILD_FINGERPRINT="$fingerprint" \
WRITE_DIAG="$WRITE_DIAG_MODE" \
WRITE_PATH="$WRITE_PATH_MODE"
}
build_installer() {
local fingerprint="$1"
echo "==> building installer (embeds debugger)"
make -C installer
make -B -C installer \
BUILD_FINGERPRINT="$fingerprint" \
WRITE_DIAG=0 \
WRITE_PATH="$WRITE_PATH_MODE"
}
publish_artifact() {
@@ -44,11 +147,17 @@ publish_artifact() {
}
if [ "${1:-}" = "clean" ]; then
clean_build
exit 0
echo "ERROR: './build.sh clean' is intentionally disabled; it destroys the preserved release SDK install." >&2
echo " Clean debugger/ and installer/ independently when a clean payload rebuild is required." >&2
exit 1
fi
require_build_host
validate_write_build_mode
verify_sdk_links
build_sdk
build_debugger
build_installer
verify_toolchain
BUILD_FINGERPRINT="$(compute_build_fingerprint)"
build_debugger "$BUILD_FINGERPRINT"
build_installer "$BUILD_FINGERPRINT"
publish_artifact
+24
View File
@@ -5,10 +5,34 @@
#include <stdint.h>
enum debug_target_phase {
DEBUG_PHASE_DETACHED = 0,
DEBUG_PHASE_ATTACHING,
DEBUG_PHASE_RUNNING,
DEBUG_PHASE_RESUMING,
DEBUG_PHASE_PROCESSING_EVENT,
DEBUG_PHASE_EVENT_PENDING,
DEBUG_PHASE_EVENT_STOPPED,
DEBUG_PHASE_STEPPING,
DEBUG_PHASE_RPC_ACTIVE,
DEBUG_PHASE_DETACHING,
DEBUG_PHASE_BROKEN
};
/* Internal return used to fail remote calls without retrying when the debugger
owns a target stop. It never appears on the wire; handlers return CMD_ERROR. */
#define PROC_RPC_ERR_DEBUG_STATE (-2)
extern uint32_t g_stopgo_mode;
extern uint32_t g_stopgo_target_pid;
extern uint32_t g_stopgo_last_signal;
extern uint32_t g_stopgo_resume_pid;
extern uint32_t g_stopgo_resume_signal;
extern volatile uint32_t g_debug_phase;
extern volatile uint32_t g_debug_pending_wait_valid;
extern uint32_t g_debug_pending_wait_pid;
extern int32_t g_debug_pending_wait_status;
extern void *g_debug_arbiter_mutex;
#endif
+34
View File
@@ -13,6 +13,7 @@ int proc_list_handle(int fd, struct cmd_packet *packet);
int proc_read_handle(int fd, struct cmd_packet *packet);
int proc_read_stack_handle(int fd, struct cmd_packet *packet);
int proc_write_handle(int fd, struct cmd_packet *packet);
int proc_write_multi_handle(int fd, struct cmd_packet *packet);
int proc_maps_handle(int fd, struct cmd_packet *packet);
int proc_info_handle(int fd, struct cmd_packet *packet);
int proc_protect_handle(int fd, struct cmd_packet *packet);
@@ -50,6 +51,7 @@ long ptrace_raw (int op, int pid, void *addr, int data);
long ptrace_elev(int op, int pid, void *addr, int data);
int elev_save_and_set(struct elev_state *st);
void elev_restore (struct elev_state *st);
int proc_arena_set(int on, char *buf, int bufsz);
long sys_proc_rw_w0(uint64_t pid, uint64_t address, uint64_t length,
void *data, uint64_t arg5);
@@ -62,6 +64,20 @@ int proc_scan_aob_multi_handle(int fd, struct cmd_packet *packet);
int proc_scan_start_handle(int fd, struct cmd_packet *packet);
int proc_scan_count_handle(int fd, struct cmd_packet *packet);
int proc_scan_get_handle(int fd, struct cmd_packet *packet);
int proc_turboscan_caps_handle(int fd, struct cmd_packet *packet);
int proc_turboscan_config_handle(int fd, struct cmd_packet *packet);
int proc_turboscan_start_handle(int fd, struct cmd_packet *packet, unsigned char client_idx);
int proc_turboscan_count_handle(int fd, struct cmd_packet *packet, unsigned char client_idx);
int proc_turboscan_get_handle(int fd, struct cmd_packet *packet, unsigned char client_idx);
int proc_turboscan_end_handle(int fd, struct cmd_packet *packet, unsigned char client_idx);
int proc_turboscan_regions_handle(int fd, struct cmd_packet *packet);
int proc_turboscan_cancel_handle(int fd, struct cmd_packet *packet); /* 0xBDAACC17 (from a 2nd connection) */
void turboscan_session_free_idx(unsigned char client_idx);
void turboscan_alias_free_idx(unsigned char client_idx);
void turboscan_startup_cleanup(void);
int proc_auth_handle(int fd, struct cmd_packet *packet);
int proc_assemble_handle(int fd, struct cmd_packet *packet);
@@ -109,6 +125,24 @@ int proc_ptwalk_augment(uint32_t pid,
int proc_ptwalk_read(uint32_t pid, uint64_t va, uint64_t len, void *dst);
uint64_t proc_ptwalk_dmap_base(void);
int proc_ptwalk_probe(uint32_t pid, uint64_t va, uint64_t *out_phys,
int *out_level, uint64_t *out_pagesize, uint64_t *out_pte);
int proc_ptwalk_leaf_addr(uint32_t pid, uint64_t va, uint64_t *out_pte_kaddr,
uint64_t *out_pte_val, int *out_level);
int proc_ptwalk_span_resolve(uint32_t pid, uint64_t span2m, int *out_huge,
uint64_t *out_phys_base, uint64_t *out_leaf_pt_kaddr,
uint64_t *out_pte);
#define PROC_PTW_WRITE_OK 0
#define PROC_PTW_WRITE_RESOLVE_FAILED 1
#define PROC_PTW_WRITE_COPY_FAILED 2
#define PROC_PTW_WRITE_VERIFY_FAILED 3
int proc_ptwalk_write(uint32_t pid, uint64_t va, uint64_t len, const void *src);
int proc_aux_range_contains(uint32_t pid, uint64_t addr, uint64_t len);
void proc_read_mem(uint32_t pid, uint64_t addr, uint64_t len, void *buf);
int proc_write_mem(uint32_t pid, uint64_t addr, uint64_t len, const void *buf);
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <stdint.h>
struct proc_field_offsets {
uint32_t name;
uint32_t path;
uint32_t titleid;
uint32_t contentid;
int known;
};
int proc_get_field_offsets(struct proc_field_offsets *out);
+197
View File
@@ -17,6 +17,42 @@
#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)
@@ -112,6 +148,27 @@ struct cmd_proc_write_packet {
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));
@@ -177,6 +234,146 @@ struct cmd_proc_scan_count_packet {
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;
+9 -2
View File
@@ -2,5 +2,12 @@
#pragma once
#define PS5DEBUG_NG_VERSION_STR "1.2.4"
#define PS5DEBUG_NG_BRAND_STR "ps5debug-NG by OSR v" PS5DEBUG_NG_VERSION_STR
#define PS5DEBUG_NG_VERSION_STR "1.3.2"
#ifndef PS5DEBUG_NG_BUILD_FINGERPRINT_STR
#define PS5DEBUG_NG_BUILD_FINGERPRINT_STR "unfingerprinted"
#endif
#define PS5DEBUG_NG_BRAND_STR \
"ps5debug-NG by OSR v" PS5DEBUG_NG_VERSION_STR \
" [" PS5DEBUG_NG_BUILD_FINGERPRINT_STR "]"
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <stdint.h>
#define PS5DEBUG_WRITE_PATH_AUTO 0
#define PS5DEBUG_WRITE_PATH_DMAP 1
#define PS5DEBUG_WRITE_PATH_MDBG 2
#ifndef PS5DEBUG_FORCE_WRITE_PATH
#define PS5DEBUG_FORCE_WRITE_PATH PS5DEBUG_WRITE_PATH_AUTO
#endif
#ifndef PS5DEBUG_WRITE_DIAGNOSTICS
#define PS5DEBUG_WRITE_DIAGNOSTICS 0
#endif
#if PS5DEBUG_FORCE_WRITE_PATH < PS5DEBUG_WRITE_PATH_AUTO \
|| PS5DEBUG_FORCE_WRITE_PATH > PS5DEBUG_WRITE_PATH_MDBG
#error "PS5DEBUG_FORCE_WRITE_PATH must be 0 (auto), 1 (DMAP), or 2 (mdbg)"
#endif
static inline int ps5debug_write_needs_prefix(uint64_t address)
{
/* The workaround preserves address-1 with a non-atomic read-modify-write. */
return (address & 0xFFu) == 0xFFu;
}
static inline int ps5debug_write_diagnostics_enabled(void)
{
return PS5DEBUG_WRITE_DIAGNOSTICS;
}
+39
View File
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-only
#include <ps5/kernel.h>
#include "proc_field_offsets.h"
int proc_get_field_offsets(struct proc_field_offsets *out)
{
static int cached = 0;
static struct proc_field_offsets c;
if (!cached) {
uint32_t fw = kernel_get_fw_version() & 0xffff0000u;
c.name = fw >= 0x12000000u ? 0x5E4u :
fw >= 0x10000000u ? 0x5DCu :
fw >= 0x7000000u ? 0x5D4u :
fw >= 0x6000000u ? 0x5C4u : 0x59Cu;
c.path = fw >= 0x12000000u ? 0x604u :
fw >= 0x10000000u ? 0x5FCu :
fw >= 0x7000000u ? 0x5F4u :
fw >= 0x6000000u ? 0x5E4u : 0x5BCu;
c.titleid = fw >= 0x8000000u ? 0x470u :
fw >= 0x7000000u ? 0x49Au :
fw >= 0x6000000u ? 0x498u : 0x470u;
c.contentid = fw >= 0x12000000u ? 0x504u :
fw >= 0x8000000u ? 0x4F4u :
fw >= 0x7000000u ? 0x4FCu :
fw >= 0x6000000u ? 0x4ECu : 0x4C4u;
c.known = 1;
cached = 1;
}
*out = c;
return 0;
}
+72 -4
View File
@@ -2,9 +2,12 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "sdk_shim.h"
#include "proc.h"
#include "kern_rw_fast.h"
#include "write_policy.h"
extern void *g_proc_rw_mutex;
@@ -42,9 +45,9 @@ static int sys_proc_rw_inner(uint32_t pid, uint64_t address, void *data,
return 0;
}
priv_syscall_573(cmd_struct, args_struct, out_struct);
int syscall_rc = priv_syscall_573(cmd_struct, args_struct, out_struct);
if (out_struct[1] != length) return 1;
if (syscall_rc != 0 || out_struct[1] != length) return 1;
if (arg5_out) *arg5_out = length;
return 0;
@@ -54,9 +57,74 @@ static long sys_proc_rw(uint64_t pid, uint64_t address, uint64_t length,
void *data, uint64_t arg5, int write)
{
scePthreadMutexLock(&g_proc_rw_mutex);
sys_proc_rw_inner((uint32_t)pid, address, data, length, (uint64_t *)arg5, write);
int rc;
if (write && length != 0 && ps5debug_write_needs_prefix(address)) {
int diagnostic = ps5debug_write_diagnostics_enabled();
rc = 1;
if (data && length != SIZE_MAX) {
uint8_t *prefixed = (uint8_t *)malloc((size_t)length + 1);
if (prefixed) {
uint64_t read = 0;
rc = sys_proc_rw_inner((uint32_t)pid, address - 1,
prefixed, 1, &read, 0);
if (rc == 0 && read == 1) {
memcpy(prefixed + 1, data, (size_t)length);
uint8_t leading = prefixed[0];
uint64_t wrote = 0;
if (diagnostic) {
klog_printf("[write:mdbg] pid=%u va=0x%llx len=%llu "
"step=prefix-normalize shifted=0x%llx "
"shifted-len=%llu\n",
(uint32_t)pid,
(unsigned long long)address,
(unsigned long long)length,
(unsigned long long)(address - 1),
(unsigned long long)(length + 1));
}
int write_rc =
sys_proc_rw_inner((uint32_t)pid, address - 1,
prefixed, length + 1,
&wrote, 1);
const uint8_t *expected = (const uint8_t *)data;
prefixed[0] = leading ^ 0xFFu;
for (uint64_t i = 0; i < length; i++)
prefixed[i + 1] = expected[i] ^ 0xFFu;
uint64_t verified = 0;
int verify_rc =
sys_proc_rw_inner((uint32_t)pid, address - 1,
prefixed, length + 1,
&verified, 0);
int exact = write_rc == 0 && wrote == length + 1
&& verify_rc == 0
&& verified == length + 1
&& prefixed[0] == leading
&& memcmp(prefixed + 1, expected,
(size_t)length) == 0;
if (diagnostic || !exact) {
klog_printf("[write:mdbg] pid=%u va=0x%llx len=%llu "
"write-rc=%d wrote=%llu verify-rc=%d "
"verified=%llu exact=%d\n",
(uint32_t)pid,
(unsigned long long)address,
(unsigned long long)length,
write_rc, (unsigned long long)wrote,
verify_rc,
(unsigned long long)verified, exact);
}
rc = exact ? 0 : 1;
if (exact && arg5)
*(uint64_t *)(uintptr_t)arg5 = length;
}
free(prefixed);
}
}
} else {
rc = sys_proc_rw_inner((uint32_t)pid, address, data, length,
(uint64_t *)(uintptr_t)arg5, write);
}
scePthreadMutexUnlock(&g_proc_rw_mutex);
return 0;
return rc;
}
long sys_proc_rw_w0(uint64_t pid, uint64_t address, uint64_t length,
+57 -50
View File
@@ -1,50 +1,57 @@
# SPDX-License-Identifier: GPL-3.0-only
SDK := ../ps5-payload-sdk/install
ifeq (,$(wildcard $(SDK)/toolchain/prospero.mk))
$(error SDK not built - run ../build.sh from the project root)
endif
include $(SDK)/toolchain/prospero.mk
ELF := build/debugger.elf
BUILD_DIR := build
ZYDIR := third_party/zydis
ZYDIS_OBJ := $(BUILD_DIR)/Zydis.o
KSDIR := third_party/keystone
CXXRT := third_party/cxxrt
CFLAGS := -Wall -O2 -I../common/include -Iinclude -I$(ZYDIR) -I$(KSDIR)/include
LDFLAGS := -Wl,--wrap=__patch_init
LDLIBS := -lSceLibcInternal -lkernel_sys -lSceNet -lSceNetCtl -lSceSysCore
ZYDIS_CFLAGS := -O3 -DNDEBUG -DZYAN_NO_LIBC -w -I../common/include -Iinclude -I$(ZYDIR)
CXXFILES := $(wildcard source/*.cpp)
CXX_OBJS := $(patsubst source/%.cpp,$(BUILD_DIR)/%.o,$(CXXFILES))
CXXFLAGS := -O2 -I../common/include -Iinclude -I$(KSDIR)/include
CXX_LDLIBS := -Wl,--start-group -L$(KSDIR)/lib -L$(CXXRT)/lib -lkeystone -lc++ -lc++abi -lunwind -Wl,--end-group
CFILES := $(wildcard ../common/source/*.c) $(wildcard source/*.c)
all: $(ELF)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(ZYDIS_OBJ): $(ZYDIR)/Zydis.c $(ZYDIR)/Zydis.h | $(BUILD_DIR)
$(CC) -c -o $@ $< $(ZYDIS_CFLAGS)
$(BUILD_DIR)/%.o: source/%.cpp | $(BUILD_DIR)
$(CXX) $(CXXFLAGS) -c -o $@ $<
$(ELF): $(CFILES) $(CXX_OBJS) $(ZYDIS_OBJ) | $(BUILD_DIR)
$(CC) $(CFLAGS) -o $@ $(CFILES) $(CXX_OBJS) $(ZYDIS_OBJ) $(LDFLAGS) $(LDLIBS) $(CXX_LDLIBS)
clean:
rm -rf $(BUILD_DIR)
.PHONY: all clean
# SPDX-License-Identifier: GPL-3.0-only
SDK := ../ps5-payload-sdk/install
ifeq (,$(wildcard $(SDK)/toolchain/prospero.mk))
$(error SDK not built - run ../build.sh from the project root)
endif
include $(SDK)/toolchain/prospero.mk
ELF := build/debugger.elf
BUILD_DIR := build
ZYDIR := third_party/zydis
ZYDIS_OBJ := $(BUILD_DIR)/Zydis.o
KSDIR := third_party/keystone
CXXRT := third_party/cxxrt
CFLAGS := -Wall -O2 -I../common/include -Iinclude -I$(ZYDIR) -I$(KSDIR)/include
LDFLAGS := -Wl,--wrap=__patch_init
LDLIBS := -lSceLibcInternal -lkernel_sys -lSceNet -lSceNetCtl -lSceSysCore
WRITE_DIAG ?= 0
WRITE_PATH ?= 0
BUILD_FINGERPRINT ?= unfingerprinted
CFLAGS += -DPS5DEBUG_WRITE_DIAGNOSTICS=$(WRITE_DIAG)
CFLAGS += -DPS5DEBUG_FORCE_WRITE_PATH=$(WRITE_PATH)
CFLAGS += -DPS5DEBUG_NG_BUILD_FINGERPRINT_STR=\"$(BUILD_FINGERPRINT)\"
ZYDIS_CFLAGS := -O3 -DNDEBUG -DZYAN_NO_LIBC -w -I../common/include -Iinclude -I$(ZYDIR)
CXXFILES := $(wildcard source/*.cpp)
CXX_OBJS := $(patsubst source/%.cpp,$(BUILD_DIR)/%.o,$(CXXFILES))
CXXFLAGS := -O2 -I../common/include -Iinclude -I$(KSDIR)/include
CXX_LDLIBS := -Wl,--start-group -L$(KSDIR)/lib -L$(CXXRT)/lib -lkeystone -lc++ -lc++abi -lunwind -Wl,--end-group
CFILES := $(wildcard ../common/source/*.c) $(wildcard source/*.c)
all: $(ELF)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(ZYDIS_OBJ): $(ZYDIR)/Zydis.c $(ZYDIR)/Zydis.h | $(BUILD_DIR)
$(CC) -c -o $@ $< $(ZYDIS_CFLAGS)
$(BUILD_DIR)/%.o: source/%.cpp | $(BUILD_DIR)
$(CXX) $(CXXFLAGS) -c -o $@ $<
$(ELF): $(CFILES) $(CXX_OBJS) $(ZYDIS_OBJ) | $(BUILD_DIR)
$(CC) $(CFLAGS) -o $@ $(CFILES) $(CXX_OBJS) $(ZYDIS_OBJ) $(LDFLAGS) $(LDLIBS) $(CXX_LDLIBS)
clean:
rm -rf $(BUILD_DIR)
.PHONY: all clean
+193
View File
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-3.0-only
//
// Region-parallel multi-pattern AOB scan core (host-testable; no PS4 deps).
//
// The address range is split into N contiguous, ascending, disjoint sub-ranges - one per
// worker thread. Each worker scans its OWN sub-range for ALL patterns once (so every byte is
// read once total), using a shared read-only first-byte dispatch table to skip patterns that
// cannot match at a position. A worker reads (max_plen-1) bytes past its sub-range end so a
// pattern straddling the boundary is still fully evaluated by the worker that owns its start
// position - no double counting, no missed boundary matches.
//
// Because the region is split, a single pattern's matches are spread across workers, so
// uniqueness / target_count cannot be decided by any one worker. Each worker records, per
// pattern, only its earliest (target_count+1) match addresses (ascending). aob_merge() then
// concatenates the workers in ascending range order (worker 0 = lowest addresses) - which IS
// the global ascending match order - and applies the exact sequential rule:
// matches < target_count -> 0 (not found)
// matches == target_count -> address of the target_count-th match
// matches >= target_count+1, unique -> 0 (invalidated)
// matches >= target_count+1, !unique -> address of the target_count-th match
// (target_count+1) recorded per worker is sufficient: the global target_count-th match and the
// existence of a (target_count+1)-th are both decided within the first target_count+1 of the
// concatenation, and each worker contributes its earliest target_count+1.
#ifndef _AOB_SCAN_H
#define _AOB_SCAN_H
#include <stdint.h>
#include <stdbool.h>
// memset/memcpy are provided by the including TU (ps4.h on the payload, <string.h> in the test).
struct aob_pat {
const uint8_t *pat;
const uint8_t *msk; // mask byte 1 = must match, 0 = wildcard
uint32_t plen;
uint8_t target_count; // 1-based occurrence to return
int16_t first_fixed; // pat[0] if msk[0]==1, else -1 (wildcard first byte)
};
// First-byte dispatch over all patterns; built once, read-only during the scan.
struct aob_dispatch {
uint32_t boff[257]; // bucket offsets into bidx; bucket b = [boff[b], boff[b+1])
uint32_t *bidx; // pattern indices grouped by fixed first byte
uint32_t *widx; // patterns whose first byte is a wildcard (checked every position)
uint32_t wild_count;
};
// fill(ctx, off, buf, len): copy `len` target bytes at region offset `off` into buf.
typedef void (*aob_fill_fn)(void *ctx, uint64_t off, uint8_t *buf, uint32_t len);
struct aob_worker {
// shared, read-only
const struct aob_pat *pats;
uint32_t pat_count;
const struct aob_dispatch *disp;
uint64_t base_addr; // absolute address of region offset 0
uint64_t length; // total region length
uint32_t max_plen; // max plen over ALL patterns
uint32_t chunkSize; // start positions processed per read
aob_fill_fn fill;
void *fill_ctx;
const uint32_t *slot_off; // [pat_count+1]; prefix sum of (target_count+1)
// this worker's start-position offset range [start, end)
uint64_t start;
uint64_t end;
// private (per worker)
uint8_t *readBuf; // chunkSize + max_plen bytes
uint64_t *addrs; // slot_off[pat_count] entries; ascending matches per pattern
uint32_t *counts; // pat_count; capped at target_count+1
uint8_t *done; // pat_count; 1 once counts[pi] hit its cap
};
// Build the first-byte dispatch. bidx_storage/widx_storage must each hold pat_count entries.
static inline void aob_build_dispatch(const struct aob_pat *pats, uint32_t pat_count,
struct aob_dispatch *d,
uint32_t *bidx_storage, uint32_t *widx_storage) {
uint32_t bcount[256];
memset(bcount, 0, sizeof(bcount));
for (uint32_t pi = 0; pi < pat_count; pi++) {
if (pats[pi].first_fixed >= 0) bcount[(uint8_t)pats[pi].first_fixed]++;
}
d->boff[0] = 0;
for (uint32_t b = 0; b < 256; b++) d->boff[b + 1] = d->boff[b] + bcount[b];
uint32_t cursor[256];
memcpy(cursor, d->boff, 256 * sizeof(uint32_t));
d->bidx = bidx_storage;
d->widx = widx_storage;
d->wild_count = 0;
for (uint32_t pi = 0; pi < pat_count; pi++) {
if (pats[pi].first_fixed >= 0) {
d->bidx[cursor[(uint8_t)pats[pi].first_fixed]++] = pi;
} else {
d->widx[d->wild_count++] = pi;
}
}
}
static inline bool aob_match_at(const uint8_t *buf, uint32_t pos, const struct aob_pat *p) {
const uint8_t *pat = p->pat, *msk = p->msk;
for (uint32_t k = 0; k < p->plen; k++) {
if (buf[pos + k] != pat[k] && msk[k] == 1) return false;
}
return true;
}
// Test one pattern at a buffer position; record the match if it fits and we still need it.
// Returns 1 if this call made the pattern reach its cap (so the caller can drop it), else 0.
static inline int aob_try(struct aob_worker *w, uint32_t pi, uint32_t jj,
uint32_t read_len, uint64_t goff, uint64_t abs_addr) {
if (w->done[pi]) return 0;
const struct aob_pat *p = &w->pats[pi];
if (goff + p->plen > w->length) return 0; // pattern would run past the region
if (jj + p->plen > read_len) return 0; // not enough bytes read (region-end safety)
if (!aob_match_at(w->readBuf, jj, p)) return 0;
uint32_t cap = (uint32_t)p->target_count + 1;
uint32_t c = w->counts[pi];
if (c < cap) {
w->addrs[w->slot_off[pi] + c] = abs_addr;
w->counts[pi] = ++c;
if (c == cap) { w->done[pi] = 1; return 1; }
}
return 0;
}
// Scan this worker's sub-range. Writes counts[]/addrs[]/done[]; safe to run on its own thread.
static inline void aob_scan_range(struct aob_worker *w) {
for (uint32_t pi = 0; pi < w->pat_count; pi++) { w->counts[pi] = 0; w->done[pi] = 0; }
uint32_t active = w->pat_count;
const struct aob_dispatch *d = w->disp;
uint64_t off = w->start;
while (off < w->end && active > 0) {
uint64_t own64 = w->end - off;
uint32_t own = (own64 < w->chunkSize) ? (uint32_t)own64 : w->chunkSize; // owned start positions
uint64_t avail = w->length - off;
uint32_t read_len = own + w->max_plen - 1;
if ((uint64_t)read_len > avail) read_len = (uint32_t)avail;
memset(w->readBuf, 0, read_len);
w->fill(w->fill_ctx, off, w->readBuf, read_len);
for (uint32_t jj = 0; jj < own; jj++) {
uint8_t b = w->readBuf[jj];
uint64_t goff = off + jj;
uint64_t abs_addr = w->base_addr + goff;
for (uint32_t t = d->boff[b]; t < d->boff[b + 1]; t++) {
active -= (uint32_t)aob_try(w, d->bidx[t], jj, read_len, goff, abs_addr);
}
for (uint32_t t = 0; t < d->wild_count; t++) {
active -= (uint32_t)aob_try(w, d->widx[t], jj, read_len, goff, abs_addr);
}
if (active == 0) break;
}
off += own;
}
}
// Merge the workers' per-pattern results into output[pat_count] (absolute address or 0).
// worker_addrs[wkr]/worker_counts[wkr] are worker wkr's arrays; workers MUST be in ascending
// range order (worker 0 owns the lowest addresses).
static inline void aob_merge(const struct aob_pat *pats, uint32_t pat_count, bool stop_unique,
const uint32_t *slot_off, uint32_t num_workers,
const uint64_t *const *worker_addrs,
const uint32_t *const *worker_counts,
uint64_t *output) {
for (uint32_t pi = 0; pi < pat_count; pi++) {
uint32_t tc = pats[pi].target_count;
uint64_t cap = (uint64_t)tc + 1;
uint64_t total = 0;
uint64_t result = 0;
bool found = false;
for (uint32_t wkr = 0; wkr < num_workers; wkr++) {
uint32_t c = worker_counts[wkr][pi];
if (!found && tc >= 1 && total < tc && total + c >= tc) {
uint32_t local_idx = (uint32_t)(tc - total - 1); // 0-based within this worker
result = worker_addrs[wkr][slot_off[pi] + local_idx];
found = true;
}
total += c;
if (total > cap) total = cap;
}
if (!found) output[pi] = 0;
else if (stop_unique && total >= (uint64_t)tc + 1) output[pi] = 0;
else output[pi] = result;
}
}
#endif
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-only
//
// Phase 3 aliasing read engine (scan_alias.c). Maps a target process's physical
// pages into OUR address space by writing not-present leaf PTEs in our own pmap,
// so the turbo-scan compare runs in place at DRAM bandwidth instead of copying via
// mdbg/DMAP. PANIC-CAPABLE: a wrong leaf-PTE write corrupts page tables. Every
// write is gated (B1 leaf level==3, B2 leaf not-present, B3 leaf kaddr in
// [dmap, dmap+0x10_0000_0000)); on ANY guard trip / verify mismatch the map is
// abandoned and the caller falls back to proc_read_mem. Restore-then-leak: written
// PTEs are zeroed on release; the window VA is NEVER munmap'd (munmap/pmap_remove
// would walk our PTEs and free the GAME's phys pages).
//
// Built on the read-only accessors in proc_ptwalk.c (proc_ptwalk_leaf_addr /
// _probe / _dmap_base) and the kernel R/W primitive kernel_copyin_fast/_out_fast.
// Mechanism HW-proven in benchmark.c "R4C". Gated behind TSE_ALIASING + the
// per-request TS_USE_ALIASING flag (client opt-in, default OFF); mdbg is always
// the fallback.
#pragma once
#include <stdint.h>
#include <stddef.h>
typedef struct scan_alias_ctx scan_alias_ctx;
// Begin an aliasing context for `pid` with a VA arena of `arena_cap` bytes (0 =>
// default 256 MB). Returns NULL if aliasing is unavailable (no DMAP base / mmap
// fail) -> caller must read via proc_read_mem. The arena is leaked at end().
scan_alias_ctx *scan_alias_begin(uint32_t pid, uint64_t arena_cap);
// Make target bytes [tgt, tgt+len) readable in our address space. On success
// returns a CPU pointer p such that p[0..len) == target[tgt..tgt+len), AFTER
// verifying sampled pages byte-identically against proc_read_mem. Returns NULL on
// any failure (unavailable, exhaustion, guard trip, uncached/PCD page, verify
// mismatch) -> caller reads this range via proc_read_mem. *out_mapped_len (if
// non-NULL) gets `len` on success. One active map at a time: call
// scan_alias_release() before the next map.
const void *scan_alias_map(scan_alias_ctx *c, uint64_t tgt, uint64_t len,
uint64_t *out_mapped_len);
// Restore the active map's leaf PTEs to 0 (not-present) and clear it. Safe to call
// with no active map. The window VA is left leaked (never munmap'd) and never
// reused (advancing VA needs no TLB flush).
void scan_alias_release(scan_alias_ctx *c);
// Release any active map, leak the arena VA, free the context struct.
void scan_alias_end(scan_alias_ctx *c);
// Reuse an existing context for a new scan (Phase 3 #1: per-connection arena reuse).
// Releases any active map, rebinds to `pid`, and invalidates the huge-page derivation
// cache (it is pid-specific). The arena + advancing cursor are PRESERVED so repeated
// scans on one connection share VA instead of leaking a fresh arena each time.
void scan_alias_rebind(scan_alias_ctx *c, uint32_t pid);
+70
View File
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <stdint.h>
#include "protocol.h"
/* Shared client/server wire policy for Turbo float and double scans.
* Value-type numbers are the published scan protocol encoding:
* 8 = IEEE-754 binary32, 9 = IEEE-754 binary64. */
#define TS_FLOAT_VALUE_TYPE_F32 8u
#define TS_FLOAT_VALUE_TYPE_F64 9u
#define TS_FLOAT_DEFAULT_EXPONENTS 11u
static inline int ts_float_policy_is_float(uint8_t value_type) {
return value_type == TS_FLOAT_VALUE_TYPE_F32 || value_type == TS_FLOAT_VALUE_TYPE_F64;
}
static inline uint32_t ts_float_policy_exponent_limit(uint32_t flags) {
uint32_t limit = (flags & TS_FLOAT_EXPONENT_MASK) >> TS_FLOAT_EXPONENT_SHIFT;
return limit ? limit : TS_FLOAT_DEFAULT_EXPONENTS;
}
/* Mirrors the clients' Simple predicate exactly. Positive zero is retained;
* every other value must have an exponent within +/-limit of 1.0's exponent.
* The 7-bit wire field bounds limit to 127. */
static inline int ts_float_policy_simple_value(uint32_t flags, uint8_t value_type,
const void *value) {
if (!(flags & TS_FLOAT_SIMPLE) || !ts_float_policy_is_float(value_type) || !value)
return 1;
uint32_t limit = ts_float_policy_exponent_limit(flags);
if (value_type == TS_FLOAT_VALUE_TYPE_F32) {
uint32_t bits;
__builtin_memcpy(&bits, value, sizeof(bits));
if (bits == 0u) return 1;
uint32_t exponent = (bits >> 23) & 0xffu;
uint32_t distance = exponent > 127u ? exponent - 127u : 127u - exponent;
return distance <= limit;
}
uint64_t bits;
__builtin_memcpy(&bits, value, sizeof(bits));
if (bits == 0u) return 1;
uint32_t exponent = (uint32_t)((bits >> 52) & 0x7ffu);
uint32_t distance = exponent > 1023u ? exponent - 1023u : 1023u - exponent;
return distance <= limit;
}
/* FloatExact in Phoenix/Cheater-NG means numeric IEEE equality, not a raw-bit
* comparison: +0 equals -0 and NaN never equals NaN. Unaligned wire/memory
* values are copied before conversion. */
static inline int ts_float_policy_exact_equal(uint8_t value_type,
const void *memory_value,
const void *scan_value) {
if (!memory_value || !scan_value) return 0;
if (value_type == TS_FLOAT_VALUE_TYPE_F32) {
float memory_float, scan_float;
__builtin_memcpy(&memory_float, memory_value, sizeof(memory_float));
__builtin_memcpy(&scan_float, scan_value, sizeof(scan_float));
return memory_float == scan_float;
}
if (value_type == TS_FLOAT_VALUE_TYPE_F64) {
double memory_double, scan_double;
__builtin_memcpy(&memory_double, memory_value, sizeof(memory_double));
__builtin_memcpy(&scan_double, scan_value, sizeof(scan_double));
return memory_double == scan_double;
}
return 0;
}
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-3.0-only
//
// Turbo-scan support (Phase 1): typed/SIMD comparators + region filtering for the
// new TURBOSCAN command family (0xBDAACC10-0xBDAACC1F). Additive - the legacy and
// async scan paths are untouched.
#pragma once
#include <stdint.h>
#include <stddef.h>
// valtype encoding (matches proc_scan_getSizeOfValueType): 0=u8 1=i8 2=u16 3=i16
// 4=u32 5=i32 6=u64 7=i64 8=f32 9=f64.
// Find every element in [buf, buf+len) that EXACTLY equals *value (valtype-typed),
// stepping by `step` bytes (step >= element size; typically == element size for an
// aligned scan). Writes matching byte-offsets (relative to buf) into out_off,
// up to max_out. Returns the TOTAL match count (may exceed max_out; caller detects
// truncation when return > max_out). SIMD-accelerated for u32/i32 aligned scans;
// typed-scalar for the rest. Replaces the per-element double-switch on the turbo
// path only.
size_t scan_simd_find_exact(unsigned char valtype, const uint8_t *buf, size_t len,
const void *value, uint32_t step,
uint32_t *out_off, size_t max_out);
// --- Typed point comparator (rescan turbo path) -------------------------------
// proc_scan_compareValues is a non-inlined cmpType-then-valType double-switch
// (~0.8 GB/s). The rescan path (TURBOSCAN_COUNT) tests one candidate at a time, so
// it can't use the buffer-oriented scan_simd_find_exact; instead it selects ONCE
// per scan (cmpType+valType are loop-invariant) whether the typed point comparator
// applies, then calls scan_point_compare per candidate.
//
// scan_point_turbo_supported() returns nonzero for the (cmpType,valType) pairs the
// typed comparator handles byte-identically to proc_scan_compareValues:
// - cmpType 0 (exact) for integer valtypes 0..7 only (float/double exact uses the
// fuzzy path; arrbytes uses the mask path -> both fall back);
// - cmpType 5/6/7/8/9/10 (increased / increased-by / decreased / decreased-by /
// changed / unchanged - the common rescan delta types) for all numeric types.
// Everything else (1 within-1.0, 2/3 >/< value, 4 between, 11 nonzero, 12, arrbytes,
// float/double exact) returns 0 -> caller must use proc_scan_compareValues.
int scan_point_turbo_supported(unsigned char cmpType, unsigned char valType);
// Typed point comparison, byte-identical to proc_scan_compareValues for every
// (cmpType,valType) pair scan_point_turbo_supported() accepts. `mem` = current
// memory value; `scan` = the user's scan value (the "by" amount for 6/8); `prev` =
// the candidate's previous value (required for cmpType 5..10; unused for 0).
// Precondition: scan_point_turbo_supported(cmpType,valType) != 0. Returns 0/1.
int scan_point_compare(unsigned char cmpType, unsigned char valType,
const void *mem, const void *scan, const void *prev);
// A readable address range selected for scanning.
struct scan_range { uint64_t start; uint64_t end; };
// Region-exclusion mode for scan_turbo_regions().
#define SCAN_EXCLUDE_NONE 0 // all readable regions
#define SCAN_EXCLUDE_PCD 1 // drop leaf-PTE PCD=1 (cheap; misses WB-mapped slow mem)
#define SCAN_EXCLUDE_SPEED 2 // drop regions reading < min_mbps (robust; costs a probe/region)
// Enumerate the target's readable regions into out[0..max). `mode` selects the
// exclusion strategy (see above); `min_mbps` applies to SCAN_EXCLUDE_SPEED (e.g.
// 100). Returns the number of ranges; *out_total_bytes (if non-NULL) gets their
// summed size. Probe failure -> region is INCLUDED (conservative).
int scan_turbo_regions(uint32_t pid, int mode, uint32_t min_mbps,
struct scan_range *out, int max, uint64_t *out_total_bytes);
+26 -9
View File
@@ -4,12 +4,13 @@
#include "sdk_shim.h"
#include "net.h"
#include "kern_rw_fast.h"
#include "proc_field_offsets.h"
#include <stdarg.h>
extern int find_titleid(const uint8_t *buf, size_t len, char *out);
extern int find_contentid(const uint8_t *buf, size_t len, char *out, size_t out_size);
#define PROC_NEXT_OFFSET 0x00
#define PROC_TITLE_ID_OFFSET 0x470
#define PROC_CONTENT_ID_OFFSET 0x4C4
#define PROC_SELFINFO_NAME_OFFSET 0x59C
#define PROC_SELFINFO_NAME_SIZE 32
struct cmd_console_notify_packet {
@@ -184,6 +185,13 @@ int console_foreground_app_handle(int fd, struct cmd_packet *packet) {
struct cmd_console_foreground_app_response resp;
memset(&resp, 0, sizeof(resp));
struct proc_field_offsets off;
if (proc_get_field_offsets(&off) != 0) {
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
intptr_t allproc_head = (intptr_t)KERNEL_ADDRESS_ALLPROC;
intptr_t kproc = 0;
if (kernel_copyout_fast(allproc_head, &kproc, sizeof(kproc)) != 0) {
@@ -197,7 +205,7 @@ int console_foreground_app_handle(int fd, struct cmd_packet *packet) {
for (uint32_t guard = 0; cur != 0 && guard < 0x1000; guard++) {
char nm[PROC_SELFINFO_NAME_SIZE + 1];
nm[PROC_SELFINFO_NAME_SIZE] = 0;
if (kernel_copyout_fast(cur + PROC_SELFINFO_NAME_OFFSET, nm, PROC_SELFINFO_NAME_SIZE) == 0
if (kernel_copyout_fast(cur + off.name, nm, PROC_SELFINFO_NAME_SIZE) == 0
&& nm[0] != 0
&& strncmp(nm, "eboot.bin", 9) == 0
&& (nm[9] == '\0' || nm[9] == ' ')) {
@@ -231,13 +239,19 @@ int console_foreground_app_handle(int fd, struct cmd_packet *packet) {
const uint8_t *p = (const uint8_t *)proc_buf;
resp.pid = (uint32_t)found_pid;
memcpy(resp.name, p + PROC_SELFINFO_NAME_OFFSET, PROC_SELFINFO_NAME_SIZE);
memcpy(resp.titleid, p + PROC_TITLE_ID_OFFSET, sizeof(resp.titleid));
memcpy(resp.contentid, p + PROC_CONTENT_ID_OFFSET, sizeof(resp.contentid));
memcpy(resp.name, p + off.name, PROC_SELFINFO_NAME_SIZE);
if (find_contentid(p + 0x440, 0x200, resp.contentid, sizeof(resp.contentid)) != 0) {
memcpy(resp.contentid, p + off.contentid, sizeof(resp.contentid));
}
char titleid_z[17];
memcpy(titleid_z, resp.titleid, 16);
titleid_z[16] = '\0';
memset(titleid_z, 0, sizeof(titleid_z));
if (find_titleid(p + 0x440, 0x200, titleid_z) != 0) {
memcpy(titleid_z, p + off.titleid, 16);
titleid_z[16] = '\0';
}
memcpy(resp.titleid, titleid_z, sizeof(resp.titleid));
char meta_dir[96];
snprintf(meta_dir, sizeof(meta_dir), "/system_data/priv/appmeta/%s", titleid_z);
@@ -370,6 +384,9 @@ void *klog_server_thread(void *arg)
}
if (connfd == 0) continue;
int32_t nosig = 1;
sceNetSetsockopt(connfd, 0xFFFF, 0x0800, &nosig, 4);
int klog_fd = open("/dev/klog", 0, 0);
if (klog_fd == -1) {
+375 -78
View File
@@ -24,6 +24,23 @@ extern uint32_t g_debug_attached;
extern void *g_server_mutex;
extern void *g_proc_rw_mutex;
#define DEBUG_WAIT_POLL_US 250u
#define DEBUG_WAIT_POLLS 8000u
static int debug_wait4_bounded(int pid, int *out_status) {
int status = 0;
for (unsigned int i = 0; i < DEBUG_WAIT_POLLS; i++) {
int rc = wait4(pid, &status, 1, NULL);
if (rc > 0) {
if (out_status) *out_status = status;
return 1;
}
if (rc < 0) return -1;
sceKernelUsleep(DEBUG_WAIT_POLL_US);
}
return 0;
}
struct dbgctx {
uint32_t pid;
int dbgfd;
@@ -182,11 +199,16 @@ extern int kern_proc_install_dbregs(int pid, int lwpid, void *fpu_buf);
extern int kern_get_proc_info_by_pid(int pid, int lwpid, void *regs_buf);
extern int kern_apply_thread_dbgctx(int pid, int lwpid, void *regs_buf);
extern int kern_get_lwp_full_state(int pid, int lwpid, void *regs, void *dbreg, void *fpu);
extern int kern_get_fpregs(int pid, int lwpid, void *fpu_buf);
extern int kern_set_fpregs(int pid, int lwpid, const void *fpu_buf);
extern int kern_get_fsgsbase(int pid, int lwpid, void *out16);
extern int kern_set_fsgsbase(int pid, int lwpid, const void *in16);
extern void kern_thread_cache_flush(void);
#define REG_BLOB_SIZE 0xB0
#define FPREG_BLOB_SIZE 0x340
#define DBREG_BLOB_SIZE 0x80
#define REG_BLOB_SIZE 0xB0
#define FPREG_BLOB_SIZE 0x340
#define DBREG_BLOB_SIZE 0x80
#define FSGSBASE_BLOB_SIZE 0x10
struct cmd_debug_lwp_packet {
uint32_t lwpid;
@@ -200,8 +222,6 @@ struct cmd_debug_setreg_packet {
#define DBGCTX() ((struct dbgctx *)curdbgctx)
int connect_debugger(struct dbgctx *ctx, void *client_sockaddr_in) {
g_debug_attached = 1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
@@ -238,6 +258,7 @@ extern int sys_proc_vm_map(uint32_t pid, void **out_maps, int *out_count);
extern int gettimeofday(struct timeval *tp, void *tzp);
#define INT3_REWIND_MAX 16
#define DBG_BP_SLOT_SIZE 0x18
static uint64_t g_recently_disabled_bps[INT3_REWIND_MAX];
static int g_recently_disabled_count = 0;
@@ -329,11 +350,35 @@ static void int3_iterative_sweep(int pid, const uint64_t *bp_addrs, int n_bps) {
}
}
static void debug_clear_breakpoint_slots(void *svc) {
if (!svc) return;
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
char *bp_entry = (char *)svc + i * DBG_BP_SLOT_SIZE;
*(uint32_t *)(bp_entry + 0x08) = 0;
*(uint64_t *)(bp_entry + 0x10) = 0;
}
}
static void debug_clear_watchpoint_shadow(void *svc) {
if (!svc) return;
memset((char *)svc + 0x2D8, 0, DBREG_BLOB_SIZE);
}
void debug_full_teardown(void *svc) {
if (!g_debug_attached) {
debug_clear_breakpoint_slots(svc);
debug_clear_watchpoint_shadow(svc);
g_debug_pending_wait_valid = 0;
g_debug_pending_wait_pid = 0;
g_debug_pending_wait_status = 0;
g_debug_phase = DEBUG_PHASE_DETACHED;
return;
}
g_debug_phase = DEBUG_PHASE_DETACHING;
g_recently_disabled_count = 0;
debug_resume_cache_flush();
@@ -368,22 +413,24 @@ void debug_full_teardown(void *svc) {
int alive_rc = sys_proc_vm_map((uint32_t)pid, &vm_maps, &vm_count);
if (alive_rc != 0) {
if (vm_maps) free(vm_maps);
ptrace_raw(PT_DETACH, pid, NULL, 0);
goto close_socket_and_unlock;
}
if (vm_maps) free(vm_maps);
char *rbx = (char *)svc + 0x18;
char *bp_end = (char *)svc + 0x2E8;
uint64_t teardown_bp_addrs[INT3_REWIND_MAX];
int teardown_n_bps = 0;
while (rbx != bp_end) {
uint64_t address = *(uint64_t *)(rbx - 8);
if (address == 0) break;
sys_proc_rw_w1((uint64_t)pid, address, 1, rbx, 0);
if (teardown_n_bps < INT3_REWIND_MAX) {
teardown_bp_addrs[teardown_n_bps++] = address;
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
char *bp_entry = (char *)svc + i * DBG_BP_SLOT_SIZE;
uint32_t enabled = *(uint32_t *)(bp_entry + 0x08);
uint64_t address = *(uint64_t *)(bp_entry + 0x10);
if (enabled && address) {
proc_write_mem((uint32_t)pid, address, 1,
bp_entry + DBG_BP_SLOT_SIZE);
if (teardown_n_bps < INT3_REWIND_MAX) {
teardown_bp_addrs[teardown_n_bps++] = address;
}
}
rbx += 0x18;
}
int3_iterative_sweep(pid, teardown_bp_addrs, teardown_n_bps);
@@ -405,10 +452,12 @@ void debug_full_teardown(void *svc) {
int rc = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (rc == -1) {
if (errno != 16 ) {
goto teardown_done;
goto free_and_detach;
}
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
goto free_and_detach;
}
kill(pid, 17 );
wait4(pid, NULL, 0, NULL);
we_stopped = 1;
ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
}
@@ -418,32 +467,30 @@ void debug_full_teardown(void *svc) {
int rc = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (rc == -1) {
if (errno != 16) {
goto teardown_done;
goto free_and_detach;
}
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
goto free_and_detach;
}
kill(pid, 17);
wait4(pid, NULL, 0, NULL);
we_stopped = 1;
rc = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
count = rc;
lwpids = (int *)net_alloc_buffer((unsigned long)(count * 4));
if (!lwpids) {
resume_app_unelev(&es, pid);
ptrace_raw(PT_CONTINUE, pid, (void *)1, 0);
goto close_socket_and_unlock;
goto free_and_detach;
}
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, count) == -1) {
resume_app_unelev(&es, pid);
ptrace_raw(PT_CONTINUE, pid, (void *)1, 0);
goto close_socket_and_unlock;
goto free_and_detach;
}
} else {
count = rc;
lwpids = (int *)net_alloc_buffer((unsigned long)(count * 4));
if (!lwpids) {
goto close_socket_and_unlock;
goto free_and_detach;
}
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, count) == -1) {
goto close_socket_and_unlock;
goto free_and_detach;
}
}
@@ -476,7 +523,16 @@ close_socket_and_unlock:
close((int)dbgfd);
}
teardown_done:
/* A command connection can attach again without being recreated. Clear
every slot after any live-target restores, including the dead-target
liveness-failure path, so a later arm cannot match stale state. */
debug_clear_breakpoint_slots(svc);
debug_clear_watchpoint_shadow(svc);
g_debug_pending_wait_valid = 0;
g_debug_pending_wait_pid = 0;
g_debug_pending_wait_status = 0;
g_debug_phase = DEBUG_PHASE_DETACHED;
if (es_active) elev_restore(&es);
}
@@ -488,16 +544,48 @@ int debug_stopgo_handle(int pid, char action) {
if (g_debug_attached) {
if (action == 0) {
if (g_debug_phase == DEBUG_PHASE_RUNNING) {
scePthreadMutexUnlock(&g_server_mutex);
return 0;
}
if (g_debug_phase != DEBUG_PHASE_EVENT_STOPPED) {
scePthreadMutexUnlock(&g_server_mutex);
return -1;
}
g_debug_phase = DEBUG_PHASE_RESUMING;
g_stopgo_resume_signal = 0;
g_stopgo_resume_pid = (uint32_t)pid;
g_stopgo_last_signal = 0;
g_stopgo_mode = 2;
} else if (action == 1) {
if (g_debug_phase == DEBUG_PHASE_EVENT_STOPPED) {
scePthreadMutexUnlock(&g_server_mutex);
return 0;
}
if (g_debug_phase != DEBUG_PHASE_RUNNING) {
scePthreadMutexUnlock(&g_server_mutex);
return -1;
}
int stop_status = 0;
if (kill(pid, 0x11) != 0
|| debug_wait4_bounded(pid, &stop_status) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
scePthreadMutexUnlock(&g_server_mutex);
return -1;
}
if (((stop_status >> 8) & 0xFF) != 0x11) {
g_debug_pending_wait_pid = (uint32_t)pid;
g_debug_pending_wait_status = stop_status;
g_debug_pending_wait_valid = 1;
g_debug_phase = DEBUG_PHASE_EVENT_PENDING;
scePthreadMutexUnlock(&g_server_mutex);
return 0;
}
g_stopgo_last_signal = 0x11;
kill(pid, 0x11);
wait4(pid, NULL, 0, NULL);
g_debug_phase = DEBUG_PHASE_EVENT_STOPPED;
g_stopgo_mode = 2;
} else if (action == 2) {
g_debug_phase = DEBUG_PHASE_DETACHING;
g_stopgo_resume_signal = 9;
g_stopgo_resume_pid = (uint32_t)pid;
g_stopgo_last_signal = 9;
@@ -508,14 +596,18 @@ int debug_stopgo_handle(int pid, char action) {
uint32_t sig = k_stopgo_signal_table[(unsigned char)action];
g_stopgo_mode = 1;
g_stopgo_last_signal = sig;
kill(pid, (int)sig);
wait4(pid, NULL, 0, NULL);
if (kill(pid, (int)sig) != 0 || debug_wait4_bounded(pid, NULL) != 1) {
scePthreadMutexUnlock(&g_server_mutex);
return -1;
}
if (action == 0 && g_debug_attached) {
resume_app_via_self_id((int)g_stopgo_resume_pid);
}
} else {
kill(pid, 0);
wait4(pid, NULL, 0, NULL);
if (kill(pid, 0) != 0 || debug_wait4_bounded(pid, NULL) != 1) {
scePthreadMutexUnlock(&g_server_mutex);
return -1;
}
}
}
@@ -533,16 +625,18 @@ int debug_process_stop_handle(int fd, struct cmd_packet *packet) {
int pid = *(int *)data;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
debug_stopgo_handle(pid, (char)action);
if (debug_stopgo_handle(pid, (char)action) != 0) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
extern long ptrace_raw(int op, int pid, void *addr, int data);
extern int *__error(void);
int debug_attach_handle(int fd, struct cmd_packet *packet) {
if (g_debug_attached) {
if (g_debug_attached || g_debug_phase != DEBUG_PHASE_DETACHED) {
net_send_int32(fd, CMD_ALREADY_DEBUG);
return 1;
}
@@ -550,39 +644,58 @@ int debug_attach_handle(int fd, struct cmd_packet *packet) {
struct cmd_debug_attach_packet *ap =
(struct cmd_debug_attach_packet *)packet->data;
if (!ap) {
curdbgcli = NULL;
curdbgctx = NULL;
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
g_debug_phase = DEBUG_PHASE_ATTACHING;
DBGCTX()->pid = ap->pid;
DBGCTX()->dbgfd = -1;
struct elev_state es;
if (elev_save_and_set(&es) != 0) {
g_debug_phase = DEBUG_PHASE_DETACHED;
curdbgcli = NULL;
curdbgctx = NULL;
net_send_int32(fd, CMD_ERROR);
return 1;
}
if (ptrace_raw(PT_ATTACH, ap->pid, NULL, 0) == -1) {
elev_restore(&es);
g_debug_phase = DEBUG_PHASE_DETACHED;
curdbgcli = NULL;
curdbgctx = NULL;
net_send_int32(fd, CMD_ERROR);
return 1;
}
elev_restore(&es);
/* From this point on free_client must run the full remote teardown on any
error. Claim the lifecycle only after PT_ATTACH has actually succeeded. */
g_debug_attached = 1;
*(uint32_t *)((char *)curdbgcli + 8) = 1;
int wait_status = 0;
*__error() = 0;
long wait_rc = __crt_syscall(7 , ap->pid, &wait_status, 0, 0);
if (wait_rc == -1) {
sceKernelUsleep(20000);
if (debug_wait4_bounded(ap->pid, &wait_status) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
net_send_int32(fd, CMD_ERROR);
return 1;
}
resume_app_via_self_id(ap->pid);
if (elev_save_and_set(&es) != 0) {
g_debug_phase = DEBUG_PHASE_BROKEN;
net_send_int32(fd, CMD_ERROR);
return 1;
}
if (ptrace_raw(PT_CONTINUE, ap->pid, (void *)1, 0) != 0) {
elev_restore(&es);
g_debug_phase = DEBUG_PHASE_BROKEN;
net_send_int32(fd, CMD_ERROR);
return 1;
}
@@ -591,12 +704,12 @@ int debug_attach_handle(int fd, struct cmd_packet *packet) {
void *evt_client_sockaddr = (char *)curdbgcli + 0x0C;
if (connect_debugger(DBGCTX(), evt_client_sockaddr) != 0) {
g_debug_phase = DEBUG_PHASE_BROKEN;
net_send_int32(fd, CMD_ERROR);
return 1;
}
DBGCTX()->pid = ap->pid;
*(uint32_t *)((char *)curdbgcli + 8) = 1;
g_debug_phase = DEBUG_PHASE_RUNNING;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
@@ -608,8 +721,6 @@ int debug_detach_handle(int fd, struct cmd_packet *packet) {
return 0;
}
#define DBG_BP_SLOT_SIZE 0x18
int debug_set_breakpoint_handle(int fd, struct cmd_packet *packet) {
if (!g_debug_attached) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = DBGCTX()->pid;
@@ -627,13 +738,46 @@ int debug_set_breakpoint_handle(int fd, struct cmd_packet *packet) {
char *saved_byte_slot = bp_entry + DBG_BP_SLOT_SIZE;
if (bp->enabled) {
if (bp->address == 0) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
uint32_t was_enabled = *(uint32_t *)(bp_entry + 0x08);
uint64_t old_address = *(uint64_t *)(bp_entry + 0x10);
/* Repeating the exact same arm is idempotent. Re-reading here would
save the already-patched 0xCC and destroy the restore byte. Moving
an enabled slot must be expressed as a disarm followed by an arm. */
if (was_enabled) {
if (old_address == bp->address) {
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
net_send_int32(fd, CMD_ERROR);
return 0;
}
/* Debug commands are serialized by handle_client's debugger locks.
Enforce one physical INT3 owner per address while those locks are
held so no second slot can save 0xCC as its original byte. */
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
if (i == (int)bp->index) continue;
char *other = (char *)curdbgctx + i * DBG_BP_SLOT_SIZE;
if (*(uint32_t *)(other + 0x08)
&& *(uint64_t *)(other + 0x10) == bp->address) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
}
*(uint32_t *)(bp_entry + 0x08) = 1;
*(uint64_t *)(bp_entry + 0x10) = bp->address;
sys_proc_rw_w0((uint64_t)pid, bp->address, 1, saved_byte_slot, 0);
unsigned char int3 = 0xCC;
sys_proc_rw_w1((uint64_t)pid, bp->address, 1, &int3, 0);
proc_write_mem((uint32_t)pid, bp->address, 1, &int3);
} else {
uint32_t was_enabled = *(uint32_t *)(bp_entry + 0x08);
@@ -646,7 +790,7 @@ int debug_set_breakpoint_handle(int fd, struct cmd_packet *packet) {
int stuck_lw[INT3_REWIND_MAX]; int stuck_n = 0;
int stuck = int3_scan_stuck(pid, &bp_addr, 1, stuck_lw, INT3_REWIND_MAX, &stuck_n);
sys_proc_rw_w1((uint64_t)pid, bp_addr, 1, saved_byte_slot, 0);
proc_write_mem((uint32_t)pid, bp_addr, 1, saved_byte_slot);
if (stuck > 0) {
int3_resume_lwps(pid, stuck_lw, stuck_n);
@@ -699,7 +843,11 @@ int debug_set_watchpoint_handle(int fd, struct cmd_packet *packet) {
}
} else {
if (errno != 16 ) goto wp_err_no_resume;
kill(pid, 17); wait4(pid, NULL, 0, NULL);
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
goto wp_err_no_resume;
}
count = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
we_stopped = 1;
lwpids = (int *)net_alloc_buffer((unsigned long)(count * 4));
@@ -772,8 +920,12 @@ int debug_get_thread_list_handle(int fd, struct cmd_packet *packet) {
if (count == -1) {
if (errno != 16 ) { net_send_int32(fd, CMD_ERROR); return 1; }
kill(pid, 17 );
wait4(pid, NULL, 0, NULL);
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
net_send_int32(fd, CMD_ERROR);
return 1;
}
count = (int)ptrace_elev(PT_GETNUMLWPS, pid, NULL, 0);
we_stopped = 1;
}
@@ -878,7 +1030,10 @@ int debug_getfpregs_handle(int fd, struct cmd_packet *packet) {
if (!gp) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
memset(buf, 0, FPREG_BLOB_SIZE);
if (ptrace_elev(PT_GETFPREGS, gp->lwpid, buf, 0) == -1 && errno != 0) goto err;
if (kern_get_fpregs(pid, gp->lwpid, buf) != 0) {
if (ptrace_elev(PT_GETFPREGS, gp->lwpid, buf, 0) == -1 && errno != 0) goto err;
}
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, buf, FPREG_BLOB_SIZE);
@@ -896,11 +1051,18 @@ int debug_setfpregs_handle(int fd, struct cmd_packet *packet) {
struct cmd_debug_setreg_packet *sp = (struct cmd_debug_setreg_packet *)packet->data;
if (!sp) goto err;
if (sp->length > FPREG_BLOB_SIZE) goto err;
memset(buf, 0, FPREG_BLOB_SIZE);
net_send_int32(fd, CMD_SUCCESS);
net_recv_all(fd, buf, sp->length, 1);
if (net_recv_all(fd, buf, sp->length, 1) < 0) goto err;
if (ptrace_elev(PT_SETFPREGS, pid, buf, 0) == -1 && errno != 0) goto err;
int applied = 0;
if (sp->length == FPREG_BLOB_SIZE)
applied = (kern_set_fpregs(pid, sp->lwpid, buf) == 0);
if (!applied) {
if (ptrace_elev(PT_SETFPREGS, pid, buf, 0) == -1 && errno != 0) goto err;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
@@ -920,8 +1082,11 @@ int debug_getdbregs_handle(int fd, struct cmd_packet *packet) {
int we_stopped = 0;
if (is_process_stopped(pid) == 0) {
kill(pid, 17 );
wait4(pid, NULL, 0, NULL);
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
goto err;
}
we_stopped = 1;
}
@@ -958,9 +1123,12 @@ int debug_setdbregs_handle(int fd, struct cmd_packet *packet) {
net_send_int32(fd, CMD_SUCCESS);
net_recv_all(fd, buf, sp->length, 1);
ptrace_elev(PT_CONTINUE, pid, (void *)1, 0);
kill(pid, 17 );
wait4(pid, NULL, 0, NULL);
if (ptrace_elev(PT_CONTINUE, pid, (void *)1, 0) != 0
|| kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
goto err;
}
if (ptrace_elev(PT_SETDBREGS, sp->lwpid, buf, 0) == -1 && errno != 0) goto err;
@@ -974,6 +1142,50 @@ err:
return 1;
}
int debug_getfsgsbase_handle(int fd, struct cmd_packet *packet) {
char buf[FSGSBASE_BLOB_SIZE];
if (!g_debug_attached) goto err;
int pid = DBGCTX()->pid;
if (pid == 0) goto err;
struct cmd_debug_lwp_packet *gp = (struct cmd_debug_lwp_packet *)packet->data;
if (!gp) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
memset(buf, 0, FSGSBASE_BLOB_SIZE);
if (kern_get_fsgsbase(pid, gp->lwpid, buf) != 0) goto err;
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, buf, FSGSBASE_BLOB_SIZE);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_setfsgsbase_handle(int fd, struct cmd_packet *packet) {
char buf[FSGSBASE_BLOB_SIZE];
if (!g_debug_attached) goto err;
int pid = DBGCTX()->pid;
if (pid == 0) goto err;
struct cmd_debug_setreg_packet *sp = (struct cmd_debug_setreg_packet *)packet->data;
if (!sp) goto err;
if (sp->length != FSGSBASE_BLOB_SIZE) goto err;
memset(buf, 0, FSGSBASE_BLOB_SIZE);
net_send_int32(fd, CMD_SUCCESS);
if (net_recv_all(fd, buf, sp->length, 1) < 0) goto err;
if (kern_set_fsgsbase(pid, sp->lwpid, buf) != 0) goto err;
net_send_int32(fd, CMD_SUCCESS);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_continue_handle(int fd, struct cmd_packet *packet) {
int pid = DBGCTX()->pid;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
@@ -981,7 +1193,10 @@ int debug_continue_handle(int fd, struct cmd_packet *packet) {
void *data = packet->data;
if (!data) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
debug_stopgo_handle(pid, *(char *)data);
if (debug_stopgo_handle(pid, *(char *)data) != 0) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
@@ -1059,6 +1274,7 @@ int debug_step_handle(int fd, struct cmd_packet *packet) {
net_send_int32(fd, CMD_ERROR);
return 1;
}
g_debug_phase = DEBUG_PHASE_STEPPING;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
@@ -1088,6 +1304,7 @@ int debug_step_thread_handle(int fd, struct cmd_packet *packet) {
}
if (rc != 0) { net_send_int32(fd, CMD_ERROR); return 1; }
g_debug_phase = DEBUG_PHASE_STEPPING;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
@@ -1109,12 +1326,19 @@ static void debug_handle_breakpoint_resume(void) {
sig = (int)g_stopgo_resume_signal;
}
ptrace_raw(PT_CONTINUE, pid, (void *)1, sig);
errno = 0;
long continue_rc = ptrace_raw(PT_CONTINUE, pid, (void *)1, sig);
int continue_errno = errno;
if ((int)g_stopgo_resume_signal == 17) {
wait4(pid, NULL, 0, NULL);
if (debug_wait4_bounded(pid, NULL) != 1) continue_rc = -1;
}
g_debug_phase = (continue_rc == 0
|| (sig == 0 && continue_rc == -1 && continue_errno == 16))
? DEBUG_PHASE_RUNNING
: DEBUG_PHASE_BROKEN;
g_stopgo_resume_signal = 0xFFFFFFFFu;
g_stopgo_resume_pid = 0;
}
@@ -1131,27 +1355,50 @@ int dispatch_debug_events(void) {
gettimeofday((struct timeval *)&now, NULL);
if (((uint32_t)now.tv_sec - g_last_alive_check) > 4) {
if (!kern_thread_step_walker((int)DBGCTX()->pid)) {
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
g_last_alive_check = (uint32_t)now.tv_sec;
}
int debug_pid = (int)DBGCTX()->pid;
int status = 0;
int wait_rc = wait4((int)DBGCTX()->pid, &status, 1 , NULL);
int wait_rc;
if (g_debug_pending_wait_valid
&& g_debug_pending_wait_pid == (uint32_t)debug_pid) {
status = g_debug_pending_wait_status;
g_debug_pending_wait_valid = 0;
g_debug_pending_wait_pid = 0;
g_debug_pending_wait_status = 0;
wait_rc = debug_pid;
} else {
wait_rc = wait4(debug_pid, &status, 1 , NULL);
}
if (wait_rc == 0) DDE_RETURN(0);
if (wait_rc < 0) {
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
g_debug_phase = DEBUG_PHASE_PROCESSING_EVENT;
uint8_t sig = (uint8_t)((status >> 8) & 0xFF);
if (sig == 17) DDE_RETURN(0);
if (sig == 17) {
g_debug_phase = DEBUG_PHASE_EVENT_STOPPED;
DDE_RETURN(0);
}
if (sig == 9) {
int dead_pid = debug_pid;
debug_full_teardown(curdbgctx);
ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 9);
ptrace_raw(PT_CONTINUE, dead_pid, (void *)1, 9);
DDE_RETURN(0);
}
uint8_t lwpi[160];
if (ptrace_raw(PT_LWPINFO, (int)DBGCTX()->pid, lwpi, 160) != 0) {
resume_app_via_self_id((int)DBGCTX()->pid);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
@@ -1164,7 +1411,8 @@ int dispatch_debug_events(void) {
for (int _i = 0; _i < 30; _i++) {
uint64_t _a = *(uint64_t *)(_abs_base + _i * 0x18);
if (_a != 0 && _a == _abs_rm1) {
ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 0);
long rc = ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 0);
g_debug_phase = (rc == 0) ? DEBUG_PHASE_RUNNING : DEBUG_PHASE_BROKEN;
DDE_RETURN(0);
}
}
@@ -1179,8 +1427,8 @@ int dispatch_debug_events(void) {
for (int _i = 0; _i < 30; _i++) {
uint64_t _a = *(uint64_t *)(_pabs_base + _i * 0x18);
if (_a != 0 && _a == _pabs_rip) {
ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 0);
long rc = ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 0);
g_debug_phase = (rc == 0) ? DEBUG_PHASE_RUNNING : DEBUG_PHASE_BROKEN;
DDE_RETURN(0);
}
}
@@ -1210,11 +1458,15 @@ int dispatch_debug_events(void) {
} else {
if (kern_get_proc_info_by_pid(pid, lwpid, regs_buf) != 0
&& ptrace_raw(PT_GETREGS, lwpid, regs_buf, 0) != 0) {
resume_app_via_self_id(pid); DDE_RETURN(1);
resume_app_via_self_id(pid);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
if (kern_proc_install_dbregs(pid, lwpid, fpu_buf) != 0
&& ptrace_raw(PT_GETFPREGS, lwpid, fpu_buf, 0) != 0) {
resume_app_via_self_id(pid); DDE_RETURN(1);
resume_app_via_self_id(pid);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
if (kern_get_dbregs(pid, lwpid, dbreg_buf) == 0) {
got_dbreg = true;
@@ -1222,17 +1474,23 @@ int dispatch_debug_events(void) {
}
} else {
if (ptrace_raw(PT_GETREGS, lwpid, regs_buf, 0) != 0) {
resume_app_via_self_id(pid); DDE_RETURN(1);
resume_app_via_self_id(pid);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
memset(fpu_buf, 0, 832);
if (ptrace_raw(PT_GETFPREGS, lwpid, fpu_buf, 0) != 0) {
resume_app_via_self_id(pid); DDE_RETURN(1);
resume_app_via_self_id(pid);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
}
if (!got_dbreg) {
if (ptrace_raw(PT_GETDBREGS, lwpid, dbreg_buf, 0) != 0) {
resume_app_via_self_id(pid); DDE_RETURN(1);
resume_app_via_self_id(pid);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
}
@@ -1254,6 +1512,9 @@ int dispatch_debug_events(void) {
int matched_wp = -1;
uint64_t *pkt_dr = (uint64_t *)(pkt + 0x420);
/* Internal DBREG writes clear live DR6; retain this event's cause for the client. */
uint64_t event_dr6 = pkt_dr[6];
bool report_event_dr6 = true;
for (int i = 0; i < 4; i++) {
if (pkt_dr[i] != 0 && pkt_dr[i] == pkt_rip) { matched_wp = i; break; }
}
@@ -1261,10 +1522,15 @@ int dispatch_debug_events(void) {
if (matched_bp_v34 != NULL &&
*(int32_t *)(lwpi + 0x38) == 2 ) {
ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 0);
long rc = ptrace_raw(PT_CONTINUE, (int)DBGCTX()->pid, (void *)1, 0);
g_debug_phase = (rc == 0) ? DEBUG_PHASE_RUNNING : DEBUG_PHASE_BROKEN;
DDE_RETURN(0);
}
if (*(int32_t *)(lwpi + 0x38) == 2) {
pkt_dr[6] = 0;
}
uint8_t length_buf[176];
if (matched_wp >= 0) {
@@ -1359,6 +1625,27 @@ int dispatch_debug_events(void) {
}
}
if (matched_bp_v34 == NULL && matched_wp < 0 && *(int32_t *)(lwpi + 0x38) == 2) {
uint64_t _dr7 = pkt_dr[7];
int _is_data_wp = 0;
for (int _i = 0; _i < 4; _i++) {
int _en = (int)((_dr7 >> (_i * 2)) & 3);
int _rw = (int)((_dr7 >> (16 + _i * 4)) & 3);
if (_en && _rw != 0 && pkt_dr[_i] != 0) { _is_data_wp = 1; break; }
}
if (_is_data_wp) {
uint64_t _saved_dr6 = pkt_dr[6];
pkt_dr[6] = 0;
if (use_kernel) {
if (kern_set_dbregs(pid, lwpid, pkt + 0x420) != 0)
ptrace_raw(PT_SETDBREGS, lwpid, pkt + 0x420, 0);
} else {
ptrace_raw(PT_SETDBREGS, lwpid, pkt + 0x420, 0);
}
pkt_dr[6] = _saved_dr6;
}
}
if (matched_bp_v34 != NULL) {
uint64_t bp_addr = *(uint64_t *)(matched_bp_v34 + 8);
char *saved_byte_ptr = matched_bp_v34 + 16;
@@ -1367,7 +1654,7 @@ int dispatch_debug_events(void) {
uint64_t saved_authid_1 = 0;
int authid_bumped_1 = (authid_bump_safe(&saved_authid_1) == 0);
sys_proc_rw_w1((uint64_t)pid, bp_addr, 1, saved_byte_ptr, 0);
proc_write_mem((uint32_t)pid, bp_addr, 1, saved_byte_ptr);
*(uint64_t *)(pkt + 0x030 + 0x88) -= 1;
if (use_kernel) {
@@ -1458,7 +1745,7 @@ int dispatch_debug_events(void) {
}
}
}
sys_proc_rw_w1((uint64_t)pid, bp_addr, 1, &int3, 0);
proc_write_mem((uint32_t)pid, bp_addr, 1, &int3);
if (authid_bumped_2) authid_restore_safe(saved_authid_2);
}
@@ -1467,10 +1754,12 @@ int dispatch_debug_events(void) {
int stepping = (int)g_stepping_lwpid;
if (stepping > 0) {
if ((int)pkt_lwpid != stepping) {
ptrace_raw(PT_CONTINUE, (int)pkt_lwpid, (void *)1, 0);
long rc = ptrace_raw(PT_CONTINUE, (int)pkt_lwpid, (void *)1, 0);
g_debug_phase = (rc == 0) ? DEBUG_PHASE_STEPPING : DEBUG_PHASE_BROKEN;
DDE_RETURN(0);
}
*(uint64_t *)(pkt + 0x420 + 0x30) = 0;
report_event_dr6 = false;
}
g_stepping_lwpid = 0;
@@ -1485,6 +1774,7 @@ int dispatch_debug_events(void) {
if (_is_fatal && _match_last) {
if (++g_storm_count > DBG_STORM_LIMIT) {
g_debug_phase = DEBUG_PHASE_EVENT_STOPPED;
DDE_RETURN(0);
}
} else {
@@ -1495,7 +1785,12 @@ int dispatch_debug_events(void) {
g_storm_sig = _final_sig;
}
net_send_all(DBGCTX()->dbgfd, pkt, 1184);
g_debug_phase = DEBUG_PHASE_EVENT_STOPPED;
if (report_event_dr6) pkt_dr[6] = event_dr6;
if (net_send_all(DBGCTX()->dbgfd, pkt, 1184) < 0) {
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
resume_app_via_self_id((int)DBGCTX()->pid);
DDE_RETURN(0);
@@ -1518,6 +1813,8 @@ int debug_handle(int fd, struct cmd_packet *packet) {
case 0xBDBB000Bu: return debug_setfpregs_handle(fd, packet);
case 0xBDBB000Cu: return debug_getdbregs_handle(fd, packet);
case 0xBDBB000Du: return debug_setdbregs_handle(fd, packet);
case 0xBDBB000Eu: return debug_getfsgsbase_handle(fd, packet);
case 0xBDBB000Fu: return debug_setfsgsbase_handle(fd, packet);
case 0xBDBB0010u: return debug_continue_handle(fd, packet);
case 0xBDBB0011u: return debug_thread_info_handle(fd, packet);
case 0xBDBB0012u: return debug_step_handle(fd, packet);
+69
View File
@@ -21,6 +21,11 @@ bool fw_uses_kernel_dbreg_path(void) {
case 0x6000000u: case 0x6020000u: case 0x6500000u: /* 6.00 6.02 6.50 */
case 0x7200000u: case 0x7400000u: case 0x7600000u: case 0x7610000u: /* 7.20 7.40 7.60 7.61 (not 7.00/7.01) */
case 0x8000000u: case 0x8200000u: case 0x8400000u: case 0x8600000u: /* 8.00 8.20 8.40 8.60 */
case 0x9000000u: case 0x9050000u: case 0x9200000u: case 0x9400000u: case 0x9600000u: /* 9.00 9.05 9.20 9.40 9.60 */
case 0x10000000u: case 0x10010000u: case 0x10200000u: case 0x10400000u: case 0x10600000u: /* 10.00 10.01 10.20 10.40 10.60 */
case 0x11000000u: case 0x11200000u: case 0x11400000u: case 0x11600000u: /* 11.00 11.20 11.40 11.60 */
case 0x12000000u: case 0x12020000u: case 0x12200000u: case 0x12400000u: case 0x12600000u: case 0x12700000u: /* 12.00 12.02 12.20 12.40 12.60 12.70 */
case 0x13000000u: case 0x13200000u: case 0x13400000u: case 0x13420000u: case 0x13600000u: /* 13.00 13.20 13.40 13.42 13.60 */
return true;
default:
return false;
@@ -162,6 +167,70 @@ int kern_proc_install_dbregs(int pid, int lwpid, void *fpu_buf) {
return (rc < 0) ? 1 : 0;
}
int kern_get_fpregs(int pid, int lwpid, void *fpu_buf) {
if (pid == 0) return 1;
if (!fpu_buf) return 1;
intptr_t kthread = kern_thread_addr(pid, lwpid);
if (!kthread) return 6;
intptr_t pcb_addr;
if (kernel_copyout_fast(kthread + 0x3f8, &pcb_addr, 8) < 0) return 1;
intptr_t kfpu_addr;
if (kernel_copyout_fast(pcb_addr + 0x148, &kfpu_addr, 8) < 0) return 1;
if (!kfpu_addr) return 1;
if (kernel_copyout_fast(kfpu_addr, fpu_buf, 0x340) < 0) return 1;
return 0;
}
int kern_set_fpregs(int pid, int lwpid, const void *fpu_buf) {
if (pid == 0) return 1;
if (!fpu_buf) return 1;
intptr_t kthread = kern_thread_addr(pid, lwpid);
if (!kthread) return 6;
intptr_t pcb_addr;
if (kernel_copyout_fast(kthread + 0x3f8, &pcb_addr, 8) < 0) return 1;
intptr_t kfpu_addr;
if (kernel_copyout_fast(pcb_addr + 0x148, &kfpu_addr, 8) < 0) return 1;
if (!kfpu_addr) return 1;
if (kernel_copyin_fast(fpu_buf, kfpu_addr, 0x340) < 0) return 1;
return 0;
}
int kern_get_fsgsbase(int pid, int lwpid, void *out16) {
if (pid == 0) return 1;
if (!out16) return 1;
intptr_t kthread = kern_thread_addr(pid, lwpid);
if (!kthread) return 6;
intptr_t pcb_addr;
if (kernel_copyout_fast(kthread + 0x3f8, &pcb_addr, 8) < 0) return 1;
if (kernel_copyout_fast(pcb_addr + 0x40, out16, 16) < 0) return 1;
return 0;
}
int kern_set_fsgsbase(int pid, int lwpid, const void *in16) {
if (pid == 0) return 1;
if (!in16) return 1;
intptr_t kthread = kern_thread_addr(pid, lwpid);
if (!kthread) return 6;
intptr_t pcb_addr;
if (kernel_copyout_fast(kthread + 0x3f8, &pcb_addr, 8) < 0) return 1;
if (kernel_copyin_fast(in16, pcb_addr + 0x40, 16) < 0) return 1;
return 0;
}
int kern_get_proc_info_by_pid(int pid, int lwpid, void *regs_buf) {
if (pid == 0) return 1;
if (!regs_buf) return 1;
+136 -22
View File
@@ -9,6 +9,7 @@
#include <ps5/kernel.h>
#include "sdk_shim.h"
#include "kern_rw_fast.h"
#include "write_policy.h"
static const uint64_t kr_write_setup_const[2] = { 0x0ULL, 0x4000000000000000ULL };
static const uint64_t kr_read_setup_const [2] = { 0x4000000040000000ULL, 0x4000000000000000ULL };
@@ -36,70 +37,183 @@ static __attribute__((noinline)) int kr_lazy_init(void) {
typedef unsigned __int128 u128_alias __attribute__((may_alias));
static int kr_setup_field(int fd, void *field, const char *direction,
const char *step, intptr_t kaddr, size_t len,
int diagnostic)
{
errno = 0;
long rc = __crt_syscall(0x69, fd, 0x29, 0x2e, field, 0x14);
int saved_errno = errno;
if (diagnostic || rc != 0) {
klog_printf("[write:pipe] dir=%s kaddr=0x%llx len=%llu "
"step=%s rc=%ld errno=%d\n",
direction, (unsigned long long)kaddr,
(unsigned long long)len, step, rc, saved_errno);
}
return rc == 0 ? 0 : -1;
}
__attribute__((target("no-avx")))
static int32_t kernel_copyin_fast_inner(const void *udaddr, intptr_t kaddr, size_t len)
{
uint64_t f[7];
int diagnostic = ps5debug_write_diagnostics_enabled();
*(u128_alias *)&f[0] = *(const u128_alias *)kr_write_setup_const;
f[4] = kr_kpipe_addr;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "in", "flags-address",
kaddr, len, diagnostic) != 0)
return -1;
f[2] = 0;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "in", "flags-value",
kaddr, len, diagnostic) != 0)
return -1;
f[0] = (uint64_t)kaddr;
*(u128_alias *)&f[1] = 0;
f[4] = kr_kpipe_addr + 0x10;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "in", "data-address",
kaddr, len, diagnostic) != 0)
return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "in", "data-value",
kaddr, len, diagnostic) != 0)
return -1;
__crt_syscall(4, kr_rwpipe_1, (uint64_t)udaddr, (uint64_t)len);
return 0;
}
int32_t kernel_copyin_fast(const void *udaddr, intptr_t kaddr, size_t len)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
scePthreadMutexLock(&kr_fast_mutex);
int32_t rc = kernel_copyin_fast_inner(udaddr, kaddr, len);
scePthreadMutexUnlock(&kr_fast_mutex);
return rc;
errno = 0;
long rc = __crt_syscall(SYS_write, kr_rwpipe_1,
(uint64_t)udaddr, (uint64_t)len);
int saved_errno = errno;
if (diagnostic || rc != (long)len) {
klog_printf("[write:pipe] dir=in kaddr=0x%llx len=%llu "
"step=transfer rc=%ld errno=%d\n",
(unsigned long long)kaddr, (unsigned long long)len,
rc, saved_errno);
}
return rc == (long)len ? 0 : -1;
}
__attribute__((target("no-avx")))
static int32_t kernel_copyout_fast_inner(intptr_t kaddr, void *udaddr, size_t len)
{
uint64_t f[7];
int diagnostic = 0;
*(u128_alias *)&f[0] = *(const u128_alias *)kr_read_setup_const;
f[4] = kr_kpipe_addr;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "out", "flags-address",
kaddr, len, diagnostic) != 0)
return -1;
f[2] = 0;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "out", "flags-value",
kaddr, len, diagnostic) != 0)
return -1;
f[0] = (uint64_t)kaddr;
*(u128_alias *)&f[1] = 0;
f[4] = kr_kpipe_addr + 0x10;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "out", "data-address",
kaddr, len, diagnostic) != 0)
return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "out", "data-value",
kaddr, len, diagnostic) != 0)
return -1;
__crt_syscall(3, kr_rwpipe_0, udaddr, (uint64_t)len);
return 0;
errno = 0;
long rc = __crt_syscall(SYS_read, kr_rwpipe_0,
udaddr, (uint64_t)len);
int saved_errno = errno;
if (diagnostic || rc != (long)len) {
klog_printf("[write:pipe] dir=out kaddr=0x%llx len=%llu "
"step=transfer rc=%ld errno=%d\n",
(unsigned long long)kaddr, (unsigned long long)len,
rc, saved_errno);
}
return rc == (long)len ? 0 : -1;
}
static int32_t kernel_copyin_fast_locked(const void *udaddr,
intptr_t kaddr, size_t len)
{
if (len == 0
|| !ps5debug_write_needs_prefix((uint64_t)kaddr))
return kernel_copyin_fast_inner(udaddr, kaddr, len);
if (!udaddr || len == SIZE_MAX) return -1;
uint8_t *prefixed = (uint8_t *)malloc(len + 1);
if (!prefixed) return -1;
int32_t rc = kernel_copyout_fast_inner(kaddr - 1, prefixed, 1);
if (rc == 0) {
uint8_t leading = prefixed[0];
memcpy(prefixed + 1, udaddr, len);
if (ps5debug_write_diagnostics_enabled()) {
klog_printf("[write:pipe] dir=in kaddr=0x%llx len=%llu "
"step=prefix-normalize shifted=0x%llx shifted-len=%llu\n",
(unsigned long long)kaddr, (unsigned long long)len,
(unsigned long long)(kaddr - 1),
(unsigned long long)(len + 1));
}
rc = kernel_copyin_fast_inner(prefixed, kaddr - 1, len + 1);
uint8_t observed = leading ^ 0xFFu;
if (rc == 0
&& (kernel_copyout_fast_inner(kaddr - 1, &observed, 1) != 0
|| observed != leading)) {
klog_printf("[write:pipe] dir=in kaddr=0x%llx len=%llu "
"prefix-preserve failed expected=%02x actual=%02x\n",
(unsigned long long)kaddr, (unsigned long long)len,
leading, observed);
rc = -1;
}
}
free(prefixed);
return rc;
}
static int32_t kernel_copyout_fast_locked(intptr_t kaddr,
void *udaddr, size_t len)
{
if (len == 0
|| !ps5debug_write_needs_prefix((uint64_t)kaddr))
return kernel_copyout_fast_inner(kaddr, udaddr, len);
if (!udaddr || len == SIZE_MAX) return -1;
uint8_t *prefixed = (uint8_t *)malloc(len + 1);
if (!prefixed) return -1;
if (ps5debug_write_diagnostics_enabled()) {
klog_printf("[write:pipe] dir=out kaddr=0x%llx len=%llu "
"step=prefix-normalize shifted=0x%llx shifted-len=%llu\n",
(unsigned long long)kaddr, (unsigned long long)len,
(unsigned long long)(kaddr - 1),
(unsigned long long)(len + 1));
}
int32_t rc =
kernel_copyout_fast_inner(kaddr - 1, prefixed, len + 1);
if (rc == 0) memcpy(udaddr, prefixed + 1, len);
free(prefixed);
return rc;
}
int32_t kernel_copyin_fast(const void *udaddr, intptr_t kaddr, size_t len)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
scePthreadMutexLock(&kr_fast_mutex);
int32_t rc = kernel_copyin_fast_locked(udaddr, kaddr, len);
scePthreadMutexUnlock(&kr_fast_mutex);
return rc;
}
int32_t kernel_copyout_fast(intptr_t kaddr, void *udaddr, size_t len)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
scePthreadMutexLock(&kr_fast_mutex);
int32_t rc = kernel_copyout_fast_inner(kaddr, udaddr, len);
int32_t rc = kernel_copyout_fast_locked(kaddr, udaddr, len);
scePthreadMutexUnlock(&kr_fast_mutex);
return rc;
}
+40 -10
View File
@@ -22,6 +22,7 @@ static void run_init_array_once(void)
extern void server_state_init(void);
extern void *alloc_client(void);
extern void free_client(void *svc);
extern void turboscan_startup_cleanup(void);
extern int dispatch_debug_events(void);
extern void *klog_server_thread(void *arg);
extern int g_klog_listen_fd;
@@ -42,6 +43,7 @@ extern void *curdbgctx;
extern uint32_t g_debug_attached;
extern void *g_server_mutex;
extern void *g_proc_rw_mutex;
extern void *g_debug_arbiter_mutex;
#define SERVER_PORT 744
#define SERVER_MAXCLIENTS 12
@@ -62,6 +64,9 @@ void configure_socket(int fd) {
setsockopt(fd, 6, 1, &one, 4);
one = 1;
setsockopt(fd, 0xFFFF, 4, &one, 4);
one = 1;
setsockopt(fd, 0xFFFF, 0x0800, &one, 4);
}
static int handle_client(struct server_client *svc) {
@@ -106,43 +111,52 @@ static int handle_client(struct server_client *svc) {
packet.data = data;
}
if (g_debug_attached == 0 && packet.cmd == CMD_DEBUG_ATTACH) {
curdbgcli = svc;
curdbgctx = (void *)((char *)svc + 0x20);
}
unsigned char client_idx = (unsigned char)((svc->active - 1) & 0xFFu);
int _needs_dbg_lock = ((packet.cmd & 0xFFFF0000u) == 0xBDBB0000u);
if (_needs_dbg_lock) {
scePthreadMutexLock(&g_debug_arbiter_mutex);
scePthreadMutexLock(&g_proc_rw_mutex);
scePthreadMutexLock(&g_server_mutex);
}
if (g_debug_attached == 0
&& g_debug_phase == DEBUG_PHASE_DETACHED
&& packet.cmd == CMD_DEBUG_ATTACH) {
curdbgcli = svc;
curdbgctx = (void *)((char *)svc + 0x20);
}
int rc = cmd_handler(fd, &packet, client_idx);
if (_needs_dbg_lock) {
scePthreadMutexUnlock(&g_server_mutex);
scePthreadMutexUnlock(&g_proc_rw_mutex);
scePthreadMutexUnlock(&g_debug_arbiter_mutex);
}
if (data) free(data);
if (rc != 0) return rc;
if (svc->debugging && g_stopgo_resume_signal != 0xFFFFFFFFu) {
if (svc->debugging
&& (g_stopgo_resume_signal != 0xFFFFFFFFu
|| g_debug_pending_wait_valid)) {
scePthreadMutexLock(&g_debug_arbiter_mutex);
scePthreadMutexLock(&g_proc_rw_mutex);
scePthreadMutexLock(&g_server_mutex);
int dde_rc = dispatch_debug_events();
scePthreadMutexUnlock(&g_server_mutex);
scePthreadMutexUnlock(&g_proc_rw_mutex);
scePthreadMutexUnlock(&g_debug_arbiter_mutex);
if (dde_rc != 0) return 0;
}
continue;
}
if (svc->debugging) {
scePthreadMutexLock(&g_debug_arbiter_mutex);
scePthreadMutexLock(&g_proc_rw_mutex);
scePthreadMutexLock(&g_server_mutex);
int dde_rc = dispatch_debug_events();
scePthreadMutexUnlock(&g_server_mutex);
scePthreadMutexUnlock(&g_proc_rw_mutex);
scePthreadMutexUnlock(&g_debug_arbiter_mutex);
if (dde_rc != 0) return 0;
}
if (errno == ECONNRESET) return 0;
@@ -216,6 +230,8 @@ static void *broadcast_thread(void *arg) {
static int start_server(void) {
turboscan_startup_cleanup(); /* reclaim any orphaned /data snapshot files */
{
ScePthread bcast_tid;
if (scePthreadCreate(&bcast_tid, NULL, broadcast_thread,
@@ -268,6 +284,9 @@ static int start_server(void) {
int32_t one_a = 1;
sceNetSetsockopt(fd, 6, 1, &one_a, 4);
one_a = 1;
sceNetSetsockopt(fd, 0xFFFF, 0x0800, &one_a, 4);
struct server_client *slot = (struct server_client *)alloc_client();
if (!slot) {
@@ -312,10 +331,21 @@ teardown:
static void debugger_server_loop(void) {
static const char disconnect_msg[] = "ps5debug-NG disconnected.";
static const char loaded_msg[] = PS5DEBUG_NG_BRAND_STR " loaded!\n"
"Coded by OpenSourcereR\n"
"Special thanks to\n"
"golden, Ctn & SiSTRo! \xE2\x9D\xA4\n";
uint32_t fw = kernel_get_fw_version();
uint16_t fw_high = (uint16_t)(fw >> 16);
unsigned fw_major = ((fw_high >> 12) & 0xF) * 10 + ((fw_high >> 8) & 0xF);
unsigned fw_minor = ((fw_high >> 4) & 0xF) * 10 + (fw_high & 0xF);
char loaded_msg[256];
snprintf(loaded_msg, sizeof(loaded_msg),
PS5DEBUG_NG_BRAND_STR " loaded!\n"
"Firmware: %u.%02u\n"
"Coded by OpenSourcereR\n"
"Special thanks to\n"
"golden, Ctn, SiSTRo, EchoStretch, \n"
"Sonic-Iso & Pharaoh2k! \xE2\x9D\xA4\n",
fw_major, fw_minor);
char ip_buf[16];
uint32_t retry = 0;
+27 -2
View File
@@ -25,8 +25,9 @@ int handle_version(int fd, struct cmd_packet *packet) {
int handle_branding(int fd, struct cmd_packet *packet) {
(void)packet;
char brand[] = PS5DEBUG_NG_BRAND_STR;
uint32_t len = (uint32_t)(sizeof(brand) - 1);
/* Capability 1.1 adds content-verified process writes. */
static const char brand[] = PS5DEBUG_NG_BRAND_STR "\0" "1.1";
uint32_t len = (uint32_t)(sizeof(brand) - 1);
net_send_all(fd, &len, sizeof(uint32_t));
net_send_all(fd, brand, (int)len);
return 0;
@@ -92,6 +93,7 @@ void *curdbgctx = NULL;
uint32_t g_debug_attached = 0;
void *g_server_mutex = NULL;
void *g_proc_rw_mutex = NULL;
void *g_debug_arbiter_mutex = NULL;
void *kr_fast_mutex = NULL;
uint32_t g_stopgo_mode = 0;
@@ -100,6 +102,11 @@ uint32_t g_stopgo_last_signal = 0xFFFFFFFFu;
uint32_t g_stopgo_resume_pid = 0;
uint32_t g_stopgo_resume_signal = 0xFFFFFFFFu;
volatile uint32_t g_debug_phase = DEBUG_PHASE_DETACHED;
volatile uint32_t g_debug_pending_wait_valid = 0;
uint32_t g_debug_pending_wait_pid = 0;
int32_t g_debug_pending_wait_status = 0;
void server_state_init(void) {
if (g_proc_rw_mutex == NULL) {
scePthreadMutexInit(&g_proc_rw_mutex, NULL, "ps5d_procrw");
@@ -107,6 +114,9 @@ void server_state_init(void) {
if (g_server_mutex == NULL) {
scePthreadMutexInit(&g_server_mutex, NULL, "ps5d_server");
}
if (g_debug_arbiter_mutex == NULL) {
scePthreadMutexInit(&g_debug_arbiter_mutex, NULL, "ps5d_debug_arbiter");
}
if (kr_fast_mutex == NULL) {
scePthreadMutexInit(&kr_fast_mutex, NULL, "ps5d_krfast");
}
@@ -117,6 +127,10 @@ void server_state_init(void) {
g_stopgo_last_signal = 0xFFFFFFFFu;
g_stopgo_target_pid = 0;
g_stopgo_mode = 0;
g_debug_phase = DEBUG_PHASE_DETACHED;
g_debug_pending_wait_valid = 0;
g_debug_pending_wait_pid = 0;
g_debug_pending_wait_status = 0;
curdbgcli = NULL;
curdbgctx = NULL;
}
@@ -133,11 +147,22 @@ void *alloc_client(void) {
void free_client(void *svc_) {
struct server_client *svc = (struct server_client *)svc_;
// free any server-resident turbo-scan session + persistent aliasing arena for this
// connection (leak/UAF guard)
long slot = svc - g_clients;
if (slot >= 0 && slot < SERVER_MAXCLIENTS) {
turboscan_session_free_idx((unsigned char)slot);
turboscan_alias_free_idx((unsigned char)slot);
}
svc->active = 0;
close(svc->fd);
if (svc->debugging != 0) {
scePthreadMutexLock(&g_debug_arbiter_mutex);
debug_full_teardown(svc->dbgctx);
scePthreadMutexUnlock(&g_debug_arbiter_mutex);
}
memset(svc, 0, SERVER_CLIENT_STRIDE);
}
+7 -2
View File
@@ -9,7 +9,8 @@ int net_send_all(int fd, const void *buf, int len) {
int left = len;
while (left > 0) {
int chunk = (left > NET_MAX_LENGTH) ? NET_MAX_LENGTH : left;
ssize_t n = send(fd, p, chunk, 0);
ssize_t n = send(fd, p, chunk, MSG_NOSIGNAL);
if (n <= 0) return -1;
p += n;
left -= (int)n;
@@ -42,7 +43,11 @@ int net_send_int32(int fd, uint32_t value) {
extern long __crt_syscall(long sysno, ...);
int net_select(int fd, void *readfds, void *writefds, void *exceptfds, void *timeout) {
return (int)__crt_syscall(93, fd, readfds, writefds, exceptfds, timeout);
// ps5debug_syscall (typed long params) promotes every arg before the varargs hop,
// so int/pointer args can't arrive with garbage upper bits. Behavior-identical to
// the prior __crt_syscall(93, ...) for these already-clean values.
return (int)ps5debug_syscall(93 /*select*/, (long)fd, (long)readfds, (long)writefds,
(long)exceptfds, (long)timeout, 0L);
}
int net_get_ip_address(char *out) {
+367 -20
View File
@@ -6,16 +6,23 @@
#include "proc.h"
#include "kdbg.h"
#include "kern_rw_fast.h"
#include "proc_field_offsets.h"
#include "write_policy.h"
#include "Zydis.h"
#define PROC_NEXT_OFFSET 0x00
#define PROC_VMSPACE_OFFSET 0x200
#define PROC_TITLE_ID_OFFSET 0x470
#define PROC_CONTENT_ID_OFFSET 0x4C4
#define PROC_SELFINFO_NAME_OFFSET 0x59C
#define PROC_PATH_OFFSET 0x5BC
#define PROC_SELFINFO_NAME_SIZE 32
struct sce_app_info {
uint32_t app_id;
uint64_t unknown1;
uint32_t app_type;
char title_id[10];
char unknown2[0x3c];
};
extern int sceKernelGetAppInfo(pid_t pid, struct sce_app_info *info);
static void copy_cstr_from_buf(const uint8_t *src, char *out, size_t max) {
if (max == 0) return;
size_t i;
@@ -29,6 +36,54 @@ static void copy_cstr_from_buf(const uint8_t *src, char *out, size_t max) {
out[max - 1] = 0;
}
int find_titleid(const uint8_t *buf, size_t len, char *out) {
if (len < 9) return 1;
for (size_t i = 0; i + 9 <= len; i++) {
int ok = 1;
for (int j = 0; j < 4; j++)
if (buf[i + j] < 'A' || buf[i + j] > 'Z') { ok = 0; break; }
if (!ok) continue;
for (int j = 4; j < 9; j++)
if (buf[i + j] < '0' || buf[i + j] > '9') { ok = 0; break; }
if (!ok) continue;
memcpy(out, buf + i, 9);
out[9] = '\0';
return 0;
}
return 1;
}
int find_contentid(const uint8_t *buf, size_t len, char *out, size_t out_size) {
if (out_size == 0) return 1;
if (len < 17) return 1;
for (size_t i = 0; i + 17 <= len; i++) {
const uint8_t *s = buf + i;
int ok = 1;
for (int j = 0; j < 2; j++)
if (s[j] < 'A' || s[j] > 'Z') { ok = 0; break; }
if (!ok) continue;
for (int j = 2; j < 6; j++)
if (s[j] < '0' || s[j] > '9') { ok = 0; break; }
if (!ok) continue;
if (s[6] != '-') continue;
for (int j = 7; j < 11; j++)
if (s[j] < 'A' || s[j] > 'Z') { ok = 0; break; }
if (!ok) continue;
for (int j = 11; j < 16; j++)
if (s[j] < '0' || s[j] > '9') { ok = 0; break; }
if (!ok) continue;
if (s[16] != '_') continue;
size_t k = 0;
while (i + k < len && s[k] != 0 && k < out_size - 1) {
out[k] = (char)s[k];
k++;
}
out[k] = '\0';
return 0;
}
return 1;
}
#define DISASM_READ_CHUNK 0x10000
static void disasm_fill_entry(struct disasm_instr_entry *out,
@@ -338,9 +393,13 @@ int proc_list_handle(int fd, struct cmd_packet *packet) {
}
memset(entries, 0, length);
struct proc_field_offsets off;
proc_get_field_offsets(&off);
cur = kproc;
for (uint32_t i = 0; i < count && cur != 0; i++) {
kernel_copyout_fast(cur + PROC_SELFINFO_NAME_OFFSET, entries[i].name, PROC_SELFINFO_NAME_SIZE);
if (off.known)
kernel_copyout_fast(cur + off.name, entries[i].name, PROC_SELFINFO_NAME_SIZE);
size_t nlen = 0;
while (nlen < PROC_SELFINFO_NAME_SIZE && entries[i].name[nlen] != 0) nlen++;
if (nlen < PROC_SELFINFO_NAME_SIZE) {
@@ -362,11 +421,140 @@ int proc_list_handle(int fd, struct cmd_packet *packet) {
return 0;
}
void proc_read_mem(uint32_t pid, uint64_t addr, uint64_t len, void *buf) {
static int proc_read_mem_status(uint32_t pid, uint64_t addr,
uint64_t len, void *buf) {
if ((int32_t)pid <= 0 || !buf || len == 0 || addr + len < addr)
return 1;
if (proc_aux_range_contains(pid, addr, len) &&
proc_ptwalk_read(pid, addr, len, buf) == 0)
return;
sys_proc_rw_w0((uint64_t)pid, addr, len, buf, 0);
return 0;
uint64_t read = 0;
long rc = sys_proc_rw_w0((uint64_t)pid, addr, len, buf,
(uint64_t)(uintptr_t)&read);
return rc == 0 && read == len ? 0 : 1;
}
void proc_read_mem(uint32_t pid, uint64_t addr, uint64_t len, void *buf) {
(void)proc_read_mem_status(pid, addr, len, buf);
}
static int proc_verify_mem_exact(uint32_t pid, uint64_t addr,
uint64_t len, const void *expected,
int diagnostic) {
uint8_t *actual = (uint8_t *)malloc((size_t)len);
if (!actual) return 1;
const uint8_t *wanted = (const uint8_t *)expected;
for (uint64_t i = 0; i < len; i++)
actual[i] = wanted[i] ^ 0xFFu;
int read_rc = proc_read_mem_status(pid, addr, len, actual);
int exact = read_rc == 0
&& memcmp(actual, wanted, (size_t)len) == 0;
if (!exact) {
uint64_t mismatch = 0;
while (mismatch < len && actual[mismatch] == wanted[mismatch])
mismatch++;
klog_printf("[write:verify] pid=%u va=0x%llx len=%llu "
"read-rc=%d exact=0 mismatch=+0x%llx\n",
pid, (unsigned long long)addr,
(unsigned long long)len, read_rc,
(unsigned long long)mismatch);
} else if (diagnostic) {
klog_printf("[write:verify] pid=%u va=0x%llx len=%llu "
"read-rc=0 exact=1\n",
pid, (unsigned long long)addr,
(unsigned long long)len);
}
free(actual);
return exact ? 0 : 1;
}
static uint8_t proc_write_mem_status(uint32_t pid, uint64_t addr,
uint64_t len, const void *buf) {
if ((int32_t)pid <= 0 || !buf || len == 0 || addr + len < addr)
return PROC_WRITE_STATUS_INVALID;
static uint32_t s_fw = 0;
static int s_fw_needs_dmap = -1;
if (s_fw_needs_dmap < 0) {
s_fw = kernel_get_fw_version();
s_fw_needs_dmap =
((s_fw & 0xffff0000u) >= 0x08400000u) ? 1 : 0;
}
int diagnostic = ps5debug_write_diagnostics_enabled();
int try_dmap =
PS5DEBUG_FORCE_WRITE_PATH == PS5DEBUG_WRITE_PATH_DMAP
|| (PS5DEBUG_FORCE_WRITE_PATH == PS5DEBUG_WRITE_PATH_AUTO
&& s_fw_needs_dmap);
if (diagnostic) {
const char *mode =
PS5DEBUG_FORCE_WRITE_PATH == PS5DEBUG_WRITE_PATH_DMAP ? "dmap"
: PS5DEBUG_FORCE_WRITE_PATH == PS5DEBUG_WRITE_PATH_MDBG ? "mdbg"
: "auto";
klog_printf("[write] pid=%u va=0x%llx len=%llu fw=0x%x mode=%s\n",
pid, (unsigned long long)addr,
(unsigned long long)len, s_fw, mode);
}
if (try_dmap) {
if (diagnostic) {
uint64_t phys = 0, page_size = 0, pte = 0;
int level = -1;
int probe_rc = proc_ptwalk_probe(pid, addr, &phys, &level,
&page_size, &pte);
klog_printf("[write] path=dmap probe=%d level=%d phys=0x%llx "
"page=0x%llx pte=0x%llx\n",
probe_rc, level, (unsigned long long)phys,
(unsigned long long)page_size,
(unsigned long long)pte);
}
int dmap_rc = proc_ptwalk_write(pid, addr, len, buf);
if (dmap_rc == PROC_PTW_WRITE_OK) {
if (diagnostic) klog_printf("[write] path=dmap result=OK\n");
return PROC_WRITE_STATUS_OK;
}
klog_printf("[write] pid=%u va=0x%llx len=%llu "
"path=dmap result=FAIL rc=%d\n",
pid, (unsigned long long)addr,
(unsigned long long)len, dmap_rc);
if (dmap_rc == PROC_PTW_WRITE_VERIFY_FAILED)
return PROC_WRITE_STATUS_VERIFY_FAILED;
if (PS5DEBUG_FORCE_WRITE_PATH == PS5DEBUG_WRITE_PATH_DMAP)
return PROC_WRITE_STATUS_DMAP_FAILED;
}
uint64_t wrote = 0;
long mdbg_rc = sys_proc_rw_w1((uint64_t)pid, addr, len, (void *)buf,
(uint64_t)(uintptr_t)&wrote);
if (mdbg_rc != 0 || wrote != len) {
klog_printf("[write] pid=%u va=0x%llx len=%llu "
"path=mdbg rc=%ld wrote=%llu result=FAIL\n",
pid, (unsigned long long)addr,
(unsigned long long)len, mdbg_rc,
(unsigned long long)wrote);
return PROC_WRITE_STATUS_MDBG_FAILED;
}
int verify_rc = proc_verify_mem_exact(pid, addr, len, buf, diagnostic);
if (diagnostic) {
klog_printf("[write] pid=%u va=0x%llx len=%llu "
"path=mdbg rc=%ld wrote=%llu verify=%s\n",
pid, (unsigned long long)addr,
(unsigned long long)len, mdbg_rc,
(unsigned long long)wrote,
verify_rc == 0 ? "OK" : "FAIL");
}
return verify_rc == 0
? PROC_WRITE_STATUS_OK
: PROC_WRITE_STATUS_VERIFY_FAILED;
}
int proc_write_mem(uint32_t pid, uint64_t addr, uint64_t len, const void *buf) {
return proc_write_mem_status(pid, addr, len, buf);
}
int proc_read_handle(int fd, struct cmd_packet *packet) {
@@ -509,20 +697,122 @@ int proc_write_handle(int fd, struct cmd_packet *packet) {
uint64_t length = wp->length;
uint64_t address = wp->address;
uint8_t status = PROC_WRITE_STATUS_OK;
while (length > 0x10000) {
net_recv_all(fd, data, 0x10000, 1);
sys_proc_rw_w1((uint64_t)wp->pid, address, 0x10000, data, 0);
if (net_recv_all(fd, data, 0x10000, 1) < 0) {
free(data);
return 1;
}
uint8_t chunk_status =
proc_write_mem_status(wp->pid, address, 0x10000, data);
if (status == PROC_WRITE_STATUS_OK)
status = chunk_status;
address += 0x10000;
length -= 0x10000;
}
if (length > 0) {
net_recv_all(fd, data, (int)length, 1);
sys_proc_rw_w1((uint64_t)wp->pid, address, length, data, 0);
if (net_recv_all(fd, data, (int)length, 1) < 0) {
free(data);
return 1;
}
uint8_t chunk_status =
proc_write_mem_status(wp->pid, address, length, data);
if (status == PROC_WRITE_STATUS_OK)
status = chunk_status;
}
net_send_int32(fd, status == PROC_WRITE_STATUS_OK
? CMD_SUCCESS : CMD_ERROR);
free(data);
return 0;
}
int proc_write_multi_handle(int fd, struct cmd_packet *packet) {
struct cmd_proc_write_multi_packet *mp =
(struct cmd_proc_write_multi_packet *)packet->data;
if (!mp) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
uint32_t pid = mp->pid;
uint32_t count = mp->count;
int want_status = (mp->flags & PROC_WRITE_MULTI_F_STATUS) ? 1 : 0;
if (count > PROC_WRITE_MULTI_MAX_COUNT) {
net_send_int32(fd, CMD_ERROR);
return 1;
}
uint8_t *buf = (uint8_t *)net_alloc_buffer(0x10000);
if (!buf) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
uint8_t *status = NULL;
if (want_status && count > 0) {
status = (uint8_t *)net_alloc_buffer(count);
if (!status) {
free(buf);
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
}
net_send_int32(fd, CMD_SUCCESS);
free(data);
int any_failed = 0;
for (uint32_t i = 0; i < count; i++) {
uint8_t hdr[12];
if (net_recv_all(fd, hdr, 12, 1) < 0) {
if (status) free(status);
free(buf);
return 1;
}
uint64_t addr;
uint32_t len32;
memcpy(&addr, hdr, 8);
memcpy(&len32, hdr + 8, 4);
if (len32 > PROC_WRITE_MULTI_MAX_ENTRY) {
if (status) free(status);
free(buf);
net_send_int32(fd, CMD_ERROR);
return 1;
}
uint64_t length = len32;
uint64_t a = addr;
uint8_t failed = PROC_WRITE_STATUS_OK;
while (length > 0) {
uint64_t to_recv = (length > 0x10000ULL) ? 0x10000ULL : length;
if (net_recv_all(fd, buf, (int)to_recv, 1) < 0) {
if (status) free(status);
free(buf);
return 1;
}
uint8_t chunk_status =
proc_write_mem_status(pid, a, to_recv, buf);
if (failed == PROC_WRITE_STATUS_OK)
failed = chunk_status;
a += to_recv;
length -= to_recv;
}
if (failed != PROC_WRITE_STATUS_OK) any_failed = 1;
if (status) status[i] = failed;
}
if (status) {
net_send_all(fd, status, (int)count);
free(status);
}
net_send_int32(fd, !want_status && any_failed
? CMD_ERROR : CMD_SUCCESS);
free(buf);
return 0;
}
@@ -607,7 +897,10 @@ int proc_call_handle(int fd, struct cmd_packet *packet) {
args.r8 = cp->rpc_r8;
args.r9 = cp->rpc_r9;
if (sys_proc_cmd(cp->pid, SYS_PROC_CALL, &args)) return -1;
if (sys_proc_cmd(cp->pid, SYS_PROC_CALL, &args)) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
resp.pid = cp->pid;
resp.rpc_rax = args.rax;
@@ -731,6 +1024,12 @@ int proc_info_handle(int fd, struct cmd_packet *packet) {
return 1;
}
struct proc_field_offsets off;
if (proc_get_field_offsets(&off) != 0) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
void *proc_buf = kernel_get_proc_struct_fast(ip->pid);
if (!proc_buf) {
net_send_int32(fd, CMD_DATA_NULL);
@@ -743,11 +1042,31 @@ int proc_info_handle(int fd, struct cmd_packet *packet) {
const uint8_t *p = (const uint8_t *)proc_buf;
memcpy(resp.name, p + PROC_SELFINFO_NAME_OFFSET, PROC_SELFINFO_NAME_SIZE);
memcpy(resp.name, p + off.name, PROC_SELFINFO_NAME_SIZE);
copy_cstr_from_buf(p + PROC_PATH_OFFSET, resp.path, sizeof(resp.path));
copy_cstr_from_buf(p + PROC_TITLE_ID_OFFSET, resp.titleid, sizeof(resp.titleid));
copy_cstr_from_buf(p + PROC_CONTENT_ID_OFFSET, resp.contentid, sizeof(resp.contentid));
copy_cstr_from_buf(p + off.path, resp.path, sizeof(resp.path));
uint8_t aibuf[0x80];
memset(aibuf, 0, sizeof(aibuf));
int ai_rc = sceKernelGetAppInfo((pid_t)ip->pid, (struct sce_app_info *)aibuf);
char tid[16];
memset(tid, 0, sizeof(tid));
if (ai_rc == 0 && find_titleid(aibuf, sizeof(aibuf), tid) == 0) {
} else if (find_titleid(p + 0x440, 0x200, tid) == 0) {
} else {
copy_cstr_from_buf(p + off.titleid, tid, sizeof(tid));
}
{
size_t tcap = sizeof(resp.titleid);
memcpy(resp.titleid, tid, tcap < sizeof(tid) ? tcap : sizeof(tid));
}
if (find_contentid(p + 0x440, 0x200, resp.contentid, sizeof(resp.contentid)) != 0) {
copy_cstr_from_buf(p + off.contentid, resp.contentid, sizeof(resp.contentid));
}
free(proc_buf);
@@ -793,7 +1112,10 @@ int proc_free_handle(int fd, struct cmd_packet *packet) {
args.address = fp->address;
args.length = fp->length;
sys_proc_cmd(fp->pid, SYS_PROC_FREE, &args);
if (sys_proc_cmd(fp->pid, SYS_PROC_FREE, &args) != 0) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
@@ -861,8 +1183,23 @@ int proc_alloc_hinted_handle(int fd, struct cmd_packet *packet) {
return 0;
}
int proc_arena_handle(int fd, struct cmd_packet *packet) {
uint32_t on = 1;
if (packet->data && packet->datalen >= 4) on = *(uint32_t *)packet->data;
char *buf = (char *)net_alloc_buffer(512);
if (!buf) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
int n = proc_arena_set((int)on, buf, 512);
if (n < 0) n = 0;
if (n > 512) n = 512;
net_send_int32(fd, CMD_SUCCESS);
uint32_t len = (uint32_t)n;
net_send_all(fd, &len, 4);
net_send_all(fd, buf, n);
free(buf);
return 0;
}
int proc_handle(int fd, struct cmd_packet *packet, unsigned char client_idx) {
(void)client_idx;
uint32_t cmd = packet->cmd;
switch (cmd) {
@@ -871,6 +1208,7 @@ int proc_handle(int fd, struct cmd_packet *packet, unsigned char client_idx) {
case 0xBDAA0023u: return proc_read_stack_handle(fd, packet);
case 0xBDAA0024u: return proc_assemble_handle(fd, packet);
case 0xBDAA0003u: return proc_write_handle(fd, packet);
case 0xBDAACC04u: return proc_write_multi_handle(fd, packet);
case 0xBDAA0004u: return proc_maps_handle(fd, packet);
case 0xBDAA0005u: return proc_install_handle(fd, packet);
case 0xBDAA0006u: return proc_call_handle(fd, packet);
@@ -892,6 +1230,15 @@ int proc_handle(int fd, struct cmd_packet *packet, unsigned char client_idx) {
case 0xBDAACC01u: return proc_scan_start_handle(fd, packet);
case 0xBDAACC02u: return proc_scan_count_handle(fd, packet);
case 0xBDAACC03u: return proc_scan_get_handle(fd, packet);
case 0xBDAACC10u: return proc_turboscan_caps_handle(fd, packet);
case 0xBDAACC11u: return proc_turboscan_start_handle(fd, packet, client_idx);
case 0xBDAACC12u: return proc_turboscan_count_handle(fd, packet, client_idx);
case 0xBDAACC13u: return proc_turboscan_get_handle(fd, packet, client_idx);
case 0xBDAACC14u: return proc_turboscan_end_handle(fd, packet, client_idx);
case 0xBDAACC15u: return proc_turboscan_config_handle(fd, packet);
case 0xBDAACC16u: return proc_turboscan_regions_handle(fd, packet);
case 0xBDAACC17u: return proc_turboscan_cancel_handle(fd, packet);
case 0xBDAACC24u: return proc_arena_handle(fd, packet);
}
net_send_int32(fd, CMD_ERROR);
+2 -9
View File
@@ -254,14 +254,7 @@ static int proc_resolve_call_nid_Jp7F(int pid, void *scratch)
static void compute_fw_vmmap_adjustments(uint64_t *out_nentries_adj,
uint64_t *out_name_adj) {
uint32_t fw = kernel_get_fw_version();
int write_adj = 0;
switch (fw & 0xffff0000u) {
case 0x6000000u: case 0x6020000u: case 0x6500000u: /* 6.00 6.02 6.50 */
case 0x7000000u: case 0x7010000u: /* 7.00 7.01 */
case 0x8000000u: case 0x8200000u: case 0x8400000u: case 0x8600000u: /* 8.00 8.20 8.40 8.60 */
write_adj = 1; break;
default: write_adj = 0; break;
}
int write_adj = ((fw & 0xffff0000u) >= 0x6000000u) ? 1 : 0;
*out_nentries_adj = write_adj ? 8 : 0;
*out_name_adj = write_adj ? 0xE : 0;
}
@@ -391,7 +384,7 @@ static int kern_init_dbgctx_at_addr_kern(uint32_t pid, uint64_t addr)
static uint64_t proc_call_remote_sce(int pid)
{
void *mmap_rv = freebsd_mmap((unsigned long)pid, (void *)0, 0x4000, 3,
void *mmap_rv = freebsd_mmap((unsigned long)pid, (void *)0, 0x4000, 7,
0x1002, -1, 0);
if ((long)mmap_rv == -1L) return 0;
uint64_t scratch = (uint64_t)mmap_rv;
+273
View File
@@ -4,6 +4,7 @@
#include "protocol.h"
#include "proc.h"
#include "kern_rw_fast.h"
#include "write_policy.h"
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
@@ -362,6 +363,122 @@ int proc_ptwalk_augment(uint32_t pid,
return rc;
}
static int ptw_verify_phys(uint32_t pid, uint64_t va, uint64_t phys,
const uint8_t *expected, uint64_t len,
int diagnostic) {
uint8_t actual[0x1000];
uint64_t done = 0;
while (done < len) {
uint64_t n = len - done;
if (n > sizeof(actual)) n = sizeof(actual);
if (kernel_copyout_fast((intptr_t)(g_ptw_dmap + phys + done),
actual, (size_t)n) != 0) {
klog_printf("[write:dmap] pid=%u va=0x%llx len=%llu "
"verify-read failed at +0x%llx\n",
pid, (unsigned long long)va,
(unsigned long long)len,
(unsigned long long)done);
return PROC_PTW_WRITE_VERIFY_FAILED;
}
if (memcmp(actual, expected + done, (size_t)n) != 0) {
uint64_t mismatch = 0;
while (mismatch < n
&& actual[mismatch] == expected[done + mismatch])
mismatch++;
klog_printf("[write:dmap] pid=%u verify mismatch va=0x%llx "
"expected=%02x actual=%02x\n",
pid, (unsigned long long)(va + done + mismatch),
expected[done + mismatch], actual[mismatch]);
return PROC_PTW_WRITE_VERIFY_FAILED;
}
done += n;
}
if (diagnostic) {
klog_printf("[write:dmap] pid=%u va=0x%llx len=%llu verify=OK\n",
pid, (unsigned long long)va,
(unsigned long long)len);
}
return PROC_PTW_WRITE_OK;
}
int proc_ptwalk_write(uint32_t pid, uint64_t va, uint64_t len, const void *src) {
if ((int32_t)pid <= 0 || !src || len == 0 || va + len < va)
return PROC_PTW_WRITE_RESOLVE_FAILED;
if (ptw_discover() != 1) return PROC_PTW_WRITE_RESOLVE_FAILED;
intptr_t kproc = kernel_get_proc_fast((pid_t)pid);
if (!kproc) return PROC_PTW_WRITE_RESOLVE_FAILED;
uint64_t vmspace = 0;
if (kernel_copyout_fast((intptr_t)(kproc + PTW_PROC_VMSPACE_OFF),
&vmspace, 8) != 0 || !vmspace)
return PROC_PTW_WRITE_RESOLVE_FAILED;
uint64_t pair[2];
if (kernel_copyout_fast((intptr_t)(vmspace + g_ptw_pmap_off),
pair, sizeof(pair)) != 0)
return PROC_PTW_WRITE_RESOLVE_FAILED;
uint64_t cr3 = pair[1];
if (cr3 == 0 || (cr3 & 0xFFF) || cr3 >= PTW_PHYS_BOUND)
return PROC_PTW_WRITE_RESOLVE_FAILED;
if (pair[0] - cr3 != g_ptw_dmap)
return PROC_PTW_WRITE_RESOLVE_FAILED;
const uint8_t *in = (const uint8_t *)src;
uint64_t done = 0;
while (done < len) {
uint64_t cur_va = va + done;
uint64_t e = 0;
int lvl = -1;
if (ptw_walk_leaf(g_ptw_dmap, cr3, cur_va, &e, &lvl) != 0)
return PROC_PTW_WRITE_RESOLVE_FAILED;
uint64_t page_size;
if (lvl == 3) page_size = 0x1000ULL;
else if (lvl == 2) page_size = 0x200000ULL;
else if (lvl == 1) page_size = 0x40000000ULL;
else return PROC_PTW_WRITE_RESOLVE_FAILED;
uint64_t page_mask = page_size - 1;
uint64_t off_in_page = cur_va & page_mask;
uint64_t phys = ((e & PTW_PHYS_MASK) & ~page_mask) + off_in_page;
uint64_t n = len - done;
uint64_t avail = page_size - off_in_page;
if (n > avail) n = avail;
if (phys >= PTW_PHYS_BOUND || phys + n < phys
|| phys + n > PTW_PHYS_BOUND)
return PROC_PTW_WRITE_RESOLVE_FAILED;
int diagnostic = ps5debug_write_diagnostics_enabled();
if (diagnostic) {
klog_printf("[write:dmap] pid=%u va=0x%llx len=%llu "
"level=%d phys=0x%llx pte=0x%llx\n",
pid, (unsigned long long)cur_va,
(unsigned long long)n, lvl,
(unsigned long long)phys,
(unsigned long long)e);
}
if (kernel_copyin_fast(in + done,
(intptr_t)(g_ptw_dmap + phys), n) != 0) {
klog_printf("[write:dmap] pid=%u va=0x%llx len=%llu "
"copy failed level=%d phys=0x%llx pte=0x%llx\n",
pid, (unsigned long long)cur_va,
(unsigned long long)n, lvl,
(unsigned long long)phys,
(unsigned long long)e);
return PROC_PTW_WRITE_COPY_FAILED;
}
int verify_rc = ptw_verify_phys(pid, cur_va, phys, in + done,
n, diagnostic);
if (verify_rc != PROC_PTW_WRITE_OK) return verify_rc;
done += n;
}
return PROC_PTW_WRITE_OK;
}
int proc_ptwalk_read(uint32_t pid, uint64_t va, uint64_t len, void *dst) {
if ((int32_t)pid <= 0 || !dst || len == 0) return 1;
if (ptw_discover() != 1) return 1;
@@ -412,3 +529,159 @@ int proc_ptwalk_read(uint32_t pid, uint64_t va, uint64_t len, void *dst) {
}
return 0;
}
// --- benchmark/diagnostic accessors (read-only) ---
uint64_t proc_ptwalk_dmap_base(void) { return g_ptw_dmap; }
// Return the kernel (DMAP) address where the 4K leaf PTE for `va` LIVES, plus
// its current value. Mirrors the proven ptw_walk_leaf exactly. For aliasing
// (caller overwrites the leaf PTE). Return: 0 = 4K leaf addr returned (value may
// be not-present, which is expected for an untouched window page); 2 = an upper
// level was not present (PT not allocated - touch a sibling first); 3 = huge
// page (no 4K leaf); 1 = error.
int proc_ptwalk_leaf_addr(uint32_t pid, uint64_t va, uint64_t *out_pte_kaddr,
uint64_t *out_pte_val, int *out_level) {
if ((int32_t)pid <= 0) return 1;
if (ptw_discover() != 1) return 1;
intptr_t kproc = kernel_get_proc_fast((pid_t)pid);
if (!kproc) return 1;
uint64_t vmspace = 0;
if (kernel_copyout_fast((intptr_t)(kproc + PTW_PROC_VMSPACE_OFF),
&vmspace, 8) != 0 || !vmspace)
return 1;
uint64_t pair[2];
if (kernel_copyout_fast((intptr_t)(vmspace + g_ptw_pmap_off),
pair, sizeof(pair)) != 0)
return 1;
uint64_t cr3 = pair[1];
if (cr3 == 0 || (cr3 & 0xFFF) || cr3 >= PTW_PHYS_BOUND) return 1;
if (pair[0] - cr3 != g_ptw_dmap) return 1;
uint64_t table = cr3 & PTW_PHYS_MASK;
for (int level = 0; level < 4; level++) {
if (table >= PTW_PHYS_BOUND) return 1;
int shift = 39 - level * 9;
uint64_t idx = (va >> shift) & 0x1FF;
uint64_t ent_kaddr = g_ptw_dmap + table + idx * 8;
uint64_t e = 0;
if (kernel_copyout_fast((intptr_t)ent_kaddr, &e, 8) != 0) return 1;
if (level == 3) {
if (out_pte_kaddr) *out_pte_kaddr = ent_kaddr;
if (out_pte_val) *out_pte_val = e;
if (out_level) *out_level = 3;
return 0;
}
if (!(e & PTW_PTE_PRESENT)) return 2;
if ((level == 1 || level == 2) && (e & PTW_PTE_PS)) return 3;
table = e & PTW_PHYS_MASK;
}
return 1;
}
// Resolve one VA to its leaf phys + page size (4K/2M/1G). For benchmarking the
// DMAP read path: lets the caller find a huge-page-backed VA and copyout from
// g_ptw_dmap+phys directly.
int proc_ptwalk_probe(uint32_t pid, uint64_t va, uint64_t *out_phys,
int *out_level, uint64_t *out_pagesize, uint64_t *out_pte) {
if ((int32_t)pid <= 0) return 1;
if (ptw_discover() != 1) return 1;
intptr_t kproc = kernel_get_proc_fast((pid_t)pid);
if (!kproc) return 1;
uint64_t vmspace = 0;
if (kernel_copyout_fast((intptr_t)(kproc + PTW_PROC_VMSPACE_OFF),
&vmspace, 8) != 0 || !vmspace)
return 1;
uint64_t pair[2];
if (kernel_copyout_fast((intptr_t)(vmspace + g_ptw_pmap_off),
pair, sizeof(pair)) != 0)
return 1;
uint64_t cr3 = pair[1];
if (cr3 == 0 || (cr3 & 0xFFF) || cr3 >= PTW_PHYS_BOUND) return 1;
if (pair[0] - cr3 != g_ptw_dmap) return 1;
uint64_t e = 0;
int lvl = -1;
if (ptw_walk_leaf(g_ptw_dmap, cr3, va, &e, &lvl) != 0) return 1;
if (!(e & PTW_PTE_PRESENT)) return 1;
uint64_t page_size = (lvl == 3) ? 0x1000ULL
: (lvl == 2) ? 0x200000ULL
: (lvl == 1) ? 0x40000000ULL : 0;
if (!page_size) return 1;
uint64_t page_mask = page_size - 1;
if (out_phys) *out_phys = ((e & PTW_PHYS_MASK) & ~page_mask) + (va & page_mask);
if (out_level) *out_level = lvl;
if (out_pagesize) *out_pagesize = page_size;
if (out_pte) *out_pte = e;
return 0;
}
// Unified per-2MB-span resolve for the aliasing read engine (read-only). One PD-walk
// (PML4->PDPT->PD, ~3 kernel reads) for a 2MB-aligned `span2m`, then branch on the PS
// bit: a 1GB (PDPT PS) or 2MB (PD PS) superpage returns is_huge + the phys of span2m's
// first byte (the 2MB span is phys-contiguous inside any superpage) + the huge PTE; a
// non-PS PD entry returns is_huge=0 + the DMAP kaddr of the 512-entry leaf PT page (the
// caller bulk-reads it once and indexes per 4K page). ~256-512x cheaper than per-4K-page
// proc_ptwalk_probe on 4K-paged targets; identical on huge. Mirrors ptw_walk_leaf's reads.
int proc_ptwalk_span_resolve(uint32_t pid, uint64_t span2m, int *out_huge,
uint64_t *out_phys_base, uint64_t *out_leaf_pt_kaddr,
uint64_t *out_pte) {
if ((int32_t)pid <= 0) return 1;
if (ptw_discover() != 1) return 1;
intptr_t kproc = kernel_get_proc_fast((pid_t)pid);
if (!kproc) return 1;
uint64_t vmspace = 0;
if (kernel_copyout_fast((intptr_t)(kproc + PTW_PROC_VMSPACE_OFF),
&vmspace, 8) != 0 || !vmspace)
return 1;
uint64_t pair[2];
if (kernel_copyout_fast((intptr_t)(vmspace + g_ptw_pmap_off),
pair, sizeof(pair)) != 0)
return 1;
uint64_t cr3 = pair[1];
if (cr3 == 0 || (cr3 & 0xFFF) || cr3 >= PTW_PHYS_BOUND) return 1;
if (pair[0] - cr3 != g_ptw_dmap) return 1;
uint64_t table = cr3 & PTW_PHYS_MASK;
for (int level = 0; level < 3; level++) { // PML4(0) -> PDPT(1) -> PD(2)
if (table >= PTW_PHYS_BOUND) return 1;
int shift = 39 - level * 9;
uint64_t idx = (span2m >> shift) & 0x1FF;
uint64_t e = 0;
if (kernel_copyout_fast((intptr_t)(g_ptw_dmap + table + idx * 8), &e, 8) != 0)
return 1;
if (!(e & PTW_PTE_PRESENT)) return 1;
if ((level == 1 || level == 2) && (e & PTW_PTE_PS)) { // 1GB or 2MB superpage
uint64_t pgsz = (level == 1) ? (1ULL << 30) : (1ULL << 21);
uint64_t base = (e & PTW_PHYS_MASK) & ~(pgsz - 1);
if (out_huge) *out_huge = 1;
if (out_phys_base) *out_phys_base = base + (span2m & (pgsz - 1));
if (out_pte) *out_pte = e;
if (out_leaf_pt_kaddr) *out_leaf_pt_kaddr = 0;
return 0;
}
table = e & PTW_PHYS_MASK; // descend
}
// PD entry was a non-PS pointer -> `table` is now the leaf PT page's phys.
if (table >= PTW_PHYS_BOUND) return 1;
if (out_huge) *out_huge = 0;
if (out_leaf_pt_kaddr) *out_leaf_pt_kaddr = g_ptw_dmap + table;
if (out_phys_base) *out_phys_base = 0;
if (out_pte) *out_pte = 0;
return 0;
}
File diff suppressed because it is too large. Load diff
+170 -83
View File
@@ -5,6 +5,7 @@
#include "net.h"
#include "proc.h"
#include "kern_rw_fast.h"
#include "aob_scan.h"
#include <ps5/kernel.h>
static int vmmap_offs_initialized = 0;
@@ -14,17 +15,7 @@ static uint64_t vmmap_name_adj = 0;
static void vmmap_init_offsets(void) {
uint32_t fw = kernel_get_fw_version();
int write_adj = 0;
switch (fw & 0xffff0000u) {
case 0x6000000u: case 0x6020000u: case 0x6500000u: /* 6.00 6.02 6.50 */
case 0x7000000u: case 0x7010000u: /* 7.00 7.01 */
case 0x8000000u: case 0x8200000u: case 0x8400000u: case 0x8600000u: /* 8.00 8.20 8.40 8.60 */
write_adj = 1;
break;
default:
write_adj = 0;
break;
}
int write_adj = ((fw & 0xffff0000u) >= 0x6000000u) ? 1 : 0;
if (write_adj) {
vmmap_nentries_adj = 8;
@@ -244,6 +235,28 @@ int proc_scan_aob_handle(int fd, struct cmd_packet *packet) {
return 0;
}
/* Region-parallel multi-pattern AOB scan (core in aob_scan.h). The range is split into
g_aob_scan_threads contiguous sub-ranges (one worker each, worker 0 inline); each worker
scans its sub-range once for ALL patterns via the shared first-byte dispatch table, then
aob_merge() combines results preserving target_count / uniqueness. Reads via proc_read_mem.
The first-byte dispatch is the big win; AOB is read-bound on mdbg so workers plateau at 2-3. */
#define AOB_SCAN_THREADS_MAX 16
uint32_t g_aob_scan_threads = 3; /* read-bound on mdbg: 2-3 is the sweet spot */
uint32_t g_aob_chunk_size = 0x40000u; /* 256 KiB per-worker read window */
struct aob_ps5_fill { uint32_t pid; uint64_t base; };
static void aob_ps5_fill_fn(void *ctx, uint64_t off, uint8_t *buf, uint32_t len) {
struct aob_ps5_fill *f = (struct aob_ps5_fill *)ctx;
proc_read_mem(f->pid, f->base + off, len, buf);
}
static void *aob_thread(void *arg) {
aob_scan_range((struct aob_worker *)arg);
return NULL;
}
int proc_scan_aob_multi_handle(int fd, struct cmd_packet *packet) {
struct cmd_proc_scan_aob_multi_packet *mp =
(struct cmd_proc_scan_aob_multi_packet *)packet->data;
@@ -274,87 +287,157 @@ int proc_scan_aob_multi_handle(int fd, struct cmd_packet *packet) {
pat_count++;
}
}
uint32_t output_size = (uint32_t)pat_count * 8u;
/* Region-parallel multi-pattern scan. Split [address, address+length) into
num_workers contiguous ascending sub-ranges; each worker scans its sub-range once
for ALL patterns via the shared first-byte dispatch table (each byte read once
total; most positions test ~0-1 patterns), then aob_merge() combines per-worker
results preserving exact target_count / uniqueness semantics. Reads via proc_read_mem
(mdbg). num_workers + chunk are DEV-tunable (g_aob_scan_threads / g_aob_chunk_size). */
uint32_t chunkSize = g_aob_chunk_size;
if (chunkSize < max_plen) chunkSize = (uint32_t)max_plen;
uint64_t length = mp->length;
int stop_unique = (mp->stop_flag == 1);
uint8_t *read_buf = (uint8_t *)net_alloc_buffer(0x100000);
void *output = read_buf ? net_alloc_buffer(output_size) : NULL;
uint64_t *match_counts = output ? (uint64_t *)net_alloc_buffer((uint32_t)pat_count * 8u) : NULL;
if (!read_buf || !output || !match_counts) {
if (read_buf) free(read_buf);
if (output) free(output);
if (match_counts) free(match_counts);
free(blob);
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
memset(output, 0, output_size);
memset(match_counts, 0, (size_t)pat_count * 8u);
struct aob_pat *pats = NULL;
uint32_t *bidx = NULL, *widx = NULL, *slot_off = NULL;
uint64_t *output = NULL;
uint8_t *readbufs = NULL, *wdone = NULL;
uint64_t *waddrs = NULL;
uint32_t *wcounts = NULL;
struct aob_dispatch disp;
uint32_t total_slots = 0;
uint32_t num_workers = 1;
uint64_t span = 1;
net_send_int32(fd, CMD_SUCCESS);
pats = (struct aob_pat *)net_alloc_buffer((uint32_t)((size_t)pat_count * sizeof(struct aob_pat)));
bidx = (uint32_t *)net_alloc_buffer((uint32_t)((size_t)pat_count * sizeof(uint32_t)));
widx = (uint32_t *)net_alloc_buffer((uint32_t)((size_t)pat_count * sizeof(uint32_t)));
slot_off = (uint32_t *)net_alloc_buffer((uint32_t)((size_t)(pat_count + 1) * sizeof(uint32_t)));
output = (uint64_t *)net_alloc_buffer((uint32_t)pat_count * 8u);
if (!pats || !bidx || !widx || !slot_off || !output) goto aob_fail;
uint64_t address = mp->address;
uint64_t remaining = mp->length;
uint8_t stop_flag = mp->stop_flag;
int stop_unique = (stop_flag == 1);
uint16_t found_count = 0;
uint16_t invalidated_count = 0;
if (remaining > 0) {
for (;;) {
uint64_t to_read = (remaining < 0x100000) ? remaining : 0x100000;
memset(read_buf, 0, to_read);
proc_read_mem(mp->pid, address, to_read, read_buf);
uint32_t cursor = 0;
int outer_break = 0;
for (uint16_t pi = 0; pi < pat_count; pi++) {
uint8_t target_count = blob[cursor];
uint32_t plen;
memcpy(&plen, blob + cursor + 1, 4);
const uint8_t *pat = blob + cursor + 5;
const uint8_t *msk = pat + plen;
uint64_t limit = (to_read >= plen) ? to_read - plen : 0;
for (uint64_t j = 0; j <= limit; j++) {
if (aob_match(plen, pat, &read_buf[j], 0, msk)) {
match_counts[pi]++;
if (match_counts[pi] == target_count) {
uint64_t cur_addr = address + j;
memcpy((uint8_t *)output + (size_t)pi * 8u, &cur_addr, 8);
found_count++;
if (!stop_unique) break;
} else if (stop_unique &&
match_counts[pi] == (uint64_t)target_count + 1u) {
uint64_t zero = 0;
memcpy((uint8_t *)output + (size_t)pi * 8u, &zero, 8);
invalidated_count++;
break;
}
}
}
cursor += 5 + 2u * plen;
if (!stop_unique && found_count >= pat_count) { outer_break = 1; break; }
if ( stop_unique && invalidated_count >= pat_count) { outer_break = 1; break; }
}
if (outer_break) break;
if (remaining <= 0x100000) break;
uint64_t advance = (uint64_t)0x100001 - max_plen;
address += advance;
remaining = (remaining > advance) ? remaining - advance : 0;
/* Pattern table from the validated blob, then first-byte dispatch + result-slot offsets. */
{
uint32_t cursor = 0;
for (uint32_t pi = 0; pi < pat_count; pi++) {
uint32_t plen;
memcpy(&plen, blob + cursor + 1, 4);
const uint8_t *pat = blob + cursor + 5;
const uint8_t *msk = pat + plen;
pats[pi].pat = pat;
pats[pi].msk = msk;
pats[pi].plen = plen;
pats[pi].target_count = blob[cursor];
pats[pi].first_fixed = (msk[0] == 1) ? (int16_t)pat[0] : (int16_t)-1;
cursor += 5 + 2 * plen;
}
}
aob_build_dispatch(pats, pat_count, &disp, bidx, widx);
net_send_all(fd, output, (int)output_size);
free(match_counts);
slot_off[0] = 0;
for (uint32_t pi = 0; pi < pat_count; pi++)
slot_off[pi + 1] = slot_off[pi] + ((uint32_t)pats[pi].target_count + 1);
total_slots = slot_off[pat_count];
num_workers = g_aob_scan_threads;
if (num_workers < 1) num_workers = 1;
if (num_workers > AOB_SCAN_THREADS_MAX) num_workers = AOB_SCAN_THREADS_MAX;
if ((uint64_t)num_workers > length) num_workers = (length > 0) ? (uint32_t)length : 1;
span = (length + num_workers - 1) / num_workers;
if (span == 0) span = 1;
num_workers = (uint32_t)((length + span - 1) / span);
if (num_workers < 1) num_workers = 1;
if (num_workers > AOB_SCAN_THREADS_MAX) num_workers = AOB_SCAN_THREADS_MAX;
readbufs = (uint8_t *)net_alloc_buffer((uint32_t)((size_t)num_workers * (chunkSize + max_plen)));
waddrs = (uint64_t *)net_alloc_buffer((uint32_t)((size_t)num_workers * total_slots * 8));
wcounts = (uint32_t *)net_alloc_buffer((uint32_t)((size_t)num_workers * pat_count * sizeof(uint32_t)));
wdone = (uint8_t *)net_alloc_buffer((uint32_t)((size_t)num_workers * pat_count));
if (!readbufs || !waddrs || !wcounts || !wdone) goto aob_fail;
net_send_int32(fd, CMD_SUCCESS);
memset(output, 0, (size_t)pat_count * 8u);
{
struct aob_ps5_fill fillctx;
struct aob_worker workers[AOB_SCAN_THREADS_MAX];
ScePthread tids[AOB_SCAN_THREADS_MAX];
bool spawned[AOB_SCAN_THREADS_MAX];
const uint64_t *ca[AOB_SCAN_THREADS_MAX];
const uint32_t *cc[AOB_SCAN_THREADS_MAX];
fillctx.pid = mp->pid;
fillctx.base = mp->address;
for (uint32_t w = 0; w < num_workers; w++) {
uint64_t s = (uint64_t)w * span;
uint64_t e = s + span;
if (s > length) s = length;
if (e > length) e = length;
workers[w].pats = pats;
workers[w].pat_count = pat_count;
workers[w].disp = &disp;
workers[w].base_addr = mp->address;
workers[w].length = length;
workers[w].max_plen = (uint32_t)max_plen;
workers[w].chunkSize = chunkSize;
workers[w].fill = aob_ps5_fill_fn;
workers[w].fill_ctx = &fillctx;
workers[w].slot_off = slot_off;
workers[w].start = s;
workers[w].end = e;
workers[w].readBuf = readbufs + (size_t)w * (chunkSize + max_plen);
workers[w].addrs = waddrs + (size_t)w * total_slots;
workers[w].counts = wcounts + (size_t)w * pat_count;
workers[w].done = wdone + (size_t)w * pat_count;
spawned[w] = false;
}
/* Spawn workers 1..N-1, run worker 0 inline, then join (or run inline on spawn fail). */
for (uint32_t w = 1; w < num_workers; w++) {
if (scePthreadCreate(&tids[w], NULL, aob_thread, &workers[w], "aobscan") == 0)
spawned[w] = true;
}
aob_scan_range(&workers[0]);
for (uint32_t w = 1; w < num_workers; w++) {
if (spawned[w]) scePthreadJoin(tids[w], NULL);
else aob_scan_range(&workers[w]);
}
for (uint32_t w = 0; w < num_workers; w++) {
ca[w] = waddrs + (size_t)w * total_slots;
cc[w] = wcounts + (size_t)w * pat_count;
}
aob_merge(pats, pat_count, stop_unique, slot_off, num_workers, ca, cc, output);
}
net_send_all(fd, output, (int)((size_t)pat_count * 8u));
if (wdone) free(wdone);
if (wcounts) free(wcounts);
if (waddrs) free(waddrs);
if (readbufs) free(readbufs);
free(output);
free(read_buf);
free(slot_off);
free(widx);
free(bidx);
free(pats);
free(blob);
net_send_int32(fd, CMD_SUCCESS);
return 0;
aob_fail:
if (wdone) free(wdone);
if (wcounts) free(wcounts);
if (waddrs) free(waddrs);
if (readbufs) free(readbufs);
if (output) free(output);
if (slot_off) free(slot_off);
if (widx) free(widx);
if (bidx) free(bidx);
if (pats) free(pats);
free(blob);
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
int proc_scan_start_handle(int fd, struct cmd_packet *packet) {
@@ -502,6 +585,7 @@ int proc_scan_count_handle(int fd, struct cmd_packet *packet) {
int needs_previous_flag = g_cmptype_needs_previous[cp->compareType] != 0;
int needs_scan_value = needs_value_flag || needs_extra_flag;
int is_arrbytes = (cp->valueType == 10);
int is_between = (cp->compareType == 4);
uint64_t value_length = proc_scan_getSizeOfValueType(cp->valueType);
if (value_length == 0) value_length = cp->lenData;
@@ -545,6 +629,9 @@ int proc_scan_count_handle(int fd, struct cmd_packet *packet) {
if (pattern) net_recv_all(fd, pattern, (int)cp->lenData, 1);
if (mask) net_recv_all(fd, mask, (int)value_length, 1);
const uint8_t *between_hi = (is_between && pattern && cp->lenData >= 2 * value_length)
? (pattern + value_length) : NULL;
int includes_prev = needs_previous_flag;
uint64_t entry_size = includes_prev ? (4 + value_length) : 4;
uint64_t flush_thresh = 0x3FFE8ULL - 2 * value_length;
@@ -576,7 +663,7 @@ int proc_scan_count_handle(int fd, struct cmd_packet *packet) {
for (uint64_t off = 0; off + entry_size <= chunk_len; off += entry_size) {
uint32_t entry_offset;
memcpy(&entry_offset, chunk_buf + off, 4);
const uint8_t *prev_value_ptr = includes_prev ? (chunk_buf + off + 4) : NULL;
const uint8_t *prev_value_ptr = includes_prev ? (chunk_buf + off + 4) : between_hi;
uint64_t addr = cp->base_address + entry_offset;
if (addr >= window_end) {
+237
View File
@@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "scan_alias.h"
#include "proc.h"
#include "kern_rw_fast.h"
#include "sdk_shim.h"
#define SA_SPAN 0x200000ULL
#define SA_PT_ENTRIES 512u
#define SA_TOUCH_IDX 511u
#define SA_PAGE 0x1000ULL
#define SA_ARENA_DEF (64ULL << 20)
#define SA_PHYS_MASK 0x000FFFFFFFFFF000ULL
#define SA_DMAP_BOUND 0x1000000000ULL
#define SA_PTE_PRESENT 0x1ULL
#define SA_PTE_PCD 0x10ULL
#define SA_PTE_ALIAS 0x7ULL
#define SA_MAX_PAGES 511u
#define SA_VERIFY_MAX 32u
#define SA_MAX_SPANS 128u
#define SA_VERIFY_IDX 384u
#define SA_VERIFY_DIV 16
struct scan_alias_ctx {
uint32_t pid;
uint32_t self_pid;
uint64_t map_seq;
uint64_t dmap;
uint64_t arena_cap;
uint64_t arena_raw;
uint64_t arena_mapsz;
uint64_t arena_end;
uint64_t cursor;
uint64_t used_pt[SA_MAX_SPANS];
uint64_t used_count;
int map_active;
uint64_t map_pt_base;
uint64_t map_npages;
int sc_valid;
uint64_t sc_span;
int sc_huge;
uint64_t sc_phys_base;
uint64_t sc_pte;
uint64_t span_pt[SA_PT_ENTRIES];
};
static void sa_reclaim_or_leak(scan_alias_ctx *c);
static int sa_arena_alloc(scan_alias_ctx *c) {
uint64_t mapsz = c->arena_cap + SA_SPAN;
long raw = ps5debug_syscall(477 , 0L, (long)mapsz,
3L , 0x1002L , -1L, 0L);
if (raw == -1 || raw == 0) return -1;
uint64_t base = ((uint64_t)raw + (SA_SPAN - 1)) & ~(SA_SPAN - 1);
c->arena_raw = (uint64_t)raw;
c->arena_mapsz = mapsz;
c->arena_end = base + c->arena_cap;
c->cursor = base;
c->used_count = 0;
return 0;
}
static int sa_next_span(scan_alias_ctx *c, uint64_t *out_span) {
if (c->cursor == 0 || c->cursor + SA_SPAN > c->arena_end) {
if (c->arena_raw) sa_reclaim_or_leak(c);
if (sa_arena_alloc(c) != 0) return -1;
}
*out_span = c->cursor;
c->cursor += SA_SPAN;
return 0;
}
scan_alias_ctx *scan_alias_begin(uint32_t pid, uint64_t arena_cap) {
uint64_t dmap = proc_ptwalk_dmap_base();
if (dmap == 0) return NULL;
scan_alias_ctx *c = (scan_alias_ctx *)malloc(sizeof(*c));
if (!c) return NULL;
memset(c, 0, sizeof(*c));
c->pid = pid;
c->self_pid = (uint32_t)getpid();
c->dmap = dmap;
c->arena_cap = arena_cap ? arena_cap : SA_ARENA_DEF;
if (sa_arena_alloc(c) != 0) { free(c); return NULL; }
return c;
}
void scan_alias_release(scan_alias_ctx *c) {
if (!c || !c->map_active) return;
uint64_t zeros[SA_MAX_PAGES];
memset(zeros, 0, c->map_npages * 8);
kernel_copyin_fast(zeros, (intptr_t)c->map_pt_base, (size_t)(c->map_npages * 8));
c->map_active = 0;
c->map_npages = 0;
c->map_pt_base = 0;
}
static void sa_reclaim_or_leak(scan_alias_ctx *c) {
if (c->arena_raw == 0) return;
if (c->map_active) scan_alias_release(c);
int clean = (c->used_count <= SA_MAX_SPANS);
for (uint64_t i = 0; clean && i < c->used_count; i++) {
uint64_t pt[SA_PT_ENTRIES];
if (kernel_copyout_fast((intptr_t)c->used_pt[i], pt, sizeof(pt)) != 0) { clean = 0; break; }
for (unsigned k = 0; k < SA_VERIFY_IDX; k++)
if (pt[k] & SA_PTE_PRESENT) { clean = 0; break; }
}
if (clean)
ps5debug_syscall(73 , (long)c->arena_raw, (long)c->arena_mapsz,
0L, 0L, 0L, 0L);
c->arena_raw = 0; c->arena_mapsz = 0; c->arena_end = 0; c->cursor = 0; c->used_count = 0;
}
const void *scan_alias_map(scan_alias_ctx *c, uint64_t tgt, uint64_t len,
uint64_t *out_mapped_len) {
if (!c || len == 0) return NULL;
if (c->map_active) scan_alias_release(c);
uint64_t tgt_pg = tgt & ~(SA_PAGE - 1);
uint64_t off = tgt & (SA_PAGE - 1);
uint64_t npages = (off + len + (SA_PAGE - 1)) >> 12;
if (npages == 0 || npages > SA_MAX_PAGES) return NULL;
uint64_t span;
if (sa_next_span(c, &span) != 0) return NULL;
*(volatile uint8_t *)(uintptr_t)(span + (uint64_t)SA_TOUCH_IDX * SA_PAGE) = 0;
uint64_t pt_base = 0, pt_val = 0; int pt_lv = -1;
if (proc_ptwalk_leaf_addr(c->self_pid, span, &pt_base, &pt_val, &pt_lv) != 0)
return NULL;
if (pt_lv != 3) return NULL;
if (pt_base < c->dmap || pt_base >= c->dmap + SA_DMAP_BOUND)
return NULL;
uint64_t pt[SA_PT_ENTRIES];
if (kernel_copyout_fast((intptr_t)pt_base, pt, sizeof(pt)) != 0) return NULL;
uint64_t vw[SA_VERIFY_MAX], vt[SA_VERIFY_MAX]; unsigned ns = 0;
for (uint64_t k = 0; k < npages; k++) {
uint64_t tpage = tgt_pg + k * SA_PAGE;
uint64_t span2m = tpage & ~(SA_SPAN - 1);
uint64_t tphys;
int newly_resolved = 0;
if (!c->sc_valid || c->sc_span != span2m) {
int huge = 0; uint64_t pbase = 0, leaf_kaddr = 0, pte = 0;
if (proc_ptwalk_span_resolve(c->pid, span2m, &huge, &pbase, &leaf_kaddr, &pte) != 0)
return NULL;
if (huge) {
if (pte & SA_PTE_PCD) return NULL;
c->sc_huge = 1; c->sc_phys_base = pbase; c->sc_pte = pte;
} else {
if (kernel_copyout_fast((intptr_t)leaf_kaddr, c->span_pt, sizeof(c->span_pt)) != 0)
return NULL;
c->sc_huge = 0;
}
c->sc_span = span2m; c->sc_valid = 1;
newly_resolved = 1;
}
if (c->sc_huge) {
tphys = c->sc_phys_base + (tpage - span2m);
} else {
uint64_t e = c->span_pt[(tpage >> 12) & 0x1FF];
if (!(e & SA_PTE_PRESENT)) return NULL;
if (e & SA_PTE_PCD) return NULL;
tphys = e & SA_PHYS_MASK;
}
if (pt[k] & SA_PTE_PRESENT) return NULL;
pt[k] = (tphys & SA_PHYS_MASK) | SA_PTE_ALIAS;
if ((newly_resolved || k == 0 || k == npages - 1) && ns < SA_VERIFY_MAX) {
vw[ns] = span + k * SA_PAGE;
vt[ns] = tpage;
ns++;
}
}
if ((pt_base & 0xFFFULL) != 0 || npages > SA_MAX_PAGES) return NULL;
if (kernel_copyin_fast(pt, (intptr_t)pt_base, (size_t)(npages * 8)) != 0) {
uint64_t zeros[SA_MAX_PAGES];
memset(zeros, 0, (size_t)(npages * 8));
kernel_copyin_fast(zeros, (intptr_t)pt_base, (size_t)(npages * 8));
return NULL;
}
c->map_active = 1;
c->map_pt_base = pt_base;
c->map_npages = npages;
if (c->used_count < SA_MAX_SPANS) c->used_pt[c->used_count] = pt_base;
c->used_count++;
int do_verify = ((c->map_seq % SA_VERIFY_DIV) == 0);
c->map_seq++;
if (do_verify) {
for (unsigned s = 0; s < ns; s++) {
uint8_t a[64], b[64];
memcpy(a, (const void *)(uintptr_t)vw[s], sizeof(a));
memset(b, 0, sizeof(b));
proc_read_mem(c->pid, vt[s], sizeof(b), b);
if (memcmp(a, b, sizeof(a)) != 0) {
scan_alias_release(c);
return NULL;
}
}
}
if (out_mapped_len) *out_mapped_len = len;
return (const void *)(uintptr_t)(span + off);
}
void scan_alias_end(scan_alias_ctx *c) {
if (!c) return;
scan_alias_release(c);
sa_reclaim_or_leak(c);
free(c);
}
void scan_alias_rebind(scan_alias_ctx *c, uint32_t pid) {
if (!c) return;
if (c->map_active) scan_alias_release(c);
c->pid = pid;
c->sc_valid = 0;
}
+6 -2
View File
@@ -94,8 +94,8 @@ bool aob_match(uint64_t pattern_length,
case 8: return *(const float *)P_MEM == (*(const float *)P_BASE OP_BIN *(const float *)P_DELTA); \
case 9: { \
\
double delta_as_float = (double)(*(const float *)P_DELTA); \
return *(const double *)P_MEM == (*(const double *)P_BASE OP_BIN delta_as_float); \
double delta_d = *(const double *)P_DELTA; \
return *(const double *)P_MEM == (*(const double *)P_BASE OP_BIN delta_d); \
} \
default: return false; \
}
@@ -118,6 +118,8 @@ bool proc_scan_legacy_compareValues(unsigned char cmpType, unsigned char valType
const unsigned char *pMem = (const unsigned char *)memValue;
const unsigned char *pExtra = (const unsigned char *)extraValue;
if (cmpType >= 4 && cmpType <= 10 && pExtra == NULL) return false;
switch (cmpType) {
case 0: {
if (valueLength <= 1) return false;
@@ -190,6 +192,8 @@ bool proc_scan_compareValues(unsigned char cmpType, unsigned char valType,
const unsigned char *pPrev = (const unsigned char *)prevValue;
const unsigned char *pMask = (const unsigned char *)mask;
if (cmpType >= 4 && cmpType <= 10 && pPrev == NULL) return false;
switch (cmpType) {
case 0: {
+139
View File
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "scan_turbo.h"
#include <immintrin.h>
static size_t elem_size(unsigned char vt) {
switch (vt) {
case 0: case 1: return 1;
case 2: case 3: return 2;
case 4: case 5: case 8: return 4;
case 6: case 7: case 9: return 8;
default: return 0;
}
}
__attribute__((target("avx2")))
static size_t find_u32_avx2(const uint8_t *buf, size_t len, uint32_t target,
uint32_t *out, size_t max) {
const uint32_t *p = (const uint32_t *)buf;
size_t n = len / 4;
size_t cnt = 0, i = 0;
__m256i t = _mm256_set1_epi32((int)target);
for (; i + 8 <= n; i += 8) {
__m256i v = _mm256_loadu_si256((const __m256i *)(p + i));
__m256i eq = _mm256_cmpeq_epi32(v, t);
int mask = _mm256_movemask_ps(_mm256_castsi256_ps(eq));
if (mask) {
for (int b = 0; b < 8; b++)
if (mask & (1 << b)) { if (cnt < max) out[cnt] = (uint32_t)((i + b) * 4); cnt++; }
}
}
for (; i < n; i++)
if (p[i] == target) { if (cnt < max) out[cnt] = (uint32_t)(i * 4); cnt++; }
return cnt;
}
static size_t find_scalar(unsigned char vt, const uint8_t *buf, size_t len,
const void *val, uint32_t step,
uint32_t *out, size_t max) {
size_t esz = elem_size(vt);
if (esz == 0 || step == 0 || len < esz) return 0;
uint8_t v8 = *(const uint8_t *)val;
uint16_t v16 = *(const uint16_t *)val;
uint32_t v32 = *(const uint32_t *)val;
uint64_t v64 = *(const uint64_t *)val;
float vf = *(const float *)val;
double vd = *(const double *)val;
size_t cnt = 0;
size_t end = len - esz;
for (size_t off = 0; off <= end; off += step) {
const uint8_t *m = buf + off;
int eq = 0;
switch (vt) {
case 0: case 1: eq = (*(const uint8_t *)m == v8); break;
case 2: case 3: eq = (*(const uint16_t *)m == v16); break;
case 4: case 5: eq = (*(const uint32_t *)m == v32); break;
case 6: case 7: eq = (*(const uint64_t *)m == v64); break;
case 8: eq = (*(const float *)m == vf); break;
case 9: eq = (*(const double *)m == vd); break;
default: return cnt;
}
if (eq) { if (cnt < max) out[cnt] = (uint32_t)off; cnt++; }
}
return cnt;
}
size_t scan_simd_find_exact(unsigned char valtype, const uint8_t *buf, size_t len,
const void *value, uint32_t step,
uint32_t *out_off, size_t max_out) {
if (!buf || !value || !out_off) return 0;
if ((valtype == 4 || valtype == 5) && step == 4)
return find_u32_avx2(buf, len, *(const uint32_t *)value, out_off, max_out);
return find_scalar(valtype, buf, len, value, step, out_off, max_out);
}
#define _SPC_BIN(P_LHS, P_RHS, OP) \
switch (valType) { \
case 0: return *(const uint8_t *)P_LHS OP *(const uint8_t *)P_RHS; \
case 1: return *(const int8_t *)P_LHS OP *(const int8_t *)P_RHS; \
case 2: return *(const uint16_t *)P_LHS OP *(const uint16_t *)P_RHS; \
case 3: return *(const int16_t *)P_LHS OP *(const int16_t *)P_RHS; \
case 4: return *(const uint32_t *)P_LHS OP *(const uint32_t *)P_RHS; \
case 5: return *(const int32_t *)P_LHS OP *(const int32_t *)P_RHS; \
case 6: return *(const uint64_t *)P_LHS OP *(const uint64_t *)P_RHS; \
case 7: return *(const int64_t *)P_LHS OP *(const int64_t *)P_RHS; \
case 8: return *(const float *)P_LHS OP *(const float *)P_RHS; \
case 9: return *(const double *)P_LHS OP *(const double *)P_RHS; \
default: return 0; \
}
#define _SPC_BY(P_MEM, P_BASE, P_DELTA, OP_BIN) \
switch (valType) { \
case 0: return *(const uint8_t *)P_MEM == (uint8_t )(*(const uint8_t *)P_BASE OP_BIN *(const uint8_t *)P_DELTA); \
case 1: return *(const int8_t *)P_MEM == (int8_t )(*(const int8_t *)P_BASE OP_BIN *(const int8_t *)P_DELTA); \
case 2: return *(const uint16_t *)P_MEM == (uint16_t)(*(const uint16_t *)P_BASE OP_BIN *(const uint16_t *)P_DELTA); \
case 3: return *(const int16_t *)P_MEM == (int16_t )(*(const int16_t *)P_BASE OP_BIN *(const int16_t *)P_DELTA); \
case 4: return *(const uint32_t *)P_MEM == (*(const uint32_t *)P_BASE OP_BIN *(const uint32_t *)P_DELTA); \
case 5: return *(const int32_t *)P_MEM == (*(const int32_t *)P_BASE OP_BIN *(const int32_t *)P_DELTA); \
case 6: return *(const uint64_t *)P_MEM == (*(const uint64_t *)P_BASE OP_BIN *(const uint64_t *)P_DELTA); \
case 7: return *(const int64_t *)P_MEM == (*(const int64_t *)P_BASE OP_BIN *(const int64_t *)P_DELTA); \
case 8: return *(const float *)P_MEM == (*(const float *)P_BASE OP_BIN *(const float *)P_DELTA); \
case 9: { double delta_d = *(const double *)P_DELTA; \
return *(const double *)P_MEM == (*(const double *)P_BASE OP_BIN delta_d); } \
default: return 0; \
}
int scan_point_turbo_supported(unsigned char cmpType, unsigned char valType) {
if (valType > 9) return 0;
switch (cmpType) {
case 0: return valType <= 7;
case 5: case 6: case 7:
case 8: case 9: case 10: return 1;
default: return 0;
}
}
int scan_point_compare(unsigned char cmpType, unsigned char valType,
const void *mem, const void *scan, const void *prev) {
const void *pMem = mem;
const void *pScan = scan;
const void *pPrev = prev;
switch (cmpType) {
case 0: _SPC_BIN(pMem, pScan, ==)
case 5: _SPC_BIN(pMem, pPrev, >)
case 6: _SPC_BY (pMem, pPrev, pScan, +)
case 7: _SPC_BIN(pMem, pPrev, <)
case 8: _SPC_BY (pMem, pPrev, pScan, -)
case 9: _SPC_BIN(pMem, pPrev, !=)
case 10: _SPC_BIN(pMem, pPrev, ==)
default: return 0;
}
}
#undef _SPC_BIN
#undef _SPC_BY
File diff suppressed because it is too large. Load diff
+49 -42
View File
@@ -1,42 +1,49 @@
# SPDX-License-Identifier: GPL-3.0-only
SDK := ../ps5-payload-sdk/install
ifeq (,$(wildcard $(SDK)/toolchain/prospero.mk))
$(error SDK not built - run ../build.sh from the project root)
endif
include $(SDK)/toolchain/prospero.mk
ELF := build/ps5debug-NG.elf
BUILD_DIR := build
PAYLOAD_ELF := ../debugger/build/debugger.elf
EMBEDDED_BIN := $(BUILD_DIR)/embedded_inner.elf.bin
EMBEDDED_OBJ := $(BUILD_DIR)/embedded_inner.o
EMBEDDED_S := source/embedded_inner.S
CFLAGS := -Wall -O2 -I../common/include -ffunction-sections -fdata-sections
LDFLAGS := -Wl,--gc-sections
LDLIBS := -lSceLibcInternal -lkernel_sys -lSceNet -lSceNetCtl -lSceSysCore
CFILES := $(wildcard ../common/source/*.c) $(wildcard source/*.c)
all: $(ELF)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(EMBEDDED_BIN): $(PAYLOAD_ELF) | $(BUILD_DIR)
cp $< $@
$(EMBEDDED_OBJ): $(EMBEDDED_S) $(EMBEDDED_BIN) | $(BUILD_DIR)
$(CC) -c -o $@ $(EMBEDDED_S) -I$(BUILD_DIR)
$(ELF): $(CFILES) $(EMBEDDED_OBJ) | $(BUILD_DIR)
$(CC) $(CFLAGS) -o $@ $(CFILES) $(EMBEDDED_OBJ) $(LDFLAGS) $(LDLIBS)
clean:
rm -rf $(BUILD_DIR)
.PHONY: all clean
# SPDX-License-Identifier: GPL-3.0-only
SDK := ../ps5-payload-sdk/install
ifeq (,$(wildcard $(SDK)/toolchain/prospero.mk))
$(error SDK not built - run ../build.sh from the project root)
endif
include $(SDK)/toolchain/prospero.mk
ELF := build/ps5debug-NG.elf
BUILD_DIR := build
PAYLOAD_ELF := ../debugger/build/debugger.elf
EMBEDDED_BIN := $(BUILD_DIR)/embedded_inner.elf.bin
EMBEDDED_OBJ := $(BUILD_DIR)/embedded_inner.o
EMBEDDED_S := source/embedded_inner.S
CFLAGS := -Wall -O2 -I../common/include -ffunction-sections -fdata-sections
LDFLAGS := -Wl,--gc-sections
LDLIBS := -lSceLibcInternal -lkernel_sys -lSceNet -lSceNetCtl -lSceSysCore
WRITE_DIAG ?= 0
WRITE_PATH ?= 0
BUILD_FINGERPRINT ?= unfingerprinted
CFLAGS += -DPS5DEBUG_WRITE_DIAGNOSTICS=$(WRITE_DIAG)
CFLAGS += -DPS5DEBUG_FORCE_WRITE_PATH=$(WRITE_PATH)
CFLAGS += -DPS5DEBUG_NG_BUILD_FINGERPRINT_STR=\"$(BUILD_FINGERPRINT)\"
CFILES := $(wildcard ../common/source/*.c) $(wildcard source/*.c)
all: $(ELF)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(EMBEDDED_BIN): $(PAYLOAD_ELF) | $(BUILD_DIR)
cp $< $@
$(EMBEDDED_OBJ): $(EMBEDDED_S) $(EMBEDDED_BIN) | $(BUILD_DIR)
$(CC) -c -o $@ $(EMBEDDED_S) -I$(BUILD_DIR)
$(ELF): $(CFILES) $(EMBEDDED_OBJ) | $(BUILD_DIR)
$(CC) $(CFLAGS) -o $@ $(CFILES) $(EMBEDDED_OBJ) $(LDFLAGS) $(LDLIBS)
clean:
rm -rf $(BUILD_DIR)
.PHONY: all clean
+18 -30
View File
@@ -4,44 +4,32 @@
#include <stddef.h>
#include <string.h>
#include "sdk_shim.h"
#include "kern_rw_fast.h"
#define PROC_NEXT_OFFSET 0x00
#define PROC_SELFINFO_NAME_OFFSET 0x59C
#define PROC_SELFINFO_NAME_SIZE 32
#define PROC_LIST_LIMIT 0x1000
int find_proc_pid_by_name(const char *name)
{
if (!name) return -1;
intptr_t kproc = 0;
if (kernel_copyout_fast((intptr_t)KERNEL_ADDRESS_ALLPROC,
&kproc, sizeof(kproc)) != 0)
return -1;
for (int pid = 1; pid < 0x2710; pid++) {
int mib[4];
uint8_t kinfo[0x448];
size_t outlen = 0x448;
intptr_t cur = kproc;
char proc_name[PROC_SELFINFO_NAME_SIZE];
mib[0] = 1;
mib[1] = 0xE;
mib[2] = 1;
mib[3] = pid;
for (uint32_t i = 0; i < PROC_LIST_LIMIT && cur != 0; i++) {
memset(proc_name, 0, sizeof(proc_name));
if (kernel_copyout_fast(cur + PROC_SELFINFO_NAME_OFFSET,
proc_name, PROC_SELFINFO_NAME_SIZE) == 0
&& proc_name[PROC_SELFINFO_NAME_SIZE - 1] == 0
&& strcmp(proc_name, name) == 0)
{
int32_t pid = -1;
if (kernel_copyout_fast(cur + KERNEL_OFFSET_PROC_P_PID,
&pid, sizeof(pid)) != 0)
return -1;
return (int)pid;
}
if (ps5debug_syscall(202 /*sysctl*/, (long)(intptr_t)mib, 4L /*namelen*/,
(long)(intptr_t)kinfo,
(long)(intptr_t)&outlen, 0L, 0L) != 0)
continue;
intptr_t next = 0;
if (kernel_copyout_fast(cur + PROC_NEXT_OFFSET,
&next, sizeof(next)) != 0)
return -1;
cur = next;
char comm[0x14];
memcpy(comm, &kinfo[0x1BF], 0x13);
comm[0x13] = 0;
if (strcmp(comm, name) == 0)
return pid;
}
return -1;
}
+142 -18
View File
@@ -9,6 +9,7 @@
#include <ps5/kernel.h>
#include "sdk_shim.h"
#include "kern_rw_fast.h"
#include "write_policy.h"
static const uint64_t kr_write_setup_const[2] = { 0x0ULL, 0x4000000000000000ULL };
static const uint64_t kr_read_setup_const [2] = { 0x4000000040000000ULL, 0x4000000000000000ULL };
@@ -34,58 +35,181 @@ static __attribute__((noinline)) int kr_lazy_init(void) {
typedef unsigned __int128 u128_alias __attribute__((may_alias));
__attribute__((target("no-avx")))
int32_t kernel_copyin_fast(const void *udaddr, intptr_t kaddr, size_t len)
static int kr_setup_field(int fd, void *field, const char *direction,
const char *step, intptr_t kaddr, size_t len,
int diagnostic)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
errno = 0;
long rc = __crt_syscall(0x69, fd, 0x29, 0x2e, field, 0x14);
int saved_errno = errno;
if (diagnostic || rc != 0) {
klog_printf("[write:pipe] dir=%s kaddr=0x%llx len=%llu "
"step=%s rc=%ld errno=%d\n",
direction, (unsigned long long)kaddr,
(unsigned long long)len, step, rc, saved_errno);
}
return rc == 0 ? 0 : -1;
}
__attribute__((target("no-avx")))
static int32_t kernel_copyin_fast_inner(const void *udaddr,
intptr_t kaddr, size_t len)
{
uint64_t f[7];
int diagnostic = ps5debug_write_diagnostics_enabled();
*(u128_alias *)&f[0] = *(const u128_alias *)kr_write_setup_const;
f[4] = kr_kpipe_addr;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "in", "flags-address",
kaddr, len, diagnostic) != 0)
return -1;
f[2] = 0;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "in", "flags-value",
kaddr, len, diagnostic) != 0)
return -1;
f[0] = (uint64_t)kaddr;
*(u128_alias *)&f[1] = 0;
f[4] = kr_kpipe_addr + 0x10;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "in", "data-address",
kaddr, len, diagnostic) != 0)
return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "in", "data-value",
kaddr, len, diagnostic) != 0)
return -1;
__crt_syscall(4, kr_rwpipe_1, (uint64_t)udaddr, (uint64_t)len);
return 0;
errno = 0;
long rc = __crt_syscall(SYS_write, kr_rwpipe_1,
(uint64_t)udaddr, (uint64_t)len);
int saved_errno = errno;
if (diagnostic || rc != (long)len) {
klog_printf("[write:pipe] dir=in kaddr=0x%llx len=%llu "
"step=transfer rc=%ld errno=%d\n",
(unsigned long long)kaddr, (unsigned long long)len,
rc, saved_errno);
}
return rc == (long)len ? 0 : -1;
}
__attribute__((target("no-avx")))
int32_t kernel_copyout_fast(intptr_t kaddr, void *udaddr, size_t len)
static int32_t kernel_copyout_fast_inner(intptr_t kaddr,
void *udaddr, size_t len)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
uint64_t f[7];
int diagnostic = 0;
*(u128_alias *)&f[0] = *(const u128_alias *)kr_read_setup_const;
f[4] = kr_kpipe_addr;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "out", "flags-address",
kaddr, len, diagnostic) != 0)
return -1;
f[2] = 0;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "out", "flags-value",
kaddr, len, diagnostic) != 0)
return -1;
f[0] = (uint64_t)kaddr;
*(u128_alias *)&f[1] = 0;
f[4] = kr_kpipe_addr + 0x10;
*(u128_alias *)&f[5] = 0;
if (__crt_syscall(0x69, kr_rwpair_0, 0x29, 0x2e, &f[4], 0x14) != 0) return -1;
if (__crt_syscall(0x69, kr_rwpair_1, 0x29, 0x2e, &f[0], 0x14) != 0) return -1;
if (kr_setup_field(kr_rwpair_0, &f[4], "out", "data-address",
kaddr, len, diagnostic) != 0)
return -1;
if (kr_setup_field(kr_rwpair_1, &f[0], "out", "data-value",
kaddr, len, diagnostic) != 0)
return -1;
__crt_syscall(3, kr_rwpipe_0, udaddr, (uint64_t)len);
return 0;
errno = 0;
long rc = __crt_syscall(SYS_read, kr_rwpipe_0,
udaddr, (uint64_t)len);
int saved_errno = errno;
if (diagnostic || rc != (long)len) {
klog_printf("[write:pipe] dir=out kaddr=0x%llx len=%llu "
"step=transfer rc=%ld errno=%d\n",
(unsigned long long)kaddr, (unsigned long long)len,
rc, saved_errno);
}
return rc == (long)len ? 0 : -1;
}
static int32_t kernel_copyin_fast_normalized(const void *udaddr,
intptr_t kaddr, size_t len)
{
if (len == 0
|| !ps5debug_write_needs_prefix((uint64_t)kaddr))
return kernel_copyin_fast_inner(udaddr, kaddr, len);
if (!udaddr || len == SIZE_MAX) return -1;
uint8_t *prefixed = (uint8_t *)malloc(len + 1);
if (!prefixed) return -1;
int32_t rc = kernel_copyout_fast_inner(kaddr - 1, prefixed, 1);
if (rc == 0) {
uint8_t leading = prefixed[0];
memcpy(prefixed + 1, udaddr, len);
if (ps5debug_write_diagnostics_enabled()) {
klog_printf("[write:pipe] dir=in kaddr=0x%llx len=%llu "
"step=prefix-normalize shifted=0x%llx shifted-len=%llu\n",
(unsigned long long)kaddr, (unsigned long long)len,
(unsigned long long)(kaddr - 1),
(unsigned long long)(len + 1));
}
rc = kernel_copyin_fast_inner(prefixed, kaddr - 1, len + 1);
uint8_t observed = leading ^ 0xFFu;
if (rc == 0
&& (kernel_copyout_fast_inner(kaddr - 1, &observed, 1) != 0
|| observed != leading)) {
klog_printf("[write:pipe] dir=in kaddr=0x%llx len=%llu "
"prefix-preserve failed expected=%02x actual=%02x\n",
(unsigned long long)kaddr, (unsigned long long)len,
leading, observed);
rc = -1;
}
}
free(prefixed);
return rc;
}
static int32_t kernel_copyout_fast_normalized(intptr_t kaddr,
void *udaddr, size_t len)
{
if (len == 0
|| !ps5debug_write_needs_prefix((uint64_t)kaddr))
return kernel_copyout_fast_inner(kaddr, udaddr, len);
if (!udaddr || len == SIZE_MAX) return -1;
uint8_t *prefixed = (uint8_t *)malloc(len + 1);
if (!prefixed) return -1;
if (ps5debug_write_diagnostics_enabled()) {
klog_printf("[write:pipe] dir=out kaddr=0x%llx len=%llu "
"step=prefix-normalize shifted=0x%llx shifted-len=%llu\n",
(unsigned long long)kaddr, (unsigned long long)len,
(unsigned long long)(kaddr - 1),
(unsigned long long)(len + 1));
}
int32_t rc =
kernel_copyout_fast_inner(kaddr - 1, prefixed, len + 1);
if (rc == 0) memcpy(udaddr, prefixed + 1, len);
free(prefixed);
return rc;
}
int32_t kernel_copyin_fast(const void *udaddr, intptr_t kaddr, size_t len)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
return kernel_copyin_fast_normalized(udaddr, kaddr, len);
}
int32_t kernel_copyout_fast(intptr_t kaddr, void *udaddr, size_t len)
{
if (!kr_initialized && kr_lazy_init() != 0) return -1;
return kernel_copyout_fast_normalized(kaddr, udaddr, len);
}
intptr_t kernel_get_proc_fast(pid_t pid)
+238 -203
View File
@@ -1,203 +1,238 @@
// SPDX-License-Identifier: GPL-3.0-only
#include <stdint.h>
#include "sdk_shim.h"
extern void port_outer_init_mutexes(void);
extern int find_proc_pid_by_name(const char *name);
extern int sys_proc_call_remote_func(int pid, void *elf_buf,
uint64_t a3_unused, void *thread_name);
extern const uint8_t embedded_inner_start[];
extern const uint8_t embedded_inner_end[];
extern const uint64_t embedded_inner_size;
#define GUARD_SYS_close 6
#define GUARD_SYS_socket 97
#define GUARD_SYS_connect 98
#define PS5DEBUG_PORT 744
struct guard_sockaddr_in {
uint8_t sin_len;
uint8_t sin_family;
uint16_t sin_port;
uint32_t sin_addr;
uint8_t sin_zero[8];
};
static void guard_notify(const char *msg)
{
typedef struct { char useless1[45]; char message[3075]; } req_t;
req_t req;
memset(&req, 0, sizeof(req));
strncpy(req.message, msg, sizeof(req.message) - 1);
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
}
static int ps5debug_already_running(void)
{
long fd = ps5debug_syscall(GUARD_SYS_socket, 2 , 1 ,
0, 0, 0, 0);
if (fd < 0) return 0;
struct guard_sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_len = 0x10;
sa.sin_family = 2;
sa.sin_port = (uint16_t)((PS5DEBUG_PORT << 8) | (PS5DEBUG_PORT >> 8));
sa.sin_addr = 0x0100007Fu;
long rc = ps5debug_syscall(GUARD_SYS_connect, fd, (long)&sa, 0x10, 0, 0, 0);
ps5debug_syscall(GUARD_SYS_close, fd, 0, 0, 0, 0, 0);
return (rc == 0);
}
static int unlock_target_syscall_filter(int pid)
{
intptr_t kproc = kernel_get_proc((pid_t)pid);
if (!kproc) return -1;
intptr_t filter_addr = 0;
if (kernel_copyout(kproc + 0x3e8, &filter_addr, sizeof(filter_addr)) < 0)
return -1;
if (!filter_addr) return -1;
unsigned long uaddr;
uaddr = 0UL;
if (kernel_copyin(&uaddr, filter_addr + 0xf0, sizeof(uaddr)) < 0) return -1;
uaddr = (unsigned long)-1L;
if (kernel_copyin(&uaddr, filter_addr + 0xf8, sizeof(uaddr)) < 0) return -1;
return 0;
}
static int install_kernel_patch(void)
{
intptr_t kbase = (intptr_t)KERNEL_ADDRESS_DATA_BASE;
uint32_t raw = kernel_get_fw_version();
uint32_t fw = raw & 0xffff0000u;
klog_printf("port_outer: kpatch FW raw=0x%x kbase=0x%lx\n",
raw, (unsigned long)kbase);
intptr_t patch_addr;
const char *fw_label;
switch (fw) {
case 0x3000000u: case 0x3100000u: case 0x3200000u: case 0x3210000u: /* 3.00 3.10 3.20 3.21 */
patch_addr = kbase + 0x6466498ULL;
fw_label = "FW 3.x";
break;
case 0x4020000u: /* 4.02 */
patch_addr = kbase + 0x6505498ULL;
fw_label = "FW 4.0";
break;
case 0x4000000u: case 0x4030000u: case 0x4500000u: case 0x4510000u: /* 4.00 4.03 4.50 4.51 */
patch_addr = kbase + 0x6506498ULL;
fw_label = "FW 4.x (incl. 4.51)";
break;
case 0x5000000u: case 0x5020000u: case 0x5100000u: case 0x5500000u: /* 5.00 5.02 5.10 5.50 */
patch_addr = kbase + 0x6646710ULL;
fw_label = "FW 5.x";
break;
case 0x6000000u: case 0x6020000u: case 0x6500000u: /* 6.00 6.02 6.50 */
patch_addr = kbase + 0x6596910ULL;
fw_label = "FW 6.x";
break;
case 0x7000000u: case 0x7010000u: /* 7.00 7.01 */
patch_addr = kbase + 0xAC8088ULL;
fw_label = "FW 7.x";
break;
case 0x7200000u: case 0x7400000u: case 0x7600000u: case 0x7610000u: /* 7.20 7.40 7.60 7.61 */
patch_addr = kbase + 0xAC8088ULL;
fw_label = "FW 7.5x";
break;
case 0x8000000u: case 0x8200000u: case 0x8400000u: case 0x8600000u: /* 8.00 8.20 8.40 8.60 */
patch_addr = kbase + 0xAC3088ULL;
fw_label = "FW 8.x";
break;
default:
klog_printf("port_outer: kpatch SKIP - unsupported FW magic 0x%x\n", fw);
return -1;
}
klog_printf("port_outer: kpatch %s recognized; addr=0x%lx\n",
fw_label, (unsigned long)patch_addr);
uint8_t scratch[16];
if (kernel_copyout(patch_addr, scratch, 16) < 0) {
klog_puts("port_outer: kpatch READ failed\n");
return -1;
}
klog_printf("port_outer: kpatch read byte[1]=0x%02x (will OR with 3)\n",
scratch[1]);
scratch[1] |= 3;
if (kernel_copyin(scratch, patch_addr, 16) < 0) {
klog_puts("port_outer: kpatch WRITE failed\n");
return -1;
}
klog_printf("port_outer: kpatch WRITE OK; byte[1] now 0x%02x\n", scratch[1]);
uint8_t verify[16];
if (kernel_copyout(patch_addr, verify, 16) < 0) {
klog_puts("port_outer: kpatch verify read failed\n");
return -1;
}
klog_printf("port_outer: kpatch verify byte[1]=0x%02x stuck=%s\n",
verify[1], ((verify[1] & 3) == 3) ? "YES" : "NO");
return ((verify[1] & 3) == 3) ? 0 : -1;
}
int main(int argc, char *argv[])
{
(void)argc; (void)argv;
port_outer_init_mutexes();
if (ps5debug_already_running()) {
klog_puts("port_outer: ps5debug already running on port 744 - install aborted\n");
guard_notify("ps5debug-NG is already running - injection skipped");
payload_exit(0);
return 0;
}
klog_printf("port_outer: embedded inner ELF = %lu B\n",
(unsigned long)embedded_inner_size);
int pid = find_proc_pid_by_name("SceShellCore");
if (pid < 0) {
klog_puts("port_outer: SceShellCore not found in allproc walk\n");
payload_exit(1);
return 1;
}
klog_printf("port_outer: SceShellCore pid = %d\n", pid);
if (install_kernel_patch() == 0) {
klog_puts("port_outer: kernel patch installed\n");
} else {
klog_puts("port_outer: WARNING - kernel patch failed; PT_ATTACH on games will not work\n");
}
if (unlock_target_syscall_filter(pid) == 0) {
klog_puts("port_outer: SceShellCore syscall_filter unlocked\n");
} else {
klog_puts("port_outer: WARNING - syscall_filter unlock failed\n");
}
long rc = sys_proc_call_remote_func(pid,
(void *)embedded_inner_start,
0,
(void *)"libSceShareInternal.native.sprx");
klog_printf("port_outer: sys_proc_call_remote_func rc = %ld\n", rc);
if (rc != 0) {
klog_puts("port_outer: inject FAILED\n");
payload_exit(2);
return 2;
}
klog_puts("port_outer: inject OK - SceShellCore now hosts inner\n");
payload_exit(0);
return 0;
}
// SPDX-License-Identifier: GPL-3.0-only
#include <stdint.h>
#include "sdk_shim.h"
extern void port_outer_init_mutexes(void);
extern int find_proc_pid_by_name(const char *name);
extern int sys_proc_call_remote_func(int pid, void *elf_buf,
uint64_t a3_unused, void *thread_name);
extern const uint8_t embedded_inner_start[];
extern const uint8_t embedded_inner_end[];
extern const uint64_t embedded_inner_size;
#define GUARD_SYS_close 6
#define GUARD_SYS_socket 97
#define GUARD_SYS_connect 98
#define PS5DEBUG_PORT 744
struct guard_sockaddr_in {
uint8_t sin_len;
uint8_t sin_family;
uint16_t sin_port;
uint32_t sin_addr;
uint8_t sin_zero[8];
};
static void guard_notify(const char *msg)
{
typedef struct { char useless1[45]; char message[3075]; } req_t;
req_t req;
memset(&req, 0, sizeof(req));
strncpy(req.message, msg, sizeof(req.message) - 1);
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
}
static int ps5debug_already_running(void)
{
long fd = ps5debug_syscall(GUARD_SYS_socket, 2 , 1 ,
0, 0, 0, 0);
if (fd < 0) return 0;
struct guard_sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_len = 0x10;
sa.sin_family = 2;
sa.sin_port = (uint16_t)((PS5DEBUG_PORT << 8) | (PS5DEBUG_PORT >> 8));
sa.sin_addr = 0x0100007Fu;
long rc = ps5debug_syscall(GUARD_SYS_connect, fd, (long)&sa, 0x10, 0, 0, 0);
ps5debug_syscall(GUARD_SYS_close, fd, 0, 0, 0, 0, 0);
return (rc == 0);
}
static int unlock_target_syscall_filter(int pid)
{
intptr_t kproc = kernel_get_proc((pid_t)pid);
if (!kproc) return -1;
intptr_t filter_addr = 0;
if (kernel_copyout(kproc + 0x3e8, &filter_addr, sizeof(filter_addr)) < 0)
return -1;
if (!filter_addr) return -1;
unsigned long uaddr;
uaddr = 0UL;
if (kernel_copyin(&uaddr, filter_addr + 0xf0, sizeof(uaddr)) < 0) return -1;
uaddr = (unsigned long)-1L;
if (kernel_copyin(&uaddr, filter_addr + 0xf8, sizeof(uaddr)) < 0) return -1;
return 0;
}
static int install_kernel_patch(void)
{
intptr_t kbase = (intptr_t)KERNEL_ADDRESS_DATA_BASE;
uint32_t raw = kernel_get_fw_version();
uint32_t fw = raw & 0xffff0000u;
klog_printf("port_outer: kpatch FW raw=0x%x kbase=0x%lx\n",
raw, (unsigned long)kbase);
intptr_t patch_addr;
const char *fw_label;
switch (fw) {
case 0x3000000u: case 0x3100000u: case 0x3200000u: case 0x3210000u: /* 3.00 3.10 3.20 3.21 */
patch_addr = kbase + 0x6466498ULL;
fw_label = "FW 3.x";
break;
case 0x4020000u: /* 4.02 */
patch_addr = kbase + 0x6505498ULL;
fw_label = "FW 4.0";
break;
case 0x4000000u: case 0x4030000u: case 0x4500000u: case 0x4510000u: /* 4.00 4.03 4.50 4.51 */
patch_addr = kbase + 0x6506498ULL;
fw_label = "FW 4.x (incl. 4.51)";
break;
case 0x5000000u: case 0x5020000u: case 0x5100000u: case 0x5500000u: /* 5.00 5.02 5.10 5.50 */
patch_addr = kbase + 0x6646710ULL;
fw_label = "FW 5.x";
break;
case 0x6000000u: case 0x6020000u: case 0x6500000u: /* 6.00 6.02 6.50 */
patch_addr = kbase + 0x6596910ULL;
fw_label = "FW 6.x";
break;
case 0x7000000u: case 0x7010000u: case 0x7010100u: case 0x7200000u:
case 0x7400000u: case 0x7600000u: case 0x7610000u: /* 7.00 7.01 7.01.01 7.20 7.40 7.60 7.61*/
patch_addr = kbase + 0xAC8088ULL;
fw_label = "FW 7.x";
break;
case 0x8000000u: case 0x8200000u: case 0x8400000u: case 0x8600000u: /* 8.00 8.20 8.40 8.60 */
patch_addr = kbase + 0xAC3088ULL;
fw_label = "FW 8.x";
break;
case 0x9000000u: /* 9.00 */
patch_addr = kbase + 0xD72088ULL;
fw_label = "FW 9.00";
break;
case 0x9050000u: case 0x9200000u:
case 0x9400000u: case 0x9600000u: /* 9.05 9.20 9.40 9.60 */
patch_addr = kbase + 0xD73088ULL;
fw_label = "FW 9.x";
break;
case 0x10000000u: case 0x10010000u: case 0x10200000u:
case 0x10400000u: case 0x10600000u: /* 10.00 10.01 10.20 10.40 10.60 */
patch_addr = kbase + 0xD79088ULL;
fw_label = "FW 10.x";
break;
case 0x11000000u: case 0x11200000u:
case 0x11400000u: case 0x11600000u: /* 11.00 11.20 11.40 11.60 */
patch_addr = kbase + 0xD8C088ULL;
fw_label = "FW 11.x";
break;
case 0x12000000u: case 0x12020000u: case 0x12200000u:
case 0x12400000u: case 0x12600000u: case 0x12700000u: /* 12.00 12.02 12.20 12.40 12.60 12.70 */
patch_addr = kbase + 0xD83088ULL;
fw_label = "FW 12.x";
break;
case 0x13000000u: case 0x13200000u:
case 0x13400000u: case 0x13420000u: /* 13.00 13.20 13.40 13.42 */
patch_addr = kbase + 0xD99088ULL;
fw_label = "FW 13.0x-13.4x";
break;
case 0x13600000u: /* 13.60 */
patch_addr = kbase + 0xD9C088ULL;
fw_label = "FW 13.60";
break;
default:
klog_printf("port_outer: kpatch SKIP - unsupported FW magic 0x%x\n", fw);
return -1;
}
klog_printf("port_outer: kpatch %s recognized; addr=0x%lx\n",
fw_label, (unsigned long)patch_addr);
uint8_t scratch[16];
if (kernel_copyout(patch_addr, scratch, 16) < 0) {
klog_puts("port_outer: kpatch READ failed\n");
return -1;
}
klog_printf("port_outer: kpatch read byte[1]=0x%02x (will OR with 3)\n",
scratch[1]);
if ((scratch[1] & 3) == 3) {
klog_puts("port_outer: kpatch already set by jailbreak\n");
return 0;
}
scratch[1] |= 3;
if (kernel_copyin(scratch, patch_addr, 16) < 0) {
klog_puts("port_outer: kpatch WRITE failed\n");
return -1;
}
klog_printf("port_outer: kpatch WRITE OK; byte[1] now 0x%02x\n", scratch[1]);
uint8_t verify[16];
if (kernel_copyout(patch_addr, verify, 16) < 0) {
klog_puts("port_outer: kpatch verify read failed\n");
return -1;
}
klog_printf("port_outer: kpatch verify byte[1]=0x%02x stuck=%s\n",
verify[1], ((verify[1] & 3) == 3) ? "YES" : "NO");
return ((verify[1] & 3) == 3) ? 0 : -1;
}
int main(int argc, char *argv[])
{
(void)argc; (void)argv;
port_outer_init_mutexes();
if (ps5debug_already_running()) {
klog_puts("port_outer: ps5debug already running on port 744 - install aborted\n");
guard_notify("ps5debug-NG is already running - injection skipped");
payload_exit(0);
return 0;
}
klog_printf("port_outer: embedded inner ELF = %lu B\n",
(unsigned long)embedded_inner_size);
int pid = find_proc_pid_by_name("SceShellCore");
if (pid < 0) {
klog_puts("port_outer: SceShellCore not found in allproc walk\n");
payload_exit(1);
return 1;
}
klog_printf("port_outer: SceShellCore pid = %d\n", pid);
if (install_kernel_patch() == 0) {
klog_puts("port_outer: kernel patch installed\n");
} else {
klog_puts("port_outer: WARNING - kernel patch failed; PT_ATTACH on games will not work\n");
}
if (unlock_target_syscall_filter(pid) == 0) {
klog_puts("port_outer: SceShellCore syscall_filter unlocked\n");
} else {
klog_puts("port_outer: WARNING - syscall_filter unlock failed\n");
}
long rc = sys_proc_call_remote_func(pid,
(void *)embedded_inner_start,
0,
(void *)"libSceShareInternal.native.sprx");
klog_printf("port_outer: sys_proc_call_remote_func rc = %ld\n", rc);
if (rc != 0) {
klog_puts("port_outer: inject FAILED\n");
payload_exit(2);
return 2;
}
klog_puts("port_outer: inject OK - SceShellCore now hosts inner\n");
payload_exit(0);
return 0;
}
+2 -10
View File
@@ -255,14 +255,7 @@ static int proc_resolve_call_nid_Jp7F(int pid, void *scratch)
static void compute_fw_vmmap_adjustments(uint64_t *out_nentries_adj,
uint64_t *out_name_adj) {
uint32_t fw = kernel_get_fw_version();
int write_adj = 0;
switch (fw & 0xffff0000u) {
case 0x6000000u: case 0x6020000u: case 0x6500000u: /* 6.00 6.02 6.50 */
case 0x7000000u: case 0x7010000u: /* 7.00 7.01 */
case 0x8000000u: case 0x8200000u: case 0x8400000u: case 0x8600000u: /* 8.00 8.20 8.40 8.60 */
write_adj = 1; break;
default: write_adj = 0; break;
}
int write_adj = ((fw & 0xffff0000u) >= 0x6000000u) ? 1 : 0;
*out_nentries_adj = write_adj ? 8 : 0;
*out_name_adj = write_adj ? 0xE : 0;
}
@@ -391,7 +384,7 @@ static int kern_init_dbgctx_at_addr_kern(uint32_t pid, uint64_t addr)
static uint64_t proc_call_remote_sce(int pid)
{
void *mmap_rv = freebsd_mmap((unsigned long)pid, (void *)0, 0x4000, 3,
void *mmap_rv = freebsd_mmap((unsigned long)pid, (void *)0, 0x4000, 7,
0x1002, -1, 0);
if ((long)mmap_rv == -1L) return 0;
uint64_t scratch = (uint64_t)mmap_rv;
@@ -716,7 +709,6 @@ int sys_proc_call_remote_func(int pid, void *elf_buf, uint64_t a3_unused,
long loaded = proc_load_elf_inject((uint32_t)pid, (char *)elf_buf,
(const char *)thread_name);
if (loaded <= 0) {
kernel_set_ucred_caps_fast(pid, saved_caps);
pt_detach(pid);
return -1;
+3 -1
View File
@@ -40,7 +40,9 @@ static void debugger_usleep(unsigned long us) { sceKernelUsleep((unsigned int)us
extern long __crt_syscall(long sysno, ...);
long ptrace_raw(int op, int pid, void *addr, int data) {
return __crt_syscall(26, op, pid, addr, data);
// ps5debug_syscall promotes op/pid/data (int) + addr (pointer) to long at the
// typed parameter boundary -> no undefined upper bits. Same syscall, same args.
return ps5debug_syscall(26 /*ptrace*/, (long)op, (long)pid, (long)addr, (long)data, 0L, 0L);
}
#if 0
+54 -1
View File
@@ -66,6 +66,7 @@ const unsigned long KERNEL_OFFSET_UCRED_CR_RUID = 0x08;
const unsigned long KERNEL_OFFSET_UCRED_CR_SVUID = 0x0C;
const unsigned long KERNEL_OFFSET_UCRED_CR_RGID = 0x14;
const unsigned long KERNEL_OFFSET_UCRED_CR_SVGID = 0x18;
const unsigned long KERNEL_OFFSET_UCRED_CR_PRISON = 0x30;
const unsigned long KERNEL_OFFSET_UCRED_CR_SCEAUTHID = 0x58;
const unsigned long KERNEL_OFFSET_UCRED_CR_SCECAPS = 0x60;
@@ -157,6 +158,8 @@ kernel_get_fw_version(void) {
}
unsigned long kernel_get_ucred_prison(int pid);
int
__kernel_init(payload_args_t* args) {
if((rw_pair[0]=args->rwpair[0]) < 0) {
@@ -395,7 +398,39 @@ __kernel_init(payload_args_t* args) {
KERNEL_ADDRESS_BUS_DATA_DEVICES = KERNEL_ADDRESS_DATA_BASE + 0x20757E8;
KERNEL_OFFSET_VMSPACE_P_ROOT = 0x1d0;
break;
case 0x13000000:
case 0x13200000:
KERNEL_ADDRESS_TEXT_BASE = KERNEL_ADDRESS_DATA_BASE - 0x0CB0000;
KERNEL_ADDRESS_ALLPROC = KERNEL_ADDRESS_DATA_BASE + 0x28C5E00;
KERNEL_ADDRESS_SECURITY_FLAGS = KERNEL_ADDRESS_DATA_BASE + 0x0D99064;
KERNEL_ADDRESS_ROOTVNODE = KERNEL_ADDRESS_DATA_BASE + 0x3133510;
KERNEL_ADDRESS_BUS_DATA_DEVICES = KERNEL_ADDRESS_DATA_BASE + 0x20981E8;
KERNEL_OFFSET_VMSPACE_P_ROOT = 0x1d0;
KERNEL_ADDRESS_PRISON0 = kernel_get_ucred_prison(0);
break;
case 0x13400000:
case 0x13420000:
KERNEL_ADDRESS_TEXT_BASE = KERNEL_ADDRESS_DATA_BASE - 0x0CB0000;
KERNEL_ADDRESS_ALLPROC = KERNEL_ADDRESS_DATA_BASE + 0x28C9E80;
KERNEL_ADDRESS_SECURITY_FLAGS = KERNEL_ADDRESS_DATA_BASE + 0x0D99064;
KERNEL_ADDRESS_ROOTVNODE = KERNEL_ADDRESS_DATA_BASE + 0x3137510;
KERNEL_ADDRESS_BUS_DATA_DEVICES = KERNEL_ADDRESS_DATA_BASE + 0x20981E8;
KERNEL_OFFSET_VMSPACE_P_ROOT = 0x1d0;
KERNEL_ADDRESS_PRISON0 = kernel_get_ucred_prison(0);
break;
case 0x13600000:
KERNEL_ADDRESS_TEXT_BASE = KERNEL_ADDRESS_DATA_BASE - 0x0CC0000;
KERNEL_ADDRESS_ALLPROC = KERNEL_ADDRESS_DATA_BASE + 0x28C9E80;
KERNEL_ADDRESS_SECURITY_FLAGS = KERNEL_ADDRESS_DATA_BASE + 0x0D9C064;
KERNEL_ADDRESS_ROOTVNODE = KERNEL_ADDRESS_DATA_BASE + 0x314B510;
KERNEL_ADDRESS_BUS_DATA_DEVICES = KERNEL_ADDRESS_DATA_BASE + 0x2098228;
KERNEL_OFFSET_VMSPACE_P_ROOT = 0x1d0;
KERNEL_ADDRESS_PRISON0 = kernel_get_ucred_prison(0);
break;
default:
return -ENOSYS;
}
@@ -978,6 +1013,24 @@ kernel_get_proc_ucred(int pid) {
}
unsigned long
kernel_get_ucred_prison(int pid) {
unsigned long ucred = 0;
unsigned long prison = 0;
if(!(ucred=kernel_get_proc_ucred(pid))) {
return 0;
}
if(kernel_copyout(ucred + KERNEL_OFFSET_UCRED_CR_PRISON, &prison,
sizeof(prison))) {
return 0;
}
return prison;
}
unsigned long
kernel_get_ucred_authid(int pid) {
unsigned long authid = 0;