From 8bc471224ae4546222875d43080433a8cb7a25b3 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 18:02:54 +0000 Subject: [PATCH] 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. --- docs/re/mission-freeze-heap-exhaustion.md | 67 +++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/re/mission-freeze-heap-exhaustion.md b/docs/re/mission-freeze-heap-exhaustion.md index 432f7825..0b161971 100644 --- a/docs/re/mission-freeze-heap-exhaustion.md +++ b/docs/re/mission-freeze-heap-exhaustion.md @@ -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.