diff --git a/docs/re/mission-freeze-heap-exhaustion.md b/docs/re/mission-freeze-heap-exhaustion.md index 432f782..0b16197 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.