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>
114 lines
7.7 KiB
Markdown
114 lines
7.7 KiB
Markdown
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 RegisterToFactory<silph::*>" — 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
|
||
1. `sub_82457EF0` fires 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 a `thread_proc` invoked by `ExCreateThread`. tid=6
|
||
spawns and runs through sub_82457EF0 → sub_82458B90 in our run.
|
||
|
||
2. `sub_8245FEB8` is **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**.
|
||
|
||
3. `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.
|