diff --git a/docs/re/script-runtime-probe.md b/docs/re/script-runtime-probe.md index c14520d4..4ada3a3f 100644 --- a/docs/re/script-runtime-probe.md +++ b/docs/re/script-runtime-probe.md @@ -407,3 +407,47 @@ could not be the `1500` the pilot logs. **Wrong.** This run reads `0x44BB8000` = **1500.0f** at the same offset. It is the same field; the value simply differs between runs (craft or loadout). The "different field or scale" note is retracted. + +## ✅ The JIT keeps the guest context in `%rsi` — from Canary's own source + +The watchpoint's ceiling was that the writer is JIT code with no host symbols. +The way past it is in the emulator's source, not the disassembly: + +``` +src/xenia/cpu/backend/x64/x64_emitter.cc:881 + Xbyak::Reg64 X64Emitter::GetContextReg() const { return rsi; } + Xbyak::Reg64 X64Emitter::GetMembaseReg() const { return rdi; } +``` + +So at any JIT instruction, **`%rsi` is the `PPCContext*`** — which is also why +the faulting instruction read `0x110(%rsi)`: it was loading a guest register. +The whole guest register file is available at the moment of the write, and a +guest code address (`0x82xxxxxx`) picked out of it resolves against +`sylpheed.db` to name the calling function. + +`trigger_watch.sh` now dumps `x/128wx $rsi` at the hit rather than a useless host +backtrace. ⚠️ Reading the LR *by offset* would need `PPCContext`'s layout; +scanning the dump for `0x82…`-range words avoids parsing a 900-line struct and +is what the script does. + +## 🔴 The re-run could not locate the mission — unexplained + +The run reached flight (`readyroom at 18s`, `IN FLIGHT at 40s`), the pilot bound, +the guest was **animating**, and yet `find_mission` returned `NOTFOUND`. +Narrowing it: + +* the `.ssb` **header is not in guest memory** — 0 hits for its 20-byte + signature, where previous runs hit it immediately; +* **`ADN110` is not in guest memory either** — so the script's symbol table is + not resident; +* but **`Stage02.ssb` (the manifest string) *is*** present, at `0xBDA6C50B`. + +So guest memory is readable and the manifest is loaded, while the script itself +is not — in a mission that is demonstrably flying. That contradicts four earlier +runs where the header was found within seconds of flight. + +**I do not have an explanation**, and I am not going to invent one. Candidates +worth separating next time: the script is loaded later than I assumed and the +earlier runs sampled later; the probe raced a load; or this run entered flight +by a different path. The cheap discriminator is to poll for the header from the +moment flight starts and record *when* it appears, rather than sampling once. diff --git a/tools/re-capture/trigger_watch.sh b/tools/re-capture/trigger_watch.sh index 04c41fb5..cba80abd 100755 --- a/tools/re-capture/trigger_watch.sh +++ b/tools/re-capture/trigger_watch.sh @@ -44,7 +44,16 @@ kill -INT "$pid"; sleepfor 3 echo "--- watching ${WATCH_S}s" sleepfor "$WATCH_S" kill -INT "$pid"; sleepfor 3 -{ echo 'echo === WHO WROTE IT ===\n'; echo 'bt 8'; echo 'x/3i $pc' +# The host stack is useless here: the write happens in JIT-compiled guest code, +# which is unsymbolised and not host-unwindable. But Xenia's x64 backend keeps +# the guest context in %rsi (x64_emitter.cc: `GetContextReg() { return rsi; }`), +# so the guest register file is right there. Dump it and pick out the words that +# look like guest code addresses (0x82xxxxxx) -- the guest LR is among them, and +# that names the calling guest function. +{ echo 'echo === WHO WROTE IT ===\n'; echo 'x/3i $pc' + echo 'info registers rsi rdi' + echo 'echo === GUEST CONTEXT ===\n' + echo 'x/128wx $rsi' echo 'echo === END ===\n'; echo 'delete'; echo 'continue'; } >> "$CMD" sleepfor 10 tail -c +$((before + 1)) "$OUT" | grep -vE '^\s*$' | tail -60