re: ISL opcodes decoded; the branch base is PER PHASE and isl.py was wrong
All 25 opcodes now have meanings. Ops 2/4/6/8 are integer compound assignment (+= -= *= /=) and 3/5/7/9 the float versions; 10 and 11 are integer and float compare writing three condition bits; 13-18 are je/jne/jl/jle/jg/jge; 21-24 are push.i/push.f/pop.i/pop.f over deques at phase+44 and phase+64. The shared-handler question is answered: the dispatcher leaves the opcode in r4 and the shared thunks never overwrite it, so those helpers take an extra opcode argument and index a secondary table (0x82271448, 0x8227152C). CORRECTION to my own tool and note: the branch/jump base is [phase+232], which the phase initialiser sets to 0x24 + the phase's entry from the mission-level stream -- 0xE4 / 0x14AA8 / 0x24B4C for Stage 02's three phases, not the file's 0x24. Measured on phase 1: base 0xE4 puts 525 of 525 branch targets on an instruction boundary; base 0x24 manages 188. isl.py had been using 0x24 for every phase, so its jump targets were wrong throughout. Fixed via isl.phase_bases(). That also settles two things mission-script-ssb.md left open: offsets ARE code-base-relative, and 0x1883's operand IS a code pointer -- the earlier worry that some 'land on IEEE floats' was an artefact of adding the wrong base.
This commit is contained in:
52
tools/re-capture/host_addr.py
Executable file
52
tools/re-capture/host_addr.py
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Translate the trigger-count guest VA into a HOST address gdb can watch.
|
||||
|
||||
gdb debugs the host emulator process, so a guest VA is meaningless to it. Canary
|
||||
backs guest memory with one shared-memory file, so the chain is:
|
||||
|
||||
guest VA -> file offset gmem.va_to_off
|
||||
file offset -> host address the shm mapping in /proc/<pid>/maps,
|
||||
host = map_start - map_file_offset + off
|
||||
|
||||
Prints the host address on stdout, and the working on stderr.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import gmem
|
||||
import isl
|
||||
import squadron_state as S
|
||||
|
||||
ssb = isl.load('/tmp/Stage02.ssb')
|
||||
path = gmem.mem_path()
|
||||
size = os.path.getsize(path)
|
||||
with open(path, 'rb', buffering=0) as f:
|
||||
m, _fb = S.find_mission(f, size, ssb)
|
||||
if m is None:
|
||||
print('NOTFOUND'); sys.exit(1)
|
||||
ph = S.u32(f, m + 4)
|
||||
|
||||
va = ph + 272 + 20
|
||||
off = gmem.va_to_off(va)
|
||||
shm = os.path.basename(path)
|
||||
pids = [p for p in os.listdir('/proc') if p.isdigit()
|
||||
and os.path.exists('/proc/%s/comm' % p)
|
||||
and open('/proc/%s/comm' % p).read().strip() == 'xenia_canary']
|
||||
if not pids:
|
||||
print('NOPID'); sys.exit(1)
|
||||
host = None
|
||||
for line in open('/proc/%s/maps' % pids[0]):
|
||||
if shm not in line:
|
||||
continue
|
||||
rng, _perm, mo = line.split()[0], line.split()[1], line.split()[2]
|
||||
a, b = (int(x, 16) for x in rng.split('-'))
|
||||
mo = int(mo, 16)
|
||||
if mo <= off < mo + (b - a):
|
||||
host = a - mo + off
|
||||
break
|
||||
if host is None:
|
||||
print('NOMAP'); sys.exit(1)
|
||||
print('mission 0x%08X phase 0x%08X va 0x%08X off 0x%X -> host 0x%X'
|
||||
% (m, ph, va, off, host), file=sys.stderr)
|
||||
print('0x%X' % host)
|
||||
@@ -17,9 +17,15 @@ kind selectors passed to the operand resolvers as `r4`.
|
||||
(integer assignment; resolvers 0x82271D40 / 0x82272030)
|
||||
op 1 same shape with fmr/stfd (float assignment; 0x82271F10/0x82272120)
|
||||
op 12 JUMP: r31 = [phase+232] + word@+4
|
||||
-> jump operands are **relative to the code base**, which is the .ssb
|
||||
header's code offset (0x24). That settles the "file- or
|
||||
code-base-relative" question for this opcode at least.
|
||||
-> jump operands are relative to `[phase+232]`, which is **PER PHASE**,
|
||||
not the file's 0x24. The phase initialiser sub_82270DF8 writes it
|
||||
as 0x24 + the phase's entry from the mission-level stream, whose
|
||||
three `0x1883` records carry 0xC0 / 0x14A84 / 0x24B28 for Stage 02
|
||||
-> bases 0xE4 / 0x14AA8 / 0x24B4C.
|
||||
MEASURED: with 0xE4, 525 of 525 phase-1 branch targets land on an
|
||||
instruction boundary; with 0x24, only 188. Using 0x24 for every
|
||||
phase -- which this tool did -- gives wrong targets in phases 2
|
||||
and 3, and mostly-wrong ones in phase 1.
|
||||
op 19 CALL BUILT-IN: `sub_82272220` reads the id from **word@+4**
|
||||
(`lwz r11,4(r28); cmplwi 0x92` -> 147 built-ins, table 0x8227226C)
|
||||
and word@+8 into [phase+200].
|
||||
@@ -200,7 +206,7 @@ def dis(b, off, count=40, code_base=0x24, args=True, sym2=None):
|
||||
extra += '(' + ', '.join(parts) + ')'
|
||||
staged = {}
|
||||
elif op == 12 and words:
|
||||
extra = ' -> code+0x%X (file 0x%X)' % (words[0], code_base + words[0])
|
||||
extra = ' -> code+0x%X (file 0x%X)' % (words[0], code_base + words[0])
|
||||
out.append('%06X: %08X %-6s len=%-3d k=%02x,%02x %s%s' % (
|
||||
off, w, name, ln, k1, k0,
|
||||
' '.join('%08X' % x for x in words), extra))
|
||||
@@ -252,6 +258,16 @@ def resync(b, target, back=400):
|
||||
return None
|
||||
|
||||
|
||||
def phase_bases(b):
|
||||
"""The per-phase code bases, from the mission-level stream's 0x1883 records."""
|
||||
out = []
|
||||
off = struct.unpack_from('>I', b, CODE_BASE_FIELD)[0]
|
||||
for o in range(0x24, 0x100, 4):
|
||||
if struct.unpack_from('>I', b, o)[0] == 0x1883:
|
||||
out.append(off + struct.unpack_from('>I', b, o + 4)[0])
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
b = load(sys.argv[1])
|
||||
if sys.argv[2:3] == ['--calls']:
|
||||
|
||||
51
tools/re-capture/trigger_watch.sh
Executable file
51
tools/re-capture/trigger_watch.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
{ echo 'echo === WHO WROTE IT ===\n'; echo 'bt 8'; echo 'x/3i $pc'
|
||||
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"
|
||||
Reference in New Issue
Block a user