[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) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 12:15:33 +02:00
parent 1517a63974
commit 87e4a67b61
2 changed files with 65 additions and 0 deletions

View File

@@ -1378,6 +1378,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<bool> 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<uint32_t*>(addr);
if (host_addr) {
val = xe::load_and_swap<uint32_t>(host_addr);
}
if (val) {
auto* host_val = memory()->TranslateVirtual<uint32_t*>(val);
if (host_val) {
vtable = xe::load_and_swap<uint32_t>(host_val);
}
}
if (vtable) {
auto* host_vt0 = memory()->TranslateVirtual<uint32_t*>(vtable);
auto* host_vt6 = memory()->TranslateVirtual<uint32_t*>(vtable + 24);
if (host_vt0) m0 = xe::load_and_swap<uint32_t>(host_vt0);
if (host_vt6) m6 = xe::load_and_swap<uint32_t>(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();