diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 22e53a3..ed53097 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -732,6 +732,20 @@ search cannot find a *schedule*. like **host stack**, not heap, so either xenia places them unusually or `rbx` isn't `Wait`'s `this` after unwind. **Check: dump a few words at `$rbx` — XEvent-like (vtable, KernelState*, handle) vs saved registers.** +* 🟡 **(2026-08-25) Refined: TWO wait types, and the read self-checks.** + Re-extracting per thread (not by grep): of **18** `XObject::Wait` frames, **8** + have `[rbx]` = `vtable for xe::kernel::XEvent`+16, **2** = `vtable for + xe::kernel::XSemaphore`+16, and **8** hold a non-vtable mmap pointer. ⇒ waits + are on **XEvent and XSemaphore**; "it's an XEvent" was the majority, not the + whole picture. ✅ **The 8 misses are the method validating itself** — a + polymorphic object's first word is always a vtable, so a value only counts if + `[rbx]` resolves to a `vtable for …` symbol; `rbx` simply wasn't restorable for + those frames. That also dissolves the "looks like stack" worry: `0x7ffc…` is the + shared mmap region (stacks *and* big allocations), and the **vtable check**, not + the address range, is the discriminator. 🔴 The follow-up run adding + `/proc/maps` classification + `x/8gx` **never booted** (`EMULATOR GONE at 0s`, + stale emulator/lock from the prior gdb session), so that check and the **frozen** + capture are still unrun. * ~~🚧 BLOCKER: t=210/240 unreachable in one turn~~ — **superseded, see above**; it rested on an untested assumption that a turn is one shell call. 595 s shell cap − ~220 s boot (a ~190 s title movie that cannot be tapped through) − ~25 s startup = **~350 s observation ≈ 193 game-seconds**. diff --git a/docs/re/mission-freeze-resume-spin.md b/docs/re/mission-freeze-resume-spin.md index 8208ec5..7ccdcfd 100644 --- a/docs/re/mission-freeze-resume-spin.md +++ b/docs/re/mission-freeze-resume-spin.md @@ -821,3 +821,48 @@ recorded rather than explained away. against the object pointer recovered from the shim frame above, or dump a few words at `$rbx` and see whether they look like an `XEvent` (a vtable, then a `KernelState*`, then handle/type fields) or like saved registers. + +## 🟡 2026-08-25 — refined: two object types, and the read is self-checking + +Re-extracting the same capture per thread rather than by grep changes the +picture, and qualifies the previous entry. + +Of **18** threads whose frame 3 is `XObject::Wait`: + +| `[rbx]` | count | resolves to | +|---|---|---| +| `0x5555562db8f0` | **8** | `vtable for xe::kernel::XEvent` + 16 | +| `0x5555562dbb40` | **2** | `vtable for xe::kernel::XSemaphore` + 16 | +| a pointer into the mmap region | **8** | not a vtable — see below | + +So the waits are on **`XEvent`** and **`XSemaphore`** objects, two distinct +kernel types, and the earlier "the object is an XEvent" was right for the +majority but not the whole picture. + +## ✅ The 8 non-vtable readings are the method checking itself + +For eight threads `[rbx]` is something like `0x7ffd08bcdeb0` — inside the +mmap region, **not** in the binary's `.data` where vtables live. A polymorphic +object's first word is always a vtable pointer, so those reads are simply **not +`this`**: `rbx` could not be restored for that thread's frame, and the unwind +handed back whatever the register happened to hold. + +That is a useful property rather than a flaw. **The read validates itself:** a +value is a genuine object pointer only if `[rbx]` lands in the binary's vtable +range and resolves to a `vtable for …` symbol. Anything else is discarded rather +than interpreted. Ten of eighteen resolve; the rest are honestly unknown. + +This also answers the previous entry's worry about the `0x7ffc…`/`0x7ffd…` +addresses looking like stack. They are the shared mmap region, which holds thread +stacks *and* large allocations, and the vtable check — not the address range — is +what separates an object from a stack slot. + +## 🔴 The follow-up run never booted + +The run that would have added `/proc//maps` classification and a wider +`x/8gx` dump died immediately: `EMULATOR GONE at 0s (before the window appeared)`, +`BOOT FAILED (skip_intro exit 4)`. Most likely a stale emulator or lockfile from +the preceding gdb session — the gdb wrapper leaves a differently-parented process +tree and the usual cleanup did not catch it. **So the map classification and the +multi-word object dump are still unrun**, and the frozen-state capture still has +not been taken. diff --git a/tools/re-capture/freeze_waitobj.sh b/tools/re-capture/freeze_waitobj.sh index 2075cfb..90652f8 100755 --- a/tools/re-capture/freeze_waitobj.sh +++ b/tools/re-capture/freeze_waitobj.sh @@ -50,12 +50,18 @@ for line in txt.splitlines(): print('thread %s' % cur) print('frame 3') print('info registers rbx') - print('x/1gx $rbx') + print('x/8gx $rbx') # vtable, KernelState*, fields -- or saved regs cur=None print(r'echo === END OBJ ===\n') print('continue') PY sleep 12 +# Is $rbx in a STACK mapping or an anonymous/heap one? 0x7ffc.. is the shared +# mmap region on x86-64: thread stacks live there, but so do large allocations, +# so the address range alone cannot tell them apart. /proc//maps can. +epid=$(pgrep -x xenia_canary | head -1) +[ -n "$epid" ] && cp "/proc/$epid/maps" /tmp/fz-maps.txt 2>/dev/null && \ + echo "--- saved /proc/$epid/maps ($(wc -l < /tmp/fz-maps.txt) mappings)" screenshot /tmp/fz-screen.png >/dev/null 2>&1 echo "--- screen at capture time:" python3 "$SD/screen_id.py" /tmp/fz-screen.png 2>/dev/null | head -2