re: route 1 is viable — XObject::Wait keeps this in %rbx and .eh_frame restores it

Pure static analysis, no run spent. XObject::Wait's prologue does mov %rdi,%rbx
at 8fbc9c, so the this pointer lives in a callee-saved register rather than a
stack slot. And the binary carries full unwind information: .eh_frame with
127231 FDEs, which survives in Release builds because C++ exceptions need it,
including an FDE covering 8fbc90 to 8fbde2 that tracks rbx explicitly.

Together those mean that from a thread frozen deep in pthread_cond_wait, moving
to the XObject::Wait frame and reading rbx yields the XObject being waited on --
gdb reconstructs callee-saved registers during the unwind from .eh_frame alone,
with no DWARF involved. Reading the first quadword at that pointer gives the
vtable, and vtable symbols are in the symtab, so the object's concrete type is
identifiable too.

This revises the previous entry, which listed route 1 as per-frame archaeology
that must be redone whenever the binary changes, and route 2, a RelWithDebInfo
rebuild, as what would make the question 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 in this corpus was taken
against.

Not yet executed on a frozen run, which is the next step and is now a small one.
This commit is contained in:
Sylpheed RE agent
2026-08-25 07:42:12 +00:00
parent 0120677722
commit 4ef15a1dbe
2 changed files with 53 additions and 0 deletions

View File

@@ -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**.