diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 543921a..1e6907b 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -708,6 +708,17 @@ search cannot find a *schedule*. archaeology; (2) a `RelWithDebInfo` build via `build-canary` — makes this and all future freeze questions easy, at a full compile and a binary differing from the one every other measurement used. +* ✅ **(2026-08-25) Route 1 is viable and cheap — `this` is in `%rbx`.** + Static analysis, no run spent: `XObject::Wait`'s prologue does `mov %rdi,%rbx` + at `8fbc9c`, so `this` sits in a **callee-saved** register rather than a stack + slot. And the binary carries **`.eh_frame` with 127 231 FDEs** (Release builds + keep it for C++ exceptions), including one covering `8fbc90..8fbde2` that + tracks `rbx` explicitly. ⇒ from a frozen thread, **`frame 3` + `info registers + rbx` gives the `XObject*` being waited on**, and `x/gx $rbx` → vtable symbol + (in symtab) gives its concrete type — **no DWARF and no rebuild needed**. + **Revises the previous entry**, which called route 1 "per-frame archaeology" + and route 2 (RelWithDebInfo rebuild) the way to make it easy. **Next: execute on + a frozen run** — two gdb commands per thread. * ~~🚧 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 a142471..d512972 100644 --- a/docs/re/mission-freeze-resume-spin.md +++ b/docs/re/mission-freeze-resume-spin.md @@ -728,3 +728,45 @@ Route 1 is cheaper and keeps the oracle identical; route 2 is what makes this an every future freeze question easy. Neither is attempted here — recorded so the next pass picks with the costs visible instead of discovering the missing DWARF mid-run. + +## ✅ 2026-08-25 — route 1 is viable: `this` lives in `%rbx`, and `.eh_frame` can restore it + +The prologue-guided route turns out to need no archaeology at all, and the +groundwork is pure static analysis — no run spent finding it out. + +**`XObject::Wait` keeps `this` in a callee-saved register**, not a stack slot: + +``` +8fbc90 : + 8fbc90 push %rbp / push %r15 / push %r14 / push %rbx / push %rax + 8fbc97 mov %r8,%r15 + 8fbc9a mov %ecx,%ebp + 8fbc9c mov %rdi,%rbx <-- `this` +``` + +**And the binary has full unwind information**, which is what makes that +recoverable from a deep frame. `.eh_frame` is present with **127 231 FDEs** — it +survives in Release builds because C++ exceptions need it — and the FDE covering +`Wait` tracks `rbx` explicitly: + +``` +FDE pc=00000000008fbc90..00000000008fbde2 + LOC CFA rbx rbp r14 r15 ra + 8fbc90 rsp+8 u u u u c-8 + 8fbc91 rsp+16 u u u u c-8 +``` + +So from a frozen thread parked in `pthread_cond_wait`, `frame 3` (the +`XObject::Wait` frame) plus `info registers rbx` yields the **`XObject*` being +waited on** — gdb reconstructs callee-saved registers during the unwind from +`.eh_frame` alone. Reading `x/gx $rbx` then gives the vtable pointer, and vtable +symbols *are* in the symtab (`_ZTVN2xe6kernel6XEventE` and friends), so the +object's concrete type is identifiable without any debug info. + +**Revises the previous entry**, which listed route 1 as "per-frame archaeology" +and route 2 (a `RelWithDebInfo` rebuild) as the way to make this easy. Route 1 is +neither expensive nor fragile: two gdb commands per thread, no rebuild, and the +oracle stays byte-identical to the binary every other measurement used. + +Not yet executed on a frozen run — that is the next step, and it is now a small +one.