re: the trigger-count watchpoint fires, but the writer is JIT guest code

trigger_watch.sh + host_addr.py translate the guest VA to a host address
(0xBE14DEA4 -> host 0x1BE14DEA4) and watch it. It fires: Thread 50 'Main
XThread', old 0, new 16777216 -- which is big-endian 1 read little-endian, so
the count going 0 -> 1, independently confirming the field. The write happens on
the guest's own main thread, not an emulator worker.

But the writer cannot be named from the host stack: the faulting PC is
0xa0c65f23 with no symbol, executing 'mov 0x110(%rsi),%rbx', i.e. Xenia's
JIT-compiled guest code, and the frames above it are not host-unwindable. So the
watchpoint answers when and which thread, not which guest function.

Recorded as a ceiling of the method rather than retried blindly. The way past it
is that the JIT holds the guest context in a register (%rsi here), so the guest
PC is recoverable from the context block -- which needs Xenia's context layout
from the xenia-rs sources on this box, a separate tractable piece of work.
This commit is contained in:
Sylpheed RE agent
2026-08-25 18:52:35 +00:00
parent 28a4b1ead1
commit da2d02db77

View File

@@ -368,3 +368,36 @@ watchpoint on the count word** during a live mission. The address is known at
runtime (`ScriptPhase + 272 + 20`), the count demonstrably changes within ~2 runtime (`ScriptPhase + 272 + 20`), the count demonstrably changes within ~2
minutes of flight, and the watchpoint reports the writing instruction directly minutes of flight, and the watchpoint reports the writing instruction directly
instead of inferring it from static shape. instead of inferring it from static shape.
## 🟡 The watchpoint fired — the writer is JIT-compiled GUEST code, not host code
`tools/re-capture/trigger_watch.sh` + `host_addr.py` translate the guest VA into
a host address and set a gdb watchpoint on it:
```
mission 0xBC7A2A20 phase 0xBE14DD80 va 0xBE14DEA4 off 0x11E14DEA4 -> host 0x1BE14DEA4
Hardware watchpoint 1: *(unsigned int*)0x1BE14DEA4
Thread 50 "Main XThread" hit it: Old value = 0 New value = 16777216
```
**Two things confirmed.** `16777216` is `0x01000000` — big-endian `1` read
little-endian, so this is exactly the count going **0 → 1**, independently
confirming that `[ScriptPhase+272+20]` is the field. And the write happens on
the **guest's own Main XThread**, not on an emulator worker.
🔴 **But the writer cannot be named from the host stack.** The faulting PC is
`0xa0c65f23`, with no symbol, and the instruction is
`mov 0x110(%rsi),%rbx` — this is **Xenia's JIT-compiled guest code**. The
backtrace above it is garbage (`0x45e0000000`, `0x100000000`), because JIT frames
are not host-unwindable.
So the host watchpoint answers *when* and *which guest thread*, but **not which
guest function** — the thing I actually wanted. The method has a ceiling here,
and it is worth recording rather than re-attempting the same way.
**What would get past it:** the JIT keeps the guest context in a register
(`%rsi` here, given `mov 0x110(%rsi),%rbx`), so the **guest PC is recoverable
from the context block** at the moment of the write. Reading the right offset out
of `$rsi` would name the guest instruction. That needs Xenia's context layout —
which is in the xenia-rs sources on this box — and is a separate, tractable
piece of work rather than another blind run.