From 5427f14f1bf0012e9ddd74dfe98c012b22ac03d0 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 7 Jun 2026 12:15:33 +0200 Subject: [PATCH] [Audit] JIT-prolog: optional audit_jit_prolog_mem_dump chain (3 levels) Round-14 of AUDIT-2BF (singleton-dump). Pairs with xenia-rs' --audit-mem-read-hex to emit one comparable XELOGKERNEL line resolving the bctrl target at sub_822F1AA8+0x90 (PC 0x822F1B4C): [0x828E1F08] -> singleton instance ptr [singleton+0] -> vtable [vtable+0] -> vtable[0] (= first virtual method, bctrl tgt) [vtable+24] -> vtable[24] (= slot 6, silph chain target) Two complementary hooks: 1. src/xenia/cpu/backend/x64/x64_emitter.cc: extend AuditLogJitPrologArgs. New cvar `audit_jit_prolog_mem_dump` (uint32). When non-zero and an `audit_jit_prolog_pc` fire happens, the host side dereferences the VA 3 levels deep and emits one AUDIT-MEM-READ line in the same format ours emits. Defensive per-level null + VA-range checks. 2. src/xenia/kernel/kernel_state.cc: one-shot dump in EmulateCPInterruptDPC of the same chain (hard-coded to 0x828E1F08). Useful when audit_jit_prolog_pc isn't set; fires the first time the CP interrupt path runs (after the singleton ctor has had time to populate). Read-only. Both gates default-off; no impact when cvars unset. ~65 LOC total across the two files. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/xenia/cpu/backend/x64/x64_emitter.cc | 31 +++++++++++++++++++++ src/xenia/kernel/kernel_state.cc | 34 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index 558d3e53d..d9d984e11 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -49,6 +49,15 @@ DEFINE_uint32(audit_jit_prolog_pc, 0, "bytes at host(r3). Zero (default) disables the probe. Audit-059 " "round 7+ JIT-prolog probe — generic PC-configurable variant.", "Auditing"); +DEFINE_uint32(audit_jit_prolog_mem_dump, 0, + "Audit-059 round 14 — paired memory-dump VA. When non-zero " + "AND audit_jit_prolog_pc fires, dereference the guest VA 3 " + "levels deep and emit one XELOGKERNEL line: addr -> val " + "(singleton instance), val -> vtable, vtable -> vtable[0] / " + "vtable[24]. Reads the singleton at [0x828E1F08], its vtable, " + "and its vtable[0] (first virtual method = bctrl target at " + "sub_822F1AA8+0x90). Zero (default) disables.", + "Auditing"); DEFINE_bool(ignore_undefined_externs, true, "Don't exit when an undefined extern is called.", "CPU"); DEFINE_bool(emit_source_annotations, false, @@ -503,6 +512,28 @@ uint64_t AuditLogJitPrologArgs(void* raw_context, uint64_t /*unused*/) { XELOGKERNEL("AUDIT-HLC JitProlog pc={:08X} r3 out of VA range, skipping dump", pc); } + + // Audit-059 round 14 — paired 3-level dereference. When + // `audit_jit_prolog_mem_dump` is set, read the singleton at that VA, + // its vtable, vtable[0] (first virtual method = bctrl target), and + // vtable[24] (slot 6 = silph chain method per round 9). + uint32_t mem_addr = static_cast(cvars::audit_jit_prolog_mem_dump); + if (mem_addr != 0u && mem_addr >= 0x10000 && mem_addr < 0xE0000000) { + auto load_be32 = [&](uint32_t va) -> uint32_t { + if (va < 0x10000 || va >= 0xE0000000) return 0u; + uint8_t* h = ctx->TranslateVirtual(va); + if (!h) return 0u; + return xe::load_and_swap(h); + }; + uint32_t val = load_be32(mem_addr); + uint32_t vtable = val != 0u ? load_be32(val) : 0u; + uint32_t m0 = vtable != 0u ? load_be32(vtable) : 0u; + uint32_t m6 = vtable != 0u ? load_be32(vtable + 24u) : 0u; + XELOGKERNEL( + "AUDIT-MEM-READ addr={:08X} val={:08X} vtable={:08X} vtable[0]={:08X} " + "vtable[24]={:08X} pc={:08X} tid={:08X}", + mem_addr, val, vtable, m0, m6, pc, tid); + } return 0; } diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 20f4ba9b4..d37e52bb3 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -1379,6 +1379,40 @@ void KernelState::EmulateCPInterruptDPC(uint32_t interrupt_callback, "AUDIT-HLC EmulateCPInterruptDPC callback={:08X} data={:08X} source={} " "cpu={}", interrupt_callback, interrupt_callback_data, source, cpu); + + // AUDIT-2BF round 14 — one-shot singleton + vtable dump. Resolves + // the silph init chain bctrl target at PC 0x822F1B4C (vtable[0] of + // ANON_Class_2D56F86D at [0x828E1F08]). Dereferences 3 deep: + // [0x828E1F08] (singleton) → vtable → vtable[0] (=first virtual + // method, the bctrl target) and vtable[24] (=slot 6, canary's silph + // chain target sub_821B55D8). Done once per process via atomic + // flag. Defensive null-checks at each level. + static std::atomic g_audit_singleton_dumped{false}; + bool expected = false; + if (g_audit_singleton_dumped.compare_exchange_strong(expected, true)) { + const uint32_t addr = 0x828E1F08; + uint32_t val = 0, vtable = 0, m0 = 0, m6 = 0; + auto* host_addr = memory()->TranslateVirtual(addr); + if (host_addr) { + val = xe::load_and_swap(host_addr); + } + if (val) { + auto* host_val = memory()->TranslateVirtual(val); + if (host_val) { + vtable = xe::load_and_swap(host_val); + } + } + if (vtable) { + auto* host_vt0 = memory()->TranslateVirtual(vtable); + auto* host_vt6 = memory()->TranslateVirtual(vtable + 24); + if (host_vt0) m0 = xe::load_and_swap(host_vt0); + if (host_vt6) m6 = xe::load_and_swap(host_vt6); + } + XELOGKERNEL( + "AUDIT-HLC singleton[0x828E1F08]={:08X} vtable={:08X} " + "vtable[0]={:08X} vtable[24]={:08X}", + val, vtable, m0, m6); + } } auto thread = kernel::XThread::GetCurrentThread();