Files
ps5debug-NG/debugger/source/debug.c
T
Pharaoh2k 2e9dff1442 Preserve watchpoint DR6 across internal debug-register writes
The event dispatcher's internal DBREG accesses clear the live DR6, hiding which watchpoint fired from the client. Snapshot the event DR6 and re-apply it to the outgoing packet, except on single-step events. Also clear the watchpoint/debug-register shadow on every teardown path, matching the breakpoint-slot clearing, so a reattaching connection cannot inherit stale watchpoint state.
2026-07-29 10:47:42 +00:00

1826 lines
59 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
#include "protocol.h"
#include "sdk_shim.h"
#include "net.h"
#include "debug.h"
#include "proc.h"
#include "kern_rw_fast.h"
#include "debug_state.h"
struct server_client {
uint32_t active;
int fd;
uint32_t debugging;
uint8_t client_addr[16];
uint8_t pad_1c[4];
uint8_t dbgctx[0x358];
};
extern struct server_client g_clients[12];
extern void *curdbgcli;
extern void *curdbgctx;
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;
};
static int g_cached_app_pid = 0;
static int g_cached_app_id = 0;
#define LWP_CACHE_MAX 512
static int g_lwp_cache_pid = 0;
static uint32_t g_lwp_cache_list[LWP_CACHE_MAX];
static int g_lwp_cache_count = 0;
static uint32_t g_lwp_cache_gen = 0;
static int get_lwp_list_cached(int pid, uint32_t **out_list, int *out_count) {
if (pid != g_lwp_cache_pid || g_lwp_cache_count == 0 || g_lwp_cache_gen >= 1024) {
int num = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (num <= 0 || num > LWP_CACHE_MAX) return -1;
long r = ptrace_raw(PT_GETLWPLIST, pid, g_lwp_cache_list, num);
if (r <= 0) return -1;
g_lwp_cache_count = (int)r;
g_lwp_cache_pid = pid;
g_lwp_cache_gen = 0;
} else {
g_lwp_cache_gen++;
}
*out_list = g_lwp_cache_list;
*out_count = g_lwp_cache_count;
return 0;
}
static void lwp_list_cache_flush(void) {
g_lwp_cache_pid = 0;
g_lwp_cache_count = 0;
g_lwp_cache_gen = 0;
}
extern intptr_t kern_thread_addr(int pid, int lwpid);
#define RIP_CACHE_N 128
static struct {
int32_t pid;
int32_t lwpid;
intptr_t rip_kaddr;
} g_rip_cache[RIP_CACHE_N];
static int g_rip_cache_next = 0;
static uint32_t g_rip_cache_gen = 0;
static int kern_get_rip_fast(int pid, int lwpid, uint64_t *out_rip) {
if (++g_rip_cache_gen >= 1024) {
for (int i = 0; i < RIP_CACHE_N; i++) g_rip_cache[i].rip_kaddr = 0;
g_rip_cache_gen = 0;
}
for (int i = 0; i < RIP_CACHE_N; i++) {
if (g_rip_cache[i].pid == pid && g_rip_cache[i].lwpid == lwpid
&& g_rip_cache[i].rip_kaddr) {
return kernel_copyout_fast(g_rip_cache[i].rip_kaddr, out_rip, 8) < 0 ? 1 : 0;
}
}
intptr_t kthread = kern_thread_addr(pid, lwpid);
if (!kthread) return 1;
intptr_t frame_addr;
if (kernel_copyout_fast(kthread + 0x460, &frame_addr, 8) < 0) return 1;
int slot = g_rip_cache_next;
g_rip_cache[slot].pid = pid;
g_rip_cache[slot].lwpid = lwpid;
g_rip_cache[slot].rip_kaddr = frame_addr + 0xe8;
g_rip_cache_next = (g_rip_cache_next + 1) % RIP_CACHE_N;
return kernel_copyout_fast(frame_addr + 0xe8, out_rip, 8) < 0 ? 1 : 0;
}
static int kern_set_rip_fast(int pid, int lwpid, uint64_t new_rip) {
for (int i = 0; i < RIP_CACHE_N; i++) {
if (g_rip_cache[i].pid == pid && g_rip_cache[i].lwpid == lwpid
&& g_rip_cache[i].rip_kaddr) {
return kernel_copyin_fast(&new_rip, g_rip_cache[i].rip_kaddr, 8) < 0 ? 1 : 0;
}
}
intptr_t kthread = kern_thread_addr(pid, lwpid);
if (!kthread) return 1;
intptr_t frame_addr;
if (kernel_copyout_fast(kthread + 0x460, &frame_addr, 8) < 0) return 1;
int slot = g_rip_cache_next;
g_rip_cache[slot].pid = pid;
g_rip_cache[slot].lwpid = lwpid;
g_rip_cache[slot].rip_kaddr = frame_addr + 0xe8;
g_rip_cache_next = (g_rip_cache_next + 1) % RIP_CACHE_N;
return kernel_copyin_fast(&new_rip, frame_addr + 0xe8, 8) < 0 ? 1 : 0;
}
static void rip_cache_flush(void) {
for (int i = 0; i < RIP_CACHE_N; i++) {
g_rip_cache[i].pid = 0;
g_rip_cache[i].lwpid = 0;
g_rip_cache[i].rip_kaddr = 0;
}
g_rip_cache_next = 0;
g_rip_cache_gen = 0;
}
void debug_resume_cache_flush(void) {
g_cached_app_pid = 0;
g_cached_app_id = 0;
lwp_list_cache_flush();
rip_cache_flush();
}
static int resume_app_via_self_id(int pid) {
int app_id;
if (pid != 0 && pid == g_cached_app_pid && g_cached_app_id != 0) {
app_id = g_cached_app_id;
} else {
int rc = _sceApplicationGetAppId(pid, &app_id);
if (rc != 0) return rc;
g_cached_app_pid = pid;
g_cached_app_id = app_id;
}
return sceApplicationContinue(app_id);
}
static inline void resume_app_unelev(struct elev_state *es, int pid) {
int was_valid = es->valid;
if (was_valid) elev_restore(es);
resume_app_via_self_id(pid);
if (was_valid) (void)elev_save_and_set(es);
}
static inline int authid_bump_safe(uint64_t *out_saved) {
intptr_t ucred = kernel_get_proc_ucred_fast((pid_t)getpid());
if (ucred == 0) return -1;
if (kernel_copyout_fast(ucred + 0x58, out_saved, 8) != 0) return -1;
if (*out_saved == 0) return -1;
uint64_t priv_authid = 0x4800000000000006ULL;
if (kernel_copyin_fast(&priv_authid, ucred + 0x58, 8) != 0) return -1;
return 0;
}
static inline void authid_restore_safe(uint64_t saved_authid) {
intptr_t ucred = kernel_get_proc_ucred_fast((pid_t)getpid());
if (ucred != 0) {
kernel_copyin_fast(&saved_authid, ucred + 0x58, 8);
}
}
extern bool fw_uses_kernel_dbreg_path(void);
extern bool is_process_stopped(int pid);
extern bool kern_thread_step_walker(int pid);
extern int kern_get_dbregs(int pid, int lwpid, void *dbreg_buf);
extern int kern_set_dbregs(int pid, int lwpid, void *dbreg_buf);
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 FSGSBASE_BLOB_SIZE 0x10
struct cmd_debug_lwp_packet {
uint32_t lwpid;
} __attribute__((packed));
struct cmd_debug_setreg_packet {
uint32_t lwpid;
uint32_t length;
} __attribute__((packed));
#define DBGCTX() ((struct dbgctx *)curdbgctx)
int connect_debugger(struct dbgctx *ctx, void *client_sockaddr_in) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(755);
addr.sin_addr.s_addr = *(uint32_t *)((uint8_t *)client_sockaddr_in + 4);
int sock = socket(AF_INET, SOCK_STREAM, 0);
ctx->dbgfd = sock;
if (sock <= 0) return 1;
extern void configure_socket(int fd);
configure_socket(sock);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(sock);
ctx->dbgfd = -1;
return 1;
}
return 0;
}
uint32_t g_stepping_lwpid = 0;
#define DBG_STORM_FATAL_SIGS(sig) \
((sig) == 11 || (sig) == 7 || (sig) == 4 || (sig) == 8 )
#define DBG_STORM_LIMIT 32
static uint32_t g_storm_lwpid = 0;
static uint64_t g_storm_rip = 0;
static uint8_t g_storm_sig = 0;
static uint32_t g_storm_count = 0;
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;
static int int3_scan_stuck(int pid, const uint64_t *bp_addrs, int n_bps,
int *out_lwpids, int out_cap, int *out_n) {
if (out_n) *out_n = 0;
int stuck = 0;
if (n_bps <= 0) return 0;
int numlwps = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (numlwps <= 0) return 0;
int *lwpids = (int *)net_alloc_buffer((unsigned long)(numlwps * 4));
if (!lwpids) return 0;
long lr = ptrace_raw(PT_GETLWPLIST, pid, lwpids, numlwps);
if (lr > 0) {
uint8_t regs_buf[176];
for (int i = 0; i < numlwps; i++) {
errno = 0;
if (ptrace_raw(PT_GETREGS, lwpids[i], regs_buf, 0) != 0) continue;
uint64_t rip = *(uint64_t *)(regs_buf + 0x88);
for (int b = 0; b < n_bps; b++) {
uint64_t a = bp_addrs[b];
int hit = 0;
if (rip == a + 1) {
uint8_t li_buf[160];
if (ptrace_raw(PT_LWPINFO, lwpids[i], li_buf, 160) != 0) {
continue;
}
if (*(int32_t *)(li_buf + 0x04) != 1 ) continue;
if (*(int32_t *)(li_buf + 0x30) != 5 ) continue;
if (*(int32_t *)(li_buf + 0x38) != 1 ) continue;
*(uint64_t *)(regs_buf + 0x88) = a;
ptrace_raw(PT_SETREGS, lwpids[i], regs_buf, 0);
hit = 1;
} else if (rip == a) {
hit = 1;
} else if (rip > a + 1 && rip < a + 16) {
hit = 1;
}
if (hit) {
if (out_lwpids && stuck < out_cap) out_lwpids[stuck] = lwpids[i];
stuck++;
break;
}
}
}
}
free(lwpids);
if (out_n) *out_n = (stuck < out_cap) ? stuck : out_cap;
return stuck;
}
static int int3_lwp_stopped(int lwp) {
uint8_t rb[176];
errno = 0;
if (ptrace_raw(PT_GETREGS, lwp, rb, 0) == 0) return 1;
return (errno == 16) ? -1 : 0;
}
static void int3_resume_lwps(int pid, const int *lwpids, int n) {
for (int i = 0; i < n && i < 256; i++) {
int lw = lwpids[i];
if (int3_lwp_stopped(lw) == 0) continue;
for (int tries = 0; tries < 12; tries++) {
errno = 0;
ptrace_raw(PT_CONTINUE, lw, (void *)1, 0);
int e1 = errno;
if (int3_lwp_stopped(lw) == 0) break;
errno = 0;
ptrace_raw(PT_CONTINUE, pid, (void *)1, 0);
int e2 = errno;
if (int3_lwp_stopped(lw) == 0) break;
sceKernelUsleep((e1 == 16 || e2 == 16) ? 1500 : 400);
}
}
}
static void int3_iterative_sweep(int pid, const uint64_t *bp_addrs, int n_bps) {
if (n_bps <= 0) return;
for (int round = 0; round < 4; round++) {
int lw[INT3_REWIND_MAX]; int n = 0;
int stuck = int3_scan_stuck(pid, bp_addrs, n_bps, lw, INT3_REWIND_MAX, &n);
if (stuck == 0) break;
int3_resume_lwps(pid, lw, n);
sceKernelUsleep(5000);
}
}
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();
kern_proc_cache_flush();
kern_thread_cache_flush();
struct elev_state es;
int es_active = (elev_save_and_set(&es) == 0);
void *old_cli = curdbgcli;
curdbgcli = NULL;
if (old_cli) {
*(uint32_t *)((char *)old_cli + 8) = 0;
}
curdbgctx = NULL;
g_debug_attached = 0;
uint32_t prev_state = g_stopgo_mode;
g_stopgo_resume_signal = 0xFFFFFFFFu;
g_stopgo_resume_pid = 0;
if (prev_state == 2) {
g_stopgo_mode = 0;
g_stopgo_last_signal = 0xFFFFFFFFu;
g_stopgo_target_pid = 0;
}
int pid = svc ? *(int *)svc : 0;
uint32_t dbgfd = svc ? *(uint32_t *)((char *)svc + 4) : 0;
int vm_count = 0;
void *vm_maps = NULL;
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);
uint64_t teardown_bp_addrs[INT3_REWIND_MAX];
int teardown_n_bps = 0;
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;
}
}
}
int3_iterative_sweep(pid, teardown_bp_addrs, teardown_n_bps);
char dr_buf[0x100];
memset(dr_buf, 0, sizeof(dr_buf));
ptrace_raw(PT_GETDBREGS, pid, dr_buf, 0);
uint8_t dr7_low = (uint8_t)dr_buf[0x38];
int have_active_dr =
(dr7_low & 0x03) || (dr7_low & 0x0C) ||
(dr7_low & 0x30) || (dr7_low & 0xC0);
int *lwpids = NULL;
int count = 0;
int we_stopped = 0;
if (!have_active_dr) {
int rc = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (rc == -1) {
if (errno != 16 ) {
goto free_and_detach;
}
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
goto free_and_detach;
}
we_stopped = 1;
ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
}
goto free_and_detach;
}
int rc = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (rc == -1) {
if (errno != 16) {
goto free_and_detach;
}
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
goto free_and_detach;
}
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) {
goto free_and_detach;
}
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, count) == -1) {
goto free_and_detach;
}
} else {
count = rc;
lwpids = (int *)net_alloc_buffer((unsigned long)(count * 4));
if (!lwpids) {
goto free_and_detach;
}
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, count) == -1) {
goto free_and_detach;
}
}
if (count == 0) {
goto free_and_detach;
}
memset(dr_buf, 0, sizeof(dr_buf));
bool use_kernel_path = fw_uses_kernel_dbreg_path();
for (int i = 0; i < count; i++) {
int lwpid = lwpids[i];
if (use_kernel_path) {
if (kern_set_dbregs(pid, lwpid, dr_buf) != 0) {
ptrace_raw(PT_SETDBREGS, lwpid, dr_buf, 0);
}
} else {
ptrace_raw(PT_SETDBREGS, lwpid, dr_buf, 0);
}
}
free_and_detach:
if (lwpids) free(lwpids);
if (we_stopped) {
resume_app_unelev(&es, pid);
}
ptrace_raw(PT_DETACH, pid, NULL, 0);
close_socket_and_unlock:
if (dbgfd > 0) {
close((int)dbgfd);
}
/* 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);
}
static const uint32_t k_stopgo_signal_table[3] = { 19u , 17u , 9u };
int debug_stopgo_handle(int pid, char action) {
scePthreadMutexLock(&g_server_mutex);
g_stopgo_target_pid = (uint32_t)pid;
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;
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;
g_stopgo_mode = 2;
}
} else {
if ((unsigned char)action <= 2) {
uint32_t sig = k_stopgo_signal_table[(unsigned char)action];
g_stopgo_mode = 1;
g_stopgo_last_signal = sig;
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 {
if (kill(pid, 0) != 0 || debug_wait4_bounded(pid, NULL) != 1) {
scePthreadMutexUnlock(&g_server_mutex);
return -1;
}
}
}
scePthreadMutexUnlock(&g_server_mutex);
return 0;
}
int debug_process_stop_handle(int fd, struct cmd_packet *packet) {
void *data = packet->data;
if (!data) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
unsigned char action = ((unsigned char *)data)[4];
if (action > 2) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = *(int *)data;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
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);
int debug_attach_handle(int fd, struct cmd_packet *packet) {
if (g_debug_attached || g_debug_phase != DEBUG_PHASE_DETACHED) {
net_send_int32(fd, CMD_ALREADY_DEBUG);
return 1;
}
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;
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;
}
elev_restore(&es);
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;
}
g_debug_phase = DEBUG_PHASE_RUNNING;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int debug_detach_handle(int fd, struct cmd_packet *packet) {
(void)packet;
debug_full_teardown(curdbgctx);
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
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;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
struct cmd_debug_breakpt_packet *bp =
(struct cmd_debug_breakpt_packet *)packet->data;
if (!bp) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
if (bp->index > MAX_BREAKPOINTS - 1) {
net_send_int32(fd, CMD_INVALID_INDEX);
return 1;
}
char *bp_entry = (char *)curdbgctx + bp->index * DBG_BP_SLOT_SIZE;
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;
proc_write_mem((uint32_t)pid, bp->address, 1, &int3);
} else {
uint32_t was_enabled = *(uint32_t *)(bp_entry + 0x08);
uint64_t bp_addr = *(uint64_t *)(bp_entry + 0x10);
if (was_enabled && bp_addr) {
struct elev_state es;
int es_active = (elev_save_and_set(&es) == 0);
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);
proc_write_mem((uint32_t)pid, bp_addr, 1, saved_byte_slot);
if (stuck > 0) {
int3_resume_lwps(pid, stuck_lw, stuck_n);
}
ptrace_raw(PT_CONTINUE, pid, (void *)1, 0);
if (es_active) elev_restore(&es);
if (g_recently_disabled_count < INT3_REWIND_MAX) {
g_recently_disabled_bps[g_recently_disabled_count++] = bp_addr;
}
}
*(uint32_t *)(bp_entry + 0x08) = 0;
*(uint64_t *)(bp_entry + 0x10) = 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int debug_set_watchpoint_handle(int fd, struct cmd_packet *packet) {
if (!g_debug_attached) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = DBGCTX()->pid;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
struct cmd_debug_watchpt_packet *wp =
(struct cmd_debug_watchpt_packet *)packet->data;
if (!wp) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
if (wp->index > 3) { net_send_int32(fd, CMD_INVALID_INDEX); return 1; }
struct elev_state es;
if (elev_save_and_set(&es) != 0) {
net_send_int32(fd, CMD_ERROR);
return 1;
}
int count;
int *lwpids = NULL;
int we_stopped;
int ret_rc;
int rc = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (rc != -1) {
count = rc; we_stopped = 0;
lwpids = (int *)net_alloc_buffer((unsigned long)(count * 4));
if (!lwpids) goto wp_err_no_resume;
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, count) == -1) {
free(lwpids); goto wp_err_no_resume;
}
} else {
if (errno != 16 ) goto wp_err_no_resume;
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));
if (!lwpids) goto wp_resume_and_err;
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, count) == -1) {
free(lwpids); goto wp_resume_and_err;
}
}
char *ctx = (char *)curdbgctx;
uint64_t *dr_addr = (uint64_t *)(ctx + 0x2D8);
uint64_t *dr7 = (uint64_t *)(ctx + 0x310);
int idx = (int)wp->index;
int cl1 = idx * 2;
int cl2 = idx * 4 + 16;
uint64_t mask = ((uint64_t)0xFu << cl2)
| (uint64_t)(int64_t)(int32_t)(3 << cl1);
*dr7 &= ~mask;
if (wp->enabled) {
dr_addr[idx] = wp->address;
uint64_t low_bits = (uint64_t)(int64_t)(int32_t)(3 << cl1);
uint64_t high_bits = (((uint64_t)wp->length << 2) | (uint64_t)wp->breaktype) << cl2;
*dr7 |= low_bits | high_bits;
} else {
dr_addr[idx] = 0;
}
void *dr_block = (void *)(ctx + 0x2D8);
for (int i = 0; i < count; i++) {
if (ptrace_raw(PT_SETDBREGS, lwpids[i], dr_block, 0) == -1) {
if (errno != 0) {
free(lwpids);
if (we_stopped) goto wp_resume_and_err;
goto wp_err_no_resume;
}
}
}
if (we_stopped) {
resume_app_unelev(&es, pid);
ptrace_raw(PT_CONTINUE, pid, (void *)1, 0);
}
net_send_int32(fd, CMD_SUCCESS);
free(lwpids);
ret_rc = 0;
goto wp_done;
wp_resume_and_err:
resume_app_unelev(&es, pid);
ptrace_raw(PT_CONTINUE, pid, (void *)1, 0);
wp_err_no_resume:
net_send_int32(fd, CMD_ERROR);
ret_rc = 1;
wp_done:
elev_restore(&es);
return ret_rc;
}
int debug_get_thread_list_handle(int fd, struct cmd_packet *packet) {
(void)packet;
if (!g_debug_attached) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = DBGCTX()->pid;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
int count = (int)ptrace_elev(PT_GETNUMLWPS, pid, NULL, 0);
int we_stopped = 0;
if (count == -1) {
if (errno != 16 ) { net_send_int32(fd, CMD_ERROR); return 1; }
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;
}
int size = count * 4;
int *buf = (int *)net_alloc_buffer((unsigned long)size);
if (!buf) goto err;
if (ptrace_elev(PT_GETLWPLIST, pid, buf, count) == -1) goto err;
if (we_stopped) {
resume_app_via_self_id(pid);
ptrace_elev(PT_CONTINUE, pid, (void *)1, 0);
}
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &count, 4);
net_send_all(fd, buf, size);
free(buf);
return 0;
err:
if (we_stopped) ptrace_elev(PT_CONTINUE, pid, (void *)1, 0);
if (buf) free(buf);
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_suspend_thread_handle(int fd, struct cmd_packet *packet) {
void *data = packet->data;
if (DBGCTX()->pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
if (!data) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
if (ptrace_elev(PT_SUSPEND, *(int *)data, NULL, 0) == -1) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int debug_resume_thread_handle(int fd, struct cmd_packet *packet) {
void *data = packet->data;
if (DBGCTX()->pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
if (!data) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
if (ptrace_elev(PT_RESUME, *(int *)data, NULL, 0) == -1) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int debug_getregs_handle(int fd, struct cmd_packet *packet) {
char buf[REG_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, REG_BLOB_SIZE);
if (ptrace_elev(PT_GETREGS, gp->lwpid, buf, 0) == -1 && errno != 0) goto err;
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, buf, REG_BLOB_SIZE);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_setregs_handle(int fd, struct cmd_packet *packet) {
char buf[REG_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;
net_send_int32(fd, CMD_SUCCESS);
net_recv_all(fd, buf, sp->length, 1);
if (ptrace_elev(PT_SETREGS, pid, buf, 0) == -1 && errno != 0) goto err;
net_send_int32(fd, CMD_SUCCESS);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_getfpregs_handle(int fd, struct cmd_packet *packet) {
char buf[FPREG_BLOB_SIZE] __attribute__((aligned(64)));
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, FPREG_BLOB_SIZE);
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);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_setfpregs_handle(int fd, struct cmd_packet *packet) {
char buf[FPREG_BLOB_SIZE] __attribute__((aligned(64)));
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 > FPREG_BLOB_SIZE) goto err;
memset(buf, 0, FPREG_BLOB_SIZE);
net_send_int32(fd, CMD_SUCCESS);
if (net_recv_all(fd, buf, sp->length, 1) < 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;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_getdbregs_handle(int fd, struct cmd_packet *packet) {
char buf[DBREG_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; }
int we_stopped = 0;
if (is_process_stopped(pid) == 0) {
if (kill(pid, 17) != 0
|| debug_wait4_bounded(pid, NULL) != 1) {
g_debug_phase = DEBUG_PHASE_BROKEN;
goto err;
}
we_stopped = 1;
}
memset(buf, 0, DBREG_BLOB_SIZE);
if (fw_uses_kernel_dbreg_path()) {
if (kern_get_dbregs(pid, gp->lwpid, buf) != 0) goto err;
} else {
if (ptrace_elev(PT_GETDBREGS, gp->lwpid, buf, 0) == -1 && errno != 0) goto err;
}
if (we_stopped) {
resume_app_via_self_id(pid);
ptrace_elev(PT_CONTINUE, pid, (void *)1, 0);
}
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, buf, DBREG_BLOB_SIZE);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
return 1;
}
int debug_setdbregs_handle(int fd, struct cmd_packet *packet) {
char buf[DBREG_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;
net_send_int32(fd, CMD_SUCCESS);
net_recv_all(fd, buf, sp->length, 1);
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;
resume_app_via_self_id(pid);
ptrace_elev(PT_CONTINUE, pid, (void *)1, 0);
net_send_int32(fd, CMD_SUCCESS);
return 0;
err:
net_send_int32(fd, CMD_ERROR);
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; }
void *data = packet->data;
if (!data) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
if (debug_stopgo_handle(pid, *(char *)data) != 0) {
net_send_int32(fd, CMD_ERROR);
return 0;
}
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
struct dbg_thrinfo_response {
uint32_t lwpid;
uint32_t priority;
char tdname[32];
} __attribute__((packed));
static int kern_get_thread_info(int pid, uint32_t *thrinfo_out)
{
if (pid == 0) return 1;
if (!thrinfo_out) return 1;
intptr_t kproc = kernel_get_proc_fast(pid);
if (!kproc) return 1;
uint8_t chain_hdr[0x30];
if (kernel_copyout_fast(kproc + 0x10, chain_hdr, 0x30) != 0) return 1;
uint64_t kthread = *(uint64_t *)chain_hdr;
if (!kthread) return 1;
uint8_t tbuf[0x680];
for (;;) {
if (kernel_copyout_fast((intptr_t)kthread, tbuf, 0x680) != 0) return 1;
if (*(uint32_t *)(tbuf + 0x9c) == thrinfo_out[0]) {
thrinfo_out[1] = *(uint16_t *)(tbuf + 0x3c0);
memcpy((char *)thrinfo_out + 8, tbuf + 0x294, 0x20);
return 0;
}
kthread = *(uint64_t *)(tbuf + 0x10);
if (!kthread) return 1;
}
}
int debug_thread_info_handle(int fd, struct cmd_packet *packet) {
if (!g_debug_attached) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = DBGCTX()->pid;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
struct cmd_debug_lwp_packet *gp = (struct cmd_debug_lwp_packet *)packet->data;
if (!gp) { net_send_int32(fd, CMD_DATA_NULL); return 1; }
uint32_t thrinfo[10];
memset(thrinfo, 0, sizeof(thrinfo));
thrinfo[0] = gp->lwpid;
if (kern_get_thread_info(pid, thrinfo) != 0) {
net_send_int32(fd, CMD_ERROR);
return 1;
}
struct dbg_thrinfo_response resp;
memset(&resp, 0, sizeof(resp));
resp.lwpid = thrinfo[0];
resp.priority = thrinfo[1];
memcpy(resp.tdname, (char *)thrinfo + 8, 32);
resp.tdname[31] = 0;
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
int debug_step_handle(int fd, struct cmd_packet *packet) {
(void)packet;
if (!g_debug_attached) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = DBGCTX()->pid;
if (pid == 0) {
net_send_int32(fd, CMD_ERROR);
return 1;
}
if (ptrace_elev(PT_STEP, pid, (void *)1, 0) != 0) {
net_send_int32(fd, CMD_ERROR);
return 1;
}
g_debug_phase = DEBUG_PHASE_STEPPING;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int debug_step_thread_handle(int fd, struct cmd_packet *packet) {
if (!g_debug_attached) { net_send_int32(fd, CMD_ERROR); return 1; }
int pid = DBGCTX()->pid;
if (pid == 0) { net_send_int32(fd, CMD_ERROR); return 1; }
struct cmd_debug_lwp_packet *gp = (struct cmd_debug_lwp_packet *)packet->data;
long rc;
if (gp) {
scePthreadMutexLock(&g_server_mutex);
int drain_status = 0;
int drain_count = 0;
while (drain_count < 64 && wait4(pid, &drain_status, 1, NULL) > 0) {
drain_count++;
}
g_stepping_lwpid = gp->lwpid;
rc = ptrace_elev(PT_STEP, (int)gp->lwpid, (void *)1, 0);
scePthreadMutexUnlock(&g_server_mutex);
} else {
rc = ptrace_elev(PT_STEP, pid, (void *)1, 0);
}
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;
}
static void debug_handle_breakpoint_resume(void) {
if (!g_debug_attached) return;
int sig = (int)g_stopgo_resume_signal;
if (sig == -1) return;
int pid = (int)g_stopgo_resume_pid;
if (sig == 0 && g_recently_disabled_count > 0) {
int3_iterative_sweep(pid, g_recently_disabled_bps, g_recently_disabled_count);
g_recently_disabled_count = 0;
}
if (sig == 0) {
resume_app_via_self_id(pid);
sig = (int)g_stopgo_resume_signal;
}
errno = 0;
long continue_rc = ptrace_raw(PT_CONTINUE, pid, (void *)1, sig);
int continue_errno = errno;
if ((int)g_stopgo_resume_signal == 17) {
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;
}
static uint32_t g_last_alive_check = 0;
int dispatch_debug_events(void) {
#define DDE_RETURN(v) return (v)
debug_handle_breakpoint_resume();
struct { uint64_t tv_sec; uint64_t tv_usec; } now = {0,0};
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;
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) {
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, 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);
}
if (*(int32_t *)(lwpi + 0x38) == 2 ) {
int _abs_lwpid = (int)*(uint32_t *)lwpi;
uint64_t _abs_rip = 0;
if (kern_get_rip_fast((int)DBGCTX()->pid, _abs_lwpid, &_abs_rip) == 0) {
uint64_t _abs_rm1 = _abs_rip - 1;
char *_abs_base = (char *)curdbgctx + 0x10;
for (int _i = 0; _i < 30; _i++) {
uint64_t _a = *(uint64_t *)(_abs_base + _i * 0x18);
if (_a != 0 && _a == _abs_rm1) {
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) == 1 ) {
int _pabs_lwpid = (int)*(uint32_t *)lwpi;
uint64_t _pabs_rip = 0;
if (kern_get_rip_fast((int)DBGCTX()->pid, _pabs_lwpid, &_pabs_rip) == 0) {
char *_pabs_base = (char *)curdbgctx + 0x10;
for (int _i = 0; _i < 30; _i++) {
uint64_t _a = *(uint64_t *)(_pabs_base + _i * 0x18);
if (_a != 0 && _a == _pabs_rip) {
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);
}
}
}
}
char pkt[1184];
memset(pkt, 0, 1184);
*(uint32_t *)(pkt + 0x000) = *(uint32_t *)lwpi;
*(uint32_t *)(pkt + 0x004) = (uint32_t)status;
memcpy(pkt + 0x008, lwpi + 0x80, 24);
uint8_t regs_buf[176];
uint8_t fpu_buf[832];
uint8_t dbreg_buf[128];
int pid = (int)DBGCTX()->pid;
int lwpid = (int)*(uint32_t *)lwpi;
bool got_dbreg = false;
memset(regs_buf, 0, 176);
memset(dbreg_buf, 0, 128);
bool use_kernel = fw_uses_kernel_dbreg_path();
if (use_kernel) {
if (kern_get_lwp_full_state(pid, lwpid, regs_buf, dbreg_buf, fpu_buf) == 0) {
got_dbreg = true;
} 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);
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);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
if (kern_get_dbregs(pid, lwpid, dbreg_buf) == 0) {
got_dbreg = true;
}
}
} else {
if (ptrace_raw(PT_GETREGS, lwpid, regs_buf, 0) != 0) {
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);
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);
g_debug_phase = DEBUG_PHASE_BROKEN;
DDE_RETURN(1);
}
}
memcpy(pkt + 0x030, regs_buf, 176);
memcpy(pkt + 0x0E0, fpu_buf, 832);
memcpy(pkt + 0x420, dbreg_buf, 128);
uint64_t pkt_rip = *(uint64_t *)(pkt + 0x030 + 0x88);
uint64_t rip_minus_1 = pkt_rip - 1;
char *bp_addrs_base = (char *)curdbgctx + 0x10;
char *matched_bp_v34 = NULL;
for (int i = 0; i < 30; i++) {
uint64_t addr = *(uint64_t *)(bp_addrs_base + i * 0x18);
if (addr != 0 && addr == rip_minus_1) {
matched_bp_v34 = (char *)curdbgctx + i * 0x18 + 8;
break;
}
}
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; }
}
if (matched_bp_v34 != NULL &&
*(int32_t *)(lwpi + 0x38) == 2 ) {
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) {
int cl1 = matched_wp * 2;
int cl2 = matched_wp * 4 + 16;
uint64_t saved_dr7 = pkt_dr[7];
uint64_t rw_bits = (saved_dr7 >> cl2) & 3;
uint64_t clear_mask = ~(((uint64_t)0xF << cl2) | ((uint64_t)3 << cl1));
int numlwps = (int)ptrace_raw(PT_GETNUMLWPS, pid, NULL, 0);
if (numlwps > 0) {
uint32_t *lwpids = (uint32_t *)net_alloc_buffer((uint64_t)numlwps * 4);
if (lwpids) {
if (ptrace_raw(PT_GETLWPLIST, pid, lwpids, numlwps) > 0) {
for (int i = 0; i < numlwps; i++) {
int lwp = (int)lwpids[i];
if (lwp == lwpid) {
if (rw_bits == 0) {
uint64_t saved_pkt_dr7 = pkt_dr[7];
pkt_dr[7] &= clear_mask;
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);
}
ptrace_raw(PT_STEP, lwpid, (void *)1, 0);
int s2 = 0;
while (wait4(pid, &s2, 1, NULL) == 0) {
sceKernelUsleep(50);
}
pkt_dr[7] = saved_pkt_dr7;
if (use_kernel) {
kern_set_dbregs(pid, lwpid, pkt + 0x420);
} else {
ptrace_raw(PT_SETDBREGS, lwpid, pkt + 0x420, 0);
}
}
continue;
}
if (use_kernel) {
if (kern_get_proc_info_by_pid(pid, lwp, length_buf) != 0) {
ptrace_raw(PT_GETREGS, lwp, length_buf, 0);
}
} else {
ptrace_raw(PT_GETREGS, lwp, length_buf, 0);
}
uint64_t thr_rip = *(uint64_t *)(length_buf + 0x88);
if (thr_rip != pkt_rip) continue;
uint8_t small_dbreg[128];
ptrace_raw(PT_GETDBREGS, lwp, small_dbreg, 0);
uint64_t thr_dr7 = *(uint64_t *)(small_dbreg + 0x38);
if (((thr_dr7 >> cl2) & 3) != 0) continue;
uint64_t saved_thr_dr7 = thr_dr7;
*(uint64_t *)(small_dbreg + 0x38) = thr_dr7 & clear_mask;
ptrace_raw(PT_SETDBREGS, lwp, small_dbreg, 0);
ptrace_raw(PT_STEP, lwp, (void *)1, 0);
int s3 = 0;
while (wait4(pid, &s3, 1, NULL) == 0) {
sceKernelUsleep(50);
}
*(uint64_t *)(small_dbreg + 0x38) = saved_thr_dr7;
if (use_kernel) {
kern_set_dbregs(pid, lwp, small_dbreg);
} else {
ptrace_raw(PT_SETDBREGS, lwp, small_dbreg, 0);
}
}
}
free(lwpids);
}
} else if (rw_bits == 0) {
uint64_t saved_pkt_dr7 = pkt_dr[7];
pkt_dr[7] = saved_pkt_dr7 & clear_mask;
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);
}
ptrace_raw(PT_STEP, lwpid, (void *)1, 0);
int s4 = 0;
while (wait4(pid, &s4, 1, NULL) == 0) sceKernelUsleep(50);
pkt_dr[7] = saved_pkt_dr7;
if (use_kernel) {
kern_set_dbregs(pid, lwpid, pkt + 0x420);
} else {
ptrace_raw(PT_SETDBREGS, lwpid, pkt + 0x420, 0);
}
}
}
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;
uint8_t int3 = 0xCC;
uint64_t saved_authid_1 = 0;
int authid_bumped_1 = (authid_bump_safe(&saved_authid_1) == 0);
proc_write_mem((uint32_t)pid, bp_addr, 1, saved_byte_ptr);
*(uint64_t *)(pkt + 0x030 + 0x88) -= 1;
if (use_kernel) {
uint64_t _new_rip = *(uint64_t *)(pkt + 0x030 + 0x88);
if (kern_set_rip_fast(pid, lwpid, _new_rip) != 0) {
if (kern_apply_thread_dbgctx(pid, lwpid, pkt + 0x030) != 0) {
ptrace_raw(PT_SETREGS, lwpid, pkt + 0x030, 0);
}
}
} else {
ptrace_raw(PT_SETREGS, lwpid, pkt + 0x030, 0);
}
ptrace_raw(PT_STEP, lwpid, (void *)1, 0);
if (authid_bumped_1) {
authid_restore_safe(saved_authid_1);
authid_bumped_1 = 0;
}
int s_step = 0;
while (wait4(pid, &s_step, 1, NULL) == 0) sceKernelUsleep(50);
uint64_t saved_authid_2 = 0;
int authid_bumped_2 = (authid_bump_safe(&saved_authid_2) == 0);
int need_scan = 0;
int cons_lw = -1;
{
uint8_t lwpi_peek[160];
if (ptrace_raw(PT_LWPINFO, pid, lwpi_peek, 160) == 0) {
cons_lw = (int)*(uint32_t *)lwpi_peek;
if (cons_lw != 0 && cons_lw != lwpid) need_scan = 1;
} else {
need_scan = 1;
}
}
if (need_scan) {
uint64_t target_rip = bp_addr + 1;
int rewound = 0;
if (cons_lw > 0 && cons_lw != lwpid) {
uint64_t cons_rip = 0;
if (kern_get_rip_fast(pid, cons_lw, &cons_rip) == 0
&& cons_rip == target_rip) {
uint8_t cons_li[160];
if (ptrace_raw(PT_LWPINFO, cons_lw, cons_li, 160) == 0
&& *(int32_t *)(cons_li + 0x04) == 1
&& *(int32_t *)(cons_li + 0x30) == 5
&& *(int32_t *)(cons_li + 0x38) == 1 ) {
uint8_t cons_regs[176];
if (kern_get_proc_info_by_pid(pid, cons_lw, cons_regs) == 0
&& *(uint64_t *)(cons_regs + 0x88) == target_rip) {
*(uint64_t *)(cons_regs + 0x88) = bp_addr;
kern_apply_thread_dbgctx(pid, cons_lw, cons_regs);
rewound = 1;
}
}
}
}
if (!rewound) {
uint32_t *sib_lwpids = NULL;
int sib_count = 0;
if (get_lwp_list_cached(pid, &sib_lwpids, &sib_count) == 0) {
for (int i = 0; i < sib_count; i++) {
int sib_lw = (int)sib_lwpids[i];
if (sib_lw == lwpid) continue;
if (sib_lw == cons_lw) continue;
uint64_t sib_rip = 0;
if (kern_get_rip_fast(pid, sib_lw, &sib_rip) != 0) continue;
if (sib_rip != target_rip) continue;
uint8_t sib_li[160];
if (ptrace_raw(PT_LWPINFO, sib_lw, sib_li, 160) != 0)
continue;
if (*(int32_t *)(sib_li + 0x04) != 1 ) continue;
if (*(int32_t *)(sib_li + 0x30) != 5 ) continue;
if (*(int32_t *)(sib_li + 0x38) != 1 ) continue;
uint8_t sib_regs[176];
if (kern_get_proc_info_by_pid(pid, sib_lw, sib_regs) != 0) continue;
if (*(uint64_t *)(sib_regs + 0x88) == target_rip) {
*(uint64_t *)(sib_regs + 0x88) = bp_addr;
kern_apply_thread_dbgctx(pid, sib_lw, sib_regs);
}
}
}
}
}
proc_write_mem((uint32_t)pid, bp_addr, 1, &int3);
if (authid_bumped_2) authid_restore_safe(saved_authid_2);
}
uint32_t pkt_lwpid = *(uint32_t *)pkt;
int stepping = (int)g_stepping_lwpid;
if (stepping > 0) {
if ((int)pkt_lwpid != stepping) {
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;
{
uint32_t _final_lwpid = *(uint32_t *)pkt;
uint64_t _final_rip = *(uint64_t *)(pkt + 0x030 + 0x88);
uint8_t _final_sig = sig;
int _is_fatal = DBG_STORM_FATAL_SIGS(_final_sig);
int _match_last = (_final_lwpid == g_storm_lwpid) &&
(_final_rip == g_storm_rip) &&
(_final_sig == g_storm_sig);
if (_is_fatal && _match_last) {
if (++g_storm_count > DBG_STORM_LIMIT) {
g_debug_phase = DEBUG_PHASE_EVENT_STOPPED;
DDE_RETURN(0);
}
} else {
g_storm_count = 0;
}
g_storm_lwpid = _final_lwpid;
g_storm_rip = _final_rip;
g_storm_sig = _final_sig;
}
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);
#undef DDE_RETURN
}
int debug_handle(int fd, struct cmd_packet *packet) {
switch (packet->cmd) {
case 0xBDBB0500u: return debug_process_stop_handle(fd, packet);
case 0xBDBB0001u: return debug_attach_handle(fd, packet);
case 0xBDBB0002u: return debug_detach_handle(fd, packet);
case 0xBDBB0003u: return debug_set_breakpoint_handle(fd, packet);
case 0xBDBB0004u: return debug_set_watchpoint_handle(fd, packet);
case 0xBDBB0005u: return debug_get_thread_list_handle(fd, packet);
case 0xBDBB0006u: return debug_suspend_thread_handle(fd, packet);
case 0xBDBB0007u: return debug_resume_thread_handle(fd, packet);
case 0xBDBB0008u: return debug_getregs_handle(fd, packet);
case 0xBDBB0009u: return debug_setregs_handle(fd, packet);
case 0xBDBB000Au: return debug_getfpregs_handle(fd, 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);
case 0xBDBB0013u: return debug_step_thread_handle(fd, packet);
}
net_send_int32(fd, CMD_ERROR);
return 0;
}