mirror of
https://github.com/Pharaoh2k/ps5debug-NG.git
synced 2026-10-20 02:49:58 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
938d1a7350 | ||
|
|
3d14b04755 | ||
|
|
fc9aef98f3 | ||
|
|
bdc1ba7780 | ||
|
|
2e9dff1442 | ||
|
|
6badda5248 | ||
|
|
23f1471e62 | ||
|
|
10c42774f1 | ||
|
|
951e54a3c4 | ||
|
|
5fc368f6fd | ||
|
|
4bb24e4844 | ||
|
|
7f667b7775 | ||
|
|
23b27a7780 | ||
|
|
2fc77a2e25 | ||
|
|
1f6db9df56 | ||
|
|
8ebdbf9ab8 | ||
|
|
1ce0bdf627 | ||
|
|
d32d2d001d | ||
|
|
1a18f94eaf | ||
|
|
43f715975d | ||
|
|
5fbdd894ee | ||
|
|
712fe0466b | ||
|
|
fedbd4f6d7 | ||
|
|
f8456241fc | ||
|
|
14fc8c16b5 | ||
|
|
81b8e07fd9 | ||
|
|
72eb2104c7 | ||
|
|
be05448527 | ||
|
|
19f0ce87ff | ||
|
|
4c3c86913a | ||
|
|
36b858471f | ||
|
|
8a81f37f9f | ||
|
|
89d08fe135 | ||
|
|
6b3cc3a216 |
No files matched your search
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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 "]"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+847
-579
File diff suppressed because it is too large.
Load diff
+170
-83
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user