Files
xenia-rs/audit-runs/audit-068-host-mem-watch/writer-report.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

8.8 KiB

AUDIT-068 Session 1 — writer report

Date: 2026-05-19

Summary

Built canary instrumentation that hooks the three host-side write surfaces I expected to cover the AUDIT-067 / Phase HostAudio-Eager findings:

  1. xe::store_and_swap<T> template family (xenia/base/memory.h, T=u8/u16/u32/u64/i8/i16/i32/i64).
  2. xe::store<T> template family (host-endian sibling of above).
  3. Memory::Zero/Fill/Copy in xenia/memory.cc.

Total instrumentation: ~190 LOC kept in canary tree (cvar-gated default-off, zero hot-path cost when both cvars empty), 2 new files in xenia/base/:

  • audit_68_host_mem_watch_fwd.h — atomic + inline checks (forward decls).
  • audit_68_host_mem_watch_base.cc — slow-path impl, lazy CSV parse, host→guest VA translation via function-pointer thunk.

Cvars in xenia/cpu/cpu_flags.{h,cc}:

  • --audit_68_host_mem_watch_values=CSV (max 8 u32 values).
  • --audit_68_host_mem_watch_addrs=CSV (max 8 VAs or START-END ranges).

Smoke test (--audit_68_host_mem_watch_values=0x12345678, 30s): 0 hits, INIT lines emitted — instrumentation operational.

Sanity test (--audit_68_host_mem_watch_values=0x00000000, ~12s): 1,639 hits, dominated by Memory::Zero (1,594) plus store_and_swap<u32> (13) and store_and_swap<u64> (2). Instrumentation works end-to-end and the guest-VA thunk resolves correctly (e.g. guest_va=0x30000000 host_ptr=...30000000 for store_and_swap).

Capture runs

Run 1 — vtable 0x8200A208 / 0x8200A928 writers

Cmdline: --audit_68_host_mem_watch_values=0x8200A208,0x8200A928,0x080082A2,0x2829820 --audit_68_host_mem_watch_addrs=0xBCE25340 (value list also includes the byte-swapped forms in case some caller passes a pre-swapped value through store<T> rather than store_and_swap<T>; addr watch on the known target instance address from AUDIT-058/067).

Wallclock: 90 s (post-10.4 s trigger window per Phase NonMatch).

Result: 0 hits. Log at run1-vtable-writers.log (81 KB; cold boot reached thread spawn through tid=29, matching Phase NonMatch trace).

Run 2 — voice-struct field clear [VOICE+0x164]

Cmdline: --audit_68_host_mem_watch_addrs=0x42500000-0x42600000.

Wallclock: 60 s.

Result: 0 hits. Log at run2-voice-struct-writers.log.

Sanity — value=0 reachability test

Cmdline: --audit_68_host_mem_watch_values=0x00000000. Wallclock: ~12 s.

Result: 1,639 hits, breakdown:

tag hits
Memory::Zero 1,594
Memory::Fill 30
store_and_swap<u32> 13
store_and_swap<u64> 2

Guest VAs span 0x30000000-0x30xxx000 (the 40 MB physical heap setup by Memory::Initialize) and 0xFFCAxxxx (kernel high range, stacks/TLS). store_and_swap<u32> hits e.g. 0xFFCAE000 / 0xFFCAD000 / 0x30002000 — kernel pointer-init scribbles. NO hits in the XEX image region 0x82000000+. Log at sanity-value0.log.

Headline finding (negative-but-informative)

Neither the vtable install nor the XEX section loader uses any of the hooked paths. A separate Sanity-2 run watched the addr range 0x82000000-0x82010000 (Sylpheed's .text start) and got 0 hits across a full boot — yet that region MUST be written to during XEX load (the image is copied in from the file). This means:

  • The XEX module loader (xenia/cpu/xex_module.cc) writes guest memory via raw memcpy() and direct *ptr = ... host-pointer writes that are NOT routed through xe::store_and_swap<T>, xe::store<T>, or Memory::Zero/Fill/Copy. Quick grep on xex_module.cc confirms: lines 286, 369, 422, 427, 525, 582, 592, 650, 668, 773, 795 all use plain memcpy(host_ptr, src, size) after a Memory::TranslateVirtual lookup.
  • The kernel-import handlers that COULD synthesize 0x8200A208 runtime (the original AUDIT-067 hypothesis) are not doing so — at least not via the hooked surfaces — within the 90 s window that includes the 10.4 s trigger.

So neither of the two main hypotheses (host-allocator vptr install via kernel handler; voice-struct clear via direct write) was captured by Session 1's instrumentation. Session 1's coverage gap is identified and is the deliverable for Session 2.

What Session 1 nonetheless confirmed

  1. The instrumentation is sound. 1,639 value=0 hits prove the store_and_swap<T> / Memory::Zero/Fill/Copy hooks and the host→guest VA translation thunk all work in default cold-boot.
  2. AUDIT-067's "host-side install" framing remains correct — guest stores were ruled out by AUDIT-067, host-side store_and_swap is now ruled out by AUDIT-068. The set of paths left for the installer is narrowed to: raw memcpy-via-TranslateVirtual, *reinterpret_cast<be<T>*>(host)=v patterns, or some other un-hooked direct host write.
  3. Cold-boot guest-VA layout (from sanity log):
    • 0x30000000-0x30xxxxxx — physical heap (Memory::Zero on Initialize).
    • 0xFFCAxxxx — kernel high (stacks etc).
    • 0x82000000+ — Sylpheed XEX image region (never touched by hooked surfaces).
    • Nothing observed in 0x42xxxxxx or 0xBCxxxxxx (yet — these allocate later than the 12 s sanity window).

Per-writer breakdown (from sanity capture)

Only writers with hits in the 12 s window are listed; this is what Session 2 will need to mirror in ours where applicable.

Memory::Zero (1,594 hits in 12 s)

  • All from tid=304 (host main thread / boot thread).
  • Affected guest VAs: 0x30000000-0x30xxx000 (heap-page zero on init), 0xFFCAB000-0xFFCAE000 (kernel stacks zero on alloc).
  • This is invoked by Memory::Initialize for heap setup and by the kernel during stack allocations.
  • Ours's analog: xenia-kernel/src/state.rs/memory.rs — Memory init and stack alloc. Likely already zero-init by Rust default; verify in Session 2.

Memory::Fill (30 hits in 12 s)

  • Same tid=304, similar VA distribution.
  • Used for RtlFillMemory and some allocator default-fill paths.

store_and_swap<u32> (13 hits in 12 s)

  • One of each: pointer-init writes by kernel-thread setup code (e.g. TIB fields).
  • Example: 0xFFCAE000, 0xFFCAD000, 0x30002000. Likely the linked-list / TLS slot pointers written by XThread::AllocateStack.

store_and_swap<u64> (2 hits)

  • Likely RtlInitMemory 64-bit-aligned scribbles.

What's missing — the writers Session 2 must catch

Both Session-1 target writers (vtable install + voice-struct clear) escape the hooked surface. The XEX loader's raw memcpy() is the obvious blind spot but does not explain the vtable install (the vptr at 0xBCE25340 is in the heap, written AFTER load). Other candidates:

  1. *xe::TranslateVirtual<be<T>*>(addr) = value; — typed-pointer cast through a host-endian be<T> reference. Lots of kernel-import code uses this pattern (e.g. xboxkrnl_rtl.cc's RtlCompareMemory returns, xboxkrnl_video.cc's frame-count writes). Doesn't go through store_and_swap.
  2. Direct *reinterpret_cast<uint32_t*>(host) = byte_swap(val) — a few performance-critical sites do this inline rather than via the template.
  3. Memory::Copy with src host-region having pre-encoded bytes — but the value-match path I added DOES catch this for the first u32 of the source, and we got 0 hits. Either Memory::Copy isn't used for vptr install, or the values don't appear as the first u32 of the copy.
  4. GPU / VFS host-side initialisation of mmio-mapped guest memory — separate APIs entirely, but Sylpheed isn't doing GPU vtable installs at this point.

Per-target follow-up (Session 2 capture targets)

Value/VA Status from Session 1 Session 2 plan
Vtable 0x8200A208 install at 0xBCE25340 NOT CAUGHT (host-side, but escapes store_and_swap / store / Memory::Zero/Fill/Copy) Add hooks on (a) the typed-pointer write surfaces (be<T>::operator= and *TranslateVirtualBE<T>() = v) and (b) a Memory::WriteWord32 shim that catches raw u32 stores into TranslateVirtual host pointers. Also add a Memory::Copy value-watch that scans the WHOLE copy buffer for matches, not just the first u32. Re-run with vtable-value watch + addr-range watch on the heap region around 0xBCE25340.
Voice-struct field clear [VOICE+0x164] NOT CAUGHT (same reason; plus the actual VOICE base may live outside 0x42xxxxxx) First find the actual VOICE base via guest-side enumeration in Phase HostAudio-Eager artifacts; once known, addr-range watch over the entire MmAllocatePhysicalMemoryEx block that contains the voice array.

Artifacts in this dir

  • instrumentation-design.md — surface inventory + cvar design.
  • fix-canary.diff — combined diff of the 5 modified files plus full text of the 2 new files (xenia/base/audit_68_host_mem_watch_fwd.h, xenia/base/audit_68_host_mem_watch_base.cc).
  • run1-vtable-writers.log — 0 hits.
  • run2-voice-struct-writers.log — 0 hits.
  • sanity-value0.log — 1,639 hits (instrumentation alive proof).
  • writer-report.md — this file.
  • session-2-plan.md — actionable plan for next session.