re: prove the guest PC -- an infinite copy loop in sub_82457780
The previous section withdrew this localisation for resting on a stale PPCContext. It is now re-established by a sound method. Xenia has a CPU cvar, emit_source_annotations, that makes MarkSourceOffset emit "nop; nop; mov eax,<guest_address>; nop; nop" at EVERY guest instruction boundary -- 90 90 B8 xx xx xx xx 90 90 in the code bytes. So the guest PC reads straight out of memory around rip, with no context and no offset guessing. Dumping backwards from rip gives a clean run of them -- 0x82457874, 78, 7C, 80, 84 -- and the annotation immediately before rip is 0x824578A0. That is `sth r6, 0(r9)` inside sub_82457780: the exact instruction predicted from the disassembly last time, now reached independently. The loop advances r11 by 8 and terminates on `bne cr6` -- exact inequality. If r31 - r7 is not a positive multiple of 8, the comparison never becomes equal and the loop never exits. And the thread is genuinely running: over 4 s, Main XThread consumed 890 ms of CPU, the top of any thread, with the process at ~238%. A spin, not a block. One bad metric recorded: I tried to show the loop marching through memory by watching the last non-zero byte above 0x70200000. It never moved -- but that statistic saturates in an already-written region, so it could not have moved. Not evidence either way, though it briefly looked like a refutation. Open and now narrow: what makes r31 - r7 non-congruent to 0 mod 8. r7 comes from 0(r30), r31 is the container end, and r3 holds a fresh buffer from 0x824F7240 -- read those three from HOST registers at the freeze.
This commit is contained in:
@@ -809,3 +809,73 @@ it just is not established that the guest is *in* it.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
# ✅✅ The guest PC, proven — an infinite copy loop in `sub_82457780`
|
||||
|
||||
**2026-08-26.** The previous section withdrew this localisation because it rested
|
||||
on a stale `PPCContext`. It is now **re-established by a sound method**, and the
|
||||
withdrawal was right to demand one.
|
||||
|
||||
## The method: Xenia's own JIT annotations
|
||||
|
||||
`emit_source_annotations` (a `CPU` cvar, off by default) makes `MarkSourceOffset`
|
||||
emit, at **every guest instruction boundary**:
|
||||
|
||||
nop; nop; mov eax, <guest_address>; nop; nop -> 90 90 B8 xx xx xx xx 90 90
|
||||
|
||||
So the guest PC is readable straight out of the code bytes around `rip` — no
|
||||
context, no offsets, no inference. Dumping backwards from `rip` gives a clean run
|
||||
of them:
|
||||
|
||||
... B8 74 78 45 82 ... -> guest 0x82457874
|
||||
... B8 78 78 45 82 ... -> guest 0x82457878
|
||||
... B8 7C 78 45 82 ... -> guest 0x8245787C
|
||||
... B8 80 78 45 82 ... -> guest 0x82457880
|
||||
... B8 84 78 45 82 ... -> guest 0x82457884
|
||||
|
||||
and the annotation **immediately before `rip`** is `0x824578A0`.
|
||||
|
||||
**Guest PC = `0x824578A0` = `sth r6, 0(r9)`**, inside `sub_82457780` — the exact
|
||||
instruction predicted from the disassembly, reached independently.
|
||||
|
||||
## The loop, and why it cannot end
|
||||
|
||||
82457868 mr r11, r7 ; r11 = start
|
||||
8245786c cmplw cr6, r11, r31
|
||||
82457870 beq cr6, 0x824578B4 ; skip entirely if start == end
|
||||
8245787c mr r10, r11 ; <== back-edge target
|
||||
82457884 addi r11, r11, 8 ; advance by 8
|
||||
8245788c cmplw cr6, r11, r31
|
||||
82457890 lhz r6, 0(r10) ; copy four halfwords
|
||||
824578a0 sth r6, 0(r9) ; <== the guest PC
|
||||
824578b0 bne cr6, 0x8245787C ; continue while r11 != r31
|
||||
|
||||
The termination test is **`bne` — exact inequality**, on a pointer advancing in
|
||||
steps of **8**. If `r31 − r7` is not a positive multiple of 8, the comparison
|
||||
never becomes equal and **the loop never exits**.
|
||||
|
||||
## ✅ And the thread really is running
|
||||
|
||||
Measured with a *safe* `/proc` parse (see the trap below): over 4 seconds,
|
||||
|
||||
| thread | CPU |
|
||||
|---|---|
|
||||
| **`Main XThread`** | **890 ms** |
|
||||
| `xenia_canary` | 860 ms |
|
||||
| `XThreadD55FF6C0` | 690 ms |
|
||||
| `llvmpipe-4` | 500 ms |
|
||||
|
||||
`Main XThread` is the **top consumer**, and the process burns ~238 % CPU overall.
|
||||
So this is a spin, not a block — which is what an unterminatable loop looks like.
|
||||
|
||||
⚠️ **A bad metric on the way**: I tried to show the loop "marching" through memory
|
||||
by watching the *last non-zero byte* above `0x70200000`. It never moved — but that
|
||||
statistic saturates in a region that is already written, so it could not have
|
||||
moved. It is not evidence either way, and it briefly looked like a refutation.
|
||||
|
||||
❔ Still open, and now narrow: **what makes `r31 − r7` non-congruent to 0 mod 8.**
|
||||
`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.
|
||||
|
||||
Reference in New Issue
Block a user