re: locate the stuck thread -- a grow-and-copy in sub_82457780
Ran the freeze under the corpus's gdb wrapper (ptrace_scope is 1, so a debugger
must launch rather than attach, and its handle lines are needed because Xenia
uses SIGSEGV for guest memory watches).
One thread of 80 is in guest code: thread 50, Main XThread, at rip a05be939 in
JIT output. All others are in a futex or clock_nanosleep.
Xenia's x64 backend keeps PPCContext in rsi, with r[32] at +0x20. The guest
GPRs there give r8 = a3ac0000 (the 64 MB buffer from the doubling sequence),
r12 = a3ac0a18 inside it, and r13 = 82457864 -- guest code, sub_82457780.
That address sits in a grow-and-copy: size = count*8 clamped to 0x1FFFFFFF, a
call to 0x824F7240, then a loop copying halfwords eight bytes at a time. The
host instruction it is stopped on is that copy's store:
mov %r12w,(%rdi,%rax,1) rdi = membase, rax = 0x701d0000
Sampled three times seconds apart with continue in between: rip, r13, r11 and
r31 identical every time. No progress.
The loop's exit test is beq -- equality, not >= -- so an inconsistent start/end
pair never terminates it. Recorded as amber: that is a reading of the
disassembly, not a demonstration.
Two of my own readings corrected:
* "the guest spins at ~400% CPU" was ps's CUMULATIVE AVERAGE since process
start, not an instantaneous rate. Per-thread sampling puts Main XThread
nowhere near the top.
* a SIGSEGV fault storm fitted the constant rip nicely and is refuted: 1500
minor faults in 5 s, zero major.
Caveat kept on the page: these are gdb-hosted observations and gdb intercepts
every SIGSEGV, so absolute timings are not the ungoverned ones. The freeze is
not a gdb artefact -- it reproduces in every non-gdb run.
This commit is contained in:
@@ -673,3 +673,76 @@ two frames rather than one.
|
||||
excluded: the heap failure, the leak, allocation rounding, `MmQueryStatistics`,
|
||||
a heap-size knob, the exception cvar, a kernel-object wait, a build regression,
|
||||
the navigation route, the savegame, and slow shader compilation.
|
||||
|
||||
---
|
||||
|
||||
# ✅ The stuck thread is located: a grow-and-copy in `sub_82457780`
|
||||
|
||||
**2026-08-26.** Ran the freeze under the corpus's own gdb wrapper
|
||||
(`/sylph-home/re/bin/gdb-wrap/xenia_canary` — `ptrace_scope` is 1, so a debugger
|
||||
must *launch* the process, and the wrapper's `handle` lines are required because
|
||||
Xenia uses SIGSEGV for guest memory watches).
|
||||
|
||||
**One thread of 80 is in guest code.** Every other thread sits in a futex or
|
||||
`clock_nanosleep`; thread 50, `Main XThread`, is at `rip = 0xa05be939`, inside
|
||||
JIT-generated code rather than libc.
|
||||
|
||||
## Where, in guest terms
|
||||
|
||||
Xenia's x64 backend keeps the `PPCContext` in **`rsi`**
|
||||
(`X64Emitter::GetContextReg() { return rsi; }`), with `r[32]` at `+0x20`. Reading
|
||||
the guest GPRs there:
|
||||
|
||||
| reg | value | |
|
||||
|---|---|---|
|
||||
| `r8` | `0xa3ac0000` | the 64 MB buffer from the doubling sequence |
|
||||
| `r12` | `0xa3ac0a18` | inside that buffer |
|
||||
| **`r13`** | **`0x82457864`** | **guest code — `sub_82457780`** |
|
||||
|
||||
And `0x82457864` sits in a **grow-and-copy**: a size computed as `count × 8`
|
||||
(`slwi r3, r27, 3`) clamped against `0x1FFFFFFF`, a call to `0x824F7240`, then a
|
||||
loop that copies halfwords (`lhz`/`sth`) eight bytes at a time.
|
||||
|
||||
The host instruction it is stopped on is exactly that copy's store:
|
||||
|
||||
=> 0xa05be939: mov %r12w,(%rdi,%rax,1) rdi = 0x100000000 (membase)
|
||||
rax = 0x701d0000 (guest address)
|
||||
|
||||
a **16-bit store** — the JIT's rendering of the loop's `sth`.
|
||||
|
||||
## And it is not making progress
|
||||
|
||||
Sampled three times, seconds apart, with `continue` in between:
|
||||
|
||||
rip=a05be939 r13=82457864 r11=0 r31=701cf898
|
||||
rip=a05be939 r13=82457864 r11=0 r31=701cf898
|
||||
rip=a05be939 r13=82457864 r11=0 r31=701cf898
|
||||
|
||||
Identical every time. 🟡 Worth noting the loop's exit test is `beq` — it
|
||||
terminates only when `r11` becomes **exactly equal** to `r31`, not `>=`. A start
|
||||
or end pointer that is inconsistent (or not 8-aligned relative to the other)
|
||||
would never satisfy it. That is a *reading of the disassembly*, not a
|
||||
demonstration, and `r11 = 0` against `r31 = 0x701cf898` is at least consistent
|
||||
with it.
|
||||
|
||||
## 🔴 Two of my own readings corrected
|
||||
|
||||
* **"The guest spins at ~400 % CPU"** — wrong. That was `ps`'s **cumulative
|
||||
average since process start**, not an instantaneous rate. Per-thread sampling
|
||||
puts `Main XThread` nowhere near the top; the busiest are another guest thread
|
||||
at 24 % and the `llvmpipe` software rasterizers at ~7 % each.
|
||||
* **A SIGSEGV fault storm** (Xenia's memory watches retrying forever) fitted the
|
||||
constant `rip` nicely and is **refuted**: 1 500 minor faults in 5 s, ~300/s,
|
||||
and zero major faults.
|
||||
|
||||
## ⚠️ Caveat on the method
|
||||
|
||||
These observations are under gdb, which intercepts every SIGSEGV even with
|
||||
`nostop noprint pass`, so absolute timings here are not the ungoverned ones. The
|
||||
freeze itself is not a gdb artefact — it reproduces in every non-gdb run — but
|
||||
"how slow" should be re-measured without it.
|
||||
|
||||
❔ Next: confirm or refute the `beq` reading by watching `r11`/`r31` across a
|
||||
longer window, and identify what `0x824F7240` returns — if that allocator hands
|
||||
back a buffer whose end is not `start + count×8`, the loop's equality test is the
|
||||
freeze.
|
||||
|
||||
Reference in New Issue
Block a user