From ee2f737d78e175229f121d555030fe19b175b283 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 17:38:49 +0000 Subject: [PATCH] re: audit the gdb session -- withdraw the sub_82457780 localisation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Went back to verify the instrumentation before building on it. Some does not hold. Verified: the trailing comments in ppc_context.h are not struct offsets (the struct opens with eight cr unions and fpscr), but rsi+0x20 for r[32] is nonetheless correct -- confirmed empirically because the f[32] doubles begin at +0x140 and 32*8 back from there lands on +0x20. Withdrawn: the context is STALE mid-function. The same dump shows r1 = 0, and a guest stack pointer cannot be zero inside a function -- Xenia's JIT keeps live guest registers in host registers and spills only at certain points. So r13 = 0x82457864 does not show where the guest is executing, and identifying sub_82457780 as the stuck function is withdrawn as unproven. The description of what that code does (grow-and-copy, beq exit) is still accurate; it is just not established that the guest is in it. Three readings retracted for broken instrumentation: * "rip identical across samples ⇒ no progress" -- gdb stops the process between samples, and the JIT lays code out differently per run (a05be939 vs a05bc839 for the same instruction). * "zero minor faults on the stuck thread" and "utime=0" -- both parsed /proc//task//stat with awk positional fields, but comm is parenthesised AND contains spaces ("Main XThread (F"), so every index after it was wrong. Printing state=XThread should have been the giveaway. * the SIGSEGV fault-storm refutation rested on the same parse; downgraded to unproven, though the process-wide ~300/s still argues against a tight loop. Survives: the freeze reproduces without gdb; with the process stopped exactly one thread is in userspace JIT code while a wchan census puts the rest in futex_do_wait (55) or hrtimer_nanosleep (11); the instruction is a 16-bit store through the membase to guest 0x701d0000, a stack-region address, first of a four-store group; and that memory was unchanged over seconds of running time. Next step is a host->guest code mapping from Xenia's own code cache rather than inference from a stale context. --- docs/re/mission-freeze-heap-exhaustion.md | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/re/mission-freeze-heap-exhaustion.md b/docs/re/mission-freeze-heap-exhaustion.md index de0c012..eee66eb 100644 --- a/docs/re/mission-freeze-heap-exhaustion.md +++ b/docs/re/mission-freeze-heap-exhaustion.md @@ -746,3 +746,66 @@ freeze itself is not a gdb artefact — it reproduces in every non-gdb run — b 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. + +--- + +# ⚠️ Auditing the gdb session — what survives and what does not + +**2026-08-26.** Went back to verify the previous section's instrumentation +before building on it. Some of it does not hold. + +## ✅ The context layout is now verified, not assumed + +The trailing comments in `ppc_context.h` (`uint64_t r[32]; // 0x20`) are **not +struct offsets** — the struct opens with eight `cr` unions and `fpscr`. So the +previous section's `rsi+0x20` was a guess that happened to be right. Confirmed +empirically from a dump: the `f[32]` doubles (`0x3ff0000000000000` = 1.0 and +friends) begin at **`+0x140`**, and 32 × 8 bytes back from there puts +**`r[32]` at `+0x20 … +0x11F`**. Good. + +## 🔴 But the context is **stale mid-function**, so its GPRs are not evidence + +In the same dump, **`r1 = 0`**. The guest stack pointer cannot be zero inside a +function. Xenia's JIT keeps live guest registers in *host* registers and spills +only at certain points, so a mid-function `PPCContext` read is a snapshot of +whatever was last written back. + +**Therefore `r13 = 0x82457864` does not show where the guest is executing**, and +the previous section's identification of `sub_82457780` is **withdrawn as +unproven**. The value may be a leftover from an earlier spill. What the code at +that address does (a grow-and-copy with a `beq` exit) is accurately described — +it just is not established that the guest is *in* it. + +## 🔴 Three readings retracted for broken instrumentation + +* **"`rip` identical across samples ⇒ no progress".** gdb stops the process + between samples, and the JIT lays code out differently per run — `rip` was + `a05be939` in one run and `a05bc839` in the next for the *same* instruction. + Constant `rip` across samples inside one gdb session does not establish a hang. +* **"Zero minor faults on the stuck thread"** and **"utime=0"** — both parsed + `/proc//task//stat` with `awk` positional fields. The `comm` field is + parenthesised **and contains spaces** (`Main XThread (F`), so every field index + after it was wrong. The state I printed as `state=XThread` should have been the + giveaway. Both numbers are void. +* The earlier **SIGSEGV fault-storm refutation** rested on the same per-thread + parse and is downgraded to unproven — though the process-wide rate (~300/s) + still argues against a tight fault loop. + +## ✅ What does survive + +* The freeze reproduces **without gdb**, in every run, so it is not a debugger + artefact. +* With the process stopped, exactly **one** thread is in userspace JIT code; a + `wchan` census puts every other thread in `futex_do_wait` (55) or + `hrtimer_nanosleep` (11). The active thread is `Main XThread`. +* Its host instruction is a **16-bit store through the membase**, + `mov %r12w,(%rdi,%rax,1)` with `rdi = 0x100000000` and `rax = 0x701d0000` — a + guest **stack-region** address — and it is the first of a four-store group at + `+0`, `+2`, `+4`, `+6`. +* Guest memory at `0x701d0000` and the surrounding 4 KB were **unchanged** over + several seconds of running time. + +❔ So the thread is identified and the instruction is identified; *which guest +function* it belongs to is not. The way to get that honestly is a host→guest code +mapping from Xenia itself rather than inference from a stale context — its code +cache knows the answer, and dumping that mapping is the next step.