Phase A's diagnosis (bit 28 of [0x40d09a40] gets set to exit sub_822F1AA8's loop) is falsified by direct probe + --dump-addr in 4 sub-rounds. Key evidence: - sub_821B55D8 candidate fn fires 0× in ours; sub_824AA858 (XamInputSetState wrapper) fires 0× in canary too — chain is dead code in both engines. - end-of-run dump shows [0x40d09a40+0] = 0x00000021, same as at entry — bit 28 is NEVER set. - bcctrl at PC 0x822F1B4C (sub_822F1AA8+0xA4) fires (LR=0x822F1B50) but the post-bcctrl BB head 0x822F1B50 fires 0× — bcctrl never returns. - sub_82173990 (vtable[0] of singleton at [0x828E1F08]) is the call target; tid=1 wedges inside this 768-byte function on a thread-join to handle 0x1070 (= tid=13's thread handle). - tid=13 (entry=sub_821748F0, ctx=0x4024a840, handle=0x1070) reaches sub_821C4EB0 (silph::UImpl@GamePart_Title) at cycle 1882 → audit-049 cluster IS reached, wedges on handle 0x1078 there. C.2 force-clear POC NOT EXECUTED — would be no-op since bit 28 is never set. Per plan stopping criterion, hand back instead of proceeding blind. Adds reading-error class #19: disasm-pattern-match without runtime verification (Phase A scanned 49 oris-0x1000 sites and declared one the setter without ever observing the bit get set). No xenia-rs source changes. Canary repo also unchanged (config edit reverted clean). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
128 lines
8.4 KiB
Markdown
128 lines
8.4 KiB
Markdown
# Phase C.1 — Validation refutes Phase A's bit-28 setter hypothesis
|
||
|
||
## TL;DR
|
||
|
||
Phase A claimed: "bit 28 of `[0x40d09a40]` (controller word) gets set in ours, causing sub_822F1AA8's dispatcher loop to exit early; candidate setter is `sub_821B55D8` at PC `0x821B5DA4`."
|
||
|
||
**Phase C.1 falsifies this in 4 sub-rounds:**
|
||
|
||
1. **`sub_821B55D8` is dead code** in both engines — its `XamInputSetState` wrapper `sub_824AA858` fires 0× in both.
|
||
2. **`[0x40d09a40]` is never set to anything with bit 28** — `--dump-addr` at end of run shows `+0x00 = 0x00000021`, the entry value. Bit 28 is NEVER set.
|
||
3. **The actual wedge is at the `bcctrl` at PC `0x822F1B4C`** (inside sub_822F1AA8 setup, BEFORE the dispatcher loop). tid=1 never reaches the loop top-check.
|
||
4. **The bcctrl calls `sub_82173990`** (vtable[0] of the dispatcher singleton at `[0x828E1F08]`), which eventually waits for tid=13 to terminate. tid=13 wedges in the audit-049 silph::UImpl@GamePart_Title chain on handle `0x1078`.
|
||
|
||
The C.2 force-clear POC (the planned next step) would have **zero effect** because bit 28 is never set. Skipped per plan stopping criterion.
|
||
|
||
## Probe-fire counts (ours, 50M-instr parallel)
|
||
|
||
| PC | sub-round | fires | meaning |
|
||
|---|---|---|---|
|
||
| `0x821B55D8` (Phase A candidate fn entry) | 1 | **0** | function never reached → β/γ |
|
||
| `0x821B5D98,DA0,DAC,D48` (loop BB heads) | 1 | **0** | function never reached |
|
||
| `0x822F1AA8` (sub_822F1AA8 entry) | 2,3,4 | 2-3 | reached |
|
||
| `0x822F1B38` (post-`bl 0x824AA8B0`) | 4 | 2 | reached |
|
||
| `0x822F1B50` (post-`bcctrl`) | 4 | **0** | **bcctrl never returns** |
|
||
| `0x822F1B60,B78,B80,BBC` (loop setup/top) | 3 | 0 | unreachable past bcctrl |
|
||
| `0x822F1E10` (loop exit cleanup) | 2 | 0 | loop never entered, never exited |
|
||
| `0x822F1E34` (post-thread-join) | 2 | 0 | never reached |
|
||
| `0x82173990` (vtable[0] target) | 4 | 2 | called via bcctrl, r3=singleton (LR=0x822F1B50) |
|
||
| `0x821748F0` (tid=13 entry) | 4 | 2 | tid=13 runs |
|
||
| `0x821C4EB0` (silph::UImpl@GamePart_Title) | 4 | 2 | audit-009/049 reached on tid=13 |
|
||
| `0x82457388,0x824574C0,0x82457408,0x82457490` (other oris candidates) | 2 | 0 | unreachable |
|
||
|
||
## Canary probe results
|
||
|
||
| PC | fires | meaning |
|
||
|---|---|---|
|
||
| `0x824AA858` (XamInputSetState wrapper) | **0** | sub_821B55D8 chain is dead code in CANARY too |
|
||
| `0x822F1B50` (post-bcctrl, attempted) | **0** | canary's JitProlog only fires at function entries, so not directly testable; but per audit round-33 sub_821741C8 fires 471× in canary → bcctrl DOES return in canary |
|
||
|
||
## Critical evidence: `--dump-addr=0x40d09a40` at end of run
|
||
|
||
```
|
||
addr=0x40d09a40
|
||
+0x00: 00 00 00 21 00 00 00 01 42 44 df 00 40 54 1a 40
|
||
^^^^^^^^^^^ ^^^^^^^^^^^
|
||
+0x10: 40 54 1b 40 40 54 1b 80 40 54 1b c0 00 00 10 54
|
||
+0x20: 00 00 00 00 40 24 a8 20 00 00 00 08 00 00 00 00
|
||
```
|
||
|
||
- `[+0x00] = 0x00000021` ← bit 28 (mask 0x10000000) is NOT SET. Same value as at sub_822F1AA8 entry.
|
||
- `[+0x1c] = 0x00001054` ← spawned init thread handle (= tid=8's thread handle, NOT 0x1070)
|
||
- Thread state: tid=1 waits on handle `0x1070`, tid=13 waits on handle `0x1078`.
|
||
|
||
Handle `0x1070` is **tid=13's thread handle** (per stderr: `ExCreateThread: tid=13 handle=0x1070 entry=0x821748f0 ctx=0x4024a840 suspended=true`). So tid=1's wait at the wedge point is a **thread-join on tid=13**, NOT a thread-join on the dispatcher init thread (tid=8, handle 0x1054).
|
||
|
||
## Wedge path (corrected)
|
||
|
||
```
|
||
entry_point (sub_824AB748) [tid=1 main]
|
||
└─ sub_8216EA68
|
||
└─ sub_822F1AA8(controller=0x40d09a40) [LR=0x8216EE14]
|
||
├─ ExCreateThread(entry=sub_822F1EE0, ctx=controller) [PC 0x822F1B08]
|
||
│ ⇒ tid=8 spawn, handle=0x1054 (suspended)
|
||
├─ bl 0x824AA8B0 (no-op probe) [PC 0x822F1B34]
|
||
└─ bcctrl on vtable[+0] of [0x828E1F08] singleton [PC 0x822F1B4C]
|
||
│
|
||
└─ sub_82173990(r3=singleton) [r3=0x40ba9a80, vtable=0x40111910]
|
||
└─ ... (768-byte function with ≥18 calls; calls sub_82448AA0, sub_824AA7A0,
|
||
sub_82448BC8, sub_82448C50, sub_8216F218, sub_8217C850, sub_82178E50,
|
||
sub_821835E0, ...)
|
||
└─ ... → KeWaitForSingleObject INFINITE on handle 0x1070
|
||
(= tid=13's thread handle, thread-join)
|
||
⇒ WEDGE — tid=13 never exits
|
||
|
||
(Concurrently — spawned somewhere else, not from sub_822F1AA8:)
|
||
[tid=13, spawn-handle=0x1070, ctx=0x4024a840]
|
||
└─ sub_821748F0 (worker boilerplate, entry from ExCreateThread)
|
||
├─ sub_82172798, sub_82172818
|
||
└─ sub_821749C0
|
||
└─ sub_821CF3F0
|
||
└─ ... → sub_821C4EB0 (UImpl@GamePart_Title@silph) [audit-009/049!]
|
||
└─ ... → sub_821CB030 (creates KEVENT at +0x128)
|
||
⇒ KeWaitForSingleObject INFINITE on handle 0x1078
|
||
⇒ WEDGE — handle 0x1078 is never signaled in ours
|
||
```
|
||
|
||
## Why Phase A's hypothesis is wrong
|
||
|
||
Phase A:
|
||
1. Disassembled sub_822F1AA8's body, observed the bit-28 loop-exit check at `0x822F1BB8` and end-of-iter check at `0x822F1E0C`.
|
||
2. Mem-watch on `0x40d09a40` showed zero stores → inferred "the setter writes via some path mem-watch doesn't capture."
|
||
3. DB-scanned `oris ?, ?, 0x1000` (49 sites), found `sub_821B55D8 + 0x821B5DA4` with pattern `bl sub_824AA858 ; if r3 == 0xAA: oris r11, 0x1000 ; stw`.
|
||
4. Concluded `sub_821B55D8` was the setter.
|
||
|
||
What Phase A missed:
|
||
- Mem-watch's 0-stores result was correct: **NO setter exists**. Bit 28 is never set in either engine. The mem-watch null-result was a hint that the bit-28 hypothesis itself was wrong, but Phase A interpreted it as "mem-watch misses something."
|
||
- The disasm-based hypothesis was visually compelling (a loop iterating arrays and setting bit 28 when a kernel call returns 0xAA) but never verified runtime.
|
||
- `sub_821B55D8` is itself dead code in both engines.
|
||
|
||
## Reading-error class #19: disasm-pattern-match without runtime verification
|
||
|
||
When scanning for a hypothesized signal source via DB pattern-match (`oris ?, ?, 0x1000`), the analyst must run a probe to verify the suspected site is *both reached* and *takes the suspected path* before declaring it the cause. Phase A bypassed both checks. The single `--dump-addr=0x40d09a40` flag in sub-round 2 (literally 4 keystrokes added to the existing probe command) revealed the central assumption was wrong.
|
||
|
||
## Real divergence (handed to next session)
|
||
|
||
This is the **same wedge as audit-049/058/059**: tid=13 wedges in the silph::UImpl@GamePart_Title cluster on handle `0x1078`. tid=1 wedges on tid=13's thread-handle (`0x1070`) inside `sub_82173990`'s call chain.
|
||
|
||
`sub_82173990` is vtable[0] of the dispatcher singleton at `[0x828E1F08]`. It's a 768-byte function with ≥18 calls; the actual wait site is somewhere down its tree. To localize where in `sub_82173990` the wait happens, probe its BB heads + the `KeWaitForSingleObject` thunks (`sub_824AA330`, `sub_824AA708`).
|
||
|
||
The fix-shape is **NOT** "force-clear bit 28." The fix-shape is **"signal handle 0x1078 in the audit-049 cluster, or short-circuit tid=13's wait."** Round 22 (silph_synth.rs) attempted the cluster-A version of this. Cluster B (silph::UImpl) needs its own synthesis or a kernel-side signal of handle 0x1078.
|
||
|
||
## Phase C verdict
|
||
|
||
- C.1: 4 sub-rounds executed (within budget).
|
||
- C.2: **NOT EXECUTED** — POC would be no-op since bit 28 is never set. Per plan stopping criterion, do not proceed to C.2 blind when C.1 refutes the diagnosis.
|
||
- C.3: not applicable.
|
||
- Branch state: no source changes. Audit artifacts only.
|
||
|
||
## Files in this directory
|
||
|
||
- `ours-c1-probe.log/stderr` — sub-round 1, probe at sub_821B55D8 BB heads (0 fires)
|
||
- `ours-sr2-confirm-bit28.log/stderr` — sub-round 2, probe loop top/exit + dump-addr (bit 28 NEVER SET)
|
||
- `ours-sr3-wait-trace.log/stderr` — sub-round 3, probe wait site + handle 0x1070 trace
|
||
- `ours-sr4-bcctrl-trace.log/stderr` — sub-round 4, probe pre/post bcctrl + sub_82173990 entry + tid=13 entry (decisive)
|
||
- canary side in `../round-C1-setter-validation-canary/`:
|
||
- `canary-824AA858.log` — XamInputSetState wrapper fires 0× in canary too
|
||
- `canary-822F1B50.log` — JitProlog can't probe at BB-internal PCs (function-entry-only)
|