This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/tools/re-capture/trigger_watch.sh
Sylpheed RE agent 33013a9b84 re: the trigger-queue appender found -- sub_8226EAB8, count at inner+8
The watchpoint plus Canary's source settle it. At the write, the guest context
(rsi, per x64_emitter.cc:881) holds 0x8226EAE0, inside sub_8226EAB8. That
function is a generic list-node insert: it reads the count at 8(r30), guards
against 0x3FFFFFFF overflow, does addi r11,r11,1 / stw r11,8(r30), then links the
node. It has 16 callers, so it is a shared container helper.

That explains why two static searches missed it. The trigger container at
phase+272 EMBEDS an inner list object at +12 -- which is why the push does
'addi r31, r30, 12' -- and the inner object keeps its count at its own +8. So
272 + 12 + 8 = 292 = the watched word, and the instruction is stw r11, 8(r30)
with r30 = phase+284. Searching for 'stw rN, 20(rM)' could never have found it.

Also resolves the earlier open item on +12: it is the embedded list object, not
a list head pointer, which is why it read 0x000A0009 instead of an address.

Method note kept: the static hunt assumed the field's offset in the OUTER object
would appear in the writing instruction. A watchpoint is indifferent to the
addressing form, which is why it was the right tool after two failed offset
searches.
2026-08-25 19:34:15 +00:00

78 lines
3.5 KiB
Bash
Executable File

#!/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/<pid>/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 \
</dev/null >/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'
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"