re: withdraw the 'unrestorable rbx' claim — the misses are WaitMultiple

The 8 threads whose [rbx] did not resolve to a vtable were never in
XObject::Wait. The backtrace grep matched WaitMultiple as a substring, and
there %rbx is the XObject** array (mov %rsi,%rbx) with the count in %ebp, so
[rbx] is objects[0] -- an object pointer, needing a second deref -- not a
vtable. The unwind restored rbx correctly for all 18.

freeze_waitobj.sh now takes the function and frame index from the backtrace and
applies the matching read, and captures twice in one run (healthy and after the
~270s black-screen) so the comparison is within-run. waitobj_report.py tabulates
both and diffs them, discarding any value info symbol cannot resolve.
This commit is contained in:
Sylpheed RE agent
2026-08-25 08:20:21 +00:00
parent 2830d14f87
commit 3f23ceb8e7
3 changed files with 198 additions and 51 deletions

View File

@@ -866,3 +866,50 @@ the preceding gdb session — the gdb wrapper leaves a differently-parented proc
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.
## ✅ 2026-08-25 — WITHDRAWN: the 8 "unrestorable rbx" readings are a second function
The entry above is wrong where it matters, and the disassembly says so plainly.
`rbx` was restored perfectly for all 18 threads. **Eight of them are not in
`XObject::Wait` at all — they are in `XObject::WaitMultiple`**, and `%rbx` does
not mean the same thing in the two functions. The grep that produced the table
matched `XObject::Wait` as a *substring*, so `WaitMultiple` frames were pooled in
with `Wait` frames and read with the wrong rule.
Reading the two prologues settles it (baseline `c1b57f93b`, no run needed):
```
8fbc90 XObject::Wait(this, ...) 8fbfc0 XObject::WaitMultiple(count, objects, ...)
push %rbp/%r15/%r14/%rbx/%rax push %rbp/%r15/%r14/%r13/%r12/%rbx
mov %r8,%r15 sub $0x218,%rsp
mov %ecx,%ebp mov %rsi,%rbx <-- rbx = XObject** objects
mov %rdi,%rbx <-- rbx = this mov %edi,%ebp <-- ebp = count
```
and the loop just below `WaitMultiple`'s prologue confirms what `rbx` points at
by using it:
```
8fbff0: mov (%rbx,%r15,8),%rdi ; rdi = objects[i]
8fbff4: mov (%rdi),%rax ; rax = objects[i]->vtable
8fbff7: call *0x20(%rax) ; a virtual call on it
```
So for a `WaitMultiple` frame `[rbx]` is `objects[0]` — **an `XObject*`, not a
vtable** — which is exactly the shape of the eight "misses": pointers into the
mmap region where the kernel objects live. They needed **two** derefs, not one.
The correct read is `[[rbx + 8i]]` for `i < ebp`.
Predicted values from the healthy capture, to be checked against the next run:
`objects[0]` was `0x7ffd08bcdeb0` for threads 79 and 80 and `0x7ffcfc0911f0`
for thread 78 — two threads sharing one object is itself consistent with a
group of workers waiting on the same handle.
**What survives from the previous entry:** the two confirmed object types
(`XEvent` ×8, `XSemaphore` ×2) and the self-validating read — a value counts only
if it resolves to a `vtable for …` symbol. **What is withdrawn:** "`rbx` could
not be restored for eight threads", and with it the claim that the reading is
only 10/18 reliable. The unwind was never the problem; the parser was.
`freeze_waitobj.sh` now parses the function name and frame index out of the
backtrace instead of assuming frame 3, and applies the matching rule to each.