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

7.3 KiB
Raw Blame History

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).