Files
ps5debug-NG/debugger/source/console.c
T
OpenSourcereR-dev 72eb2104c7 v1.2.6: FW 8-13.x poke/breakpoints/watchpoints + FW-agnostic title/content/version + SIGPIPE crash fix
Makes the "initial" 8-13.x support from v1.2.5 actually functional.
HW-validated on FW 8.xx, 9.xx, 10.xx and 12.xx (all watchpoint types,
breakpoints, title id, content id, version etc.). FW-agnostic by design; 11/13.x
are lower-risk but not tested.

Memory write (poke) on FW >= 8.40:
- mdbg write op (0x13) is kernel-gated with EPERM on FW >= 8.40, so pokes
  failed. Route writes through a DMAP page-table walk (kernel_copyin on the
  physical alias) on FW >= 8.40; mdbg stays the path on < 8.40 and as a
  fallback.
- PROC_ALLOC pre-faults its pages so writes land on freshly-allocated
  demand-zero memory.

Breakpoints / watchpoints on FW 9.x-13.x:
- Extend the kernel-dbreg allowlist to 9.00-13.20; HW BP/WP were silently
  no-oping on 9.x+.
- SW (INT3) breakpoints write the 0xCC via the DMAP path (mdbg rejected it
  on FW >= 8.40).
- Clear DR6 after each debug exception: FW 11/12 stops re-delivering a
  HW BP/WP trap after a few hits when the sticky DR6 status is left set;
  plus re-arm the debug regs for data watchpoints.

Game ID / Content ID / Version - firmware-agnostic:
- Resolve title id and content id by pattern-scanning the proc struct
  instead of per-FW offsets (which shifted at 8.x and 12.x).
- Foreground-app command resolves the title id robustly so the param.sfo
  game version reads correctly on 12.x.

Console crash on client disconnect (SIGPIPE):
- Closing the client mid-operation raised SIGPIPE in the server thread and
  killed the host SceShellCore. Suppress it: MSG_NOSIGNAL on sends +
  SO_NOSIGPIPE on the sockets.

Load notification now shows the running firmware version, and the credits
line was updated.

Documentation:
- Rewrote PROTOCOL.md against the v1.2.6 sources (commands, packet structs,
  status/bit-swap semantics, scan/xref wire formats, enums).
- Corrected and expanded README: per-release firmware support table with
  verification status, and accuracy fixes throughout.

Bump version 1.2.5 to 1.2.6.
2026-06-03 07:20:46 +00:00

424 lines
13 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
#include "protocol.h"
#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_SELFINFO_NAME_SIZE 32
struct cmd_console_notify_packet {
uint32_t messageType;
uint32_t length;
} __attribute__((packed));
struct cmd_console_print_packet {
uint32_t length;
} __attribute__((packed));
typedef struct notify_request {
char useless1[45];
char message[3075];
} notify_request_t;
int console_reboot_handle(int fd, struct cmd_packet *packet) {
(void)fd; (void)packet;
sceKernelReboot();
return 0;
}
int console_end_handle(int fd, struct cmd_packet *packet) {
(void)packet;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int console_print_handle(int fd, struct cmd_packet *packet) {
struct cmd_console_print_packet *pp = (struct cmd_console_print_packet *)packet->data;
if (!pp) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
void *data = net_alloc_buffer(pp->length);
if (!data) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
memset(data, 0, pp->length);
net_recv_all(fd, data, (int)pp->length, 1);
klog_puts((const char *)data);
net_send_int32(fd, CMD_SUCCESS);
free(data);
return 0;
}
int console_notify_handle(int fd, struct cmd_packet *packet) {
struct cmd_console_notify_packet *np = (struct cmd_console_notify_packet *)packet->data;
if (!np) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
void *data = net_alloc_buffer(np->length);
if (!data) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
memset(data, 0, np->length);
net_recv_all(fd, data, (int)np->length, 1);
notify_request_t req;
memset(&req, 0, sizeof(req));
uint32_t copy = np->length;
if (copy >= sizeof(req.message)) copy = sizeof(req.message) - 1;
memcpy(req.message, data, copy);
req.message[copy] = 0;
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
net_send_int32(fd, CMD_SUCCESS);
free(data);
return 0;
}
int console_info_handle(int fd, struct cmd_packet *packet) {
(void)packet;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
static int sfo_read_file(const char *path, uint8_t *buf, size_t bufsz, size_t *out_len) {
int fdf = open(path, 0, 0);
if (fdf < 0) return -1;
size_t total = 0;
while (total < bufsz) {
long n = read(fdf, buf + total, bufsz - total);
if (n <= 0) break;
total += (size_t)n;
}
close(fdf);
if (out_len) *out_len = total;
return 0;
}
static int sfo_get_string(const uint8_t *sfo, size_t sfo_len,
const char *want_key, char *out, size_t out_max) {
if (out_max == 0) return -1;
out[0] = '\0';
if (sfo_len < 20) return -1;
if (sfo[0] != 0 || sfo[1] != 'P' || sfo[2] != 'S' || sfo[3] != 'F') return -1;
uint32_t key_table = *(const uint32_t *)(sfo + 8);
uint32_t data_table = *(const uint32_t *)(sfo + 12);
uint32_t nentries = *(const uint32_t *)(sfo + 16);
if (key_table > sfo_len) return -1;
if (data_table > sfo_len) return -1;
size_t want_len = strlen(want_key);
for (uint32_t i = 0; i < nentries; i++) {
size_t eoff = 20 + (size_t)i * 16;
if (eoff + 16 > sfo_len) return -1;
const uint8_t *e = sfo + eoff;
uint16_t key_off = *(const uint16_t *)(e + 0);
uint32_t param_len = *(const uint32_t *)(e + 4);
uint32_t data_off = *(const uint32_t *)(e + 12);
size_t key_abs = (size_t)key_table + key_off;
if (key_abs + want_len + 1 > sfo_len) continue;
if (memcmp(sfo + key_abs, want_key, want_len) != 0) continue;
if (sfo[key_abs + want_len] != '\0') continue;
size_t data_abs = (size_t)data_table + data_off;
if (data_abs + param_len > sfo_len) return -1;
size_t copy = param_len;
if (copy >= out_max) copy = out_max - 1;
memcpy(out, sfo + data_abs, copy);
out[copy] = '\0';
while (copy > 0 && out[copy - 1] == '\0') copy--;
out[copy] = '\0';
return 0;
}
return -2;
}
struct cmd_console_foreground_app_response {
uint32_t pid;
char titleid[16];
char contentid[64];
char name[40];
char app_ver[16];
} __attribute__((packed));
static int json_get_string(const uint8_t *json, size_t json_len,
const char *key, char *out, size_t out_max) {
if (out_max == 0) return -1;
out[0] = '\0';
char needle[64];
int nl = snprintf(needle, sizeof(needle), "\"%s\"", key);
if (nl <= 0 || (size_t)nl >= sizeof(needle)) return -1;
size_t need_len = (size_t)nl;
const uint8_t *found = NULL;
for (size_t i = 0; i + need_len <= json_len; i++) {
if (memcmp(json + i, needle, need_len) == 0) {
found = json + i + need_len;
break;
}
}
if (!found) return -2;
const uint8_t *end = json + json_len;
while (found < end && (*found == ' ' || *found == ':' || *found == '\t'
|| *found == '\n' || *found == '\r')) {
found++;
}
if (found >= end || *found != '"') return -1;
found++;
size_t copy = 0;
while (found < end && *found != '"' && copy < out_max - 1) {
out[copy++] = (char)(*found++);
}
out[copy] = '\0';
return 0;
}
int console_foreground_app_handle(int fd, struct cmd_packet *packet) {
(void)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) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
intptr_t found_proc = 0;
int32_t found_pid = 0;
intptr_t cur = kproc;
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 + off.name, nm, PROC_SELFINFO_NAME_SIZE) == 0
&& nm[0] != 0
&& strncmp(nm, "eboot.bin", 9) == 0
&& (nm[9] == '\0' || nm[9] == ' ')) {
int32_t pid_val = 0;
kernel_copyout_fast(cur + KERNEL_OFFSET_PROC_P_PID, &pid_val, sizeof(pid_val));
if (pid_val > 0) {
found_proc = cur;
found_pid = pid_val;
break;
}
}
intptr_t next = 0;
if (kernel_copyout_fast(cur + PROC_NEXT_OFFSET, &next, sizeof(next)) != 0) break;
cur = next;
}
if (found_proc == 0) {
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
void *proc_buf = kernel_get_proc_struct_fast((uint32_t)found_pid);
if (!proc_buf) {
resp.pid = (uint32_t)found_pid;
memcpy(resp.name, "eboot.bin", 9);
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
const uint8_t *p = (const uint8_t *)proc_buf;
resp.pid = (uint32_t)found_pid;
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];
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);
char sfo_path[120];
char json_path[120];
snprintf(sfo_path, sizeof(sfo_path), "%s/param.sfo", meta_dir);
snprintf(json_path, sizeof(json_path), "%s/param.json", meta_dir);
uint8_t *meta_buf = (uint8_t *)net_alloc_buffer(8192);
if (meta_buf) {
size_t meta_len = 0;
char ver_a[16] = {0}, ver_b[16] = {0};
const char *pick = NULL;
if (sfo_read_file(sfo_path, meta_buf, 8192, &meta_len) == 0 && meta_len >= 20
&& meta_buf[0] == 0 && meta_buf[1] == 'P'
&& meta_buf[2] == 'S' && meta_buf[3] == 'F') {
sfo_get_string(meta_buf, meta_len, "APP_VER", ver_a, sizeof(ver_a));
sfo_get_string(meta_buf, meta_len, "VERSION", ver_b, sizeof(ver_b));
if (ver_a[0] && ver_b[0]) pick = (strcmp(ver_a, ver_b) >= 0) ? ver_a : ver_b;
else if (ver_a[0]) pick = ver_a;
else if (ver_b[0]) pick = ver_b;
}
if (!pick) {
meta_len = 0;
if (sfo_read_file(json_path, meta_buf, 8192, &meta_len) == 0 && meta_len >= 2
&& meta_buf[0] == '{') {
json_get_string(meta_buf, meta_len, "contentVersion", ver_a, sizeof(ver_a));
json_get_string(meta_buf, meta_len, "originContentVersion", ver_b, sizeof(ver_b));
if (ver_a[0] && ver_b[0]) pick = (strcmp(ver_a, ver_b) >= 0) ? ver_a : ver_b;
else if (ver_a[0]) pick = ver_a;
else if (ver_b[0]) pick = ver_b;
if (!pick) {
json_get_string(meta_buf, meta_len, "masterVersion", ver_a, sizeof(ver_a));
if (ver_a[0]) pick = ver_a;
}
}
}
if (pick) {
size_t plen = strlen(pick);
if (plen >= sizeof(resp.app_ver)) plen = sizeof(resp.app_ver) - 1;
memcpy(resp.app_ver, pick, plen);
resp.app_ver[plen] = '\0';
}
free(meta_buf);
}
free(proc_buf);
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
int console_handle(int fd, struct cmd_packet *packet) {
switch (packet->cmd) {
case 0xBDDD0001u: return console_reboot_handle(fd, packet);
case 0xBDDD0002u: return console_end_handle(fd, packet);
case 0xBDDD0003u: return console_print_handle(fd, packet);
case 0xBDDD0004u: return console_notify_handle(fd, packet);
case 0xBDDD0005u: return console_info_handle(fd, packet);
case 0xBDDD0006u: return console_foreground_app_handle(fd, packet);
default: return 1;
}
}
__attribute__((noinline))
void klog_format_log(const char *fmt, ...)
{
char formatted[300];
va_list ap;
va_start(ap, fmt);
vsprintf(formatted, fmt, ap);
va_end(ap);
printf("[ps5debug] %s\n", formatted);
}
struct klog_sin {
uint8_t sin_len;
uint8_t sin_family;
uint16_t sin_port;
uint32_t sin_addr;
uint8_t sin_zero[8];
};
int g_klog_listen_fd = -1;
__attribute__((noinline))
void *klog_server_thread(void *arg)
{
(void)arg;
int s = sceNetSocket((const char *)0, 2, 1, 0);
if (s < 0) return (void *)0;
int one = 1;
sceNetSetsockopt(s, 0xFFFF, 0x0004, &one, 4);
sceNetSetsockopt(s, 0xFFFF, 0x0200, &one, 4);
sceNetSetsockopt(s, 0xFFFF, 0x0008, &one, 4);
struct klog_sin sin;
memset(&sin, 0, 16);
sin.sin_family = 2;
sin.sin_port = 0xA00C;
sin.sin_addr = 0;
if (sceNetBind(s, &sin, 16) < 0) return (void *)0;
if (sceNetListen(s, 5) < 0) return (void *)0;
g_klog_listen_fd = s;
char rbuf[0x1000];
struct timespec ts;
for (;;) {
scePthreadYield();
int connfd = sceNetAccept(s, (void *)0, (void *)0);
if (connfd < 0) {
int err = *sceNetErrnoLoc();
if (err == 0xA3 || err == EBADF) {
sceNetSocketClose(s);
g_klog_listen_fd = -1;
return (void *)0;
}
continue;
}
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) {
write(connfd, "Cannot open /dev/klog\n", 0x17);
return (void *)0;
}
write(connfd, "Successfully opened klog!\n", 0x0A);
for (;;) {
ts.tv_sec = 0;
ts.tv_nsec = 100000000L;
nanosleep(&ts, (struct timespec *)0);
int err_val = 0;
unsigned int err_len = 4;
int gso = sceNetGetsockopt(connfd, 0xFFFF, 0x1007, &err_val, &err_len);
if ((gso | err_val) != 0) break;
long n = read(klog_fd, rbuf, 0xFFF);
if (n > 0) {
rbuf[n] = 0;
long expect = n + 1;
if (write(connfd, rbuf, (unsigned long)expect) != expect)
break;
}
}
klog_format_log("klog client disconnected");
close(klog_fd);
close(connfd);
}
}