Files
ps5debug-NG/debugger/source/proc_remote.c
T

848 lines
28 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
#include "protocol.h"
#include "sdk_shim.h"
#include "proc.h"
#include "kern_rw_fast.h"
#include "debug_state.h"
#include <stdarg.h>
#include <sys/mman.h>
#define PCRK_SIZEOF_REG 0xb0
#define PCRK_R_R10 0x28
#define PCRK_R_R9 0x30
#define PCRK_R_R8 0x38
#define PCRK_R_RDI 0x40
#define PCRK_R_RSI 0x48
#define PCRK_R_RDX 0x60
#define PCRK_R_RCX 0x68
#define PCRK_R_RAX 0x70
#define PCRK_R_RIP 0x88
#define PCRK_R_RSP 0xa0
#define NID_LIBKERNEL_SYSCALL "W0xkN0+ZkCE"
static void *g_proc_remote_mutex = (void *)0;
static int g_proc_remote_mutex_init_done = 0;
static void mutex_lock(void) {
if (!g_proc_remote_mutex_init_done) {
scePthreadMutexInit(&g_proc_remote_mutex, (void *)0, "proc_remote");
g_proc_remote_mutex_init_done = 1;
}
scePthreadMutexLock(&g_proc_remote_mutex);
}
static void mutex_unlock(void) { scePthreadMutexUnlock(&g_proc_remote_mutex); }
static void debugger_usleep(unsigned long us) { sceKernelUsleep((unsigned int)us); }
#define RPC_WAIT_POLL_US 250u
#define RPC_WAIT_POLLS 8000u
static int rpc_wait4_bounded(int pid, int *out_status)
{
int status = 0;
for (unsigned int i = 0; i < RPC_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;
debugger_usleep(RPC_WAIT_POLL_US);
}
return 0;
}
static uint8_t rpc_wait_signal(int status)
{
return (uint8_t)((status >> 8) & 0xFF);
}
extern void *g_debug_arbiter_mutex;
static int g_rpc_arbiter_held = 0;
static int g_rpc_debug_target = 0;
static int g_rpc_broken = 0;
static void rpc_arbiter_lock(void)
{
if (!g_rpc_arbiter_held) {
scePthreadMutexLock(&g_debug_arbiter_mutex);
g_rpc_arbiter_held = 1;
}
}
static void rpc_arbiter_unlock(void)
{
if (g_rpc_arbiter_held) {
g_rpc_arbiter_held = 0;
scePthreadMutexUnlock(&g_debug_arbiter_mutex);
}
g_rpc_debug_target = 0;
g_rpc_broken = 0;
}
static void rpc_mark_broken(void)
{
if (g_rpc_debug_target) {
g_rpc_broken = 1;
g_debug_phase = DEBUG_PHASE_BROKEN;
}
}
extern long __crt_syscall(long sysno, ...);
long ptrace_raw(int op, int pid, void *addr, int data) {
return ps5debug_syscall(26 , (long)op, (long)pid, (long)addr, (long)data, 0L, 0L);
}
#if 0
extern int sceKernelSendNotificationRequest(int unused, void *req,
size_t reqsz, int flag);
static void caveat4_diag(int self_pid) {
char buf[1024];
uint8_t sdk_caps [16] = {0};
uint8_t fast_caps[16] = {0};
intptr_t sdk_kproc = kernel_get_proc(self_pid);
intptr_t fast_kproc = kernel_get_proc_fast(self_pid);
intptr_t sdk_ucred = kernel_get_proc_ucred(self_pid);
intptr_t fast_ucred = kernel_get_proc_ucred_fast(self_pid);
uint64_t sdk_authid = kernel_get_ucred_authid(self_pid);
uint64_t fast_authid = kernel_get_ucred_authid_fast(self_pid);
int sdk_caps_rc = kernel_get_ucred_caps(self_pid, sdk_caps);
int fast_caps_rc = kernel_get_ucred_caps_fast(self_pid, fast_caps);
uint64_t saved_authid = fast_authid;
uint8_t saved_caps[16];
memcpy(saved_caps, fast_caps, 16);
uint8_t all_ones[16];
memset(all_ones, 0xFF, 16);
int set_authid_rc = kernel_set_ucred_authid_fast(self_pid, 0x4800000000010003ULL);
uint64_t authid_after_set = kernel_get_ucred_authid_fast(self_pid);
uint64_t sdk_authid_after = kernel_get_ucred_authid(self_pid);
int set_caps_rc = kernel_set_ucred_caps_fast(self_pid, all_ones);
uint8_t caps_after_set[16] = {0};
kernel_get_ucred_caps_fast(self_pid, caps_after_set);
int restore_authid_rc = kernel_set_ucred_authid_fast(self_pid, saved_authid);
int restore_caps_rc = kernel_set_ucred_caps_fast(self_pid, saved_caps);
uint64_t authid_after_restore = kernel_get_ucred_authid_fast(self_pid);
uint8_t caps_after_restore[16] = {0};
kernel_get_ucred_caps_fast(self_pid, caps_after_restore);
int snlen = snprintf(buf, sizeof(buf),
"C4-DIAG pid=%d\n"
"READ kproc: SDK=%lx FAST=%lx %s\n"
"READ ucred: SDK=%lx FAST=%lx %s\n"
"READ authid: SDK=%lx FAST=%lx %s\n"
"READ caps: SDK rc=%d %02x%02x%02x%02x%02x%02x%02x%02x\n"
" FAST rc=%d %02x%02x%02x%02x%02x%02x%02x%02x %s\n"
"WRITE set_authid(debug) FAST rc=%d after_FAST=%lx after_SDK=%lx\n"
"WRITE set_caps(0xFF*16) FAST rc=%d after_caps=%02x%02x%02x%02x%02x%02x%02x%02x\n"
"RESTORE set_authid(saved) FAST rc=%d\n"
"RESTORE set_caps(saved) FAST rc=%d\n"
"VERIFY authid: %lx %s\n"
"VERIFY caps : %02x%02x%02x%02x%02x%02x%02x%02x %s\n",
self_pid,
(unsigned long)sdk_kproc, (unsigned long)fast_kproc,
(sdk_kproc == fast_kproc) ? "MATCH" : "DIFFER",
(unsigned long)sdk_ucred, (unsigned long)fast_ucred,
(sdk_ucred == fast_ucred) ? "MATCH" : "DIFFER",
(unsigned long)sdk_authid, (unsigned long)fast_authid,
(sdk_authid == fast_authid) ? "MATCH" : "DIFFER",
sdk_caps_rc,
sdk_caps[0], sdk_caps[1], sdk_caps[2], sdk_caps[3],
sdk_caps[4], sdk_caps[5], sdk_caps[6], sdk_caps[7],
fast_caps_rc,
fast_caps[0], fast_caps[1], fast_caps[2], fast_caps[3],
fast_caps[4], fast_caps[5], fast_caps[6], fast_caps[7],
(memcmp(sdk_caps, fast_caps, 16) == 0) ? "MATCH" : "DIFFER",
set_authid_rc, (unsigned long)authid_after_set, (unsigned long)sdk_authid_after,
set_caps_rc,
caps_after_set[0], caps_after_set[1], caps_after_set[2], caps_after_set[3],
caps_after_set[4], caps_after_set[5], caps_after_set[6], caps_after_set[7],
restore_authid_rc, restore_caps_rc,
(unsigned long)authid_after_restore,
(authid_after_restore == saved_authid) ? "OK" : "BAD",
caps_after_restore[0], caps_after_restore[1], caps_after_restore[2], caps_after_restore[3],
caps_after_restore[4], caps_after_restore[5], caps_after_restore[6], caps_after_restore[7],
(memcmp(caps_after_restore, saved_caps, 16) == 0) ? "OK" : "BAD");
(void)snlen;
klog_puts(buf);
typedef struct { char useless1[45]; char message[3075]; } req_t;
req_t req;
memset(&req, 0, sizeof(req));
strncpy(req.message, buf, sizeof(req.message) - 1);
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
}
}
#endif
int elev_save_and_set(struct elev_state *st) {
int self = getpid();
st->valid = 0;
intptr_t ucred = kernel_get_proc_ucred_fast((pid_t)self);
if (ucred == 0) return -1;
if (kernel_copyout_fast(ucred + 0x58, &st->saved_authid, sizeof(st->saved_authid)) != 0) return -1;
if (st->saved_authid == 0) return -1;
if (kernel_copyout_fast(ucred + 0x60, st->saved_caps, 0x10) != 0) return -1;
uint64_t priv_authid = 0x4800000000010003ULL;
if (kernel_copyin_fast(&priv_authid, ucred + 0x58, sizeof(priv_authid)) != 0) return -1;
uint8_t all_ones[16];
memset(all_ones, 0xFF, 16);
if (kernel_copyin_fast(all_ones, ucred + 0x60, 0x10) != 0) {
kernel_copyin_fast(&st->saved_authid, ucred + 0x58, sizeof(st->saved_authid));
return -1;
}
st->valid = 1;
return 0;
}
void elev_restore(struct elev_state *st) {
if (!st->valid) return;
int self = getpid();
intptr_t ucred = kernel_get_proc_ucred_fast((pid_t)self);
if (ucred != 0) {
kernel_copyin_fast(&st->saved_authid, ucred + 0x58, sizeof(st->saved_authid));
kernel_copyin_fast(st->saved_caps, ucred + 0x60, 0x10);
}
st->valid = 0;
}
long ptrace_elev(int op, int pid, void *addr, int data) {
struct elev_state es;
if (elev_save_and_set(&es) != 0) return -1;
long rc = ptrace_raw(op, pid, addr, data);
elev_restore(&es);
return rc;
}
extern int *__error(void);
int kern_ptrace_attach_and_wait(int pid)
{
if (ptrace_elev(0x0A , pid, 0, 0) == -1) return -1;
static int s_first_w4_diag = 0;
int wait_status = 0;
*__error() = 0;
int wait_rc = rpc_wait4_bounded(pid, &wait_status);
int wait_errno = *__error();
if (!s_first_w4_diag) {
s_first_w4_diag = 1;
klog_printf("[diag] kern_ptrace_attach_and_wait FIRST - pid=%d wait4 rc=%d errno=%d status=0x%x\n",
pid, wait_rc, wait_errno, wait_status);
}
return (wait_rc == 1) ? 0 : -1;
}
static int resume_app_via_self_id(int pid)
{
int app_id = 0;
int rc = _sceApplicationGetAppId(pid, &app_id);
if (rc != 0) return rc;
return sceApplicationContinue(app_id);
}
int pt_detach(int pid) {
return (ptrace_elev(0x0B , pid, 0, 0) == -1) ? -1 : 0;
}
extern void *curdbgctx;
struct dbgctx_pid { uint32_t pid; };
int proc_rpc_stop_target(int wpid)
{
rpc_arbiter_lock();
g_rpc_debug_target = 0;
g_rpc_broken = 0;
struct dbgctx_pid *ctx = (struct dbgctx_pid *)curdbgctx;
uint32_t state = g_stopgo_last_signal;
int hit_second_kill = 0;
if (ctx != NULL) {
int target_pid = (int)ctx->pid;
if (target_pid == wpid) {
/* A debugger-visible stop belongs to the client. Continuing it here
would consume the breakpoint event and briefly run the game behind
the UI. Only a target known to be RUNNING may be borrowed for RPC. */
if (g_debug_phase != DEBUG_PHASE_RUNNING) {
rpc_arbiter_unlock();
return PROC_RPC_ERR_DEBUG_STATE;
}
g_rpc_debug_target = 1;
/* The command socket is serviced before the debug-event poll. An
event can therefore already be queued while the last published
phase is still RUNNING. Claim it before sending our SIGSTOP so
the normal dispatcher, not the RPC path, remains its owner. */
int pending_status = 0;
int pending_rc = wait4(wpid, &pending_status, 1, NULL);
if (pending_rc < 0) {
rpc_mark_broken();
rpc_arbiter_unlock();
return -1;
}
if (pending_rc > 0) {
g_debug_pending_wait_pid = (uint32_t)wpid;
g_debug_pending_wait_status = pending_status;
g_debug_pending_wait_valid = 1;
g_debug_phase = DEBUG_PHASE_EVENT_PENDING;
rpc_arbiter_unlock();
return PROC_RPC_ERR_DEBUG_STATE;
}
g_debug_phase = DEBUG_PHASE_RPC_ACTIVE;
if (kill(wpid, 0x11) != 0) {
rpc_mark_broken();
rpc_arbiter_unlock();
return -1;
}
int status = 0;
int wait_rc = rpc_wait4_bounded(wpid, &status);
if (wait_rc != 1) {
rpc_mark_broken();
rpc_arbiter_unlock();
return -1;
}
if (rpc_wait_signal(status) != 0x11) {
/* A breakpoint/watchpoint won the race with our SIGSTOP. Preserve
the consumed wait status so dispatch_debug_events handles it on
the normal callback path, then fail this RPC without retrying. */
g_debug_pending_wait_pid = (uint32_t)wpid;
g_debug_pending_wait_status = status;
g_debug_pending_wait_valid = 1;
g_debug_phase = DEBUG_PHASE_EVENT_PENDING;
rpc_arbiter_unlock();
return PROC_RPC_ERR_DEBUG_STATE;
}
return 0;
}
if (state == 0x11 && g_stopgo_target_pid == (uint32_t)wpid) {
resume_app_via_self_id(wpid);
hit_second_kill = 1;
}
} else if (state == 0x11 && g_stopgo_target_pid == (uint32_t)wpid) {
resume_app_via_self_id(wpid);
hit_second_kill = 1;
}
if (hit_second_kill) {
kill(wpid, 0x13 );
if (rpc_wait4_bounded(wpid, NULL) != 1) {
rpc_arbiter_unlock();
return -1;
}
}
int rc = kern_ptrace_attach_and_wait(wpid);
if (rc != 0) {
(void)pt_detach(wpid);
rpc_arbiter_unlock();
return -1;
}
return 0;
}
int proc_detach_or_stop(int pid)
{
struct dbgctx_pid *ctx = (struct dbgctx_pid *)curdbgctx;
uint32_t state = g_stopgo_last_signal;
if (g_rpc_debug_target && ctx != NULL && (int)ctx->pid == pid) {
resume_app_via_self_id(pid);
int rc = (int)ptrace_elev(7, pid, (void *)1, 0);
int failed = g_rpc_broken || rc != 0;
g_debug_phase = failed ? DEBUG_PHASE_BROKEN : DEBUG_PHASE_RUNNING;
rpc_arbiter_unlock();
return failed ? -1 : 0;
}
if (state == 0x11) {
uint32_t bound_pid = g_stopgo_target_pid;
resume_app_via_self_id(pid);
int rc = pt_detach(pid);
if (rc != 0) {
rpc_arbiter_unlock();
return -1;
}
if (bound_pid == (uint32_t)pid) {
kill(pid, 0x11);
if (rpc_wait4_bounded(pid, NULL) != 1) {
rpc_arbiter_unlock();
return -1;
}
}
rpc_arbiter_unlock();
return 0;
}
resume_app_via_self_id(pid);
int rc = pt_detach(pid);
rpc_arbiter_unlock();
return rc ? -1 : 0;
}
__attribute__((noinline))
uint64_t proc_call_remote_kern(int pid, uint64_t func_addr, ...)
{
uint8_t saved_regs [PCRK_SIZEOF_REG];
uint8_t modified_regs[PCRK_SIZEOF_REG];
uint64_t kdata;
int status = 0;
void *proc = (void *)kernel_get_proc_fast(pid);
if (proc == 0) return (uint64_t)-1;
uint64_t kdata_u = kernel_dynlib_mapbase_addr_by_proc_fast(proc, 0x2001);
if (kdata_u == (uint64_t)-1 || kdata_u == 0) return (uint64_t)-1;
kdata = kdata_u;
if (ptrace_elev(0x21 , pid, saved_regs, 0) != 0)
return (uint64_t)-1;
memcpy(modified_regs, saved_regs, PCRK_SIZEOF_REG);
*(uint64_t *)(modified_regs + PCRK_R_RIP) = func_addr;
*(uint64_t *)(modified_regs + PCRK_R_RSP) -= 8;
{
va_list ap;
va_start(ap, func_addr);
*(uint64_t *)(modified_regs + PCRK_R_RDI) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_RSI) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_RDX) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_RCX) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_R8 ) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_R9 ) = va_arg(ap, uint64_t);
va_end(ap);
}
if (ptrace_elev(0x22 , pid, modified_regs, 0) != 0)
return (uint64_t)-1;
{
uint64_t target_rsp = *(uint64_t *)(modified_regs + PCRK_R_RSP);
sys_proc_rw_w1((uint64_t)pid, target_rsp, 8, &kdata, 0);
}
if (ptrace_elev(7 , pid, (void *)1, 0) != 0
|| rpc_wait4_bounded(pid, &status) != 1) {
rpc_mark_broken();
return (uint64_t)-1;
}
if (ptrace_elev(0x21 , pid, modified_regs, 0) != 0)
return (uint64_t)-1;
if (ptrace_elev(0x22 , pid, saved_regs, 0) != 0)
return (uint64_t)-1;
return *(uint64_t *)(modified_regs + PCRK_R_RAX);
}
__attribute__((noinline))
uint64_t proc_call_remote_sys(int pid, int sysno, ...)
{
uint8_t saved_regs [PCRK_SIZEOF_REG];
uint8_t modified_regs[PCRK_SIZEOF_REG];
uint64_t nid_addr = kernel_dynlib_resolve_fast(pid, 1, NID_LIBKERNEL_SYSCALL);
if (nid_addr == 0) {
nid_addr = kernel_dynlib_resolve_fast(pid, 0x2001, NID_LIBKERNEL_SYSCALL);
if (nid_addr == 0) return (uint64_t)-1;
}
int step_pid = pid;
int numlwps = (int)ptrace_elev(0x0E , pid, 0, 0);
if (numlwps > 0) {
int *lwp_buf = (int *)malloc((unsigned long)numlwps * 4u);
if (lwp_buf != 0) {
ptrace_elev(0x0F , pid, lwp_buf, numlwps);
step_pid = lwp_buf[0];
free(lwp_buf);
}
}
if (ptrace_elev(0x21 , pid, saved_regs, 0) != 0)
return (uint64_t)-1;
nid_addr += 0x0A;
memcpy(modified_regs, saved_regs, PCRK_SIZEOF_REG);
*(uint64_t *)(modified_regs + PCRK_R_RIP) = (uint64_t)nid_addr;
*(uint64_t *)(modified_regs + PCRK_R_RAX) = (uint64_t)(int64_t)sysno;
{
va_list ap;
va_start(ap, sysno);
*(uint64_t *)(modified_regs + PCRK_R_RDI) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_RSI) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_RDX) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_R10) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_R8 ) = va_arg(ap, uint64_t);
*(uint64_t *)(modified_regs + PCRK_R_R9 ) = va_arg(ap, uint64_t);
va_end(ap);
}
if (ptrace_elev(0x22 , pid, modified_regs, 0) != 0)
return (uint64_t)-1;
int success = (*(uint64_t *)(modified_regs + PCRK_R_RSP) >
*(uint64_t *)(saved_regs + PCRK_R_RSP));
for (int retry = 0; retry < 3 && !success; retry++) {
if (ptrace_elev(9 , step_pid, (void *)(uintptr_t)1, 0) != 0)
return (uint64_t)-1;
if (rpc_wait4_bounded(pid, NULL) != 1) {
rpc_mark_broken();
return (uint64_t)-1;
}
if (ptrace_elev(0x21, pid, modified_regs, 0) != 0)
return (uint64_t)-1;
success = (*(uint64_t *)(modified_regs + PCRK_R_RSP) >
*(uint64_t *)(saved_regs + PCRK_R_RSP));
}
if (!success) {
if (ptrace_elev(9, step_pid, (void *)(uintptr_t)1, 0) != 0)
return (uint64_t)-1;
if (rpc_wait4_bounded(pid, NULL) != 1) {
rpc_mark_broken();
return (uint64_t)-1;
}
if (ptrace_elev(0x21, pid, modified_regs, 0) != 0)
return (uint64_t)-1;
}
int set_rc = (int)ptrace_elev(0x22, pid, saved_regs, 0);
uint64_t result = success
? *(uint64_t *)(modified_regs + PCRK_R_RAX)
: (uint64_t)-1;
if (set_rc != 0) result = (uint64_t)-1;
return result;
}
void *freebsd_mmap(unsigned long pid, void *addr, unsigned long length,
int prot, int flags, int fd, long offset)
{
return (void *)proc_call_remote_sys(
(int)pid, 0x1DD, addr, length, prot, flags, fd, offset, 0L);
}
int freebsd_munmap(int pid, unsigned long addr, unsigned long len)
{
return (int)proc_call_remote_sys(pid, 0x49, addr, len);
}
int sys_proc_alloc(uint32_t pid, void **out_addr_ptr, uint64_t length)
{
length = (length + 0x3fffull) & ~0x3fffull;
if (out_addr_ptr == 0) return 1;
int my_pid = getpid();
if (my_pid == (int)pid) {
void *result = mmap(*out_addr_ptr, (size_t)length, 7, 0x1002, -1, 0);
*out_addr_ptr = result;
uint64_t a = (uint64_t)result - 1;
if (a > 0xfffffffffffffffdull) return -1;
memset(result, 0, (size_t)length);
return 0;
}
int stop_rc = proc_rpc_stop_target((int)pid);
if (stop_rc != 0) return stop_rc;
void *result = freebsd_mmap((unsigned long)pid, *out_addr_ptr,
(unsigned long)length, 7, 0x1002, -1, 0);
*out_addr_ptr = result;
if ((intptr_t)result != -1L) {
kernel_mprotect((pid_t)pid, (intptr_t)result, (size_t)length, 7);
proc_call_remote_sys((int)pid, 203, (long)(intptr_t)result, (long)length);
}
if (proc_detach_or_stop((int)pid) != 0) return -1;
void *final = *out_addr_ptr;
uint64_t a = (uint64_t)final - 1;
if (a > 0xfffffffffffffffdull) return -1;
return 0;
}
int sys_proc_free(uint32_t pid, void *addr, uint64_t length)
{
length = (length + 0x3fffull) & ~0x3fffull;
if (addr == 0) return 1;
int my_pid = getpid();
if (my_pid == (int)pid) {
return munmap(addr, (size_t)length);
}
int stop_rc = proc_rpc_stop_target((int)pid);
if (stop_rc != 0) return stop_rc;
int rc = freebsd_munmap((int)pid, (unsigned long)(uintptr_t)addr,
(unsigned long)length);
if (proc_detach_or_stop((int)pid) != 0) return -1;
return (rc >= 0) ? rc : -1;
}
int sys_proc_call(int pid, struct sys_proc_call_args *args)
{
int stop_rc = proc_rpc_stop_target(pid);
if (stop_rc != 0) return stop_rc;
uint64_t ret = proc_call_remote_kern(
pid, args->rip,
args->rdi, args->rsi, args->rdx, args->rcx, args->r8, args->r9);
if (ret == (uint64_t)-1) {
proc_detach_or_stop(pid);
return -1;
}
args->rax = ret;
int rc = proc_detach_or_stop(pid);
return rc ? -1 : 0;
}
#define ARENA_ALIGN 0x4000ULL
#define ARENA_CHUNK (16ULL * 1024 * 1024)
#define ARENA_NPID 4
#define ALLOC_FILL_RETRIES 5
struct arena_seg { uint64_t base, size, used; struct arena_seg *next; };
struct arena_blk { uint64_t addr, size; struct arena_blk *next; };
struct arena_pid {
int inuse;
uint32_t pid;
struct arena_seg *segs;
struct arena_blk *freelist;
uint64_t next_seg_size; /* grow-on-demand target; 0 = use request size */
};
static struct arena_pid g_arenas[ARENA_NPID];
static int g_arena_enabled = 1;
static uint64_t arena_round(uint64_t n) { return (n + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1); }
static struct arena_pid *arena_get(uint32_t pid) {
int slot = -1;
for (int i = 0; i < ARENA_NPID; i++) {
if (g_arenas[i].inuse && g_arenas[i].pid == pid) return &g_arenas[i];
if (!g_arenas[i].inuse && slot < 0) slot = i;
}
if (slot < 0) slot = 0;
struct arena_pid *ap = &g_arenas[slot];
for (struct arena_seg *s = ap->segs; s; ) { struct arena_seg *n = s->next; free(s); s = n; }
for (struct arena_blk *b = ap->freelist; b; ) { struct arena_blk *n = b->next; free(b); b = n; }
ap->inuse = 1; ap->pid = pid; ap->segs = 0; ap->freelist = 0; ap->next_seg_size = 0;
return ap;
}
static struct arena_pid *arena_find(uint32_t pid) {
for (int i = 0; i < ARENA_NPID; i++)
if (g_arenas[i].inuse && g_arenas[i].pid == pid) return &g_arenas[i];
return 0;
}
static int do_real_alloc(uint32_t pid, uint64_t *out, uint64_t length, uint64_t hint) {
void *p = (void *)(uintptr_t)hint;
int rc = sys_proc_alloc(pid, &p, length);
if (rc == PROC_RPC_ERR_DEBUG_STATE) {
*out = (uint64_t)(uintptr_t)p;
return rc;
}
if (rc != 0) {
p = (void *)(uintptr_t)hint;
debugger_usleep(40000);
int retries = ALLOC_FILL_RETRIES;
for (;;) {
rc = sys_proc_alloc(pid, &p, length);
if (rc == PROC_RPC_ERR_DEBUG_STATE) {
*out = (uint64_t)(uintptr_t)p;
return rc;
}
if (rc == 0) break;
p = (void *)(uintptr_t)hint;
debugger_usleep(40000);
if (--retries == 0) { *out = (uint64_t)(uintptr_t)p; return rc; }
}
}
*out = (uint64_t)(uintptr_t)p;
return 0;
}
static int do_real_free(uint32_t pid, uint64_t addr, uint64_t length) {
int rc = sys_proc_free(pid, (void *)(uintptr_t)addr, length);
if (rc == PROC_RPC_ERR_DEBUG_STATE) return rc;
if (rc < 0) {
int retries = 11;
do {
debugger_usleep(40000);
int retry_rc = sys_proc_free(pid, (void *)(uintptr_t)addr, length);
if (retry_rc == PROC_RPC_ERR_DEBUG_STATE) return retry_rc;
if (retry_rc >= 0) { rc = 0; break; }
} while (--retries > 0);
}
return rc;
}
static int arena_alloc(uint32_t pid, uint64_t length, uint64_t *out) {
uint64_t size = arena_round(length ? length : ARENA_ALIGN);
struct arena_pid *ap = arena_get(pid);
struct arena_blk **pp = &ap->freelist;
for (struct arena_blk *b = ap->freelist; b; pp = &b->next, b = b->next) {
if (b->size >= size) {
if (b->size >= size + ARENA_ALIGN) { *out = b->addr; b->addr += size; b->size -= size; }
else { *out = b->addr; *pp = b->next; free(b); }
return 0;
}
}
for (struct arena_seg *s = ap->segs; s; s = s->next)
if (s->size - s->used >= size) { *out = s->base + s->used; s->used += size; return 0; }
/* Segment sizing (grow-on-demand): start at EXACTLY the request size - no
speculative over-reservation, so the first fill for a pid is a single
small hijack, identical exposure to the pre-arena path (proven safe on
exec-tight games). Grow geometrically as a pid keeps allocating so heavy
clients still amortize to few hijacks. NEVER a speculative failing probe:
a single-shot miss retreats to the request size and stops growing this
pid (a failing probe is a hijack that can catch a lock-holding thread). */
uint64_t want = ap->next_seg_size;
if (want < size) want = size;
if (want > ARENA_CHUNK) want = ARENA_CHUNK;
uint64_t seg_size = want;
uint64_t base = 0;
int got = 0;
{
void *p = (void *)(uintptr_t)0x4000;
int alloc_rc = sys_proc_alloc(pid, &p, seg_size);
if (alloc_rc == PROC_RPC_ERR_DEBUG_STATE) return alloc_rc;
if (alloc_rc == 0) { base = (uint64_t)(uintptr_t)p; got = 1; }
}
if (!got) {
/* Retreat to exactly the request size (the proven-safe, 1-hijack size)
with one backoff retry for transient contention, and freeze growth
for this pid so we never repeat a speculative probe. */
seg_size = size;
if (do_real_alloc(pid, &base, seg_size, 0x4000) != 0) return -1;
ap->next_seg_size = size;
} else {
ap->next_seg_size = (seg_size < ARENA_CHUNK) ? (seg_size << 1) : ARENA_CHUNK;
}
struct arena_seg *s = (struct arena_seg *)malloc(sizeof(*s));
if (!s) { *out = base; return 0; }
s->base = base; s->size = seg_size; s->used = size; s->next = ap->segs; ap->segs = s;
*out = base;
return 0;
}
static int arena_free(uint32_t pid, uint64_t addr, uint64_t length) {
struct arena_pid *ap = arena_find(pid);
if (!ap) return -1;
int owned = 0;
for (struct arena_seg *s = ap->segs; s; s = s->next)
if (addr >= s->base && addr < s->base + s->size) { owned = 1; break; }
if (!owned) return -1;
uint64_t size = arena_round(length ? length : ARENA_ALIGN);
struct arena_blk *b = (struct arena_blk *)malloc(sizeof(*b));
if (b) { b->addr = addr; b->size = size; b->next = ap->freelist; ap->freelist = b; }
return 0;
}
int proc_arena_set(int on, char *buf, int bufsz) {
g_arena_enabled = on ? 1 : 0;
int n = snprintf(buf, (size_t)bufsz,
"arena %s (1 hijack per %lluMB segment, sub-alloc 0 hijacks; PLAIN alloc only, hinted passes through)\n",
g_arena_enabled ? "ENABLED" : "disabled", (unsigned long long)(ARENA_CHUNK >> 20));
if (n < 0) n = 0;
if (n > bufsz) n = bufsz;
klog_puts(buf);
return n;
}
int proc_remote_alloc(uint32_t pid, uint64_t *out_addr,
uint64_t length, uint64_t hint)
{
mutex_lock();
int rc;
if (g_arena_enabled && hint == 0x4000)
rc = arena_alloc(pid, length, out_addr);
else
rc = do_real_alloc(pid, out_addr, length, hint);
mutex_unlock();
return rc;
}
int proc_remote_free(uint32_t pid, uint64_t addr, uint64_t length)
{
mutex_lock();
int rc;
if (g_arena_enabled && arena_free(pid, addr, length) == 0)
rc = 0;
else
rc = do_real_free(pid, addr, length);
mutex_unlock();
return rc;
}
int proc_remote_call(int pid, struct sys_proc_call_args *args)
{
mutex_lock();
int rc = sys_proc_call(pid, args);
if (rc == PROC_RPC_ERR_DEBUG_STATE) {
mutex_unlock();
return rc;
}
if (rc != 0) {
debugger_usleep(40000);
int retries = 6;
for (;;) {
rc = sys_proc_call(pid, args);
if (rc == PROC_RPC_ERR_DEBUG_STATE) break;
if (rc == 0) break;
debugger_usleep(40000);
if (--retries == 0) break;
}
}
mutex_unlock();
return rc;
}