Files
xenia-rs/audit-runs/audit-068-host-mem-watch/instrumentation-design.md
MechaCat02 ef93a4fa14 handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes
Source changes (dormant parity infra, retained from iterate 2.AI/2.AO):
- xenia-kernel/exports.rs: nt_create_event manual_reset polarity +
  related event wiring
- xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity

Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the
iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps
(.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as
regenerable local artifacts — see memory + HANDOFF for the running findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:19:08 +02:00

5.1 KiB

AUDIT-068 Session 1 — host-side memory-write watch (canary instrumentation)

Date: 2026-05-19

Goal

Capture which host C++ functions perform the writes to guest memory that ours never reproduces:

  1. Vtable install at 0xBCE25340 = 0x8200A208 (and clone 0x8200A928) — gates sub_825070F0.
  2. Voice-struct field clear [VOICE+0x164] (value 0x00000000, on guest-VA likely in heap 0x425xxxxx).
  3. Anything else surfaced.

Write-path surface inventory (canary)

A. xe::store_and_swap<T> template family (xenia-canary/src/xenia/base/memory.h:410-475)

  • Sized specializations for T = int8/uint8/int16/uint16/int32/uint32/int64/uint64/float/double.
  • String specializations recurse to store_and_swap<uint8_t> / <uint16_t>.
  • Receives void* mem = HOST pointer; does *p = byte_swap(value).
  • This is the canonical path for host-side typed writes to guest memory used by kernel-import handlers in xboxkrnl_*.cc. Confirmed wide use (16 kernel sub-modules call store_and_swap<uint32_t> alone).
  • Vtable install (PPC stw vptr,0(obj) equivalent on host side) almost certainly uses store_and_swap<uint32_t>(host_ptr, vptr).

B. Memory::Zero/Fill/Copy (xenia/memory.cc:542-554)

  • Use std::memset/std::memcpy directly via host pointer (after TranslateVirtual).
  • Wrappers for RtlZeroMemory, RtlFillMemory, RtlMoveMemory, RtlCopyMemory.
  • Bypass store_and_swap — must instrument separately if we want full coverage.
  • Voice-struct clears via 0x00000000 could plausibly come through here (RtlZeroMemory) or directly via store_and_swap<uint32_t> (typed write).

C. Direct guest writes via *TranslateVirtual<T*>() = …

  • Some sites cast and write through the host pointer directly without going through store_and_swap.
  • Lower coverage priority — start with A+B; add C only if first 2 don't catch our targets.

Cvar design (mimics audit_67 pattern)

Two new cvars in xenia/cpu/cpu_flags.{h,cc}:

DECLARE_string(audit_68_host_mem_watch_values);   // CSV of u32 values (max 8)
DECLARE_string(audit_68_host_mem_watch_addrs);    // CSV of guest VAs or VA ranges (max 8)

Format examples:

  • Values: --audit_68_host_mem_watch_values=0x8200A208,0x8200A928
  • Addrs single: --audit_68_host_mem_watch_addrs=0xBCE25340
  • Addrs range: --audit_68_host_mem_watch_addrs=0x42500000-0x42600000,0xBCE25340

Default empty → zero overhead.

Sample log line (XELOGI):

AUDIT-068-HOST-WRITE guest_va=0xBCE25340 val=0x8200A208 sz=4 fn=<host_function> host_ns=10123456789 tid=N

fn=<host_function> is filled by the caller (each store_and_swap<T> specialization passes __FUNCTION__ or a tag). We can't get a real backtrace cheaply across MSVC; we instead instrument the high-fanout entry points (kernel-import handlers, Memory::Zero/Fill/Copy) with a string tag. For Session 1, capture is sufficient with just template name + caller tag.

Implementation strategy

  1. New file xenia/audit_68_host_mem_watch.h (top-level): forward decls of helper functions in namespace xe::audit_68:
    extern std::atomic<uint8_t> g_active;  // 0=off, 1=values, 2=addrs, 3=both
    void check_host_write(const void* host_ptr, uint64_t value, uint8_t size,
                          const char* tag);
    void check_guest_write(uint32_t guest_va, uint64_t value, uint8_t size,
                           const char* tag);
    
  2. New file xenia/audit_68_host_mem_watch.cc: lazy-parse the cvars on first call, atomic-bool sets active. Performs Memory::active()->HostToGuestVirtual(host_ptr) translation, then matches against value-list and addr-range list, emits XELOGI.
  3. xenia/memory.h: add public static Memory::active() (returns active_memory_).
  4. xenia/base/memory.h: extend store_and_swap<T> specializations (uint8/uint16/uint32/uint64 only — the integer typed paths most likely to write vptrs / clear flags) to check g_active and call the helper. Hot path: 1 atomic load + branch when off. The added cost when on is one cmp+jne per byte/word/dword/qword store; acceptable for capture runs.
  5. xenia/memory.cc: instrument Memory::Zero/Fill/Copy with calls to check_guest_write (each ranges through the affected guest VAs; for capture purposes we only log the first matching byte+size+tag — we don't expand to per-byte events).

Total estimated LOC: ~120-160 LOC across 5 files.

Capture protocol

  • Build canary with the new code.
  • Smoke test: --audit_68_host_mem_watch_values=0x12345678 (no expected hits) → confirm no spurious lines, build/init OK.
  • Sanity test: --audit_68_host_mem_watch_values=0x82000000 (very common vtable-base) → confirm many lines, then revert.
  • Run 1 (vtable install): --audit_68_host_mem_watch_values=0x8200A208,0x8200A928 --mute=true. Kill after ~90s.
  • Run 2 (voice-struct clear): --audit_68_host_mem_watch_addrs=0x42500000-0x42600000 --mute=true. Kill after ~30s (this is heap-region wide; likely lots of hits, capture early window only). May need narrower range once we see the first writes.
  • Cold-protocol: backup canary's cache (xenia-canary/build-cross/bin/Windows/Debug/cache/) to /tmp/canary-cache-bak-audit-068, wipe before run, restore after.