From a35812bd269280281d8b2981f3e109c87128da66 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Tue, 25 Aug 2026 18:52:35 +0000 Subject: [PATCH] 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. --- docs/re/structures/isl-builtins.md | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/re/structures/isl-builtins.md b/docs/re/structures/isl-builtins.md index e4dc030d..98ac008e 100644 --- a/docs/re/structures/isl-builtins.md +++ b/docs/re/structures/isl-builtins.md @@ -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 minutes of flight, and the watchpoint reports the writing instruction directly 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.