Files
xenia-rs/audit-runs/audit-059-handle-disambiguation/round-A4b-ours-spawn-gate/FINDINGS.md
MechaCat02 52c30d82a7 [AUDIT-059 R-A] Phase A backward-trace: divergence is sub_822F1AA8 loop exit, not factory/registry
Round-37 anchor reframe: both engines install the SAME static .rdata vtable
0x820A183C at [0x828E1F08]. Instance VAs differ only because of ε-class
allocator divergence (audit-043). vtable bytes byte-identical; the user
prompt's "factory/registry" framing was falsified.

Phase A walkthrough (rounds A1..A8):
- A.1 canary --audit_jit_prolog_pc=0x821741C8: tid=6, r3=0xBCCC4A80 (= inner
  sub-object of [0x828E1F08]'s singleton), LR=0x822F1D5C (return-from-bctrl
  inside sub_822F1AA8)
- A.2 found tid=6 spawn site sub_821746B0 at PC 0x82174824 spawning
  entry=sub_821748F0 ctx=BC365700/BC366DA0. sub_822F1AA8 ALSO spawns a
  second thread (entry=sub_822F1EE0 ctx=BCE24A40) at PC 0x822F1B08
- A.3 sub_822F1AA8 has 2 callers, both in sub_8216EA68 (its sole caller is
  sub_824AB748 = entry_point)
- A.4 ours mirror probe: sub_821746B0 enters, [0x828E2B14] gate passes,
  ExCreateThread fires returning handle 0x1070 (= tid=13). Ours' tid=13
  IS the same logical thread as canary's spawned silph initializer
- A.5 canary --audit_jit_prolog_pc=0x821749C0: fires only 2× on short-lived
  tid=17, tid=26 (the spawned initializers — NOT tid=6)
- A.6 canary --audit_jit_prolog_pc=0x822F1AA8: fires 1× on tid=6 with
  r3=0xBCE24A40 LR=0x8216EE14 (the second sub_822F1AA8 call site)
- A.7 canary --audit_jit_prolog_pc=0x824AB748 (entry_point): fires on
  tid=00000006. CONFIRMS canary's tid=6 = canary's main thread.

Verdict: identical call chain entry_point → sub_8216EA68 → sub_822F1AA8 in
both engines; same controller (ε-divergent VA, byte-identical fields).
Canary's main thread stays in sub_822F1AA8's dispatcher loop firing
sub_821741C8 ~1678×/30s. Ours' main thread exits the loop and thread-joins
on the spawned initializer (tid=13), which is itself wedged on handle 0x1078
forever.

Loop exit is gated by bit 28 of [r30+0] (the controller's flag word). Same
value 0x21 at function entry in both engines. Some code between entry and
loop check sets bit 28 in ours but not in canary. Mem-watch on 0x40d09a40
shows zero guest stores in ours' 50M parallel run — setter is either a
kernel-side store, computed alias, or probe-quantum-elided JIT store.

Phase B classification: Class 3a (state-divergence on controller object).
The vtable is the same; the controller's bit 28 evolves differently during
sub_822F1AA8 setup. Class 4 (synthesis) is now less attractive since we
correctly reach the dispatcher with the right inputs — we just exit too
soon.

Phase C will need either JIT instrumentation to identify the bit-28 setter,
or a kernel-side hook to clear bit 28 on entry to the loop check site.

Findings notes:
- round-A4b-ours-spawn-gate/FINDINGS.md (spawn topology + tid mapping)
- round-A8-ours-822F1AA8-trace/FINDINGS.md (full loop structure + bit-28 gate)

New reading-error class #18: probe-output anchor misframing (singleton[VA]=X
vtable=Y was misread as "Y is canary-only vtable" when Y is the same
.rdata vtable in both engines).

Branch: iterate-2C/silph-ui-spawn-trace off master @ 229b46c.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 17:02:20 +02:00

168 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Round-A1..A4 findings — canary tid=6 spawn chain & divergence frontier
## Anchor reframe (round-37 misread corrected)
The "factory/registry layer divergence at [0x828E1F08]" framing is falsified.
Both engines install the SAME static-XEX `.rdata` vtable `0x820A183C` at the
singleton's `[+0]`. The instance VAs differ only because of ε-class allocator
divergence (audit-043).
| Probe | Canary | Ours |
|----------------------------|----------------------|----------------------|
| `[0x828E1F08]` | 0xBC22C910 (heap) | 0x40111910 (heap) |
| `[[0x828E1F08]+0]` vtable | 0x820A183C | 0x820A183C (SAME) |
| `vtable[+0]` thunk | 0x82175330 | 0x82175330 (SAME) |
| `vtable[+8]` thunk | 0x82175340 → b sub_821741C8 | SAME (vtable bytes from XEX `.rdata`) |
The thunks at 0x82175330+ are 8-byte `lwz r3, 8(r3); b <real_method>`
trampolines. Slot 2 (`+0x08`) is the worker dispatch entry that round 33
identified as 471× in canary tid=6 / 0× in ours.
## A.1 — Canary dispatcher loop is in sub_822F1AA8 on tid=6
Probe `--audit_jit_prolog_pc=0x821741C8 --audit_jit_prolog_r3_bytes=256` on
canary (35 s):
- ~1678 fires of sub_821741C8 on **tid=6**
- r3 at entry = `0xBCCC4A80` (the inner sub-object of the silph::UImpl
singleton — extracted via the thunk's `lwz r3, 8(r3)`)
- LR at entry = `0x822F1D5C` (return PC after the `bctrl` at 0x822F1D58 inside
sub_822F1AA8)
- Singleton's `[+C0..+D0]` UTF-16 spells "HF Frequency" (a UI label)
The dispatch site in canary (the `bctrl`) is at PC 0x822F1D58 inside
sub_822F1AA8:
```
0x822F1D40: lwz r3, 7944(r25) ; r3 = [r25+0x1F08] = [0x828E1F08]
0x822F1D4C: lwz r11, 0(r3) ; vtable
0x822F1D50: lwz r11, 8(r11) ; vtable[+8] = thunk 0x82175340
0x822F1D54: mtctr r11
0x822F1D58: bctrl ; → 0x82175340 → b 0x821741C8
```
## A.2 — Canary tid=6 spawn site is sub_821746B0 at PC 0x82174824
Enumeration of `ExCreateThread` calls in canary (35 s, 21 unique tuples):
```
entry=821748F0 start_ctx=BC365700 lr=824AC5F0 guest_lr=82174828 ← silph dispatcher #1
entry=821748F0 start_ctx=BC366DA0 lr=824AC5F0 guest_lr=82174828 ← silph dispatcher #2
```
PC `0x82174824` is the `bl 0x82172370` (the `ExCreateThread` thunk) inside
`sub_821746B0`. The setup is:
```
0x8217480C: lis r11, 0x8217
0x82174810: li r7, 0
0x82174814: li r6, 4 ; priority
0x82174818: mr r5, r29 ; start_ctx
0x8217481C: addi r4, r11, 18672 ; r4 = 0x821748F0 (entry)
0x82174820: li r3, 0
0x82174824: bl 0x82172370 ; ExCreateThread
```
The entry `0x821748F0` is a thread main that calls `bl 0x821749C0` (the
inner dispatch).
## A.3 — sub_822F1AA8 spawns a SECOND thread at 0x822F1B08
The dispatch-loop function `sub_822F1AA8` itself ALSO spawns a thread at
PC 0x822F1B08 with entry=`sub_822F1EE0` and `start_ctx=BCE24A40`:
```
0x822F1AEC: lis r11, 0x822F
0x822F1AFC: addi r4, r11, 7904 ; r4 = 0x822F1EE0
0x822F1B08: bl 0x82172370 ; ExCreateThread
```
sub_822F1EE0 → sub_822F1F20 contains its own atomic state-machine + wait loop.
## A.3' — sub_822F1AA8 has exactly 2 callers, both in sub_8216EA68
```
source=0x8216ECCC source_func=0x8216EA68 kind=call
source=0x8216EE10 source_func=0x8216EA68 kind=call
```
So sub_8216EA68 is the only function that drives sub_822F1AA8.
## A.4 — Ours' divergence is INSIDE the spawned thread, NOT at the spawn
Mirror-probed ours at `sub_821746B0` body BB heads (parallel mode, 50M
instructions, XENIA_CACHE_PERSIST=1):
| PC | Fires | Notes |
|-------------|-------|------------------------------------------------|
| 0x821746B0 | 1 | Entry. r3=0x40ba9a80 |
| 0x821746E0 | 1 | After `bl 0x8284DCFC` (critical-section) |
| 0x82174798 | 1 | After the early `beq` (r28==0 branch) |
| 0x821747B8 | 1 | **Past the gate**: `[0x828E2B14]=0x40105000` non-NULL; `bl 0x82150EF8` returned r3=0x4024a840 (NON-NULL) |
| 0x821747D8 | 1 | After the inner `bl 0x821723F0` |
| 0x8217480C | 1 | Enters the spawn block |
| 0x82174828 | 1 | **Post-`bl ExCreateThread`**, r3=0x1070 = thread handle |
**OURS DOES SPAWN THE THREAD VIA THIS SITE.** The returned handle 0x1070 is
**tid=13's thread handle** (per round 37 final state). So **ours' tid=13 IS
the same logical thread as canary's tid=6** — spawned by the identical call
site with the same entry (0x821748F0).
## A.4 — Divergence is INSIDE the spawned thread's body
Round 37's frame trail for ours' tid=13 wedge:
`0x821CB1E0 → 0x821CBAE0 → 0x821CC454 → 0x821C4F18 → 0x82174A80`
The LAST frame `0x82174A80` is **inside sub_821749C0** (= the inner dispatch
called from sub_821748F0). It's right after the vtable dispatch at
0x82174A78 (`bctrl` on `[r30+vtable][+16]`):
```
0x82174a64: mr r3, r30 ; r3 = some object
0x82174a68: lwz r11, 0(r30)
0x82174a6c: lwz r4, 4(r29)
0x82174a70: lwz r5, 8(r31)
0x82174a74: lwz r11, 16(r11) ; r11 = vtable[+0x10]
0x82174a78: mtctr r11
0x82174a7c: bctrl ; dispatch
0x82174a80: lwz r3, 0(r29) ; ← wedge frame top (LR after bctrl)
```
So `sub_821749C0`'s vtable[+0x10] dispatch on tid=13/tid=6's `r30` object
lands at audit-049 territory in ours (chain through sub_821CB030+0x128 that
ends waiting forever on handle 0x1078). In canary, the same dispatch on the
same object SHOULD land somewhere that ultimately reaches sub_822F1AA8's
dispatch loop and runs sub_821741C8 1678× via vtable[+8].
**The object `r30` is the result of `bl 0x821CF3F0`** at PC 0x821749DC. So
sub_821CF3F0 returns a registry-lookup object; the vtable on this object's
slot +0x10 method's body determines whether the thread wedges or runs.
## Phase B classification
Class 3 — **Missing init-time precondition**. Ours reaches the spawn site,
ours' tid=13 enters the chain, ours' tid=13 enters sub_821749C0, but the
vtable[+0x10] dispatch at PC 0x82174A78 in ours lands in audit-049 territory
(wait forever on 0x1078) rather than continuing through the canonical chain
toward sub_822F1AA8's outer dispatch loop.
Possible classes to refine in next round:
- **3a**: same vtable but state-dependent — `r30`'s field at a specific offset
differs in ours vs canary, causing the method body to take a different
branch.
- **3b**: the vtable in `r30` is DIFFERENT in ours vs canary (e.g., ours has
a base-class vtable but canary has a derived-class vtable).
- **4**: synthesis fallback — spawn a SECOND thread that runs sub_822F1AA8's
dispatch loop directly, bypassing the wedged sub_821749C0 chain.
## Next probe (A.4.5)
Probe both engines at sub_821749C0 entry filtering tid=13 (ours) / tid=6
(canary), capturing:
- `r3` and `r4` at entry (the factory-output object and the ctx)
- After the `bl 0x821CF3F0` at 0x821749DC: capture r30 (= sub_821CF3F0
return — the object whose vtable is dispatched at 0x82174A78)
- At PC 0x82174A78 (the divergent bctrl): r30 + r30+0 (vtable) + vtable[+0x10]
(the dispatch target)
If ours and canary have IDENTICAL `vtable[+0x10]` targets but the method
body's behavior differs → class 3a (state divergence). If targets differ →
class 3b (vtable identity divergence).