ITERATE-2.V: scheduler priority aging closes 18-day AUDIT-049 wedge
Priority aging in xenia-cpu/scheduler.rs:pick_runnable
(effective_priority = base + age_bonus(now_round - last_run_round),
capped at +31, AGING_ROUNDS_PER_BONUS=1). Strict-priority was parking
priority=0 threads behind CPU-bound priority=15 audio mixer
(sub_824D1328 guest spinwait at PC=0x824d1404 on CPU5). Aging
eventually picks the starved thread, breaking the producer-consumer
cycle that caused 5-tid wedge at PC=0x824ac578 since AUDIT-049 (10 May).
Cascade observed: tid=13 clean exit; events 121K -> 13M (107x); last
host_ns 767ms -> 51,011ms (66x); 8 new threads spawn; VdSwap 1 -> 2.
Complete two-day iterate sequence (2026-05-27 -> 2026-05-28):
- 2.F: VdSwap drain timeout 900ms -> 1ms (xenia-gpu/handle.rs); 876x
perf win on VdSwap kernel callback
- 2.H: vA0000000 physical heap bucket added (state.rs, exports.rs);
ctx_ptrs now in 0xA0000000-0xBFFFFFFF range matching canary
- 2.L: Phase-A diff harness categorized [return_value mismatch],
[status mismatch], [args_resolved.path mismatch] tags
(tools/diff-events/diff_events.py); closes reading-error #41
(silent test-harness state leak invalidating trace diffs)
- 2.M: always-on exit-thread-state.json sibling to Phase-A JSONL
(event_log.rs + xenia-app/main.rs); closes reading-error #42
(Phase-A blind to blocked-forever waits)
- 2.Q: signal.match kernel instrumentation in NtSetEvent /
NtReleaseSemaphore / KeSetEvent / KeReleaseSemaphore
(exports.rs); emits target_handle + waiter_count + waiter_tids
- 2.T: wake.requested kernel instrumentation in wake_eligible_waiters
(exports.rs); emits target_tid + transition + new_state
- 2.V: scheduler priority aging (xenia-cpu/scheduler.rs) [keystone]
Plus accumulated WIP from earlier May (contention_manifest,
phase_b_snapshot, xam/xaudio enhancements, analysis db, xex loader,
xenia-app main loop, etc.). Audit-runs/ artifacts remain untracked
per project convention.
Tests: 300 xenia-cpu / 227 xenia-kernel / 5 xenia-app / 19 xenia-path
/ 30+ smaller suites -- all PASS, 0 regressions. Determinism preserved
(2x cold runs bit-identical at 13,003,881 events post-2.V).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
23
docs/functions/INDEX.md
Normal file
23
docs/functions/INDEX.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Function dossier index
|
||||
|
||||
Sorted by guest address. Update when adding/changing a dossier. See [README.md](README.md) for schema.
|
||||
|
||||
| Address | Dossier | Classification | Synopsis | Last audit |
|
||||
|---------|---------|----------------|----------|------------|
|
||||
| `0x82172BA0` | [sub_82172BA0](sub_82172BA0.md) | `normal_callee` | Array-walk dispatcher (vtable slot 6 bctrl at +0x1E8 / PC 0x82172D88). Walks `[r29+56]` array, invokes slot 6 on each — one observed target is sub_821B55D8. Gated by `[r30+4]` 3-bit-field==4 in sole caller sub_821741C8. Fires 1-2× canary / 0× ours @ 180s. | 064 |
|
||||
| `0x82173990` | [sub_82173990](sub_82173990.md) | `normal_callee` | Synchronous task-spawn-and-join helper. PC `0x82173C60 bl 0x824AA330` (= +0x2D0) = wedge site for tid=1's join wait on tid=13's thread handle. Wait is on a sync object (event) populated by `sub_82172370` inside `sub_821746B0`. Canary completes wait in <1ms (worker exits via ExTerminateThread); ours never (tid=13 stuck in sub_821CB030). Fires 1× per boot in both engines. | 066 |
|
||||
| `0x821B55D8` | [sub_821B55D8](sub_821B55D8.md) | `normal_callee` | Vtable slot 6 dispatched from sub_82172BA0+0x1E8 bctrl. Calls sub_824F8398 at +0x584. DB static caller is EH `b` from sub_821B6DF4, but real runtime caller is the bctrl. 1× canary / 0× ours. | 064 |
|
||||
| `0x821B6DF4` | [sub_821B6DF4](sub_821B6DF4.md) | `msvc_eh_catch_handler` | MSVC C++ catch-handler thunk. FuncInfo @ .rdata:0x820C1994. 0 fires both engines at this horizon. | 060 |
|
||||
| `0x821C4EB0` | [sub_821C4EB0](sub_821C4EB0.md) | `vtable_method` | `silph::GamePart_Title::UImpl` member fn. AUDIT-061: NOT a branch-divergence gate. All 4 cond-branches in [+0x44, +0xE0] decided bit-identically. First divergence is non-returning `bl 0x821CC3F8` at +0x64 (wedge inside sub_821CB030). | 061 |
|
||||
| `0x821CB030` | [sub_821CB030](sub_821CB030.md) | `normal_callee` | Wedge primary site: creates per-call work-queue completion XEvent (+0x128), submits via sub_82452DC0 (+0x19C/+0x2EC), waits INFINITE (+0x1AC/+0x318). AUDIT-066 corrected framing: wait is on guest worker-cluster signal, NOT IO completion. AUDIT-065: ours's tid=13 wedges on FIRST sub_821CB030 call on 0x12AC; canary's tid=17 completes 16+ such calls and reaches ExTerminateThread. | 066 |
|
||||
| `0x822F1AA8` | [sub_822F1AA8](sub_822F1AA8.md) | `normal_callee` | tid=1 post-init game-loop dispatcher. Bctrl vtable[0] of *(0x828E1F08) at +0xA4 dispatches into sub_82173990 (via thunk sub_82175330). Ours wedges in the vtable[0] callee (sub_82173990+0x2D0); refined in AUDIT-065. Outer loop in sub_822F1AA8 itself iterates 4040× in canary 60s (PCs 0x822F1BCC/D58/DFC). | 065 |
|
||||
| `0x824ACB38` | [sub_824ACB38](sub_824ACB38.md) | `crt_init_driver` | Iterates runtime vtable-registration slots at 0x82870010..0x828708D4. Two loops (3 + 557 slots); 160-slot intentional zero gap at [0x828702F0, 0x82870590). | 060 |
|
||||
| `0x82452DC0` | [sub_82452DC0](sub_82452DC0.md) | `normal_callee` | Work-submitter / cluster root. AUDIT-050–060 convergence node. Ours fires ~3.21× less than canary. | 060 |
|
||||
| `0x82457EF0` | [sub_82457EF0](sub_82457EF0.md) | `thread_proc` | tid=6 thread_proc. 0 static callers is CORRECT (registered via ExCreateThread). | 060 |
|
||||
| `0x82458B90` | [sub_82458B90](sub_82458B90.md) | `normal_callee` | Canary γ-wedge signaler A. NtSetEvent caller; called via sub_82457EF0+0x24 (tid=6). Fires 1× in ours / 2× in canary. | 060 |
|
||||
| `0x8245EC10` | [sub_8245EC10](sub_8245EC10.md) | `dispatch_table_method` | Canary γ-wedge signaler B. Slot 1 of dispatch_table @ 0x820B5830, installed by sub_8245FEB8. NtSetEvent caller. | 060 |
|
||||
| `0x8245FEB8` | [sub_8245FEB8](sub_8245FEB8.md) | `normal_callee` | Vptr installer for dispatch_table @ 0x820B5830. Fires 5× in ours, 2× in canary, **same call site both engines**. | 060 |
|
||||
| `0x824F7800` | [sub_824F7800](sub_824F7800.md) | `normal_callee` | Activation chain fn #2 → bctrl vtable[1] dispatches sub_825070F0 at PC 0x824F7B20. Standard prolog. 1× canary / 0× ours. | 064 |
|
||||
| `0x824F7CD0` | [sub_824F7CD0](sub_824F7CD0.md) | `normal_callee` | Activation chain fn #3. Contains 4-way computed switch (`bctr` jump-table) at +0x40. Calls sub_824F7800. 1× canary / 0× ours. | 064 |
|
||||
| `0x824F8398` | [sub_824F8398](sub_824F8398.md) | `normal_callee` | Activation chain fn #4. Tiny 20-insn adapter constructing a 36-byte stack-record before calling sub_824F7CD0. 1× canary / 0× ours. | 064 |
|
||||
| `0x825070F0` | [sub_825070F0](sub_825070F0.md) | `vtable_method` | Slot 1 of class `ANON_Class_713383D7` vtable (0x8200A208/0x8200A928). 1× fire in canary @ ~25s wallclock; spawns 4 workers with ctx 0xBCE25340. AUDIT-064: full activation chain identified; wedge is upstream at tid=1's join-wait on tid=13 (AUDIT-049). AUDIT-067 (negative result): the vtable address `0x8200A208` is never stored via any guest store opcode in canary — install is host-side (kernel-import direct memory write / XEX-loader); search guest-code for the install is fundamentally blind. | 067 |
|
||||
141
docs/functions/README.md
Normal file
141
docs/functions/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Function dossiers — persistent RE notes for Project Sylpheed (Sylpheed.xex)
|
||||
|
||||
## What this is
|
||||
|
||||
One markdown file per guest function we've investigated during a kernel-bug audit. The dossier is a **living, append-only record** of what we know (and what we got wrong) about each function. The goal is two-fold:
|
||||
|
||||
1. **Don't re-derive understanding.** When an audit touches `sub_821C4EB0`, the next agent shouldn't have to re-walk the disasm — read [sub_821C4EB0.md](sub_821C4EB0.md) first.
|
||||
2. **Don't repeat misinterpretations.** AUDIT-060 falsified two audits of work because we'd read MSVC EH FuncInfo metadata as if it were static call edges. The dossier captures both the corrected reading AND the falsified one — so future agents see the trap was already sprung once.
|
||||
|
||||
This system is **agent-writable**. Audit agents are expected to consult dossiers before probing, and to *append* (not rewrite) when a new audit produces evidence about a known function. Agents should create new dossiers for any function they perform non-trivial work on.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
docs/functions/
|
||||
README.md — this file
|
||||
INDEX.md — one-line lookup table, sorted by address
|
||||
sub_XXXXXXXX.md — per-function dossier (one per function, address in UPPERCASE hex)
|
||||
```
|
||||
|
||||
Filename convention: `sub_` + 8-hex-uppercase + `.md`. Match the name used in `sylpheed.db.functions.name`. If the function has a symbol (e.g. `GamePart_Title::UImpl::ctor`), still use the address-based filename; record the symbol inside.
|
||||
|
||||
## Schema
|
||||
|
||||
Each dossier follows this shape:
|
||||
|
||||
```markdown
|
||||
---
|
||||
address: 0xXXXXXXXX
|
||||
classification: <one-word category — see below>
|
||||
confidence: <high | medium | low | refuted>
|
||||
last_audit: NNN
|
||||
aliases:
|
||||
- "human-readable name or prior misnomer (status)"
|
||||
---
|
||||
|
||||
# sub_XXXXXXXX
|
||||
|
||||
## Synopsis
|
||||
|
||||
One short paragraph: the current best understanding. ONLY the latest consensus —
|
||||
old interpretations live in the audit log.
|
||||
|
||||
## Evidence
|
||||
|
||||
Hard facts only. Disasm patterns, .rdata/.pdata references, runtime fires from
|
||||
instrumentation, byte-level dumps. No inference here; that goes in Activation
|
||||
or Notes.
|
||||
|
||||
## Activation
|
||||
|
||||
When/how this function runs:
|
||||
- direct bl from caller X at PC Y
|
||||
- indirect via fnptr-array slot N at 0x...
|
||||
- vtable dispatch from class C, slot K (vtable at 0x...)
|
||||
- C++ EH catch-handler dispatch (FuncInfo @ 0x...)
|
||||
- thread_proc entry point (registered via ExCreateThread call site PC Z)
|
||||
|
||||
## Static graph
|
||||
|
||||
- Callers (from sylpheed.db `xrefs` table, source_func column — never source per AUDIT-045):
|
||||
- PC `0xCCCCCCCC` inside `sub_DDDDDDDD`
|
||||
- Callees:
|
||||
- bl `sub_EEEEEEEE` at PC `0x...`
|
||||
- bctrl (computed) at PC `0x...` — candidates: ...
|
||||
|
||||
## Audit log
|
||||
|
||||
Append-only. Most recent FIRST. Each entry pairs (audit-NNN, date, observation,
|
||||
status). Status options: confirmed | falsified | superseded-by-NNN.
|
||||
|
||||
- **AUDIT-NNN (YYYY-MM-DD)** — observation + relevant data point [STATUS]
|
||||
- **AUDIT-MMM (YYYY-MM-DD)** — earlier observation [STATUS: falsified by NNN — reason]
|
||||
|
||||
## Open questions
|
||||
|
||||
Future-work bullets:
|
||||
- Specific PC to probe
|
||||
- Hypothesis to test
|
||||
- Cross-reference to verify
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Related dossiers: [sub_XXXXX](sub_XXXXX.md) (relationship)
|
||||
- Audit memory entries: `project_xenia_rs_audit_NNN_*.md`
|
||||
- Trace artifacts: `audit-runs/audit-NNN-*/...`
|
||||
```
|
||||
|
||||
## Classification vocabulary
|
||||
|
||||
Pick the **most specific** that fits. Add new ones if needed but don't bloat the list.
|
||||
|
||||
| Class | Meaning |
|
||||
|-------|---------|
|
||||
| `normal_callee` | Plain function reached by direct `bl`. The default. |
|
||||
| `vtable_method` | Virtual method dispatched via `bctrl` from a class vtable. |
|
||||
| `thread_proc` | Entry point registered via `ExCreateThread` / `KeInitializeThread`. 0 static callers is correct; check for `lr=0xbcbcbcbc` thread-entry sentinel at first fire. |
|
||||
| `msvc_eh_catch_handler` | MSVC C++ catch handler. Prolog `subi r31, r12, N; mflr r12; ...`. Referenced from `.rdata` FuncInfo (magic `0x19930520..22`). 0 static callers; dispatched by EH runtime only. **Do not treat its `.rdata` references as call edges.** |
|
||||
| `msvc_eh_state_handler` | MSVC EH state/unwind handler. Similar to above but no `subi r31, r12` prolog. |
|
||||
| `import_thunk` | Wraps an xboxkrnl import (e.g. NtCreateEvent at thunk 0x8284DF1C). Behavior is host-side. |
|
||||
| `wrapper` | Thin wrapper around a kernel import or library call. |
|
||||
| `crt_init_driver` | CRT-style iterator that walks an array of fn pointers / vtables (e.g. `sub_824ACB38`). |
|
||||
| `fnptr_array_entry` | Function reached only via enumeration by a `crt_init_driver`. |
|
||||
| `dispatch_table_method` | Function installed into a runtime dispatch table by a ctor; reached via indirect call only. |
|
||||
| `synchronization_primitive` | Function that wraps Nt/Ke wait/set/release calls. |
|
||||
| `unknown` | Not yet investigated. Synopsis describes what little we know. |
|
||||
|
||||
## Confidence levels
|
||||
|
||||
| Confidence | Meaning |
|
||||
|------------|---------|
|
||||
| `high` | Multiple converging evidence sources (disasm + runtime instrumentation + cross-engine probe). |
|
||||
| `medium` | One strong source (e.g. disasm alone or one canary trace). Plausible but not cross-checked. |
|
||||
| `low` | Inference from static call graph or one observation; should be probed if it becomes load-bearing. |
|
||||
| `refuted` | An earlier claim was falsified. Keep the dossier; document what the function actually is in synopsis + put the refuted claim in audit log with status `falsified`. |
|
||||
|
||||
## Golden rules — for agents and humans
|
||||
|
||||
1. **Append, don't overwrite.** New audits add entries to "Audit log". Old entries stay with their original wording so future readers can see the evolution.
|
||||
2. **Falsify, don't delete.** If a later audit disproves an earlier claim, mark the old audit-log entry `[STATUS: falsified by AUDIT-NNN — reason]`. The earlier interpretation taught us *something* (often that a class of disasm pattern is ambiguous) — preserve it.
|
||||
3. **Cite the source.** Every claim ties to either (a) an audit number + trace artifact path, or (b) a static-DB query you can reproduce. "X is a thread_proc" without a basis is unacceptable.
|
||||
4. **Distinguish fact from inference.** "Fires 5× at -n 500M with lr=0x8246020C all five times" is a fact. "Therefore it's a vptr installer for slot 1 of dispatch_table 0x820B5830" is an inference. Put facts in Evidence; inferences in Synopsis/Activation/Notes — and label inferences as such.
|
||||
5. **Update INDEX.md.** When you create a new dossier or change a classification, add/update the corresponding row in `INDEX.md`.
|
||||
6. **Update the `last_audit` frontmatter.** Reflects the most recent audit that touched the dossier.
|
||||
7. **One function per file.** If you find a fn is structurally a wrapper for another, write two dossiers and link them.
|
||||
|
||||
## Anti-patterns to avoid
|
||||
|
||||
- **Reading EH metadata as call edges.** `.rdata` references to a fn inside an MSVC FuncInfo struct (magic `0x19930520..22` nearby) are unwind-handler bindings, NOT bl call sites. Pattern: catch-handler prolog `subi r31, r12, N; mflr r12; stwu r1, ...`. See [sub_821B6DF4.md](sub_821B6DF4.md) for the canonical falsified example.
|
||||
- **"0 static callers" = "dead in ours".** Three legitimate reasons a fn has 0 static callers and still runs: thread_proc (ExCreateThread), fnptr_array_entry (enumerated by crt_init_driver), msvc_eh_*_handler (dispatched by EH runtime). Always check.
|
||||
- **Comparing fire counts at fixed instruction horizons across engines.** Canary @ 60s wallclock and ours @ -n 500M are different time bases. State (i) and state (ii) data points must be normalized — either both at the same wallclock or both at the same boot milestone.
|
||||
- **Trusting handle IDs across runs.** `KernelState::alloc_handle` is monotonic; handles drift run-to-run. Function-context names (e.g. "sub_821CB030+0x128 creator") are stable; handle IDs are not.
|
||||
- **Quoting xrefs.source instead of xrefs.source_func.** See AUDIT-045 reading-error #12. Use `source_func` for caller-set queries.
|
||||
|
||||
## Backfill status
|
||||
|
||||
Initial set (created in AUDIT-060 retrospective backfill, 2026-05-12):
|
||||
|
||||
- The 10 most-cited fns from AUDIT-049–060.
|
||||
|
||||
Future audits should extend coverage as they touch new fns. Backfilling earlier audit fns (AUDIT-030–048) is a nice-to-have but not blocking.
|
||||
56
docs/functions/sub_82172BA0.md
Normal file
56
docs/functions/sub_82172BA0.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
address: 0x82172BA0
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 064
|
||||
aliases:
|
||||
- "Vtable-slot-6 array-walker / AUDIT-033 T6-gateway descendant"
|
||||
---
|
||||
|
||||
# sub_82172BA0 — array-walk dispatcher (vtable slot 6)
|
||||
|
||||
## Synopsis
|
||||
|
||||
Normal-callee dispatcher. Walks an array of object pointers (header at `r29+56`: `r29+56[8]` = element count `>>2`; `r29+56[4]` = data ptr) and invokes vtable slot 6 (`lwz r11, 24(r11)`) on each. The `bctrl` at PC `0x82172D88` is the slot-6 dispatch site — observed in canary firing into [sub_821B55D8](sub_821B55D8.md). Has a critical-section prologue (`lwarx`/`stwcx.` at PC `0x82172C08..0x82172C14`) protecting the array snapshot. Only fires when caller `sub_821741C8` sees `[r30+4]` mask-3-bits-field == 4. AUDIT-064 verified canary fires 2× at 180s wallclock; ours fires 0× because tid=1's wait at `sub_82173990+0x2D0` (handle 0x12A4 = tid=13 thread handle) never completes.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm prolog at `0x82172BA0`: `mflr r12; bl 0x825F0F78 (frame helper); subi r31, r1, 176; stwu r1, -176(r1); mr r29, r3; ...` — normal-callee prolog, frame ptr `r31 = r1-176`. NOT MSVC EH handler.
|
||||
- Function size: 604 bytes / 151 insns. `has_eh=True`, `frame_size=0` per DB (dynamic).
|
||||
- Static caller xref (sole): PC `0x821744C8` inside `sub_821741C8` via `bl`. Gating disasm at `sub_821741C8+0x2C8..2C8` matches mask-bits of `[r30+4]` to value 4 to take this call.
|
||||
- The bctrl at PC `0x82172D88` operates on slot 6 (`lwz r11, 24(r11)` = byte-offset 24 in 4-byte slots = slot index 6).
|
||||
- AUDIT-064 canary 60s+180s probes: fires 1-2× with `lr=0x821744CC r3=BCCC4A80 r4=BC369160 r5=BC369160 r6=03A72328` on tid=6. PC `0x82172D88` (the bctrl) fires 2× at 60s in upstream probe.
|
||||
- AUDIT-064 ours `--ctor-probe=0x82172BA0` -n 500M: **0 fires**.
|
||||
- Critical-section pattern at `0x82172C08..0x82172C14`: `mfmsr r8; mtmsrd r13; lwarx r9, r0, r10; stwcx. r11, r0, r10; mtmsrd r8; bne 0x82172C00` — disable interrupts → atomic swap → restore.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_821741C8+0x300` (PC `0x821744C8`). Conditional: `sub_821741C8` masks `[r30+4]` via `rlwinm r11, r11, 0, 27, 29` and switches on the 3-bit field — value `4` selects this fn, value `8` selects `sub_82172E58`, else no-op.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (DB):
|
||||
- `sub_821741C8+0x300` via `bl`.
|
||||
- Callees:
|
||||
- `sub_822F2328` (PC `0x82172BC4`).
|
||||
- `sub_8284DCFC` (PC `0x82172BD4`) — likely a kernel sync primitive.
|
||||
- `sub_8228E138` (PC `0x82172BF4`).
|
||||
- Indirect via `bctrl` at PC `0x82172D88` (slot 6) and other vtable slots inside the body.
|
||||
- DB lists many `ind_call` targets recorded for PC `0x82172D88` (sub_82680370, sub_823A2258, sub_82455300, sub_827E8D60, sub_8237B020, sub_82398CC0, sub_82391BA8, sub_827ED308, sub_826B24E8, sub_822C7418, sub_821F8340, sub_823800A8, sub_824A6C00, sub_823762E8, sub_825ED990, sub_827EFED0, sub_822B06A0, sub_82455658, sub_82388FF8, sub_827FA850, sub_8232C4C0, sub_8238EC10, sub_82674028, sub_823929D0, ...). **Critical caveat**: this list is missing `sub_821B55D8` despite that being the runtime target observed in canary — the dynamic-target inference has gaps.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-064 (2026-05-12)** — disasm confirms array-walk dispatcher pattern; canary fires 1-2× / ours 0×. The runtime activation chain for sub_825070F0 starts here. **Convergence finding**: ours never reaches sub_82172BA0 because tid=1 is stalled at `sub_82173990+0x2D0` (handle 0x12A4 = tid=13's thread handle — AUDIT-049 wedge). The whole 5-level ladder downstream is gated by this wait. [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What is the array at `[r29+56]`? Likely a list of subsystem objects (graphics, audio, input, etc.) the game-loop dispatcher iterates each frame. Canary `r3=0xBCCC4A80` is the dispatcher object.
|
||||
- The `bctrl`'s xref-table is incomplete (missing `sub_821B55D8`). Investigate the dynamic-target inference's gap.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Callers: `sub_821741C8+0x300`.
|
||||
- Callees (via bctrl): `sub_821B55D8` (observed in canary), plus 50+ others recorded in DB.
|
||||
- Upstream: `sub_822F1AA8` → vtable[0]=`sub_82173990` → calls `sub_821741C8`.
|
||||
- Audits: 033 (T6 gateway analysis), 058, 064.
|
||||
- Artifacts: `audit-runs/audit-064-activation-ladder/canary-{60,120,180}s.log`, `canary-upstream-60s.log`, `canary-inside-822F1AA8.log`.
|
||||
167
docs/functions/sub_82173990.md
Normal file
167
docs/functions/sub_82173990.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
address: 0x82173990
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 066
|
||||
aliases:
|
||||
- "tid=1 join wait site (the wedge PC 0x82173C60)"
|
||||
- "synchronous task-spawn + join helper"
|
||||
---
|
||||
|
||||
# sub_82173990 — synchronous task-spawn-and-join helper
|
||||
|
||||
## Synopsis
|
||||
|
||||
Tid=1 (main) one-shot helper that builds a stack-resident task descriptor, calls
|
||||
`sub_821746B0` to allocate+initialize a 24-byte task record (which encapsulates
|
||||
a sync object created by `sub_82172370`), and waits INFINITE on that sync
|
||||
object. The wait at PC **`0x82173C60 bl 0x824AA330`** (= `sub_82173990+0x2D0`)
|
||||
is the AUDIT-049/AUDIT-064 wedge site — both canary and ours enter the wait,
|
||||
but only canary's wait completes. The wait is on the **thread handle** of the
|
||||
worker spawned by `sub_821746B0` (XThread or KE_THREAD), released when that
|
||||
worker calls `ExTerminateThread`. Function is called exactly **1× per boot** in
|
||||
both engines (entry probe fires once, all body PCs fire once each).
|
||||
|
||||
## Evidence
|
||||
|
||||
Disasm-anatomy (size 768B / 192 insns @ `0x82173990..0x82173C8C`):
|
||||
|
||||
```
|
||||
0x82173990 mflr/prologue (256-byte frame)
|
||||
0x821739B0 bl 0x8216E7E8 ; first string-init helper (r4=r11+6244)
|
||||
0x821739CC bl 0x82448AA0 ; cr0=.G. → arg!=0 path
|
||||
0x821739F0 bl 0x82448BC8 ; returns string-table entry → r28
|
||||
0x82173A38 bl 0x8216F218 ; internal copy
|
||||
0x82173A68 bl 0x821835E0 ; → r25 (ID/result); cr6-tests below
|
||||
0x82173A78 bne cr6, 0x82173A84 ; skip if r25==28
|
||||
0x82173A88 beq cr6, 0x82173BC0 ; skip if r25==0
|
||||
…
|
||||
0x82173B98 bl 0x82453910 ; signaler candidate (AUDIT-049 column)
|
||||
0x82173BC0 convergence label (string-table clean-up + dispatch)
|
||||
0x82173BE4 bl 0x824B2188 ; tid=1's outer-channel pump
|
||||
0x82173C34 bl 0x821746B0 ; allocates 24-byte task record, sub_82172370 fills [r29+4]
|
||||
0x82173C38 mr r30, r3 ; r30 = task struct
|
||||
0x82173C48 bl 0x824AA5C8 ; status query → r3 (canary r3=1 → [r31+80]=0x103 STATUS_PENDING)
|
||||
0x82173C54 bne cr6, 0x82173C64 ; guard: only wait if r11==0x103 (STATUS_PENDING)
|
||||
0x82173C5C lwz r3, 4(r30) ; r3 = task->sync_handle = [struct+4]
|
||||
0x82173C60 bl 0x824AA330 ; KeWaitForSingleObject INFINITE ← THE WEDGE
|
||||
0x82173C70 bl 0x82174AF8 ; post-wait task completion (sub_82174AF8 runs post-state transition)
|
||||
0x82173C88 epilogue (b 0x825F0FC4)
|
||||
```
|
||||
|
||||
### Canary run (AUDIT-065, 180s wallclock, --audit_61_branch_probe_pcs)
|
||||
|
||||
All 17 probed PCs fire **exactly 1× each on tid=F8000008 (= canary main / mapped from `tid=6`)**:
|
||||
|
||||
| PC | lr | r3 | r4 | tid | meaning |
|
||||
|---|---|---|---|---|---|
|
||||
| `0x82173990` | `0x822F1B50` | `BCCC4A80` | `701CF8C0` | 6 | entry; lr=post-bctrl of sub_822F1AA8 |
|
||||
| `0x821739CC` | — | `0x00000001` | `0x820A17A8` | 6 | cr0=.G. — `cmplwi r28,0` post-strcmp != 0 path |
|
||||
| `0x821739F0` | — | `BCCC4A64` | `BCCC49FC` | 6 | r28 populated; cr6=..E (==) |
|
||||
| `0x82173A38` | — | `701CF860` | `701CF840` | 6 | inner copy call entry |
|
||||
| `0x82173A68` | — | `BDE996FF` | `BDE98F14` | 6 | r25=0xBDE996FF (returned ID), cr6=..E |
|
||||
| `0x82173A78` | — | `0x0000001C` | `BDE98F14` | 6 | `bne 0x82173A84` — `r25 != 28` taken |
|
||||
| `0x82173BC0` | `0x82173A6C` | `0x1C` | … | 6 | convergence (post-bne over alloc); `beq 0x82173B14`-skipped |
|
||||
| `0x82173BE4` | `0x82173BD4` | `BE568F00` | `0x00000005` | 6 | bl 0x824B2188 entry |
|
||||
| `0x82173C34` | `0x82173C1C` | `BCCC4A80` | `0x00000000` | 6 | bl 0x821746B0 entry — calls task-alloc |
|
||||
| `0x82173C38` | `0x82173C38` | `BC365700` | `701CF6E0` | 6 | r3=task_struct, cr6=.G. |
|
||||
| `0x82173C48` | `0x82173C38` | `F8000094` | `701CF800` | 6 | post bl 0x824AA5C8; r3=F8000094 ?? actually this is r3 at BB entry post-bl |
|
||||
| `0x82173C54` | `0x82173C4C` | `0x00000001` | `0x30000000` | 6 | cmplwi r11, 0x103 — value 0x103 sets cr6=..E (eq) per actual r11; BUT cr6=..E means !lt!gt eq — wait was entered |
|
||||
| `0x82173C60` | `0x82173C4C` | **`F8000094`** | `FFFFFFFF` | 6 | **wait entry — r3 = thread handle** |
|
||||
| `0x82173C64` | `0x82173C64` | `0` | `0x1` | 6 | post-wait — wait completed! |
|
||||
| `0x82173C70` | `0x82173C64` | `BCCC4A80` | `BC365700` | 6 | bl 0x82174AF8 (cleanup) |
|
||||
| `0x82173C88` | `0x82173C88` | `701CF840` | … | 6 | epilogue |
|
||||
|
||||
Wait duration: ~445 log lines between PC `0x82173C60` (entry) and `0x82173C64` (post-wait).
|
||||
|
||||
Inside the wait window: `K> F8000094 XThread::Execute thid 17 (handle=F8000094, 'XThread01F4 (F8000094)', native=000001F4)` — i.e. F8000094 IS a thread handle. The thread loads cache files (`cache:\aab216c3\5\ee70e0a`, `cache:\87719002\c\dba806e`, `cache:\87719002\c\ec0a96e`, `cache:\87719002\a\60fcb85`, etc) and spawns child workers (`ExCreateThread(..., 824AFF88, 821C4AD0, BCA44C00, ...)` and others). The wait completes immediately after `d> F8000094 ExTerminateThread(00000000)`.
|
||||
|
||||
### Ours run (AUDIT-065, -n 500M instructions)
|
||||
|
||||
Only 6 of 24 BB-entry probed PCs fire (ours's branch probe fires only at BB-entry):
|
||||
|
||||
| PC | lr | r3 | cycle | meaning |
|
||||
|---|---|---|---|---|
|
||||
| `0x82173990` | `0x822F1B50` | `0x40ba9a80` | 6,172,194 | entry; lr= bctrl in sub_822F1AA8 |
|
||||
| `0x821739CC` | — | `0x00000001` | 6,172,686 | non-zero arg path |
|
||||
| `0x821739F0` | — | `0x40ba9a64` | 6,173,074 | str-init complete |
|
||||
| `0x82173A68` | — | `0x41d7e6ff` | 9,174,034 | r3=str-table-entry (the AUDIT-049 wait inside sub_82452DC0 already happened HERE — note the cycle gap from 6.17M to 9.17M means tid=1 has been blocked in sub_82172370/etc; actually this is the `bl 0x821835E0` post-return) |
|
||||
| `0x82173BC0` | `0x82173A6C` | `0x1C` | 9,175,368 | convergence (r25=0x41d7e6ff != 28 not equal to 28; checks ≠0 also nonzero, fall thru to skip block @ 0x82173B14) |
|
||||
| `0x82173C38` | `0x82173C38` | `0x4024a640` | 9,178,243 | post `bl 0x821746B0` — r3=`0x4024a640` (task struct, ALSO start_ctx of tid=13) |
|
||||
|
||||
**No probe fires beyond `0x82173C38`**. The next BB-entry probe was `0x82173C64` (post-wait). Mid-block PCs `0x82173C48/C54/C60/C70` don't fire in ours's branch-probe (per AUDIT-046 reading-error #13). The fact that `0x82173C64` does NOT fire confirms: **ours's tid=1 wedges between `0x82173C38` and `0x82173C64`** — at the wait at `0x82173C60`.
|
||||
|
||||
### End-of-run thread state (ours --trace-handles dump)
|
||||
|
||||
```
|
||||
handle=0x000012a4 Thread(id=13, exit=None) waiters(tid)=[1]
|
||||
handle=0x000012ac kind=Event/Auto waiters=1 signals=0 waits=1 wakes=0 <NO_SIGNALS_DESPITE_WAITS>
|
||||
[ 0] cycle=0 tid=13 lr=0x824ac578 src=do_wait_single
|
||||
handle=0x000012b8 kind=Event/Auto waiters=1 signals=0 waits=1 wakes=0 <NO_SIGNALS_DESPITE_WAITS>
|
||||
[ 0] cycle=0 tid=5 lr=0x824ac578 src=do_wait_single
|
||||
```
|
||||
|
||||
tid=13 (handle 0x12A4, exit=None) is alive but stuck on event 0x12AC inside sub_821CB030 (cache file IO completion event). tid=5 is one of the workers parked on its own idle event 0x12B8. tid=1 join-waits tid=13 → tid=13 waits 0x12AC → 0x12AC needs workers → workers parked.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_82175330+0x4` via tail-jump (post-bctrl of vtable[0] dispatched at `sub_822F1AA8+0xA4`). One static caller `sub_82175330` per `sylpheed.db`.
|
||||
|
||||
Called exactly **1× per boot** on tid=1 in both engines.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Direct callers (sylpheed.db `xrefs.source_func`):
|
||||
- `sub_82175330+0x4` via `b 0x82173990` (tail-jump from the vtable thunk).
|
||||
- Direct callees of interest:
|
||||
- `bl 0x8216E7E8` at `+0x20` — string-table helper (used twice).
|
||||
- `bl 0x82448AA0` at `+0x38`, `+0x48`, `+0x88`, `+0xC4`, `+0x168`, `+0x260` — string-table lookup.
|
||||
- `bl 0x824AA7A0` at `+0x4C` — string-helper.
|
||||
- `bl 0x82448BC8` at `+0x5C` — internal lookup.
|
||||
- `bl 0x82448C50` at `+0x78`, `+0x98`, `+0x178`, `+0x308` — string convert.
|
||||
- `bl 0x8216F218` at `+0xA8`, `+0x188` — copy / string ops.
|
||||
- `bl 0x8217C850` at `+0xAC` — query.
|
||||
- `bl 0x82178E50` at `+0xB8` — query.
|
||||
- `bl 0x821835E0` at `+0xD8` — returns ID into `r25` (key gate).
|
||||
- `bl 0x824AA830` at `+0xFC`, `+0x148` — kernel helper.
|
||||
- `bl 0x822C69C8` at `+0x104`, `+0x134` — task-helper.
|
||||
- `bl 0x822DE650` at `+0x118` — helper.
|
||||
- `bl 0x822F2328` at `+0x124`, `+0x240` — helper (calls inside outer sub_822F1AA8 too).
|
||||
- `bl 0x822DE858` at `+0x13C` — helper.
|
||||
- `bl 0x822F28C0` at `+0x144`, `+0x25C` — helper.
|
||||
- `bl 0x82674028` at `+0x15C` — kernel-debug printf? format.
|
||||
- `bl 0x82150EF8` at `+0x1A4` — heap alloc 28-byte struct.
|
||||
- `bl 0x824523E8` at `+0x1FC` — task-helper.
|
||||
- `bl 0x82453910` at `+0x208` — **signaler/notify (AUDIT-049 column)**.
|
||||
- `bl 0x821506B8` at `+0x224` — heap free.
|
||||
- `bl 0x8216E790` at `+0x22C`, `+0x2EC`, `+0x2F4` — string-cleanup.
|
||||
- `bl 0x824B2188` at `+0x254` — tid=1's outer-channel pump.
|
||||
- `bl 0x824482D0` at `+0x288` — format.
|
||||
- **`bl 0x821746B0` at `+0x2A4`** — **task allocator + worker spawn (the key call).**
|
||||
- `bl 0x824AA5C8` at `+0x2B8` — status query (returns `r3` → checked vs `0x103 STATUS_PENDING`).
|
||||
- **`bl 0x824AA330` at `+0x2D0`** — **`KeWaitForSingleObject` INFINITE — THE WEDGE PC.**
|
||||
- `bl 0x82174AF8` at `+0x2E0` — post-wait task-cleanup/state-transition.
|
||||
- `b 0x825F0FC4` at `+0x2FC` — epilogue tail-jump.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-066 (2026-05-12)** — **source-review only (READ-ONLY)**. AUDIT-065's "host-side `F8000048` IO completion thread" inference falsified by canary source review. `F8000048` is a **guest XThread thid=10**, spawned by main at canary-run.stdout:1331 via `ExCreateThread(...,824AFF88, 82450A28, 828F3B68, 0)` — entry `0x82450A28` is a Sylpheed-internal worker thread, not host infrastructure. Canary's only host helper thread is "Kernel Dispatch" (`xenia-canary/src/xenia/kernel/kernel_state.cc:524-549`) which services `CompleteOverlappedDeferred` for XAM UI/content, not file IO. Canary's `NtReadFile`/`NtReadFileScatter`/`NtWriteFile` (`xboxkrnl_io.cc:125-389`) are synchronous and signal the supplied event handle inline via `ev->Set(0, false)` after the sync read. Ours's `signal_io_completion_event` (`exports.rs:1156-1169`) is the bit-equivalent. **No "host-side IO completion signal" gap exists** in ours's IO handlers. The wait at this fn's `+0x2D0` (PC `0x82173C60`) is on the thread handle for the worker spawned via `bl 0x821746B0` (= tid=13 in ours / thid=17 in canary), released by `ExTerminateThread` per AUDIT-065 — confirmed correct framing. AUDIT-066 conclusion: brief's proposed fix locus (`xenia-kernel/src/exports.rs` IO handlers) is wrong; the bug is upstream worker-cluster bootstrap (AUDIT-057/063/064 chain). No code change of any size in `exports.rs` would unwedge tid=13. [confirmed: brief premise falsified]
|
||||
- **AUDIT-065 (2026-05-12)** — full disasm + 17-PC probe in BOTH engines. **Canary's tid=1 (= F8000008, internally tid=6) reaches PC `0x82173C60` exactly once, waits on r3=`0xF8000094` (= XThread thid 17's thread handle), and the wait completes when that worker reaches `ExTerminateThread(0)`**. Worker runs synchronous cache file IO (`cache:\aab216c3\5\ee70e0a`, `cache:\87719002\c\...` etc) and spawns child workers via `ExCreateThread(... 824AFF88, 821C4AD0, BCA44C00 ...)` before terminating. **Ours's tid=1 reaches PC `0x82173C38` (post `bl 0x821746B0`, r3=`0x4024a640` = ours's task struct = ours's tid=13's start_ctx) and stalls before `0x82173C64`** — i.e. inside the wait at `0x82173C60`. Ours's tid=13 (created by `bl 0x821746B0`'s subroutine, entry `0x821748F0`) DOES open the same cache files (`cache:/aab216c3/5`, `cache:/aab216c3`) but BLOCKS inside `sub_821CB030+0x1AC` on event `0x12AC` (NO_SIGNALS_DESPITE_WAITS). So `ExTerminateThread` is never reached on ours's tid=13 → tid=1's wait on `0x12A4` never completes. **Refines the wedge from "thread-join on tid=13" to a precise mechanism**: the wait at `+0x2D0` is structurally a synchronous task-join (canary worker exits in ~1ms; ours's worker is permanently stuck downstream). **`sub_82173990` body itself is clean** — every probed PC except the wait completion matches canary's behavior; the divergence is entirely in what happens inside `sub_821746B0`'s spawned worker (tid=13's body in `sub_821748F0` → `sub_821C4EB0` → `sub_821CC3F8` → `sub_821CBA08` → `sub_821CB030`). Same AUDIT-049 island, now framed as: **how does canary's worker get its `0x12AC`-equivalent event signaled fast enough that the worker can call `ExTerminateThread`?** [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- **What signals canary's tid=17 cache-IO completion event (the `0x12AC`-equivalent inside its `sub_821CB030`)?** Probe canary's `NtSetEvent`/`KeSetEvent` thunks (`0x8284DF5C`/`0x82490018`) filtered on tid=17's `r3`-handle in the lr-window around the corresponding tid=17 wait. Compare against ours's empty `0x12AC.signals` count.
|
||||
- The cache file open flow looks identical in both engines for `cache:\aab216c3\…` paths — confirming AUDIT-054's VFS layout fix landed correctly. The divergence is purely in the producer-side signaling.
|
||||
- Both engines pass through PC `0x82173B98` (the `sub_82453910` "signaler" candidate) on the `r25!=0 && r25!=28` path — but only if the AUDIT-046 "5/5 iter" path lands inside the block `[0x82173B14, 0x82173BC0]`. Ours's BB-entry probe shows `0x82173BC0` fires but NOT `0x82173B14/B40/B84/B98/BA0/BA8` — meaning ours's beq at `0x82173B14` is **taken** (r5==0) and we skip the entire block. Canary's BR list also shows direct jump from `0x82173A78`→`0x82173BC0` (line 1998→1999) — **so canary also skips the block at 0x82173B14**. Block is dead in both engines at this horizon. `sub_82453910` is NOT the relevant signaler at this call site.
|
||||
- `r3` at `0x82173C48` shows `F8000094` in canary at probe time, but that's the **post-`bl 0x824AA5C8` PC** (mid-block; the captured value is whatever r3 carried at that instant — likely the handle being queried, not the return). Worth a follow-up probe to confirm the status-query target.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Caller: [sub_822F1AA8](sub_822F1AA8.md) (via thunk sub_82175330, vtable[0] of `*(0x828E1F08)`).
|
||||
- Callees of interest:
|
||||
- `sub_821746B0` — task allocator + worker spawn (no dossier yet — recommend creating one).
|
||||
- `sub_82172370` — sync object creator (no dossier yet).
|
||||
- `sub_82174AF8` — post-wait cleanup (no dossier yet).
|
||||
- Worker-side wedge: [sub_821CB030](sub_821CB030.md) — fires inside the worker spawned via sub_821746B0 from sub_82173990's `bl` at `+0x2A4`.
|
||||
- Audits: 049 (original tid=1 stall localization), 064 (full activation chain to sub_825070F0), 065 (this).
|
||||
- Artifacts: `audit-runs/audit-065-sub82173990-wait-site/{sub_82173990.disasm,canary.log,canary-run.stdout,ours.log,ours-stdout.log,summary.md}`.
|
||||
53
docs/functions/sub_821B55D8.md
Normal file
53
docs/functions/sub_821B55D8.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
address: 0x821B55D8
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 064
|
||||
aliases:
|
||||
- "AUDIT-058 caller-ladder fn #5 (vtable slot 6 of class containing 0x82172D88 dispatcher)"
|
||||
---
|
||||
|
||||
# sub_821B55D8 — vtable slot 6 invoked from sub_82172BA0 dispatcher
|
||||
|
||||
## Synopsis
|
||||
|
||||
Normal callee dispatched via the `bctrl` at `sub_82172BA0+0x1E8` (PC `0x82172D88`) — slot 6 of some game-object vtable (offset 24 = `lwz r11, 24(r11)`). Calls [sub_824F8398](sub_824F8398.md) at PC `0x821B5B5C` (=+0x584). Note the **only static caller is via `b` (jump, NOT bl)** from `sub_821B6DF4+0x40` — that's the MSVC EH catch-handler trampoline at PC `0x821B6E34`. **AUDIT-064 falsifies the AUDIT-058 framing that this is reached primarily via the EH path**: at runtime it's reached via the `bctrl` slot-6 dispatch from `sub_82172BA0`, not via the EH thunk.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm prolog at `0x821B55D8`: `mflr r12; bl 0x825F0F74; stfd f31, -88(r1); subi r31, r1, 368; stwu r1, -368(r1); mr r30, r3; ...` — standard normal-callee prolog. Uses `subi r31, r1, 368` (frame-pointer is `r1-368`), NOT MSVC EH-handler's `subi r31, r12, N`.
|
||||
- Function size: 2076 bytes / 519 insns. `has_eh=True`, `frame_size=0` per DB (but the actual stack alloc is 368 bytes — `frame_size=0` likely indicates dynamic).
|
||||
- Static caller xref (sole): PC `0x821B6E34` inside `sub_821B6DF4` via `kind=j insn=b` (unconditional branch, NOT bl). This is an EH catch-handler trampoline that tail-jumps into this fn's body — it's how the MSVC EH machinery enters the fn AFTER a matching exception is caught. Pattern at `0x821B6E30..0x821B6E34`: `lwz r3, 8(r3); b 0x821B55D8`.
|
||||
- AUDIT-064 canary 60s probe: fires 1× with `lr=0x82172D8C r3=BCCC52C0 r4=FFFFFFFF r5=01000000 r6=00000000` on tid=6. `lr=0x82172D8C` is the post-bctrl PC inside `sub_82172BA0+0x1E8`. Reproduced at 120s and 180s.
|
||||
- AUDIT-064 ours `--ctor-probe=0x821B55D8` -n 500M: **0 fires**.
|
||||
|
||||
## Activation
|
||||
|
||||
**Primary (runtime)**: vtable slot 6 dispatch from `sub_82172BA0+0x1E8 bctrl` (PC `0x82172D88`). The dispatcher walks an array of objects (loaded from `[r29+56]`) and invokes vtable slot 6 on each. Slot 6 = `lwz r11, 24(r11)` where r11 is the vtable.
|
||||
|
||||
**Secondary (EH path)**: MSVC catch-handler at `sub_821B6DF4+0x40` tail-jumps here when a matching exception is caught. Not the runtime activation path observed in either engine at this horizon.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (DB):
|
||||
- `sub_821B6DF4+0x40` via `b 0x821B55D8` (EH thunk, NOT a `bl` — reached via exception dispatch only).
|
||||
- No `bl` static callers recorded — but **AUDIT-064 captured `lr=0x82172D8C` at runtime fire**, meaning the actual `bl`-equivalent caller is the bctrl at `sub_82172BA0+0x1E8`. The static analyzer's ind_call list for PC `0x82172D88` includes many observed targets but NOT this fn (gap in the dynamic-target inference).
|
||||
- Callees: `sub_824F8398` at PC `0x821B5B5C`, plus many others (`sub_821707C0`, `sub_822F13B0`, `sub_822F2A00`, `sub_823C2990`, ...).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-064 (2026-05-12)** — disasm confirms normal-callee prolog (refutes "EH handler" hypothesis). Canary fires 1× / ours 0×. **Real runtime caller is `sub_82172BA0+0x1E8 bctrl`, NOT `sub_821B6DF4` EH thunk.** The DB xref via `b` from EH is a secondary entry path. **New reading-error class observed**: static xrefs for `bctrl` indirect targets are populated by some dynamic-target inference but it has gaps — must cross-check at runtime via `--audit_61_branch_probe_pcs` + LR resolution. [confirmed]
|
||||
- **AUDIT-058 (2026-05-10)** — flagged as part of static caller ladder under `sub_821B6DF4`. [STATUS: partially falsified by AUDIT-064 — the runtime path is the bctrl from sub_82172BA0, not the EH thunk.]
|
||||
|
||||
## Open questions
|
||||
|
||||
- Which class's vtable has slot 6 = `sub_821B55D8`? The instance loaded by `sub_82172BA0` at `[r3+24]` from the array. Possibly `silph::GamePart_Title` or a sibling — would need to enumerate `sub_82172BA0`'s array-walk target instances at runtime.
|
||||
- Why does the DB's `xrefs` (kind=`ind_call`) for source `0x82172D88` not list `sub_821B55D8` as a target? The dynamic-target inference appears to populate from a separate trace, missing this one.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Callees: `sub_824F8398` (PC `0x821B5B5C`).
|
||||
- EH-secondary entry: `sub_821B6DF4+0x40` (`b 0x821B55D8`).
|
||||
- Runtime caller (bctrl): `sub_82172BA0+0x1E8` (PC `0x82172D88`).
|
||||
- Audits: 058, 060, 064.
|
||||
- Artifacts: `audit-runs/audit-064-activation-ladder/canary-{60,120,180}s.log`, `canary-upstream-60s.log`.
|
||||
58
docs/functions/sub_821B6DF4.md
Normal file
58
docs/functions/sub_821B6DF4.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
address: 0x821B6DF4
|
||||
classification: msvc_eh_catch_handler
|
||||
confidence: high
|
||||
last_audit: 060
|
||||
aliases:
|
||||
- "AUDIT-058 caller-ladder top (FALSIFIED)"
|
||||
---
|
||||
|
||||
# sub_821B6DF4 — MSVC C++ catch-handler thunk
|
||||
|
||||
## Synopsis
|
||||
|
||||
A C++ catch-handler thunk emitted by the MSVC PowerPC C++ runtime. Dispatched by the EH machinery (`_CxxFrameHandler3` equivalent) when a matching exception type is thrown — NOT a normal `bl` callee. AUDIT-058 mistakenly treated it as the top of a "static caller ladder" for `sub_825070F0`'s activation; AUDIT-060 falsified that by reading the prolog and the `.rdata` reference context.
|
||||
|
||||
**This is the canonical "MSVC EH FuncInfo metadata mistaken for call edges" case. Always check the prolog before assuming a 0-caller fn is a missing activator.**
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm at `0x821B6DF4` opens with the canonical MSVC catch-handler prolog: `subi r31, r12, 112; mflr r12; stwu r1, -96(r1); ...`. The use of `r12` (parent-frame pointer offset) and `mflr r12` is signature MSVC EH-handler shape.
|
||||
- Address `0x821B6DF4` appears as a u32 value in only two places in the binary:
|
||||
- `.rdata:0x820C1994` — embedded inside an MSVC FuncInfo struct. Bracketing bytes: `FFFFFFFF 821B6DF4 19930522 00000001 820C1990 ...`. `0x19930522` is the MSVC FuncInfo magic.
|
||||
- `.pdata:0x8211C678` — exception-unwind metadata.
|
||||
- AUDIT-060 Probe C-Win Windows Debug canary: `--log_lr_on_pc=0x821B6DF4`, runs at 120s and 240s wallclock → **0 fires both runs**. The matching exception is not thrown at this boot horizon.
|
||||
- AUDIT-060 Probe O ours: `--ctor-probe=0x821B6DF4 -n 500M` → **0 fires**.
|
||||
- Body: single `bl 0x82183B78` (an EH support routine) then return.
|
||||
|
||||
## Activation
|
||||
|
||||
C++ exception runtime dispatch. Fires iff a try-block protected by the FuncInfo at `0x820C1990` catches a thrown object whose type matches the catch's CatchTypeInfo. Neither engine throws this exception at the probed horizon.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers: **0** — and this is correct (0 callers does not imply dead; it implies "not a bl target").
|
||||
- Callees: `sub_82183B78` (EH support routine).
|
||||
- xrefs in DB will show `kind=indirect` or absent entries; the `.rdata` reference at `0x820C1994` is the FuncInfo binding, not a call edge.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-060 (2026-05-12)** — disassembled body; identified MSVC catch-handler prolog; cross-referenced `.rdata` bytes to find FuncInfo magic `0x19930522`; probed in both engines at 240s/-n500M → 0 fires both sides. AUDIT-058's "caller ladder" framing falsified. New reading-error class #16 logged. [confirmed]
|
||||
- **AUDIT-058 (2026-05-10)** — claimed as "top of static caller ladder" for `sub_825070F0` activation, walked: `sub_825070F0 ← sub_824F7800 ← sub_824F7CD0 ← sub_824F8398 ← sub_821B55D8 ← sub_821B6DF4`. All 6 fire 0× in ours; framed as missing activation. [STATUS: falsified by AUDIT-060 — the entire 6-fn chain is C++ EH unwind metadata; none of them are normal call edges; they fire only on specific exception throws.]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What exception type-id activates this catch? Parse the FuncInfo struct at `0x820C1990`:
|
||||
- TryBlockMap entries → CatchTypeArray pointer → CatchType records (each has type_info* + handler ptr).
|
||||
- The type_info string would identify the C++ class being caught.
|
||||
- Is the matching throw site reachable in either engine at *any* boot horizon? If yes, when?
|
||||
- Are the other 5 fns in the AUDIT-058 ladder ALL catch-handler thunks? Spot-check `sub_821B55D8`, `sub_824F8398`, `sub_824F7CD0`, `sub_824F7800`, `sub_825070F0`. (`sub_825070F0` DOES fire 1× per AUDIT-058 — so at least it's not pure-EH; could be the actual throw site or a normal-call leaf.)
|
||||
|
||||
## Cross-references
|
||||
|
||||
- FuncInfo location: `.rdata:0x820C1990` (start of struct), `0x820C1994` contains this fn's pointer.
|
||||
- `.pdata` unwind: `0x8211C678`.
|
||||
- Body callee: `sub_82183B78` (EH support).
|
||||
- Companion ladder fns (need separate dossiers): `sub_821B55D8`, `sub_824F8398`, `sub_824F7CD0`, `sub_824F7800`, [sub_825070F0](sub_825070F0.md).
|
||||
- Audits: 058, 060.
|
||||
- Artifacts: `audit-runs/audit-060-fnptr-array-bootstrap/canary-sub821B6DF4-120s.log`, `canary-sub821B6DF4-240s.log`, `ours-summary.md`.
|
||||
76
docs/functions/sub_821C4EB0.md
Normal file
76
docs/functions/sub_821C4EB0.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
address: 0x821C4EB0
|
||||
classification: vtable_method
|
||||
confidence: high
|
||||
last_audit: 061
|
||||
aliases:
|
||||
- "silph::GamePart_Title::UImpl member fn"
|
||||
- "AUDIT-056 early-exit (falsified by 061)"
|
||||
---
|
||||
|
||||
# sub_821C4EB0 — silph::GamePart_Title::UImpl member fn (AUDIT-061: NOT a branch-divergence gate)
|
||||
|
||||
## Synopsis
|
||||
|
||||
Member function on class `silph::GamePart_Title::UImpl` (vtable `0x820a3e00`). **AUDIT-061 falsified the "conditional-branch divergence in `[+0x44, +0xE0]`" framing**: all 4 branches in that range are decided **bit-identically** in canary and ours. The actual divergence is the call `bl 0x821CC3F8` at PC `0x821C4F14`: in canary the call returns to `0x821C4F18` and the rest of sub_821C4EB0 executes through the 5 `bl 0x821CEDF8` sites at +0x198..+0x240; in ours the call enters the chain `sub_821CC3F8 → sub_821CBA08 → sub_821CB030` and never returns (tid=13 wedge inside sub_821CB030 = AUDIT-049 wedge handle, `NtCreateEvent` at +0x128 → INFINITE wait). AUDIT-056's "5× canary / 0× ours" callsite count is an indirect consequence of the upstream wedge, not a branch-decision asymmetry in this fn.
|
||||
|
||||
## Evidence
|
||||
|
||||
- AUDIT-049: appears in tid=13 thread-create chain — `sub_821748F0 → sub_821C4EB0 (UImpl@GamePart_Title@silph) → sub_821CC3F8 → sub_821CBA08 → sub_821CB030`.
|
||||
- AUDIT-056: caller-LR `0x821C4F2C / 0x821C5014 / 0x821C5048` are post-`bl` PCs inside this fn. Reported `sub_821CEDF8` 5× canary / 0× ours.
|
||||
- AUDIT-059: in the wedge's wait-thread frame-4 saved-r29 the vtable is `0x820a3e00 = .?AUImpl@GamePart_Title@silph@@`, confirming class membership.
|
||||
- AUDIT-061 (READ-ONLY canary multi-PC probe @ ~2:00 wallclock; ours `--branch-probe` @ -n 500M):
|
||||
- Both engines call sub_821C4EB0 exactly **1×** at this horizon. Same caller LR=0x82174A80 (canary tid=17, ours tid=13).
|
||||
- Canary probe fires 17× covering entry + post-bl block entries + all 4 cond-branches: B1 `beq cr6 NOT taken` (cr6=.G., r3=0xBC220008≠0), B2 `bne cr6 NOT taken` (cr6=..E, lbz @ 0x828F3284 = 0), B3 `beq cr6 TAKEN` (cr6=..E, lwz r3,92(r30) == 0), B4 `bgt cr6 TAKEN` (cr6=.G., [r27+4] > 4). Reaches 0x821C5048 (1st `bl 0x821CEDF8`) and 0x821C504C (returned).
|
||||
- Ours probe fires 4× covering entry + 3 post-bl: 0x821C4EB0 → 0x821C4EB8 → 0x821C4ED0 → 0x821C4EEC (r3=0x40105004 returned from `bl 0x82150EF8`; cr6=.G., **same direction as canary**). After 0x821C4EEC: **never reaches 0x821C4F18 or anywhere later in the function**.
|
||||
- Chain probe (separate run) confirms ours's tid=13 enters sub_821CC3F8 (cycle 2069) → sub_821CC3F8+0x38 post-alloc (2249) → sub_821CBA08 (2258) → sub_821CB030 (3242), then stalls. Canary's tid=17 returns out of all four and reaches 0x821CC454 (post-bl-sub_821CBA08) and 0x821C4F18 (post-bl-sub_821CC3F8) cleanly.
|
||||
- First divergent INSTRUCTION (not branch): `bl 0x821CC3F8` at PC `0x821C4F14`. First divergent state: ours's r3 at function entry to sub_821CC3F8 is `0x40105004` (40xxxxxx host-allocator region) vs canary's `0xBC220008` (BCxxxxxx region) — but this VA difference is the AUDIT-043 ε-class (allocator region drift) and is BENIGN here; sub_821CC3F8 dereferences r3 as a pool handle the same way in both engines and downstream allocation succeeds (sub_82150EF8 returns valid pointer in both).
|
||||
|
||||
## Activation
|
||||
|
||||
Vtable method. Reached via `bctrl` from class-owning code in the boot UI / GamePart_Title state machine. Indirect; the dispatch site PC and vtable slot index need DB cross-reference (see Open questions).
|
||||
|
||||
## Static graph
|
||||
|
||||
- Caller chain at the wedge site (AUDIT-049):
|
||||
- `sub_821C4EB0 ← sub_821748F0` (top-level)
|
||||
- flows down to `sub_821CC3F8 (GamePart_Title)` → `sub_821CBA08` → `sub_821CB030` (where wedge fires)
|
||||
- Callees in source order:
|
||||
- `0x821C4EB4 bl 0x825F0F7C` — save-GPRs prologue helper
|
||||
- `0x821C4ECC bl 0x8284DA7C` — XAM import `XNotifyPositionUI` (xam.xex ord 652); r3=0xA → returns 0 in both engines.
|
||||
- `0x821C4EE8 bl 0x82150EF8` — pool allocator (called with allocator table @ `[0x828E0000+11028]`, size=4); returns pointer in both engines (canary BC220008, ours 0x40105004).
|
||||
- `0x821C4F14 bl 0x821CC3F8` — **first divergent instruction (AUDIT-061)**: returns in canary, wedges in ours.
|
||||
- `0x821C4F2C bl 0x82187C30` — only reached in canary at this horizon.
|
||||
- `0x821C4F60 bl 0x82172370` — only reached in canary.
|
||||
- `0x821C4F74 bl 0x824AA3E0` — conditional on prior beq; canary takes the SKIP-bl path (B3 = taken).
|
||||
- `0x821C5048 / 0x821C5074 / 0x821C50A0 / 0x821C50C8 / 0x821C50F0 bl 0x821CEDF8` — 5 sites in the bgt-taken path; only reached in canary.
|
||||
- Conditional branches in `[+0x44, +0xE0]` (enumerated AUDIT-061):
|
||||
- B1 `0x821C4EF8 beq cr6, 0x821C4F20` — after `cmplwi cr6, r3, 0` (r3 = sub_82150EF8 return). Decided NOT taken in both.
|
||||
- B2 `0x821C4F3C bne cr6, 0x821C4F7C` — after `lbz r10, 12932(0x828F0000)+cmplwi r10, 0`. Decided NOT taken in canary; UNREACHED in ours.
|
||||
- B3 `0x821C4F70 beq cr6, 0x821C4F78` — after `lwz r3, 92(r30)`. Decided TAKEN in canary; UNREACHED in ours.
|
||||
- B4 `0x821C4F90 bgt cr6, 0x821C5000` — after `cmplwi cr6, r11, 3`, r11 = `[r27+4]−1`. Decided TAKEN in canary; UNREACHED in ours.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-061 (2026-05-12)** — Multi-PC branch probe in both engines (new canary cvar `audit_61_branch_probe_pcs`, ours `--branch-probe`). All 4 conditional branches in `[+0x44, +0xE0]` decided **bit-identically** (B1 NOT-taken in both; B2/B3/B4 UNREACHED in ours because the function stalls earlier). First divergent BEHAVIOR is the call `bl 0x821CC3F8` at PC `0x821C4F14` — returns in canary, wedges in ours. The wedge is INSIDE `sub_821CB030` (chain `sub_821C4EB0 → sub_821CC3F8 → sub_821CBA08 → sub_821CB030`); tid=13 reaches sub_821CB030 at cycle 3242 and blocks indefinitely. Confirms AUDIT-049 wedge premise; matches AUDIT-059 γ-class missing-signaler. AUDIT-056's "5× sub_821CEDF8 canary / 0× ours" is an indirect consequence (those 5 sites are at +0x198..+0x240, downstream of the wedge). [confirmed — sub_821C4EB0 is NOT a branch-divergence gate]
|
||||
- **AUDIT-060 (2026-05-12)** — convergence confirmed this fn as the AUDIT-061 target after AUDIT-058/059's "missing activator" framing was refuted. [superseded by 061 — actual divergence is non-returning call, not a branch]
|
||||
- **AUDIT-056 (2026-05-10)** — identified as the primary divergence-introducer. Caller-LR is IDENTICAL canary/ours but body chooses a different path. [partially falsified by 061 — the "different path" framing was true at a high level, but it's because of a non-returning call, not a divergent conditional-branch decision in `[+0x44, +0xE0]`. The 5 sub_821CEDF8 callsites are downstream of the wedge.]
|
||||
- **AUDIT-049 (2026-05-10)** — placed on the tid=13 chain that ultimately creates wedge handle. [confirmed — AUDIT-061 directly observed tid=13 entering sub_821CB030 in ours]
|
||||
|
||||
## Open questions
|
||||
|
||||
- ~~Enumerate every conditional branch PC in `[0x821C4EF4, 0x821C4F90]`~~. **DONE in AUDIT-061**: B1/B2/B3/B4 enumerated; none divergent in decision.
|
||||
- ~~For each branch: capture cr0/cr6/cr-of-interest...~~. **DONE in AUDIT-061**.
|
||||
- ~~What input register controls the first divergent branch?~~ **Moot — no branch diverges in this fn.**
|
||||
- **NEW (AUDIT-062 target):** Where INSIDE sub_821CB030 does ours's tid=13 stall? AUDIT-049 hypothesized the wait at the event handle created at +0x128. Probe sub_821CB030's basic-block entries to find the highest-PC reached by tid=13 before stall; cross-reference with the NtCreateEvent / KeWaitForSingleObject sites.
|
||||
- Which vtable slot is `sub_821C4EB0` at in vtable `0x820a3e00`? (still open; cross-ref `xrefs` table for `target = 0x821C4EB0` with `kind = 'read'` or `'ref'` in `.data`/`.rdata`).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Vtable: `0x820a3e00 = .?AUImpl@GamePart_Title@silph@@` (class)
|
||||
- Sibling class vtable: `0x820a3dc8 = .?AVGamePart_Title@silph@@` (parent? aggregate?)
|
||||
- Callees: `sub_821CC3F8` (first-divergent-call AUDIT-061), `sub_821CEDF8` (5× sites at +0x198..+0x240, only reached in canary)
|
||||
- Callers: `sub_821748F0` (top of tid=13 chain; lr=0x82174A80 seen in both engines AUDIT-061)
|
||||
- Wedge chain: [sub_821CB030](sub_821CB030.md) is where ours's tid=13 stalls per AUDIT-061's chain probe.
|
||||
- Audits: 049, 056, 057, 058, 059, 060, **061**
|
||||
- Artifacts: `audit-runs/audit-056-producer-trace/`, `audit-runs/audit-059-gamma-wedge/`, `audit-runs/audit-061-sub821C4EB0-branch-diff/`
|
||||
62
docs/functions/sub_821CB030.md
Normal file
62
docs/functions/sub_821CB030.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
address: 0x821CB030
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 066
|
||||
aliases:
|
||||
- "wedge primary site"
|
||||
- "file-IO completion event creator+waiter"
|
||||
---
|
||||
|
||||
# sub_821CB030 — wedge primary site (creates + submits + waits file-IO completion XEvent)
|
||||
|
||||
## Synopsis
|
||||
|
||||
The function whose body creates, submits work for, and waits on the canonical AUDIT-049/058/059 γ-wedge XEvent. Used by `silph::GamePart_Title::UImpl` to load `cache:\aab216c3\5\…` files synchronously: NtCreateEvent at `+0x128`, work submit at `+0x19C` (calls `sub_82452DC0`), wait INFINITE at `+0x1AC`. The wait is what blocks the entire post-intro phase in ours.
|
||||
|
||||
## Evidence
|
||||
|
||||
- AUDIT-049: tid=13 chain ends at this fn with wait at `0x824ac578` (KeWaitForSingleObject in the wait wrapper called from `+0x1AC`).
|
||||
- AUDIT-058: canary captures `sub_821CB030+0x12c` (=PC after the NtCreateEvent bl) in stacks.
|
||||
- AUDIT-059 Probe O ours: handle `0x12AC` (Event/Auto) created at `0x821cb158` (=`+0x128`), waited at `0x821cb1dc` (=`+0x1AC`). Wedge has `signal_attempts=0` — never signaled by the worker side.
|
||||
- AUDIT-059 Probe C canary: same PCs fire; `0xF8000098` created, then `NtDuplicateObject`'d to `0xF80000A0`, original closed fast, dup signaled by worker via `sub_82458B90`/`sub_8245EC10`.
|
||||
- File-IO context: precedes synchronous file load of `cache:\aab216c3\5\…` (post-VFS work in AUDIT-054).
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_821CBA08+0xd8` (AUDIT-059 create-stack frame 1). One static caller. Higher in the chain: `sub_821CC3F8 (GamePart_Title) → sub_821CBA08 → sub_821CB030`.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Callers:
|
||||
- `sub_821CBA08+0xd8` (only static caller)
|
||||
- Callees of interest:
|
||||
- `sub_824A9F18` — NtCreateEvent wrapper, called at `+0x124 bl` (post-call PC = `+0x128 = 0x821CB158`).
|
||||
- `sub_82452DC0` — work-submitter, called at `+0x198 bl` (post-call PC = `+0x19C`).
|
||||
- `sub_824AC540` — wait wrapper, called at `+0x1A8 bl` (post-call PC = `+0x1AC = 0x821CB1DC`).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-066 (2026-05-12)** — **source-review only (READ-ONLY)**. Re-read canary's `xenia/kernel/xboxkrnl/xboxkrnl_io.cc:39-389` + `xfile.cc:19-198` + `kernel_state.cc:519-551` and ours's `xenia-kernel/src/exports.rs:1103-1518, 3747-3764`. AUDIT-065's "host-side IO completion thread `F8000048` signals each per-load event" framing is **falsified**: (i) canary's `NtReadFile`/`NtReadFileScatter`/`NtWriteFile` are synchronous and signal the supplied event handle **inline** via `ev->Set(0, false)` (lines 210-212, 296-298, 383-385); no host async-IO thread exists; the only host thread "Kernel Dispatch" (`kernel_state.cc:524-549`) services `CompleteOverlappedDeferred` for XAM overlapped UI/content, not file IO; (ii) `F8000048` in AUDIT-065 stdout is a **guest XThread** thid=10 (entry `0x82450A28`, ctx `0x828F3B68`), spawned by main at `canary-run.stdout:1331` via `ExCreateThread(...,824AFF88, 82450A28, 828F3B68, 0)` — the `F8` prefix is a guest kernel-object handle region marker, NOT a host-thread marker; (iii) cache loads at `canary-run.stdout:2127-2154` (sequence `NtCreateEvent → NtCreateFile → NtDuplicateObject → NtQueryInformationFile → NtClose`) emit **zero** `NtReadFile`/`NtSetEvent` lines — `NtQueryInformationFile` has no event-handle parameter in either engine; (iv) thid=17 (`F8000094`) terminates via `ExTerminateThread(0)` WITHOUT ever calling Wait inside its cache loop — so the canary path doesn't even hit this fn's wait sites for the cache files visible in AUDIT-065's stdout. Ours's `signal_io_completion_event` (`exports.rs:1156-1169`) called from 16 sites in `nt_read_file`/`nt_write_file`/`nt_device_io_control_file` already implements canary's `ev->Set(0, false)` semantics — **there is no missing analog**. The wait at this fn's `+0x1AC` is a wait on the `sub_82452DC0` work-queue dup'd XEvent, signaled by guest worker-cluster code (γ-signalers A/B/C/D per AUDIT-059/060) — not IO completion. Bug class confirmed = AUDIT-063 structural / bootstrap-ordering. **AUDIT-066 fix locus (`xenia-kernel/src/exports.rs` IO handlers) is the WRONG target**; the bug is upstream in worker-cluster bootstrap (`sub_825070F0` activation gate). [confirmed: NO IO-completion gap]
|
||||
- **AUDIT-065 (2026-05-12)** — wedge mechanism precisely framed via [sub_82173990](sub_82173990.md). Canary's tid=17 worker (= analog of ours's tid=13) reaches `ExTerminateThread(0)` after sequentially loading `cache:\aab216c3\5\ee70e0a`, `cache:\87719002\c\dba806e/ec0a96e`, `cache:\87719002\a\60fcb85`, `cache:\87719002\2\85d8849`, `cache:\87719002\0\1a2db9c` etc — 16+ cache file loads — AND spawning child workers via `ExCreateThread(..., 824AFF88, 821C4AD0/822C6870, ...)`. Worker's own `sub_821CB030` calls (file-IO completion event waits) complete in canary. **In ours, the very first sub_821CB030 call (on handle `0x12AC`) hangs (`NO_SIGNALS_DESPITE_WAITS`)** — tid=13 never reaches `ExTerminateThread`, tid=1's join wait on `0x12A4` never completes. Cache file opens succeed in ours (paths `cache:/aab216c3/5`, `cache:/aab216c3` etc seen in log just before the stall) — so the bug is post-VFS, in the producer→worker async-IO completion signaling, exactly as AUDIT-062 found. [confirmed]
|
||||
- **AUDIT-063 (2026-05-12)** — AUDIT-062's candidate trio (`0x822F2304`/`0x822F1D84`/`0x821743D8`) confirmed as RED HERRINGS: containing fns `sub_822F2248`/`sub_822F1AA8`/`sub_821741C8` resolved, but **none are reachable from `sub_82452DC0` in 12 hops**. Track-A probe (180s canary / 500M-instr ours): canary fires 11.7k× / ours 0× on `0x822F1D84` and `0x821743D8` — but they're downstream of an unblocked main event loop (canary tid=6 = guest main). Ours's main (tid=1) is `Blocked` on `0x12A4` (tid=13 thread-join handle, AUDIT-049), which transitively blocks on this fn's wedge `0x12AC`. Real producer is the worker cluster `sub_82458B90`/`sub_8245EC10`/`sub_8245FEB8`/`sub_8245D9D8`/`sub_8245DA78` running on the 4 workers spawned by `sub_825070F0` — **0 of those 8 workers spawn in ours** vs 8 in canary. The bug is the AUDIT-057 thread-gap closing in on itself: the cluster cannot bootstrap because the wedge isn't signaled, and the wedge isn't signaled because the cluster cannot bootstrap. NO new producer fn was missed by prior audits. [confirmed: trio is symptom not cause]
|
||||
- **AUDIT-062 (2026-05-12)** — wedge KEVENT data-flow traced. Outcome **(b)**: NtDuplicateObject thunk = `0x8284DF7C`; sub_821CB030 has NO direct bl-NtDup (dup is performed by descendant via wrapper `sub_824AA398`). Phase 2 ours `--lr-trace=0x8284DF7C`: wedge handle `0x12AC` IS duped by tid=13 cycle 26711 (alongside `0x12B0` cycle 23833). Out_ptr `0x40541E80` populated with dup_handle = source_handle = `0x12AC` (ours aliases per `exports.rs:4263`). sub_82452DC0 fires 8× in ours; line 8 = wedge submit on tid=13 cycle 8127 lr=0x821CB1D0, with r6=0x40541E80 (job struct carries the dup pointer). So **work IS submitted with the right handle**. Phase 4 ours `--lr-trace=0x8284DF5C,0x824AA2F0`: 68 NtSet fires, **0 on `0x12AC`** (neighbors 0x129C / 0x12B0 ARE signaled — infrastructure capable). γ-signalers A/B/C/D all fire (3/2/3/6+2 fires resp.) — but on non-wedge handles. **The break is upstream of γ-signaler**: ours's worker tid=5 is parked on its OWN idle event `0x12B8` (created by tid=5 via NtCreateEvent), and **no NtSetEvent in ours signals `0x12B8`** (also NO_SIGNALS_DESPITE_WAITS). Producer-side worker-wake signal is missing. Cascade A=NtDup fires correctly on wedge YES (cycle 26711); B=wedge dup NOT signaled CONFIRMED; C=outcome (b) localized to producer→worker wake gap (`0x12B8`); D=draws>0 deferred to AUDIT-063 fix. New finding: **γ-signaler D = `sub_8245D9D8` / `sub_8245DA78`** (LR `0x8245DA44` / `0x8245DB08`) — NtSet wrapper hot from worker-side, missed by AUDIT-059/060 dossier list. Canary spreads NtDup across 6 tids (6/10/16/17/18/26 → 33 fires/180s); ours across 3 (1/5/13 → 14 fires) — confirms AUDIT-057 thread-gap as enabling condition. Trace `audit-runs/audit-062-wedge-kevent-flow/`. [confirmed outcome b]
|
||||
- **AUDIT-060 (2026-05-12)** — confirmed wedge structural identification: `NtCreateEvent → NtDuplicateObject → enqueue → worker → NtSetEvent on dup` (canary path); ours stalls at the wait because workers don't signal. [confirmed]
|
||||
- **AUDIT-059 (2026-05-11)** — established as keystone γ-wedge site. Handle 0x12AC create-site is here at `+0x128`. [confirmed]
|
||||
- **AUDIT-058 (2026-05-10)** — sister mention in tid=13 chain (frames via sub_821CB1D0 ← sub_821CBAE0). [confirmed]
|
||||
- **AUDIT-049 (2026-05-10)** — original discovery that tid=13 waits INFINITE on event created here; main thread (tid=1) is downstream via thread-join handle. [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- Is the `+0x128` create the ONLY NtCreateEvent in this fn, or are there multiple? **AUDIT-062 db query: exactly 1 `bl 0x824A9F18` (NtCreateEvent wrapper) at `+0x128`.** Two `bl 0x82452DC0` (`+0x19C`, `+0x2EC`) and two `bl 0x824AA330` wait-wrappers (`+0x1AC`, `+0x318`) — same KEVENT submitted+waited twice (sequential file-IO loads), or alternative-branch fork. Canary's 2 fires at `0x821CB158` therefore mean sub_821CB030 is *invoked twice* by its caller, each creating a fresh KEVENT.
|
||||
- What does `+0x19C..+0x1A8` do between work-submit and wait? (Likely sets up the wait params.) Disassemble to confirm.
|
||||
- ~~Does ours's NtDuplicateObject correctly create a signal-aliased handle?~~ AUDIT-062 confirmed: YES — ours aliases (dup_id = source_id), out_ptr populated, refcount bumped. Bug is NOT here.
|
||||
- **Open after AUDIT-062**: which producer-side call (descendant of `sub_82452DC0`) calls `NtSetEvent` on the worker idle event (`0x12B8`-class) in canary, and why does ours skip it? Probe canary's hot NtSet wrapper LRs `0x822F2304, 0x822F1D84, 0x821743D8` (9k+ fires each) — one of these is likely the worker-wake.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Wedge handle in ours: drifts per run (0x1288/0x12A4/0x12AC across audits — see [reference_function_dossiers](docs/functions/README.md) caveat).
|
||||
- Callers: [sub_821CBA08](#) (not yet dossierd)
|
||||
- Callees: [sub_82452DC0](sub_82452DC0.md), sub_824A9F18 (NtCreateEvent wrapper)
|
||||
- Audits: 049, 058, 059, 060, 062
|
||||
- Artifacts: `audit-runs/audit-049-tid1-stall-0x1280/`, `audit-runs/audit-059-gamma-wedge/`, `audit-runs/audit-062-wedge-kevent-flow/`
|
||||
55
docs/functions/sub_822F1AA8.md
Normal file
55
docs/functions/sub_822F1AA8.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
address: 0x822F1AA8
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 065
|
||||
aliases:
|
||||
- "tid=1 post-init dispatch loop (calls sub_82173990 via vtable[0])"
|
||||
---
|
||||
|
||||
# sub_822F1AA8 — tid=1 post-init game-loop dispatcher
|
||||
|
||||
## Synopsis
|
||||
|
||||
Normal-callee invoked by tid=1's `entry_point → sub_8216EA68 → sub_822F1AA8`. Contains the per-frame game-loop pump for the post-init subsystem-dispatch tree (audit-064 chain to [sub_825070F0](sub_825070F0.md)). Runs an outer loop: (a) `KeWaitForSingleObject` infinite at PC `0x822F1DFC`, (b) dispatches vtable[0] of object at `*(0x828E1F08)` at PC `0x822F1B4C bctrl` — which is `sub_82175330` → tail-jump → `sub_82173990`. Canary executes the body 4040× in 60s (per-frame). Ours executes the function entry 1× then **blocks immediately inside sub_82173990 (the vtable[0] callee) at sub_82173990+0x2D0** — KeWaitForSingleObject INFINITE on handle `0x12A4` (= tid=13's thread handle = AUDIT-049 wedge).
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm prolog: `mflr r12; bl 0x825F0F60; stfd f30, -136(r1); stfd f31, -128(r1); subi r31, r1, 256; stwu r1, -256(r1); mr r30, r3; ...` — normal-callee.
|
||||
- Function size: 996 bytes / 249 insns. `has_eh=True`, `frame_size=0` per DB (dynamic 256-byte frame).
|
||||
- Static caller: `sub_8216EA68+0x3AC` via `bl` (the post-init dispatcher).
|
||||
- AUDIT-064 ours `--ctor-probe=0x822F1AA8` -n 500M fires 1× at tid=1, cycle=6,171,801, lr=0x8216ee14, r3=0x40d09a40. Back-chain: tid=1 thread_proc → entry_point → sub_8216EA68+0x3AC → sub_822F1AA8.
|
||||
- AUDIT-064 ours fine probe at BB-entries `0x822F1ACC/0x822F1AEC/0x822F1B20/0x822F1B30/0x822F1B38` all fire 1× — execution does pass through the function body to PC `0x822F1B38`.
|
||||
- AUDIT-064 ours `--ctor-probe=0x822F1B50` fires **0×**. The bctrl at PC `0x822F1B4C` DOES execute (sub_82175330 fires 1× per separate probe), but never returns — because sub_82175330 tail-jumps to sub_82173990 which blocks at +0x2D0.
|
||||
- AUDIT-064 canary 60s probe (`--audit_61_branch_probe_pcs`): all probes in body fire — `0x822F1B5C/0x822F1B78/0x822F1BB8` fire 1×, `0x822F1BCC` (outer-loop body) fires 4040×, `0x822F1D58` (the inner bctrl → sub_821741C8) fires 4030×, `0x822F1DFC` (outer KeWait) fires 4040×.
|
||||
- Global `0x828E1F08` is the slot holding the object pointer that the vtable[0] bctrl dispatches off. Its writers are `sub_822F14D8+0xF0` (PC `0x822F15A4`, observed in ours) and `sub_822F1638+0x84` (PC `0x822F16BC`). At cycle ~6,171,800 in ours, `[0x828E1F08]` is set to `0x40111890`; `[0x40111890+0]` evolves through multiple vtable values (`0x820AD894`, `0x820A183C`, ...) before the bctrl fires; final value at bctrl is `0x820A183C` (slot 0 = `sub_82175330`).
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_8216EA68+0x3AC` on tid=1. One-shot at boot per game session — but the function itself contains an outer loop that iterates `KeWaitForSingleObject` waits until termination.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers: `sub_8216EA68+0x3AC` via `bl` (sole).
|
||||
- Direct callees: `sub_822F13B0`, `sub_824AA2F0` (NtSetEvent wrapper), `sub_82172370`, `sub_824AA3E0`, `sub_824C1910` (leaf), `sub_824AA8B0`, `sub_82456B58`, `sub_824AA330` (KeWaitForSingleObject wrapper), `sub_824574C0`, `sub_82457038`, `sub_8284E45C` (kernel import thunk).
|
||||
- Indirect: `bctrl` at PC `0x822F1B4C` (vtable[0] of `*(0x828E1F08)`) and `bctrl` at PC `0x822F1D58` (vtable[2] of same).
|
||||
- Reads `0x828E1F08` at PCs `0x822F1B3C, 0x822F1BE8, 0x822F1D40, 0x822F1E44, ...` (11 reads).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-065 (2026-05-12)** — refined the dispatch-target understanding. The vtable[0] callee at PC `0x822F1B4C bctrl` is `sub_82175330` (2-insn tail-jump to `sub_82173990`). `sub_82173990` is a **synchronous task-spawn-and-join helper** — not an outer game loop. Canary fires this function **exactly 1× per boot** (not 4040× as the synopsis previously suggested) — the 4040× metric in audit-064 referred to PCs *downstream of* sub_82173990's return into sub_822F1AA8's outer loop (PCs `0x822F1BCC`/`0x822F1D58`/`0x822F1DFC`). Per AUDIT-065 sub_82173990 dossier, the wait at sub_82173990+0x2D0 IS a thread-join, and the body of sub_82173990 itself is clean — only the worker spawned via `sub_821746B0` (which becomes ours's tid=13) is wedged inside `sub_821CB030`. [confirmed]
|
||||
- **AUDIT-064 (2026-05-12)** — identified as the immediate dispatch chain origin for the 4-fn ladder leading to sub_825070F0. Disasm + ours fine-grained BB probes localize the wedge: tid=1 enters function (1×), passes through PCs 0x822F1ACC/0x822F1AEC/0x822F1B20/0x822F1B30/0x822F1B38, executes bctrl at PC `0x822F1B4C` → sub_82175330 → sub_82173990 → KeWaitForSingleObject(handle=0x12A4 = tid=13 thread handle) → STALL. Canary instead returns from that wait and enters the outer game-loop body (`0x822F1BCC` fires 4040× in 60s). **First divergence between canary and ours is at sub_82173990's wait inside vtable[0] of `*(0x828E1F08)` — same AUDIT-049 wedge.** [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- The vptr at `[0x40111890+0]` mutates multiple times before the bctrl fires (writes from sub_82152XXX, sub_8244e850, sub_8244e8bc, sub_82155b4c, sub_82460c40, sub_822F2758, sub_8216F110, ...). Is the final value `0x820A183C` (which has slot 0 = sub_82175330) the same as canary's final value? Run the same `--mem-watch` against canary to verify.
|
||||
- Why does canary's tid=13 finish (allowing tid=1's join wait on handle 0x12A4 to complete) while ours's tid=13 stalls? That's the AUDIT-049 root question — separately tracked.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Direct callers: `sub_8216EA68+0x3AC`.
|
||||
- Callees of interest: `sub_82173990` (via vtable[0] thunk `sub_82175330`) — where tid=1's stall occurs.
|
||||
- Downstream (when activated): `sub_82173990` → `sub_821741C8` → `sub_82172BA0` → `sub_821B55D8` → `sub_824F8398` → `sub_824F7CD0` → `sub_824F7800` → `sub_825070F0`.
|
||||
- Object dispatch: `*(0x828E1F08) = 0x40111890`, vptr `[0x40111890+0] = 0x820A183C` (vtable), slot 0 = `sub_82175330`.
|
||||
- Audits: 049 (the underlying wedge), 064.
|
||||
- Artifacts: `audit-runs/audit-064-activation-ladder/ours-fine-822F1AA8.stdout`, `ours-bb-822F1AA8.stdout`, `ours-vtable820a183c.stdout`, `ours-vptr-time.log`, `canary-inside-822F1AA8.log`.
|
||||
62
docs/functions/sub_82452DC0.md
Normal file
62
docs/functions/sub_82452DC0.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
address: 0x82452DC0
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 063
|
||||
aliases:
|
||||
- "work-submitter"
|
||||
- "audit-050 root"
|
||||
---
|
||||
|
||||
# sub_82452DC0 — work-submitter / cluster-root
|
||||
|
||||
## Synopsis
|
||||
|
||||
Central work-submission function. All AUDIT-049–060 γ-wedge chains and AUDIT-058 vtable-activation chains funnel through this function. Receives a request (likely a file-IO descriptor + completion XEvent) and dispatches it via 9 direct callees + 1 indirect call. In canary it fires ~3.21× more often than in ours per AUDIT-056 — the upstream gate is in its caller, not in it.
|
||||
|
||||
## Evidence
|
||||
|
||||
- AUDIT-050 enumerated 9 direct targets at `bl` sites: `0x8245AE50, 0x82452068, 0x82452200, 0x8245B000, 0x8245B078, 0x82454A40, 0x82452AB8, 0x82454918, 0x82452EC4`, plus 1 `ind_call`.
|
||||
- AUDIT-051 found a predicate gate at `+0x78`: `0x82452E2C beq cr6, 0x82452E88`, controlled by `sub_8245B000(r3)` returning 1 iff `[r3+0]≠0 AND [r3+4]≠0`. The 80-byte stack-local struct lives at `r31+96`.
|
||||
- AUDIT-052 found `[r3+0]` / `[r3+4]` are halves of a hash key formatted into `cache:\<HASH1>\<X>\<HASH2>` paths — i.e. the struct holds a content hash for cache resolution. Predicate refuted as the bug.
|
||||
- AUDIT-055 probed `sub_8245B078`'s body with a cache override: body executes correctly; divergence is upstream.
|
||||
- AUDIT-056: fires **canary 45/60s, ours 14/26s = 3.21× ratio**. Sharpest specific divergence: `sub_8217FA08` from `LR=0x82455E60` (`=sub_82455DF0+0x70`) canary 20 / ours 0.
|
||||
- AUDIT-059: tid=13 itself fires `sub_82452DC0` once at LR=`0x821cb1d0` (from `sub_821CB030+0x19C`) immediately before waiting on the file-IO completion XEvent.
|
||||
- AUDIT-060: confirmed convergence — `sub_8245FEB8 ← sub_824601A0 ← sub_82460118 ← sub_82452AB8 ← sub_82452DC0`. The vptr-installer chain bottoms out here.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from 34 static caller sites per AUDIT-051. Notable callers:
|
||||
- `sub_821CB030+0x19C` — drives the file-IO completion submission used by `silph::GamePart_Title::UImpl`.
|
||||
- `sub_821CB030+0x2BC` — second site in same fn.
|
||||
- `sub_821C4EB0` chain (AUDIT-056 gate).
|
||||
- `sub_82173990+0x208` (program-top frame).
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers: 34 sites across boot + tid=13 + UI cluster.
|
||||
- Static callees (direct `bl`): 9 functions above + 1 computed call.
|
||||
- The 9-target tree is the "worker activation surface". `sub_82452AB8` is the gate leading to vptr installers; `sub_8245B078`, `sub_8245B000` are the cache-key/hash gates; the others are queue management.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-063 (2026-05-12)** — static reachability surveyed: among the 60 distinct callers of NtSet wrapper `0x824AA2F0`, **only 1 is reachable within 12 hops from `sub_82452DC0`**: `sub_8245FEB8` (γ-signaler C). The AUDIT-062 candidate trio (`sub_822F2248`, `sub_822F1AA8`, `sub_821741C8`) are NOT downstream. Confirms that the "producer-side worker-wake signal" canary path is the worker cluster (`sub_82458B90`/`sub_8245EC10`/`sub_8245FEB8`/`sub_8245D9D8`/`sub_8245DA78`) reached via the 4 worker threads spawned by `sub_825070F0` — and those threads are unspawned in ours (0 vs 8 in canary). [confirmed]
|
||||
- **AUDIT-060 (2026-05-12)** — confirmed as single funnel for AUDIT-058+059 chains; the work-submitter is alive and queues but the throughput is gated by `sub_821C4EB0` early-exit per AUDIT-056. [confirmed]
|
||||
- **AUDIT-059 (2026-05-11)** — fires 8× in ours; one of those is tid=13 from `sub_821CB030+0x19C` right before waiting on the wedge XEvent. Work submitted but no signal returns. [confirmed]
|
||||
- **AUDIT-056 (2026-05-10)** — fires canary 45 / ours 14 in matched windows = 3.21× gap. Bug class refined to "δ-throughput". [confirmed]
|
||||
- **AUDIT-055 (2026-05-10)** — proved `sub_8245B078` body executes correctly; ruled out a downstream bug here. [confirmed]
|
||||
- **AUDIT-052 (2026-05-10)** — refuted AUDIT-051's "missing population" hypothesis. The struct is bit-identical to canary; `[r3+0]/[r3+4]` are a content hash. [supersedes-AUDIT-051-claim]
|
||||
- **AUDIT-051 (2026-05-10)** — initially identified `+0x78` predicate gate as bug. [STATUS: hypothesis falsified by AUDIT-052; the gate itself is real and named correctly, but it's not the bug]
|
||||
- **AUDIT-050 (2026-05-10)** — enumerated 9 direct targets + 1 indirect; framed as activation-surface root. [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- Why does `sub_82452DC0` fire 3.21× less in ours? AUDIT-061 pivots to its caller `sub_821C4EB0`'s internal branches `[+0x44, +0xE0]`.
|
||||
- The 1 indirect call (computed) — what does it dispatch to, and does our static-analyzer miss any of its candidates?
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Callers: [sub_821CB030](sub_821CB030.md), [sub_821C4EB0](sub_821C4EB0.md)
|
||||
- Callees-of-interest: [sub_82452AB8](#) (not yet dossierd), `sub_8245B078`, `sub_8245B000` (cache-hash gate)
|
||||
- Audits: 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, 060
|
||||
- Artifacts: `audit-runs/audit-050-*/`, `audit-runs/audit-056-producer-trace/`, `audit-runs/audit-059-gamma-wedge/`, `audit-runs/audit-060-fnptr-array-bootstrap/`
|
||||
47
docs/functions/sub_82457EF0.md
Normal file
47
docs/functions/sub_82457EF0.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
address: 0x82457EF0
|
||||
classification: thread_proc
|
||||
confidence: high
|
||||
last_audit: 060
|
||||
aliases:
|
||||
- "tid=6 thread_proc"
|
||||
---
|
||||
|
||||
# sub_82457EF0 — tid=6 thread_proc (worker entry)
|
||||
|
||||
## Synopsis
|
||||
|
||||
Thread procedure for tid=6 in ours. 0 static callers — and that is *correct* for a `thread_proc`: it is installed as an entry-point via `ExCreateThread` somewhere in boot, not invoked via `bl`. AUDIT-059's "only-caller of [sub_82458B90](sub_82458B90.md) has 0 callers — fnptr-array only" inference was wrong; the actual activation is thread creation.
|
||||
|
||||
## Evidence
|
||||
|
||||
- AUDIT-060 Probe O ours: fires **1× on tid=6** (HW=2, cycle=0, lr=`0xbcbcbcbc` — thread-entry sentinel).
|
||||
- `lr=0xbcbcbcbc` is the Xbox 360 / xenia convention for "this is the very first instruction of a thread proc; no return address". This is a diagnostic that distinguishes thread entry from a normal `bl` fire.
|
||||
- Calls [sub_82458B90](sub_82458B90.md) at `+0x24` (1 callee at this offset).
|
||||
|
||||
## Activation
|
||||
|
||||
Registered as a thread entry-point via `ExCreateThread` (or similar). The caller of `ExCreateThread` that installs this entry has not yet been traced — that's the *real* activation site, and tracing it would close the loop on tid=6's purpose. Once tid=6 starts, the OS scheduler runs `sub_82457EF0` from PC `0x82457EF0` with LR=`0xbcbcbcbc`.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (`bl`): **0** (correct — see classification).
|
||||
- Callees: `bl sub_82458B90` at `+0x24` (PC `0x82457F18`).
|
||||
- The "indirect call site" that activates this fn is the `ExCreateThread` invocation, captured at runtime, not in static `xrefs`.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-060 (2026-05-12)** — identified as tid=6 thread_proc via `lr=0xbcbcbcbc` thread-entry sentinel + HW=2 + cycle=0 first-fire context. AUDIT-059's static-reachability inference invalidated. [confirmed]
|
||||
- **AUDIT-059 (2026-05-11)** — flagged as "only-caller of canary signaler A; 0 callers — fnptr-array only". [STATUS: partially correct (0 callers true; fnptr-array WRONG), corrected by AUDIT-060 — it's a thread_proc.]
|
||||
|
||||
## Open questions
|
||||
|
||||
- Where is `ExCreateThread(entry=sub_82457EF0, ...)` called from? Probe the `ExCreateThread` import thunk in both engines with filtered LR/r3 to find the install site.
|
||||
- What does the thread body do beyond calling [sub_82458B90](sub_82458B90.md) once? Likely it's a loop that waits on a queue, dequeues work, and signals completion via the bl at `+0x24`. Disassemble the body.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Thread-body callee: [sub_82458B90](sub_82458B90.md).
|
||||
- Install site (`ExCreateThread` caller): not yet identified.
|
||||
- Audits: 059, 060.
|
||||
- Artifacts: `audit-runs/audit-060-fnptr-array-bootstrap/ours-phase1.stdout` (the `lr=0xbcbcbcbc` sentinel evidence).
|
||||
50
docs/functions/sub_82458B90.md
Normal file
50
docs/functions/sub_82458B90.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
address: 0x82458B90
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 060
|
||||
aliases:
|
||||
- "canary γ-wedge signaler A"
|
||||
---
|
||||
|
||||
# sub_82458B90 — canary γ-wedge signaler A (NtSetEvent caller from tid=6 thread_proc body)
|
||||
|
||||
## Synopsis
|
||||
|
||||
A function that wraps `bl 0x824AA2F0` (NtSetEvent wrapper) at an internal PC near `+0x180` (canary LR `0x82458D14`). In canary, this is one of two NtSetEvent caller-LRs that signal the AUDIT-059 file-IO completion wedge dup handle (per `(tid, r31)` cross-run invariant). Reached only from [sub_82457EF0](sub_82457EF0.md)+0x24, which is itself the **tid=6 thread_proc entry**. The "1 static caller, 0 callers above" chain in `xrefs` is structurally correct for a fn invoked from a thread loop's body.
|
||||
|
||||
## Evidence
|
||||
|
||||
- AUDIT-059 Probe C canary: at LR `0x82458D14` (=`sub_82458B90+0x184` or similar post-`bl 0x824AA2F0` internal PC), signals the wedge dup handle (matched cross-run via `r31` stack invariant — thread `F8000054` / frame `0x7036FDC0`).
|
||||
- AUDIT-060 Probe O ours: fires **1× in ours** (`--ctor-probe`), called from `sub_82457EF0+0x24` (PC `0x82457f18`).
|
||||
- Static caller chain in DB: `sub_82458B90 ← sub_82457EF0` (1 caller); `sub_82457EF0` itself has 0 static callers — it is the tid=6 thread_proc entry.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_82457EF0+0x24` (single static caller). [sub_82457EF0](sub_82457EF0.md) is a `thread_proc`, so the activation chain is:
|
||||
1. Some boot-site calls `ExCreateThread(entry=sub_82457EF0)` — installing tid=6's thread_proc.
|
||||
2. Thread tid=6 starts; PPC entry-LR sentinel `0xbcbcbcbc` indicates "first instruction of thread_proc".
|
||||
3. `sub_82457EF0` body calls this fn via `bl` at `+0x24`.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (`bl`): 1 site = `sub_82457EF0+0x24` (PC `0x82457f18`).
|
||||
- Callees: `bl 0x824AA2F0` (NtSetEvent wrapper) internal.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-060 (2026-05-12)** — confirmed alive in ours (1 fire on tid=6). AUDIT-059's "fires 1× off-wedge" wording was technically correct but misleading; the function IS active, just signaling a different KEVENT instance per call. [confirmed alive]
|
||||
- **AUDIT-059 (2026-05-11)** — identified as canary NtSetEvent signaler A for the wedge dup handle via cross-run `r31` invariant. Static reachability claim ("only-caller has 0 callers — fnptr-array only") flagged as suspect; AUDIT-060 confirms the chain is correct but the conclusion ("unreachable") was wrong. [confirmed for canary signaler role]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What r3 (handle) does `sub_82458B90` pass to `bl 0x824AA2F0` in ours's 1 fire vs canary's signaling fires? Probe entry of `sub_824AA2F0` filtered by caller=`sub_82458B90`.
|
||||
- Is `sub_82457EF0`'s thread body a "wait on queue, dequeue work, signal completion" loop? If yes, what queue? And is the queue empty in ours but populated in canary?
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Caller (thread_proc): [sub_82457EF0](sub_82457EF0.md).
|
||||
- NtSetEvent wrapper: `sub_824AA2F0` (not yet dossierd).
|
||||
- Sibling canary signaler: [sub_8245EC10](sub_8245EC10.md).
|
||||
- Audits: 059, 060.
|
||||
- Artifacts: `audit-runs/audit-059-gamma-wedge/canary-setwrapper.log`, `audit-runs/audit-060-fnptr-array-bootstrap/`.
|
||||
55
docs/functions/sub_8245EC10.md
Normal file
55
docs/functions/sub_8245EC10.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
address: 0x8245EC10
|
||||
classification: dispatch_table_method
|
||||
confidence: high
|
||||
last_audit: 060
|
||||
aliases:
|
||||
- "canary γ-wedge signaler B"
|
||||
- "dispatch_table 0x820B5830 slot 1"
|
||||
---
|
||||
|
||||
# sub_8245EC10 — dispatch_table slot 1 method, canary γ-wedge signaler B
|
||||
|
||||
## Synopsis
|
||||
|
||||
Method living at slot 1 of `dispatch_table @ 0x820B5830`. The dispatch table is installed at struct offset 0 (vptr) by [sub_8245FEB8](sub_8245FEB8.md). In canary, this method is one of two NtSetEvent caller-LRs that signal the AUDIT-059 file-IO completion wedge dup handle (LR `0x8245ED80` post-`bl 0x824AA2F0`). In ours it fires 2× total but not on the wedge handle.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Located at slot 1 of dispatch table `0x820B5830`. Slot 0 is `sub_8245F1D0`.
|
||||
- The dispatch table is referenced from:
|
||||
- `sub_8245F1D0+0x1C` (self-recursive)
|
||||
- `sub_8245FEB8+0x100` (= `0x8245FFC0`, the `stw r11, 0(r31)` vtable install)
|
||||
- AUDIT-060 Probe O ours: fires **2× in ours** (`--ctor-probe`); both fires come from `sub_8245FEB8` callers (transitively, via the installed dispatch-table dispatch).
|
||||
- AUDIT-059 Probe C canary: at LR `0x8245ED80` (`= sub_8245EC10+0x170` or similar internal PC after `bl 0x824AA2F0`), this fn is one of two distinct canary NtSetEvent caller-fns that signal the wedge dup handle (per cross-run `r31` invariant; the other is [sub_82458B90](sub_82458B90.md)).
|
||||
- Both canary signalers wrap `bl 0x824AA2F0` (NtSetEvent wrapper). Each fires once per file-IO completion in canary.
|
||||
|
||||
## Activation
|
||||
|
||||
Indirect dispatch. Reachable only via `bctrl` against an object whose vptr was set to `dispatch_table @ 0x820B5830`. The install happens via [sub_8245FEB8](sub_8245FEB8.md). No direct `bl` callers — and that is correct for a `dispatch_table_method`.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (direct `bl`): **0** (correct — indirect dispatch only).
|
||||
- Callees: includes `bl 0x824AA2F0` (NtSetEvent wrapper) at internal PC near `+0x170` (canary LR `0x8245ED80`).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-060 (2026-05-12)** — fires 2× in ours; not dead. AUDIT-059's "dead via 0 static callers" framing was too narrow — dispatch_table reachability needs runtime-installed-vptr awareness, not just static `bl` xref BFS. [confirmed alive]
|
||||
- **AUDIT-059 (2026-05-11)** — identified as canary NtSetEvent signaler B for the file-IO completion wedge dup handle. Cross-run `(tid, r31)` invariant matched. [confirmed for canary signaler role]
|
||||
- **AUDIT-059 (2026-05-11)** — claimed dead in ours due to 0 static callers + dispatch-table installer ([sub_8245FEB8](sub_8245FEB8.md)) ALSO claimed dead. [STATUS: falsified by AUDIT-060]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What handle does `sub_8245EC10` signal in ours? (Two fires — capture r3 at each fire to identify the target handles.)
|
||||
- Why doesn't it signal the wedge handle in ours? Either (a) it's running on the wrong object (different installed instance), or (b) the work item it's processing has a different completion-event field.
|
||||
- Cross-engine method match: is canary fire #1 and ours fire #1 the same logical event? Compare object base (would need new instrumentation).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Installed at: `dispatch_table @ 0x820B5830` slot 1.
|
||||
- Vptr installer: [sub_8245FEB8](sub_8245FEB8.md).
|
||||
- Sibling method (slot 0): `sub_8245F1D0` (not yet dossierd).
|
||||
- Sibling canary signaler: [sub_82458B90](sub_82458B90.md).
|
||||
- Audits: 059, 060.
|
||||
- Artifacts: `audit-runs/audit-059-gamma-wedge/canary-setwrapper.log`, `audit-runs/audit-060-fnptr-array-bootstrap/`.
|
||||
59
docs/functions/sub_8245FEB8.md
Normal file
59
docs/functions/sub_8245FEB8.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
address: 0x8245FEB8
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 060
|
||||
aliases:
|
||||
- "vptr installer for dispatch_table 0x820B5830"
|
||||
- "AUDIT-059 'dead' (FALSIFIED)"
|
||||
---
|
||||
|
||||
# sub_8245FEB8 — vptr installer for dispatch_table 0x820B5830
|
||||
|
||||
## Synopsis
|
||||
|
||||
Installs a vtable pointer / dispatch-table entry into a runtime-allocated object. Body contains `stw r11, 0(r31)` at `0x8245FFC0` writing the vtable address to the object's slot 0. After install, the object's `bctrl` dispatch reaches the methods in `dispatch_table @ 0x820B5830` (slot 0 = `sub_8245F1D0`, slot 1 = `sub_8245EC10`). Fires 5× in ours / 2× in canary; lives in the AUDIT-050 worker cluster but is NOT dead in either engine — both engines reach the same call site `sub_824601A0+0x68 (PC=0x82460208)`.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Body opcode at `0x8245FFC0`: `stw r11, 0(r31)` — vtable install pattern.
|
||||
- Two static callers per `xrefs` table (`source_func`):
|
||||
- `sub_824601A0` at `+0x68` (PC `0x82460208`) — 1 site
|
||||
- `sub_8245FB68` at `+0x198` and `+0x1C0` (PCs `0x8245FD00` and `0x8245FD28`) — 2 sites
|
||||
- AUDIT-060 Probe C-Win Windows Debug canary: `--log_lr_on_pc=0x8245FEB8`, 120s → **2 fires, both lr=0x8246020C** (= `sub_824601A0+0x6C`, post-bl PC). `r3=BC365C40` (same object), `r4=4` then `r4=1` (different slot indices), `r31=701CF2E0` then `r31=705AFAA0` (different threads).
|
||||
- AUDIT-060 Probe O ours: `--ctor-probe=0x8245FEB8 -n 500M` → **5 fires total**: 1 from tid=1 boot path at cycle 5.5M via `sub_824601A0+0x68`, 3 more from tid=1 during UI inflation, 1 on tid=13 at cycle 23788 via the wedge chain.
|
||||
- The `r4` parameter (slot index) and the `r31` saved value diverge per call; install target object differs.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from one of:
|
||||
- `sub_824601A0+0x68` (most frequent — boot path)
|
||||
- `sub_8245FB68+0x198` and `+0x1C0` (internal lib path, sub_8245FB68 itself has callers `sub_8245F880`, `sub_8245FAB0`)
|
||||
|
||||
Caller chain upward: `sub_824601A0 ← sub_82460118 ← sub_82452AB8 ← sub_82452DC0`. The vptr-install path piggybacks on the work-submitter cluster.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Callers: `sub_824601A0`, `sub_8245FB68`.
|
||||
- Body: vtable write at `+0x108` (`0x8245FFC0`). Other body content not yet detailed.
|
||||
- The vtable being installed is implicit in caller-supplied `r11` (and possibly elsewhere).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-060 (2026-05-12)** — measured 5× ours, 2× canary, identical call site both engines. AUDIT-059's framing falsified. Convergence to AUDIT-050 work-submitter cluster confirmed. [confirmed]
|
||||
- **AUDIT-059 (2026-05-11)** — claimed as "vptr installer dead in ours" because static graph showed `sub_8245EC10` (slot 1 of the installed dispatch table) had 0 static callers reachable from any caller-chain that DB classified as live. [STATUS: falsified by AUDIT-060 — was alive in ours all along; the DB caller-chain reachability call was too narrow.]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What is the *class* being installed? (Read `r11` at the `stw r11, 0(r31)` site — canary trace shows it's a specific dispatch table.)
|
||||
- The 5 ours fires vs 2 canary fires — is this a parity match (canary just had only 2 because of the same AUDIT-056 3.21× upstream gate) or does ours over-fire? Aligning instruction-horizon vs wallclock would clarify.
|
||||
- Slot-1 method `sub_8245EC10` is named the canary signaler B. It fires 2× in ours per AUDIT-060 — but not on the wedge handle (per AUDIT-059 ours signal_attempts=0 on 0x12AC). What handle is it signaling in ours?
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Installs dispatch_table at: `0x820B5830`
|
||||
- Slot 0: `sub_8245F1D0` (referenced; not yet dossierd)
|
||||
- Slot 1: [sub_8245EC10](sub_8245EC10.md)
|
||||
- Direct callers: `sub_824601A0`, `sub_8245FB68`
|
||||
- Audits: 059, 060
|
||||
- Artifacts: `audit-runs/audit-059-gamma-wedge/`, `audit-runs/audit-060-fnptr-array-bootstrap/`
|
||||
56
docs/functions/sub_824ACB38.md
Normal file
56
docs/functions/sub_824ACB38.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
address: 0x824ACB38
|
||||
classification: crt_init_driver
|
||||
confidence: high
|
||||
last_audit: 060
|
||||
aliases:
|
||||
- "CRT driver"
|
||||
- "vtable-slot enumerator (NOT a static-ctor list iterator)"
|
||||
---
|
||||
|
||||
# sub_824ACB38 — CRT init driver / vtable-slot enumerator
|
||||
|
||||
## Synopsis
|
||||
|
||||
CRT-style driver called from `entry_point` (or near it). Body is 224 bytes (`0x824ACB38..0x824ACC18`). Contains two enumeration loops over fnptr-array regions at `0x82870xxx`. AUDIT-050 framed it as "iterates 0x82870xxx fnptr arrays (557 slots, 82 non-NULL)" and concluded a half-bootstrapped state; AUDIT-060 found this framing semantically misleading — the slots are runtime vtable-registration entries, not C++ static initializers, and the "82 non-NULL" count obscures a structural 160-slot intentional zero gap.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Body anatomy at `0x824ACB38..0x824ACC18` (AUDIT-060 disasm):
|
||||
- `+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`.
|
||||
- Array layout (AUDIT-060 dumped at -n 1M and -n 500M; both identical):
|
||||
- `0x82870010..0x828702E8` — populated with `0x82xxxxxx` pointers (vtable methods).
|
||||
- `0x828702F0..0x82870580` — **PERMANENTLY ZERO** across both dumps (160 of 557 slots = 28.7%).
|
||||
- `0x82870590..0x828708C4` — populated with `0x82xxxxxx` pointers.
|
||||
- `0x828708C8..0x828708D4` — loop-A array, populated (small CRT helpers).
|
||||
- Static-DB cross-check: the 557-slot region contains 14+ separate small `vtable`-classified arrays at `0x82870014/0x24/0x94/0xC8/0x16C/0x214/0x238/0x250/0x2A8/0x2C0/0x2E4/0x5A0/0x62C/0x870`, NOT a single CRT static-ctor list. NO statically-detected arrays in `[0x82870300, 0x828705A0)` — the gap is intentional padding between two vtable clusters.
|
||||
|
||||
## Activation
|
||||
|
||||
Called once from `entry_point`-near code (per AUDIT-050 — exact caller PC not in AUDIT-060 trace). The driver enumerates all slot entries; each non-NULL entry is `bctrl`'d once.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers: 1 (from boot entry path; exact PC to confirm).
|
||||
- Callees: indirect (`bctrl`) — targets are the contents of the enumerated slots.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-060 (2026-05-12)** — disassembled body, identified structure (2 loops, gap), confirmed slot contents are runtime vtable entries rather than C++ static-ctor function pointers. The "82 non-NULL" AUDIT-050 count was correct per-slot but missed the structural 160-slot intentional gap. Driver fires 1× at -n 500M as expected (single boot enumeration). [confirmed]
|
||||
- **AUDIT-050 (2026-05-10)** — framed as "CRT driver iterates 0x82870xxx fnptr arrays (557 slots, 82 non-NULL)". Structurally correct but semantically misleading ("static-ctor list" implication was wrong). [STATUS: partially superseded by AUDIT-060 — the "iterate fnptr array" claim stands; the "static-ctor list" implication does not.]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What invokes this driver in `entry_point`? Find exact caller PC.
|
||||
- Are the 14+ small vtable clusters in `[0x82870010, 0x828708C4)` enumerated by THIS driver, or by separate driver functions? If multiple drivers exist for the same region, the gap might be region-partitioning, not padding.
|
||||
- For ours: are all 397 non-NULL slots dispatched at runtime? If some slot dispatch falls through (e.g. predicate skips it), that would be a real bug — needs runtime confirmation via `--branch-probe=0x824ACBA0,0x824ACBEC` (loop bodies).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- LZ-runtime thunk at `+0x00..+0x2C`: `0x825F1630`.
|
||||
- Fnptr-array region: `0x82870010..0x828708D4`.
|
||||
- Audits: 045 (DB schema caveat: `v_call_graph` uses `xrefs.source`; prefer `xrefs.source_func`), 050, 060.
|
||||
- Artifacts: `audit-runs/audit-060-fnptr-array-bootstrap/ours-dump-500M.stdout`.
|
||||
50
docs/functions/sub_824F7800.md
Normal file
50
docs/functions/sub_824F7800.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
address: 0x824F7800
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 064
|
||||
aliases:
|
||||
- "AUDIT-058 caller-ladder fn #2 (bctrl-dispatch site for sub_825070F0)"
|
||||
---
|
||||
|
||||
# sub_824F7800 — dispatch caller for ANON_Class_713383D7 vtable slot 1
|
||||
|
||||
## Synopsis
|
||||
|
||||
Normal callee that performs the `bctrl` invoking [sub_825070F0](sub_825070F0.md) (slot 1 of the `ANON_Class_713383D7` vtable at `0x8200A208`). Bottom of a 4-fn linear call chain (`sub_824F8398 → sub_824F7CD0 → sub_824F7800 → [bctrl] → sub_825070F0`) that runs once per game-loop activation pass. AUDIT-064 verified canary fires this fn 1× at ~60s wallclock; ours fires it 0× because the entire chain sits downstream of tid=13's audit-049 wedge.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm prolog at `0x824F7800`: `mflr r12; bl 0x825F0F60 (frame helper); stwu r1, -336(r1); mr r22, r3; ...` — standard normal-callee prolog. NOT MSVC EH-handler shape (no `subi r31, r12, N`).
|
||||
- Function size: 1232 bytes / 308 insns. `has_eh=False`, `frame_size=336`.
|
||||
- Static caller xref: 1 — `bl` from PC `0x824F8314` inside [sub_824F7CD0](sub_824F7CD0.md). No other refs (only `.pdata` entry at file offset `0x1347B0` — standard unwind metadata).
|
||||
- AUDIT-064 canary 60s probe (`--audit_61_branch_probe_pcs=0x824F7800,...`): fires 1× with `lr=0x824F8318 r3=BE568F00 r4=701CF5B0 r5=BCA44D40 r6=BCA44DE0` on tid=6. Reproduced bit-identical at 120s and 180s wallclock.
|
||||
- AUDIT-064 ours `--ctor-probe=0x824F7800` -n 500M: **0 fires**.
|
||||
- The `bctrl` at PC `0x824F7B20` (= `sub_824F7800+0x320`, slot 1 of `0x8200A208` vtable) is where [sub_825070F0](sub_825070F0.md) is dispatched from.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_824F7CD0+0x644` (PC `0x824F8314`). Both engines see the same single static caller.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (from `xrefs.source_func`):
|
||||
- PC `0x824F8314` inside `sub_824F7CD0` (the only caller).
|
||||
- Callees include the `bctrl` at PC `0x824F7B20` that dispatches to `sub_825070F0` via vtable slot 1 of `ANON_Class_713383D7` (vtable `0x8200A208`).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-064 (2026-05-12)** — disasm confirms normal-callee prolog (refutes "another EH handler" hypothesis). Canary probe fires 1× / ours 0×. Static-DB caller is the runtime caller (no surprise bctrl divergence here). The chain runs downstream of [sub_822F1AA8](sub_822F1AA8.md)'s vtable[0] dispatch through sub_82173990 — which waits on tid=13 — so ours never reaches it because tid=13 is blocked on the AUDIT-049 wedge. [confirmed]
|
||||
- **AUDIT-058 (2026-05-10)** — flagged as part of the static caller ladder for sub_825070F0. [confirmed at this level; ladder framing partially preserved — see sub_821B6DF4 for the EH-thunk caveat one step further up]
|
||||
|
||||
## Open questions
|
||||
|
||||
- Why does the bctrl at `0x824F7B20` always dispatch to `sub_825070F0` (slot 1 of vtable `0x8200A208`) at this point? Investigate where the `r3` instance pointer comes from — likely a class member loaded via the slot-1 ctor path of `ANON_Class_713383D7`.
|
||||
- The 4-fn linear chain (`sub_824F8398 → sub_824F7CD0 → sub_824F7800 → bctrl`) is rigid and runs end-to-end without branching in canary. Confirm no early-exit branches inside the chain in ours (irrelevant if we resolve the audit-049 wedge first).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Callees: `sub_825070F0` via slot 1 of vtable `0x8200A208` at `bctrl` PC `0x824F7B20`.
|
||||
- Callers: `sub_824F7CD0+0x644`.
|
||||
- Audits: 058, 064.
|
||||
- Artifacts: `audit-runs/audit-064-activation-ladder/canary-{60,120,180}s.log`, `audit-runs/audit-064-activation-ladder/ours-500M.stdout`.
|
||||
49
docs/functions/sub_824F7CD0.md
Normal file
49
docs/functions/sub_824F7CD0.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
address: 0x824F7CD0
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 064
|
||||
aliases:
|
||||
- "AUDIT-058 caller-ladder fn #3"
|
||||
---
|
||||
|
||||
# sub_824F7CD0 — middle of sub_825070F0 activation chain
|
||||
|
||||
## Synopsis
|
||||
|
||||
Normal callee in the linear 4-fn activation chain ending at [sub_825070F0](sub_825070F0.md). Calls `sub_824F7800` at PC `0x824F8314`. Has a 4-way computed `bctr` switch table near its entry (PCs `0x824F7D00..0x824F7D34` — a jump-table dispatch on `[r31+0]-1` for values 1..4). AUDIT-064 verified canary fires 1× at ~60s wallclock; ours fires 0×.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm prolog at `0x824F7CD0`: `mflr r12; bl 0x825F0F68; stwu r1, -256(r1); ...` — standard normal-callee prolog. NOT MSVC EH-handler shape.
|
||||
- Function size: 1736 bytes / 434 insns. `has_eh=False`, `frame_size=256`.
|
||||
- Static caller xref: 1 — `bl` from PC `0x824F83D4` inside [sub_824F8398](sub_824F8398.md).
|
||||
- Computed jump-table at `0x824F7D10..0x824F7D24`: `lis r12, 0x824F; addi r12, r12, 32040; slwi r0, r11, 2; lwzx r0, r12, r0; mtctr r0; bctr` — 4-way switch on argument. Targets at `0x824F7D28/2C/30/34/...` are jump-table data, NOT call edges.
|
||||
- AUDIT-064 canary 60s probe: fires 1× with `lr=0x824F83D8 r3=BE568F00 r4=701CF5B0 r5=701CF658 r6=03A72328` on tid=6. Reproduced bit-identical at 120s and 180s.
|
||||
- AUDIT-064 ours `--ctor-probe=0x824F7CD0` -n 500M: **0 fires**.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_824F8398+0x3C` (PC `0x824F83D4`).
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (from `xrefs.source_func`):
|
||||
- PC `0x824F83D4` inside `sub_824F8398`.
|
||||
- Callees include `sub_824F7800` (PC `0x824F8314`), `sub_824FD230`, `sub_824FD240`, `sub_824FC498`, `sub_824FCC18`, and others.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-064 (2026-05-12)** — disasm confirms normal-callee + 4-way computed jump-table near entry. Canary fires 1× / ours 0×. Single static caller is the actual runtime caller. Chain blocks upstream at the audit-049 wedge (tid=13 thread-join wait on handle 0x12A4). [confirmed]
|
||||
- **AUDIT-058 (2026-05-10)** — flagged as part of the ladder. [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- The 4-way switch at `0x824F7D10..0x824F7D34`: which jump-table entry corresponds to the path that calls `sub_824F7800`? Disasm shows `lwz r11, 0(r31); subi r11, r11, 1; cmplwi cr6, r11, 0x3; bgt cr6, 0x824F80E4` — so input `r4` (saved to r31) must be 1..4 to enter switch. Canary's r4 was `0x701CF5B0` (a stack ptr), so the value at `[stack]` indexes the switch.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Callees: `sub_824F7800`, `sub_824FD230/40`, `sub_824FC498`, `sub_824FCC18`.
|
||||
- Callers: `sub_824F8398+0x3C`.
|
||||
- Audits: 058, 064.
|
||||
- Artifacts: `audit-runs/audit-064-activation-ladder/canary-{60,120,180}s.log`, `audit-runs/audit-064-activation-ladder/ours-500M.stdout`.
|
||||
48
docs/functions/sub_824F8398.md
Normal file
48
docs/functions/sub_824F8398.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
address: 0x824F8398
|
||||
classification: normal_callee
|
||||
confidence: high
|
||||
last_audit: 064
|
||||
aliases:
|
||||
- "AUDIT-058 caller-ladder fn #4 (tiny adapter, 20 insns)"
|
||||
---
|
||||
|
||||
# sub_824F8398 — 20-insn adapter to sub_824F7CD0
|
||||
|
||||
## Synopsis
|
||||
|
||||
Tiny 20-insn normal-callee adapter. Zeros a stack buffer (`std r9, 0(r11)` × 10 unrolled via `bdnz`), sets `[r1+80]=1` and `[r1+112]=r8` (its r4 argument), then calls `sub_824F7CD0` with `r3` passed through and `r4=&stack_buf+80`. Essentially a 2-arg→1-arg adapter that constructs a 36-byte stack-record before dispatching. AUDIT-064 verified canary fires 1× at ~60s wallclock; ours fires 0×.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Disasm: `mflr r12; stw r12, -8(r1); stwu r1, -160(r1); mr r8, r4; addi r11, r1, 80; li r9, 0; li r10, 9; mtctr r10; std r9, 0(r11); addi r11, r11, 8; bdnz 0x824F83B8; li r11, 1; stw r8, 112(r1); addi r4, r1, 80; stw r11, 80(r1); bl 0x824F7CD0; addi r1, r1, 160; lwz r12, -8(r1); mtlr r12; blr` — clear normal-callee, no EH.
|
||||
- Function size: 80 bytes / 20 insns. `has_eh=False`.
|
||||
- Static caller xref: 1 — `bl` from PC `0x821B5B5C` inside [sub_821B55D8](sub_821B55D8.md).
|
||||
- Stack buffer at `[r1+80]..[r1+112]` is 36 bytes (9 × 8-byte zero + first u32=1 + last u32=r8).
|
||||
- AUDIT-064 canary 60s probe: fires 1× with `lr=0x821B5B60 r3=BE568F00 r4=BC369380 r5=701CF658 r6=03A72328` on tid=6. Reproduced bit-identical at 120s and 180s.
|
||||
- AUDIT-064 ours `--ctor-probe=0x824F8398` -n 500M: **0 fires**.
|
||||
|
||||
## Activation
|
||||
|
||||
Direct `bl` from `sub_821B55D8+0x584` (PC `0x821B5B5C`).
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers: PC `0x821B5B5C` inside `sub_821B55D8`.
|
||||
- Callees: `sub_824F7CD0` (PC `0x824F83D4`).
|
||||
|
||||
## Audit log
|
||||
|
||||
- **AUDIT-064 (2026-05-12)** — disasm confirms tiny adapter (20 insns). Canary fires 1× / ours 0×. The size is small enough to inline; possibly an MSVC compiler artifact. [confirmed]
|
||||
- **AUDIT-058 (2026-05-10)** — flagged as part of the ladder. [confirmed]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What does the constructed stack-record (`[1, 0, 0, 0, 0, 0, 0, 0, 0, r8]`) represent semantically? Likely a state-machine init record passed by reference to `sub_824F7CD0`'s 4-way switch.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Callees: `sub_824F7CD0`.
|
||||
- Callers: `sub_821B55D8+0x584`.
|
||||
- Audits: 058, 064.
|
||||
- Artifacts: `audit-runs/audit-064-activation-ladder/canary-{60,120,180}s.log`.
|
||||
63
docs/functions/sub_825070F0.md
Normal file
63
docs/functions/sub_825070F0.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
address: 0x825070F0
|
||||
classification: vtable_method
|
||||
confidence: high
|
||||
last_audit: 067
|
||||
aliases:
|
||||
- "ANON_Class_713383D7 vtable slot 1"
|
||||
- "AUDIT-057 top missing-thread spawner"
|
||||
---
|
||||
|
||||
# sub_825070F0 — ANON_Class_713383D7 vtable slot 1 (worker spawner)
|
||||
|
||||
## Synopsis
|
||||
|
||||
Slot 1 of class `ANON_Class_713383D7` vtable (located at `0x8200A208` and clone at `0x8200A928`). When invoked, initializes 4 worker threads with shared context `r3=0xBCE25340` (canary). The thread entry points are `0x82506528 / 0x82506558 / 0x82506588 / 0x825065B8`. In canary, this fn fires 1× at ~60s wallclock immediately after `DiscImageDevice::ResolvePath(\\dat\\movie)` (post-intro file open). In ours, it fires 0× at any horizon probed so far.
|
||||
|
||||
## Evidence
|
||||
|
||||
- AUDIT-058 Linux Debug canary: fires 1× at ~60s wallclock with `pc=0x825070F0 lr=0x824F7B24 r3=BCE25340 r4=701CF3C0 r5=BCE25AC0`.
|
||||
- AUDIT-060 Probe C-Win Windows Debug canary: same probe (`--log_lr_on_pc=0x825070F0`, 90s) → 1 fire, `lr=0x824F7B24` — **bit-identical to Linux Debug**, validating the new Wine canary oracle.
|
||||
- LR `0x824F7B24` resolves to inside `sub_824F7800+0x24` — the vtable `bctrl` dispatch site.
|
||||
- Class `ANON_Class_713383D7` lives at vtables `0x8200A208` (and clone `0x8200A928`); both are 7-method tables. Slot 1 is this fn. **Zero recorded vptr_writes in DB** — the ctor that writes this vtable is in an unreachability island OR is a computed-store-only ctor.
|
||||
- **AUDIT-067 (2026-05-12)** strengthens this: **zero `vptr_writes`, zero `xrefs`, zero u32-byte occurrences of `0x8200A208`/`0x8200A928` in the `.pe` file, zero `addis+addi/ori` pairs materializing the value**. Runtime mem-watch of all 16 guest store opcodes (`stw`/`std`/`stwx`/`stwbrx`/`stwcx.`/`stmw`/`stvx`/`stvewx`/etc.) for 211 s wallclock in canary produces **0 hits** for these values — though `sub_825070F0` itself fires 1× at ~25 s wallclock with `*r3 = 0x8200A208` implicit at the bctrl. The install is **host-side**, not guest-side.
|
||||
- AUDIT-057 named this as the top missing-thread spawner: 4 missing thread spawns in ours.
|
||||
|
||||
## Activation
|
||||
|
||||
Vtable dispatch from `sub_824F7800+0x24 bctrl` (slot 1 of vtable `0x8200A208`). **AUDIT-064 fully classified the ladder**: `sub_824F7800`, `sub_824F7CD0`, `sub_824F8398`, `sub_821B55D8` are ALL `normal_callee` (NOT EH thunks). Only `sub_821B6DF4` is the EH catch-handler; it's a secondary entry path, not the runtime activation route.
|
||||
|
||||
**Full runtime activation chain (in canary; identified by AUDIT-064 via lr-resolution at each fire)**: tid=1 `entry_point → sub_8216EA68 → sub_822F1AA8` (post-init dispatcher) → `bctrl vtable[0] of *(0x828E1F08)` → `sub_82175330` (2-insn thunk) → tail-jump → `sub_82173990` → … → `sub_821741C8` → `sub_82172BA0` (array-walk dispatcher) → `bctrl vtable[6]` → `sub_821B55D8` → `sub_824F8398` → `sub_824F7CD0` → `sub_824F7800` → `bctrl vtable[1]` → `sub_825070F0`.
|
||||
|
||||
**Wedge in ours (AUDIT-064)**: tid=1 successfully enters `sub_822F1AA8`, reaches the bctrl at `0x822F1B4C`, dispatches to `sub_82175330` → `sub_82173990` → blocks at `sub_82173990+0x2D0` on `KeWaitForSingleObject` INFINITE on handle `0x12A4` = tid=13's thread handle. Tid=13 itself is blocked on the AUDIT-049 wedge (event 0x12AC inside the audit-009 cluster). The 5-fn ladder downstream of `sub_82172BA0` is NEVER reached because tid=1 hasn't returned from the thread-join wait.
|
||||
|
||||
## Static graph
|
||||
|
||||
- Static callers (direct `bl`): 0 — it's reached only via `bctrl`. The `bctrl` site is `0x824F7B20`.
|
||||
- Callees: spawns 4 worker threads via `ExCreateThread` (or equivalent) with entries `0x82506528/58/88/B8`.
|
||||
|
||||
## Audit log
|
||||
|
||||
- **Phase Non-match Investigation (2026-05-19)** — Phase A `thread.create` events directly corroborate the AUDIT-058 framing using **runtime** evidence (previously only static + ctor-probe). Canary cold trace `canary-jitter-1.jsonl` (4.4 GB, 18.7M events) contains EXACTLY 4 `thread.create` events at `host_ns = 10.382912900 / 10.383282200 / 10.383647200 / 10.384161700` (spaced ~370–500 ns apart on tid=6 = guest main) with entries `0x82506528 / 0x82506558 / 0x82506588 / 0x825065B8`, shared `ctx_ptr=0xBCE251C0`, stack=65,536, `suspended=true`, affinity=0. These match the dossier's listed worker entries 1:1 and are bit-identical-in-structure to the AUDIT-058 fire (modulo `ctx_ptr` arena drift: AUDIT-058 cited `0xBCE25340`, this jitter sample has `0xBCE251C0` — both inside the `0xBCE25xxx` arena allocated by the same fn). FIFO-matched child tids: `0x82506528 → tid=28` (3.26M events, file IO + heavy RtlEnterCS), `0x82506558 → tid=27` (36k events), `0x82506588 → tid=29` (91k events), `0x825065B8 → never started` in the 90s window. Same canary-vs-ours digest comparison shows ours-postfix.jsonl has **0 occurrences** of `0xBCE251C0` and **0 thread.create events** after spawn #10 (1.727 s). The full set of static-analysis-invisible properties (0 vptr_writes, 0 xrefs, 0 indirect_dispatch_candidates targeting vtables `0x8200A208` / `0x8200A928`) was re-verified against current sylpheed.db — AUDIT-067's conclusion stands. New artifacts at `audit-runs/phase-nonmatch-investigation/`. **Recommended next probe**: AUDIT-068 host-side mem-watch was deferred — re-attempt now with Phase A event correlation (the 10.382 s spawn burst is the precise wall-clock window to hook). [confirmed runtime; framing intact]
|
||||
- **AUDIT-067 (2026-05-12)** — runtime mem-watch via new canary cvar `audit_67_value_watch` (~422 LOC additive instrumentation; default-empty / zero-cost; kept in canary tree per policy). Hooked **all 16 store opcodes**: `stw`, `stwu`, `stwx`, `stwux`, `stwbrx`, `stwcx.`, `stmw`, `std`, `stdu`, `stdux`, `stdx`, `stdbrx`, `stdcx.`, `stvx`/`stvxl`/`128`, `stvewx`/`128`. Each hook emits `CompareEQ(val32, watch) → TrapTrue(_,250+idx)`; trap handler logs `pc/lr/val/dst/regs/tid`. Sanity test with `watch=0x00000000` → **103,321 hits in 30s** (instrumentation verified). Main run `watch=0x8200A208,0x8200A928` for **211 s wallclock**: **0 hits** despite `AUDIT-061-BR pc=0x825070F0` firing 1× with `r3=0xBCE25340 r4=0x701CF3C0 r5=0xBCE25AC0` (bit-identical to AUDIT-058/060). **CONCLUSION**: the vtable address `0x8200A208` is never stored to guest memory via any guest PowerPC store opcode in canary — the install is **host-side** (most likely a kernel-import direct memory write via `xe::store_and_swap<uint32_t>(memory + addr, val)`, OR an XEX loader operation, OR a `RtlCopyMemory`-style host helper). Path A (static binary search) also yielded 0 matches: no `vptr_writes`, no `xrefs`, no `addis`+`addi/ori` pair (with or without mr-chain register propagation) materializing the value, no u32 occurrence anywhere in the `.pe` file. **Reading-error #19**: assumption that meaningful guest-memory writes go through guest PPC code is false — kernel imports and the image loader perform direct host writes that bypass the JIT. AUDIT-068 must hook at the `Memory::write*` / `store_and_swap<*>` level instead. [confirmed — negative result; structural finding]
|
||||
- **AUDIT-064 (2026-05-12)** — classified all 4 unclassified ladder fns: `sub_824F7800`, `sub_824F7CD0`, `sub_824F8398`, `sub_821B55D8` — **all 4 are normal_callee**, NOT EH thunks (refutes the worst-case hypothesis from AUDIT-060 that the whole chain might be EH metadata). Probed canary at 60s/120s/180s — all 4 fire 1× each, bit-identical context. Walked upward: real runtime caller of `sub_821B55D8` is `sub_82172BA0+0x1E8 bctrl` (PC `0x82172D88`), NOT the static-DB-listed `sub_821B6DF4` EH branch. **Identified the full upstream activation chain**: tid=1 entry_point → `sub_8216EA68` → [`sub_822F1AA8`](sub_822F1AA8.md) → vtable[0] of `*(0x828E1F08)` = `sub_82175330` (2-insn thunk) → tail-jump to `sub_82173990` → … (canary continues through `sub_821741C8` → [`sub_82172BA0`](sub_82172BA0.md) → vtable[6]=[`sub_821B55D8`](sub_821B55D8.md) → [`sub_824F8398`](sub_824F8398.md) → [`sub_824F7CD0`](sub_824F7CD0.md) → [`sub_824F7800`](sub_824F7800.md) → bctrl → `sub_825070F0`). **First divergence in ours**: tid=1 enters `sub_82173990` via the vtable[0] dispatch but blocks at `sub_82173990+0x2D0 bl 0x824AA330` (KeWaitForSingleObject INFINITE) on handle `0x12A4` = tid=13's thread handle. This is the **same AUDIT-049 wedge**: tid=13 itself is blocked on handle `0x12AC` waiting for the audit-009-cluster signal. Activation of sub_825070F0 is gated on resolving the tid=13 wait, NOT on any divergence in the ladder fns themselves. [confirmed]
|
||||
- **AUDIT-060 (2026-05-12)** — verified canary fire reproduces under Windows Debug oracle. Caller chain caveat added: `sub_821B6DF4` ladder-top is EH, not normal call edge. Other ladder fns need individual classification. [confirmed for canary fire; caveat on the upstream chain]
|
||||
- **AUDIT-058 (2026-05-10)** — captured canary fire context, walked static caller ladder, found all 6 ladder fns fire 0× in ours. Concluded "activation phase doesn't activate in ours". [STATUS: ladder framing partially falsified by AUDIT-060 — at least `sub_821B6DF4` is EH; the *real* gate is the AUDIT-056 sub_821C4EB0 throughput gap, upstream.]
|
||||
- **AUDIT-057 (2026-05-10)** — flagged as top missing-thread spawner (4 of 13 missing thread spawns). [confirmed quantitatively]
|
||||
|
||||
## Open questions
|
||||
|
||||
- What spawns the 4 worker threads exactly? Disassemble body. The threads have entries `0x82506528/58/88/B8` — are these consecutive 0x30-byte stubs that all forward to a common worker fn?
|
||||
- What class instance triggers the slot-1 dispatch? Is it a `silph::GamePart_Title` instance? The wallclock context (post-`\\dat\\movie` ResolvePath) suggests so.
|
||||
- **(AUDIT-067 result)** What host-side mechanism installs `0x8200A208` at `0xBCE25340+0`? Candidates: `xboxkrnl_rtl*` direct-write helpers (`RtlCopyMemory`/`RtlFillMemory`/`RtlInitializeCriticalSection` etc.), XEX loader image-rewrites, or kernel-import factory helpers. Next probe: AUDIT-068 host-side mem-watch — hook `Memory::write*` and/or `xe::store_and_swap<*>` in canary.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- Vtable: `0x8200A208` (primary), `0x8200A928` (clone), class `ANON_Class_713383D7`, slot 1.
|
||||
- Dispatch site: `sub_824F7800+0x20 bctrl` (PC `0x824F7B20`); post-bctrl PC `0x824F7B24`.
|
||||
- Worker thread entries spawned: `0x82506528, 0x82506558, 0x82506588, 0x825065B8`.
|
||||
- **Real runtime activation chain (AUDIT-064)**: `tid=1 entry_point → sub_8216EA68 → [sub_822F1AA8](sub_822F1AA8.md) → bctrl vtable[0]={sub_82175330 tail-jump→sub_82173990} → … → sub_821741C8 → [sub_82172BA0](sub_82172BA0.md) → bctrl vtable[6] → [sub_821B55D8](sub_821B55D8.md) → [sub_824F8398](sub_824F8398.md) → [sub_824F7CD0](sub_824F7CD0.md) → [sub_824F7800](sub_824F7800.md) → bctrl vtable[1] → sub_825070F0`.
|
||||
- **Wedge in ours**: tid=1 blocks at `sub_82173990+0x2D0` on KeWaitForSingleObject(handle=0x12A4 = tid=13's thread handle); tid=13 itself blocks at `sub_821CB030+0x128`-created event 0x12AC — AUDIT-049 wedge.
|
||||
- Old static-DB ladder (AUDIT-058, partly EH): `sub_824F7800 ← sub_824F7CD0 ← sub_824F8398 ← sub_821B55D8 ← [sub_821B6DF4](sub_821B6DF4.md) (EH catch-handler — secondary EH-only entry path)`.
|
||||
- Audits: 057, 058, 060, 064, 067.
|
||||
- Artifacts: `audit-runs/audit-058-sub825070F0-activation/`, `audit-runs/audit-060-fnptr-array-bootstrap/canary-sanity-825070F0.log`, `audit-runs/audit-064-activation-ladder/`, `audit-runs/audit-067-vptr-install-mem-watch/`.
|
||||
Reference in New Issue
Block a user