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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user