#!/usr/bin/env bash # Find what WRITES the trigger-queue count, by watching the word rather than # hunting the instruction statically. # # The static hunt failed (isl-builtins.md): the only writes to `+20` in the # container's code are block initialisations, yet the count demonstrably moves # 0 -> 1 -> 2 during a mission. A watchpoint names the writer directly. # # The address translation is the fiddly part, so it is explicit: # guest VA -> file offset via gmem.va_to_off # file offset -> HOST address via the /dev/shm/xenia_memory_* mapping in # /proc//maps: host = map_start - map_off + off # gdb debugs the HOST process, so a guest VA cannot be watched directly. set -u export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages export XENIA_BIN=/sylph-home/re/bin/gdb-wrap/xenia_canary SD="$(cd "$(dirname "$0")" && pwd)"; export SD CMD=/tmp/gdb-cmd; OUT=/tmp/gdb-out.log WATCH_S="${1:-240}" sleepfor(){ python3 -c "import time,sys; time.sleep(float(sys.argv[1]))" "$1"; } "$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; } CFG=/tmp/nav-tw.json for t in 1 2 3; do python3 "$SD/pad.py" set "rt=1" >/dev/null 2>&1; sleepfor 3 python3 "$SD/pad.py" clear >/dev/null 2>&1 if python3 "$SD/entities2.py" self 0x130 "$CFG" >/dev/null 2>&1; then SYLPH_HUNT=1 SYLPH_KEEPOUT=1400 nohup python3 "$SD/pilot.py" "$CFG" 900 \ /tmp/tw-pilot.log 2>&1 & echo "--- pilot flying"; break fi done # One run reached "IN FLIGHT" with the script NOT resident and the mission # unlocatable -- and it was frozen on a black screen soon after # (script-runtime-probe.md). A healthy run has the script resident at the first # sample, so a short poll separates "not loaded yet" from "this run is broken" # without waiting on a doomed one. ok=0 for _ in 1 2 3 4 5 6; do if python3 -c " import sys,os; sys.path.insert(0,'$SD') import squadron_state as S, isl, gmem ssb=isl.load('/tmp/Stage02.ssb'); p=gmem.mem_path() with open(p,'rb',buffering=0) as f: sys.exit(0 if S._find(f, os.path.getsize(p), ssb[:20]) else 1)"; then ok=1; break; fi echo "--- script not resident yet"; sleepfor 10 done [ $ok = 1 ] || { echo "SCRIPT NEVER BECAME RESIDENT -- run is broken, not slow"; exit 3; } HOSTADDR=$(python3 "$SD/host_addr.py" 2>/tmp/tw-addr.err) echo "--- translation: $(cat /tmp/tw-addr.err)" echo "--- host addr: $HOSTADDR" case "$HOSTADDR" in 0x*) ;; *) echo "could not translate"; exit 2;; esac pid=$(pgrep -x gdb | head -1); before=$(wc -c < "$OUT") kill -INT "$pid"; sleepfor 3 { echo 'echo === WATCH SET ===\n' echo "watch *(unsigned int*)$HOSTADDR" echo "continue"; } >> "$CMD" echo "--- watching ${WATCH_S}s" sleepfor "$WATCH_S" kill -INT "$pid"; sleepfor 3 # 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' # PPCContext layout (ppc_context.h): cr0..cr7 = 0x00..0x1F, then r[32] at 0x20, # ctr at 0x120, lr at 0x128, msr at 0x130. The base is pinned independently: # the faulting instruction read 0x110(%rsi), which is r[30] under this layout, # and the count write is `stw r11, 8(r30)` -- so 0x110 IS r30. echo 'echo --- r28..r31, ctr, lr, msr\n' echo 'x/8gx $rsi+0x100' echo 'echo --- guest LR\n' echo 'p/x *(unsigned long*)($rsi+0x128)' echo 'p/x *(unsigned long*)($rsi+0x120)' 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 echo "TRIGGER WATCH DONE"