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>
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:
xe::store_and_swap<T>template family (xenia/base/memory.h, T=u8/u16/u32/u64/i8/i16/i32/i64).xe::store<T>template family (host-endian sibling of above).Memory::Zero/Fill/Copyinxenia/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 orSTART-ENDranges).
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 rawmemcpy()and direct*ptr = ...host-pointer writes that are NOT routed throughxe::store_and_swap<T>,xe::store<T>, orMemory::Zero/Fill/Copy. Quick grep onxex_module.ccconfirms: lines 286, 369, 422, 427, 525, 582, 592, 650, 668, 773, 795 all use plainmemcpy(host_ptr, src, size)after aMemory::TranslateVirtuallookup. - The kernel-import handlers that COULD synthesize
0x8200A208runtime (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
- The instrumentation is sound. 1,639 value=0 hits prove the
store_and_swap<T>/Memory::Zero/Fill/Copyhooks and the host→guest VA translation thunk all work in default cold-boot. - AUDIT-067's "host-side install" framing remains correct — guest stores were ruled out by AUDIT-067, host-side
store_and_swapis now ruled out by AUDIT-068. The set of paths left for the installer is narrowed to: rawmemcpy-via-TranslateVirtual,*reinterpret_cast<be<T>*>(host)=vpatterns, or some other un-hooked direct host write. - 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
0x42xxxxxxor0xBCxxxxxx(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::Initializefor 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
RtlFillMemoryand 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 byXThread::AllocateStack.
store_and_swap<u64> (2 hits)
- Likely
RtlInitMemory64-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:
*xe::TranslateVirtual<be<T>*>(addr) = value;— typed-pointer cast through a host-endianbe<T>reference. Lots of kernel-import code uses this pattern (e.g.xboxkrnl_rtl.cc'sRtlCompareMemoryreturns,xboxkrnl_video.cc's frame-count writes). Doesn't go throughstore_and_swap.- Direct
*reinterpret_cast<uint32_t*>(host) = byte_swap(val)— a few performance-critical sites do this inline rather than via the template. Memory::Copywithsrchost-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. EitherMemory::Copyisn't used for vptr install, or the values don't appear as the first u32 of the copy.- 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.