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>
82 lines
5.1 KiB
Markdown
82 lines
5.1 KiB
Markdown
# 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}`:
|
|
|
|
```cpp
|
|
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`:
|
|
```cpp
|
|
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.
|