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>
5.1 KiB
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:
- Vtable install at
0xBCE25340 = 0x8200A208(and clone0x8200A928) — gatessub_825070F0. - Voice-struct field clear
[VOICE+0x164](value0x00000000, on guest-VA likely in heap0x425xxxxx). - 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 callstore_and_swap<uint32_t>alone). - Vtable install (PPC
stw vptr,0(obj)equivalent on host side) almost certainly usesstore_and_swap<uint32_t>(host_ptr, vptr).
B. Memory::Zero/Fill/Copy (xenia/memory.cc:542-554)
- Use
std::memset/std::memcpydirectly via host pointer (afterTranslateVirtual). - Wrappers for
RtlZeroMemory,RtlFillMemory,RtlMoveMemory,RtlCopyMemory. - Bypass
store_and_swap— must instrument separately if we want full coverage. - Voice-struct clears via
0x00000000could plausibly come through here (RtlZeroMemory) or directly viastore_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
- New file
xenia/audit_68_host_mem_watch.h(top-level): forward decls of helper functions innamespace 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); - New file
xenia/audit_68_host_mem_watch.cc: lazy-parse the cvars on first call, atomic-bool sets active. PerformsMemory::active()->HostToGuestVirtual(host_ptr)translation, then matches against value-list and addr-range list, emits XELOGI. xenia/memory.h: add public staticMemory::active()(returnsactive_memory_).xenia/base/memory.h: extendstore_and_swap<T>specializations (uint8/uint16/uint32/uint64 only — the integer typed paths most likely to write vptrs / clear flags) to checkg_activeand 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.xenia/memory.cc: instrumentMemory::Zero/Fill/Copywith calls tocheck_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.