From 94115aba1c4c9c5a90d442637df473c12f79403d Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 6 Jun 2026 19:48:47 +0200 Subject: [PATCH] [Audit] round 7: JIT-entry probe for sub_824F7800 args Adds cvar audit_log_sub824F7800_args (off by default). When set, the x64 JIT emits one CallNative to AuditLogSub824F7800Args at the start of the JIT-compiled body of guest function 0x824F7800 (silph::WorkerCtx ctor's immediate caller per audit-058). The hook reads r3..r10, LR, and 64 bytes at host(r3) from PPCContext and writes them to XELOGKERNEL. The hook is placed *after* the JIT prolog (stack push, GUEST_RET_ADDR / GUEST_CALL_RET_ADDR setup, optional trace-functions block) and *before* the first HIR body instruction, so r3..r10 and lr in PPCContext still reflect the caller's args (no LOAD/STORE_CONTEXT has executed yet inside the callee). The compile-time gate `current_guest_function_ == 0x824F7800` makes this a zero-cost no-op for all other functions and a single extra CallNative for the one target. Audit-059 round 7 probe captures r3..r10 + LR for a-prime synthetic replay of sub_824F7800 from a host hook in xenia-rs. Run with: --audit_handle_lifecycle=true --audit_log_sub824F7800_args=true Co-Authored-By: Claude Opus 4.7 (1M context) --- src/xenia/cpu/backend/x64/x64_emitter.cc | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index 694c92b4e..6894edfe9 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -41,6 +41,13 @@ DEFINE_bool(debugprint_trap_log, false, "Log debugprint traps to the active debugger", "CPU"); +DEFINE_bool(audit_log_sub824F7800_args, false, + "Emit one XELOGKERNEL line per JIT-compiled entry to guest function " + "0x824F7800 (silph::WorkerCtx ctor's immediate caller; per audit-058 " + "fires exactly 1x after DiscImageDevice::ResolvePath). Dumps " + "r3..r10, LR, and 64 bytes at host(r3). Audit-059 round 7 probe — " + "off by default.", + "Auditing"); DEFINE_bool(ignore_undefined_externs, true, "Don't exit when an undefined extern is called.", "CPU"); DEFINE_bool(emit_source_annotations, false, @@ -270,6 +277,15 @@ bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) { count on no other code modifying it. mov(GetMembaseReg(), qword[GetContextReg() + offsetof(ppc::PPCContext, virtual_membase)]); */ + // Audit-059 round 7: one-shot dump of r3..r10/LR at entry to sub_824F7800. + // Compile-time gated on guest function address; runtime gated on the cvar. + // Emits *before* any body instruction runs, so r3..r10 / LR in PPCContext + // still reflect the caller's args (no LOAD/STORE_CONTEXT has executed yet). + if (cvars::audit_log_sub824F7800_args && + current_guest_function_ == 0x824F7800u) { + extern uint64_t AuditLogSub824F7800Args(void* raw_context, uint64_t arg0); + CallNative(AuditLogSub824F7800Args, 0); + } // Body. auto block = builder->first_block(); synchronize_stack_on_next_instruction_ = false; @@ -438,6 +454,50 @@ uint64_t TrapDebugBreak(void* raw_context, uint64_t address) { return 0; } +// Audit-059 round 7 probe: dump r3..r10, LR, and 64 bytes at host(r3) when +// the guest hits the JIT-compiled entry of sub_824F7800 (silph::WorkerCtx +// ctor's immediate caller). Per audit-058 this fires exactly once after the +// DiscImageDevice::ResolvePath(\dat\movie) sequence on canary. +uint64_t AuditLogSub824F7800Args(void* raw_context, uint64_t /*unused*/) { + auto* ctx = reinterpret_cast(raw_context); + uint32_t r3 = static_cast(ctx->r[3]); + uint32_t r4 = static_cast(ctx->r[4]); + uint32_t r5 = static_cast(ctx->r[5]); + uint32_t r6 = static_cast(ctx->r[6]); + uint32_t r7 = static_cast(ctx->r[7]); + uint32_t r8 = static_cast(ctx->r[8]); + uint32_t r9 = static_cast(ctx->r[9]); + uint32_t r10 = static_cast(ctx->r[10]); + uint32_t lr = static_cast(ctx->lr); + + uint32_t tid = ctx->thread_state ? ctx->thread_state->thread_id() : 0u; + + XELOGKERNEL( + "AUDIT-HLC sub_824F7800 entry tid={:08X} r3={:08X} r4={:08X} r5={:08X} " + "r6={:08X} r7={:08X} r8={:08X} r9={:08X} r10={:08X} lr={:08X}", + tid, r3, r4, r5, r6, r7, r8, r9, r10, lr); + + // Dump 64 bytes at host(r3) if r3 looks like a plausible guest VA. + if (r3 >= 0x10000 && r3 < 0xE0000000) { + uint8_t* host = ctx->TranslateVirtual(r3); + if (host) { + for (uint32_t off = 0; off < 0x40; off += 16) { + uint32_t d0 = xe::load_and_swap(host + off + 0); + uint32_t d1 = xe::load_and_swap(host + off + 4); + uint32_t d2 = xe::load_and_swap(host + off + 8); + uint32_t d3 = xe::load_and_swap(host + off + 12); + XELOGKERNEL("AUDIT-HLC sub_824F7800 r3+{:02X}: {:08X} {:08X} {:08X} {:08X}", + off, d0, d1, d2, d3); + } + } else { + XELOGKERNEL("AUDIT-HLC sub_824F7800 r3 translate failed"); + } + } else { + XELOGKERNEL("AUDIT-HLC sub_824F7800 r3 out of VA range, skipping dump"); + } + return 0; +} + void X64Emitter::Trap(uint16_t trap_type) { switch (trap_type) { case 20: