re: WITHDRAW the infinite-loop conclusion -- gdb was the confound

The previous section concluded the guest spins forever in sub_82457780.
Measured without gdb, that is false.  Every observation behind it came from a
gdb-hosted run, and gdb intercepts every SIGSEGV -- which Xenia uses for guest
memory watches -- so it perturbs precisely what was being measured.

Same measurement, no debugger, per-thread /proc sampling parsed after the last
')':

    Main XThread          0 ms CPU per 3 s, 0 faults, state S, futex_do_wait
    GPU Commands         10 ms
    WSI swapchain queue 130 ms
    llvmpipe-0..9      ~1030 ms each per 6 s

Process at 265% of a core, essentially all in the software rasterizer.  The
guest thread is BLOCKED on a futex using zero CPU -- not spinning, and not in
sub_82457780.

Under gdb that same thread appeared as the top CPU consumer (890 ms / 4 s) and
appeared to be in guest JIT code, then in xe::ExceptionHandlerCallback.  That
was signal interception plus gdb re-stopping it between samples (state t,
wchan ptrace_stop).

Withdrawn: "an infinite copy loop in sub_82457780 is the freeze", and "Main
XThread is the top CPU consumer, so it is a spin not a block".  The opposite
holds.

Survives: the emit_source_annotations technique is sound and did prove the PC
was 0x824578A0 while the guest ran under gdb, so the guest does pass through
that copy loop.  The bne-exact-equality reading remains an accurate description
of the disassembly and a plausible hazard -- just not this freeze.

What the freeze actually is: guest blocked on a host futex at zero CPU, software
rasterizer saturated for six minutes without producing a frame, nothing
faulting, screen unchanged.  That is the guest waiting on a GPU operation that
never completes, with lavapipe spinning -- a HOST RENDERING problem, which is
also why every game-side hypothesis this session was refuted in turn.

Method rule earned the hard way and recorded on the page: do not diagnose a
performance or liveness question under a debugger.  Use it to read state at a
known stopping point, then re-measure timing and CPU detached.
This commit is contained in:
Sylpheed RE agent
2026-08-26 18:02:54 +00:00
parent abed25fd67
commit ffdccba655

View File

@@ -879,3 +879,70 @@ moved. It is not evidence either way, and it briefly looked like a refutation.
`r7` is loaded from `0(r30)` and `r31` is the container's end pointer, with a
fresh buffer from `0x824F7240` in `r3` — so the next step is to read those three
values at the moment of the freeze, from *host* registers rather than the context.
---
# 🔴🔴 The infinite-loop conclusion is WRONG — gdb was the confound
**2026-08-26.** The section above concludes the guest spins forever in
`sub_82457780`. **Measured without gdb, that is false.** Every observation
supporting it came from a gdb-hosted run, and gdb intercepts every SIGSEGV —
which Xenia uses for guest memory watches — so it perturbs exactly the thing
being measured.
## The same measurement, gdb removed
Six seconds of per-thread sampling (`/proc` parsed after the last `)`), on a run
that reached the freeze with no debugger attached:
| thread | CPU / 3 s | minor faults | state | `wchan` |
|---|---|---|---|---|
| **`Main XThread`** | **0 ms** | 0 | **S** | **`futex_do_wait`** |
| `GPU Commands` | 10 ms | 0 | S | `futex_do_wait` |
| `WSI swapchain queue` | 130 ms | 0 | S | `futex_do_wait` |
| `llvmpipe-0 … llvmpipe-9` | **~1030 ms each / 6 s** | 0 | — | — |
Process total **265 % of one core**, essentially all of it in the **software
rasterizer**.
**The guest thread is blocked on a futex and consuming zero CPU.** It is not
spinning, and it is not in `sub_82457780` doing anything.
## What was actually being measured under gdb
Under gdb the same thread appeared as the *top* CPU consumer (890 ms / 4 s) and
appeared to sit in guest JIT code, later in `xe::ExceptionHandlerCallback`. That
is the debugger's signal interception showing through, plus gdb repeatedly
re-stopping the thread (`state: t`, `wchan: ptrace_stop`) between samples.
🟡 **What survives** is narrower but real: the `emit_source_annotations`
technique is sound, and it did prove that *when the guest thread was running
under gdb* its PC was `0x824578A0`. That says the guest passes through that copy
loop; it does **not** show the loop never terminates. The `bne`-exact-equality
reading of the loop is still an accurate description of the disassembly and
still a plausible hazard — but it is not this freeze.
🔴 **Withdrawn**: "an infinite copy loop in `sub_82457780` is the freeze", and
with it "Main XThread is the top CPU consumer, so it is a spin, not a block".
The opposite is true.
## ✅ What the freeze actually looks like
* the **guest is blocked**, waiting on a host futex, using no CPU;
* the **software rasterizer is saturated** — nine `llvmpipe` threads at ~1 CPU-
second per 6 seconds each — and has been for **six minutes** in an earlier run
without ever producing a new frame;
* nothing faults, nothing allocates meaningfully, the screen never changes.
That is the signature of the guest waiting on a GPU operation that never
completes, with `lavapipe` spinning on it — a **host rendering** problem, not a
game-logic or guest-memory one. Which also explains why every game-side
hypothesis this session was refuted in turn.
❔ Next: find what the guest is waiting on and what `llvmpipe` is rendering.
`GPU Commands` idle at 10 ms/3 s while the rasterizer is pinned suggests the
command processor has already handed off and is itself waiting.
⚠️ **Method rule earned the hard way: do not diagnose a performance or liveness
question under a debugger.** Use it to read state at a known-good stopping point,
then re-measure timing and CPU with it detached.