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>
7.7 KiB
AUDIT-060 PROBE-O — fnptr-array bootstrap
Run config
- binary : xenia-rs/target/release/xenia-rs-probe (xenia-rs HEAD
e6d43a2) - instr : 500_000_000
- iso : Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso
- db : xenia-rs/sylpheed.db
Phase 1 — CTOR-PROBE fire counts (500M instr, --quiet)
| PC | function | fires |
|---|---|---|
| 0x824ACB38 | sub_824ACB38 (CRT driver) | 1 |
| 0x82457EF0 | sub_82457EF0 (canary "only-caller", AUDIT-059 said unreachable) | 1 |
| 0x82458B90 | sub_82458B90 (canary signaler A) | 1 |
| 0x8245EC10 | sub_8245EC10 (canary signaler B) | 2 |
| 0x8245FEB8 | sub_8245FEB8 (vptr installer, AUDIT-059 said "dead in ours") | 5 |
| 0x821B6DF4 | sub_821B6DF4 (ladder top, AUDIT-058) | 0 |
| 0x821B55D8 | sub_821B55D8 | 0 |
| 0x824F8398 | sub_824F8398 | 0 |
| 0x824F7CD0 | sub_824F7CD0 | 0 |
| 0x824F7800 | sub_824F7800 | 0 |
| 0x825070F0 | sub_825070F0 | 0 |
Phase 2 — sub_824ACB38 anatomy Static body (224 B, addr 0x824ACB38..0x824ACC18): +0x00..+0x2C preamble + one optional dispatch through fn-ptr at [0x82023F08] (=0x825F1630, an LZ-runtime thunk) +0x30..+0x6C loop A: enumerate u32 slots in [0x828708C8, 0x828708D4) — 3 slots filter: non-NULL bctrl at 0x824ACBA0 +0x80..+0xB8 loop B: enumerate u32 slots in [0x82870010, 0x828708C4) — 557 slots filter: non-NULL AND != 0xFFFFFFFF bctrl at 0x824ACBEC +0xC4 epilogue, blr
Phase 2/3 — Array layout (post-reloc, dumped at 1M and 500M instr; both runs identical) Region 0x82870010..0x828702E8 — populated with 0x82xxxxxx pointers (vtable methods) Region 0x828702F0..0x82870580 — PERMANENTLY ZERO across both 1M and 500M dumps (160 of 557 slots = 28.7% of array) Region 0x82870590..0x828708C4 — populated with 0x82xxxxxx pointers (vtable methods) Region 0x828708C8..0x828708D4 — loop-A array, populated (small CRT helpers)
Static-analyzer cross-check (sylpheed.db, function_pointer_arrays): The 557-slot region is NOT a single CRT init array. It contains 9+ separate small "vtable"-classified arrays (lengths 3, 9, 12, 16, 13, 3, 3, 3, 3, 3, 3, 4, 4, 3, ...) at addresses 0x82870014, 0x82870024, 0x82870094, 0x828700C8, 0x8287016C, 0x82870214, 0x82870238, 0x82870250, 0x828702A8, 0x828702C0, 0x828702E4, 0x828705A0, 0x8287062C, 0x82870870. NO statically-detected arrays/refs in 0x82870300.. 0x828705A0 — confirms the gap is intentional (unused padding between two clusters of small vtables).
This means sub_824ACB38 does NOT iterate a CRT static-ctor list. It iterates runtime vtable registration slots — likely a class-registration table where each non-NULL entry is invoked once at load time (TLS / static-init style). AUDIT-050's framing ("CRT driver iterates 0x82870xxx fnptr arrays") is structurally correct (1 fire / iteration of 557 slots) but semantically misleading: the slots are not "static initializers feeding RegisterToFactorysilph::*" — they're class vtable entries.
Phase 3 — ladder-fn references sub_821B6DF4 (ladder top) appears as a value in the binary at exactly 2 places:
- 0x820C1994 in .rdata — embedded as a u32 in an MSVC EH FuncInfo/UnwindMap structure
(surrounding bytes:
FFFFFFFF 821B6DF4 19930522 00000001 820C1990 ...; 0x19930522 = MSVC FuncInfo magic, so 0x821B6DF4 is a catch-handler dispatch target) - 0x8211C678 in .pdata — exception-unwind metadata (not a real call ref)
Disasm at 0x821B6DF4 confirms: prolog subi r31, r12, 112; mflr r12; stwu r1, -96(r1); ... is the
canonical MSVC C++ catch-handler thunk (uses r12 as parent-frame pointer offset). Body calls one bl
(0x82183B78, a label inside an EH support routine) then returns.
Verdict: the AUDIT-058 ladder sub_821B6DF4 ← sub_821B55D8 ← ... ← sub_825070F0 is not a normal
call chain. sub_821B6DF4 is dispatched only by the C++ exception runtime, when a specific
exception type is thrown during front-end-UI initialization. AUDIT-058's "static caller ladder" was
reading EH handler-array linkage as if it were a call ladder.
Phase 4 — surprising contradictions vs AUDIT-058 / AUDIT-059
-
sub_82457EF0fires 1× on tid=6 (HW=2, cycle=0, lr=0xbcbcbcbc = thread-entry sentinel). This is the THREAD ENTRY POINT for tid=6. AUDIT-059's "only-caller sub_82457EF0 has 0 callers" was correct — it has 0 static callers because it's athread_procinvoked byExCreateThread. tid=6 spawns and runs through sub_82457EF0 → sub_82458B90 in our run. -
sub_8245FEB8is NOT dead in ours — it fires 5× total, called via: • sub_824601A0+0x68 (PC=0x82460208) — once from tid=1 boot path at cycle 5.5M (callers go ..0x82448120 ← 0x8216EC10 ← 0x824AB8E0=entry_point: this is the dispatch_table @ 0x820B5830 slot 1 AUDIT-059 named, fired during entry-point processing — NOT dead) • 3 more times from tid=1 during later UI inflation (frames via sub_82175FBC / sub_82178FC8 / sub_82179148 / sub_82173A4C — the audit-009 "front-end UI" cluster) • 1× on tid=13 at cycle 23788 (frames via sub_821CB1D0 ← sub_821CBAE0 ← sub_821CC454 ← sub_821C4F18 = AUDIT-058's tid=13 chain) AUDIT-059's static-analysis (vptr-installer sub_8245FEB8 "dead in ours") is FALSIFIED at runtime. -
sub_8245EC10(canary signaler B) fires 2× in ours (callers: sub_8245FEB8). Both fires are NEW confirmations — this is on the active path.
Specific actionable finding
The AUDIT-058 ladder is not an activation chain. It is the MSVC C++ exception unwind path for a
specific exception type. sub_821B6DF4 is a catch-handler thunk; sub_821B55D8/824F8398/824F7CD0/
824F7800 are the throw-side functions. They fire 0× in ours not because of any "fnptr-array gap" or
"cluster unreachability", but because no exception is currently being thrown at this stage of our
boot. Canary throws (and catches) something that ours doesn't.
Recommended AUDIT-061 directions (in priority order):
(a) Probe RaiseException / _CxxThrowException-equivalent (Xbox xboxkrnl) and any cxx_throw site
in canary vs ours. The 058 ladder fires iff a specific exception type-id is thrown — find it.
(b) AUDIT-053 noted "warm-start regression (cxx_throw=10)" — that throw-counter mismatch may BE the
058 ladder firing in warm-state canary. Cross-reference cxx_throw=10 throws with the 058 ladder.
(c) The Phase 1 confirmation that sub_8245FEB8 IS LIVE in ours (5 fires) means AUDIT-059's
γ-investigation can scrap the "vptr-installer dead" branch. Refocus on why our worker dispatch
table at 0x820B5830 slot 1 fires but doesn't subsequently propagate signals.
(d) Optionally: AUDIT-050's "CRT driver enumerates 557 slots, 82 non-NULL" needs re-examination — the
region is a collection of vtables, not a CRT init array. Some of the "82 non-NULL" slots are
vtable methods (e.g., destructors). The fnptr-array "half-bootstrapped" framing has been
super-cited in the audit chain without verifying what's actually being enumerated.
Outputs
- audit-runs/audit-060-fnptr-array-bootstrap/ours-phase1.stdout (CTOR-PROBE log, 11 PCs, 500M instr)
- audit-runs/audit-060-fnptr-array-bootstrap/ours-dump-500M.stdout (38-region dump, post-reloc, 500M)
- audit-runs/audit-060-fnptr-array-bootstrap/array1_dump.txt (static, pre-reloc — INVALID due to reloc — kept for reference)
- audit-runs/audit-060-fnptr-array-bootstrap/array2_dump.txt (static, pre-reloc — INVALID — kept for reference)
Discipline gate
- xenia-rs source unmodified (READ-ONLY discipline upheld).
- Stop-hook safe (binary renamed to xenia-rs-probe).
- No canary patch applied this round.