65816-llvm-mos/scripts/probeReplSmoke.sh
Scott Duensing 09f7405362 Updates
2026-06-03 16:08:42 -05:00

127 lines
4 KiB
Bash
Executable file

#!/usr/bin/env bash
# probeReplSmoke.sh - non-interactive smoke check for mameDebug.py
# --repl mode. Pipes a canned script (`break main`, `run`, `where`,
# `quit`) into the REPL and asserts that:
# 1. The REPL parses each command without error
# 2. A breakpoint resolves through the link816 map
# 3. MAME launches with the bp installed and surfaces a BP-HIT line
# 4. `where` resolves the captured PC to a source line via DWARF
#
# Exit 0 on full pass. Exit 77 (autotools "skip") if MAME / toolchain
# missing. Exit 1 on any unexpected REPL output or missing capture.
#
# Usage: probeReplSmoke.sh [--verbose]
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/.." && pwd)"
VERBOSE=0
if [ "${1:-}" = "--verbose" ]; then
VERBOSE=1
fi
CLANG="$ROOT/tools/llvm-mos-build/bin/clang"
LLVMMC="$ROOT/tools/llvm-mos-build/bin/llvm-mc"
LINK="$ROOT/tools/link816"
if [ ! -x "$CLANG" ] || [ ! -x "$LLVMMC" ] || [ ! -x "$LINK" ]; then
echo "probeReplSmoke: missing toolchain (clang/llvm-mc/link816)" >&2
exit 77
fi
if ! command -v mame >/dev/null 2>&1; then
echo "probeReplSmoke: mame not on PATH; skipping" >&2
exit 77
fi
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
CFILE="$WORK/repltest.c"
OFILE="$WORK/repltest.o"
OCRT0="$WORK/crt0.o"
OLIBGCC="$WORK/libgcc.o"
BIN="$WORK/repltest.bin"
MAP="$WORK/repltest.map"
DWARF="$WORK/repltest.dwarf"
OUT="$WORK/repl.out"
cat > "$CFILE" <<'EOF'
int gAnswer = 42;
int add(int a, int b) {
int c = a + b;
return c;
}
int main(void) {
int r = add(3, 4);
gAnswer = r;
while (1) { }
return r;
}
EOF
"$CLANG" --target=w65816 -O0 -g -ffunction-sections \
-c "$CFILE" -o "$OFILE" 2>/dev/null
"$LLVMMC" -arch=w65816 -filetype=obj \
"$ROOT/runtime/src/crt0.s" -o "$OCRT0" 2>/dev/null
"$LLVMMC" -arch=w65816 -filetype=obj \
"$ROOT/runtime/src/libgcc.s" -o "$OLIBGCC" 2>/dev/null
"$LINK" -o "$BIN" --text-base 0x1000 \
--map "$MAP" --debug-out "$DWARF" \
"$OCRT0" "$OFILE" "$OLIBGCC" >/dev/null 2>&1 || true
[ -s "$BIN" ] || { echo "probeReplSmoke: empty .bin"; exit 1; }
[ -s "$DWARF" ] || { echo "probeReplSmoke: empty DWARF sidecar"; exit 1; }
[ -s "$MAP" ] || { echo "probeReplSmoke: empty map"; exit 1; }
# Pipe the canned REPL script.
printf 'break main\nrun\nwhere\nquit\n' \
| timeout 60 python3 "$HERE/mameDebug.py" --repl \
--bin "$BIN" --map "$MAP" --dwarf "$DWARF" \
--seconds 4 > "$OUT" 2>&1 || {
echo "probeReplSmoke: mameDebug.py --repl failed" >&2
cat "$OUT" >&2
exit 1
}
if [ "$VERBOSE" -eq 1 ]; then
cat "$OUT" >&2
fi
# Required output lines:
# "(dbg) break main" - command echo
# " bp #1 at 0x...... (main)" - bp set ack
# "(dbg) run" - command echo
# " PC=0x...... ... FUNC=main ..." - where output after run
# "(dbg) where" - command echo
# " PC=0x...... ... FUNC=main ..." - where output (manual)
# "(dbg) quit" - command echo
if ! grep -q "bp #1 at 0x" "$OUT"; then
echo "probeReplSmoke: missing 'bp #1 at 0x...' breakpoint ack" >&2
cat "$OUT" >&2
exit 1
fi
if ! grep -q "FUNC=main" "$OUT"; then
echo "probeReplSmoke: missing FUNC=main in 'where' output" >&2
cat "$OUT" >&2
exit 1
fi
# The `where` command (run AFTER the `run` command) must produce
# output too — verify by counting occurrences of "PC=0x" prefix lines.
PC_HITS=$(grep -c "^ PC=0x" "$OUT" || true)
if [ "$PC_HITS" -lt 2 ]; then
echo "probeReplSmoke: expected >= 2 PC=0x lines (run + where), got $PC_HITS" >&2
cat "$OUT" >&2
exit 1
fi
# Bonus: verify the captured PC equals the map entry for `main`.
MAIN_PC=$(awk '$2 == "main" { print $1; exit }' "$MAP")
[ -n "$MAIN_PC" ] || { echo "probeReplSmoke: no 'main' symbol in map"; exit 1; }
MAIN_PC_LC=$(echo "$MAIN_PC" | tr 'A-Z' 'a-z')
if ! grep -qi "PC=$MAIN_PC_LC " "$OUT"; then
echo "probeReplSmoke: captured PC does not match map[main]=$MAIN_PC" >&2
cat "$OUT" >&2
exit 1
fi
echo "probeReplSmoke: OK (bp resolved, BP-HIT captured, where decoded)"
exit 0