169 lines
5.5 KiB
Bash
Executable file
169 lines
5.5 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; }
|
|
|
|
# Phase 1: existing single-frame `bp main` smoke (kept to ensure the
|
|
# baseline path still works). Then Phase 2: `bp add` + `--start-at
|
|
# main` to exercise the multi-frame `bt` walker.
|
|
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
|
|
|
|
# Phase 2: multi-frame `bt` test. Breakpoint at `add` with --start-at
|
|
# main: the JSL frame from main->add is live at the snapshot, so `bt`
|
|
# should walk back up at least one parent (>= 2 total frames). This
|
|
# regression-checks both the .debug_frame_w65816 sidecar emit (link816)
|
|
# and the walker in mameDebug.py.
|
|
OUT2="$WORK/repl2.out"
|
|
printf 'break add\nrun\nbt\nquit\n' \
|
|
| timeout 60 python3 "$HERE/mameDebug.py" --repl \
|
|
--bin "$BIN" --map "$MAP" --dwarf "$DWARF" \
|
|
--start-at main --seconds 4 > "$OUT2" 2>&1 || {
|
|
echo "probeReplSmoke: mameDebug.py --repl (bt) failed" >&2
|
|
cat "$OUT2" >&2
|
|
exit 1
|
|
}
|
|
|
|
if [ "$VERBOSE" -eq 1 ]; then
|
|
cat "$OUT2" >&2
|
|
fi
|
|
|
|
# Count frame lines (` #N PC=0x...`) in the bt output. Need >= 2 to
|
|
# prove the .debug_frame_w65816 sidecar drove a real parent-frame walk.
|
|
FRAME_LINES=$(grep -cE "^ #[0-9]+ PC=0x" "$OUT2" || true)
|
|
if [ "$FRAME_LINES" -lt 2 ]; then
|
|
echo "probeReplSmoke: bt produced $FRAME_LINES frame lines (need >= 2)" >&2
|
|
cat "$OUT2" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Verify frame #0 is `add` and frame #1 is `main`.
|
|
if ! grep -q "^ #0 PC=0x.* FUNC=add " "$OUT2"; then
|
|
echo "probeReplSmoke: bt frame #0 is not 'add'" >&2
|
|
cat "$OUT2" >&2
|
|
exit 1
|
|
fi
|
|
if ! grep -q "^ #1 PC=0x.* FUNC=main " "$OUT2"; then
|
|
echo "probeReplSmoke: bt frame #1 is not 'main'" >&2
|
|
cat "$OUT2" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "probeReplSmoke: OK (single-frame where + multi-frame bt OK)"
|
|
exit 0
|