[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) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 19:48:47 +02:00
parent 35f58b147f
commit 94115aba1c

View File

@@ -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<ppc::PPCContext_s*>(raw_context);
uint32_t r3 = static_cast<uint32_t>(ctx->r[3]);
uint32_t r4 = static_cast<uint32_t>(ctx->r[4]);
uint32_t r5 = static_cast<uint32_t>(ctx->r[5]);
uint32_t r6 = static_cast<uint32_t>(ctx->r[6]);
uint32_t r7 = static_cast<uint32_t>(ctx->r[7]);
uint32_t r8 = static_cast<uint32_t>(ctx->r[8]);
uint32_t r9 = static_cast<uint32_t>(ctx->r[9]);
uint32_t r10 = static_cast<uint32_t>(ctx->r[10]);
uint32_t lr = static_cast<uint32_t>(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<uint32_t>(host + off + 0);
uint32_t d1 = xe::load_and_swap<uint32_t>(host + off + 4);
uint32_t d2 = xe::load_and_swap<uint32_t>(host + off + 8);
uint32_t d3 = xe::load_and_swap<uint32_t>(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: