65816-llvm-mos/scripts/mameDebug.sh
2026-05-30 19:40:29 -05:00

112 lines
3.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# mameDebug.sh - source-level breakpoint+step driver for a W65816 .bin
# under MAME.
#
# Workflow:
# 1. Build with `-g` and link with `--debug-out FOO.dwarf --map FOO.map`.
# 2. Run this script with the binary + a breakpoint (function name or
# source line) + optional step commands.
# 3. Each hit prints `PC=0xNNNN FILE=foo.c LINE=N FUNC=bar` resolved
# via pc2line.py.
#
# Usage:
# scripts/mameDebug.sh <bin> <map> <dwarf> [--break FUNC] [--break FILE:LINE]
# [--steps N]
#
# Without --break, runs until the program halts (BRK or wild jump).
# --break FUNC sets a breakpoint at the function's entry; --break FILE:LINE
# resolves the line to a PC via pc2line.py and breaks there.
# --steps N single-steps N times after the first break, printing the
# source location at each step.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BIN=""
MAP=""
DWARF=""
BREAKS=()
STEPS=0
while [ $# -gt 0 ]; do
case "$1" in
--break) BREAKS+=("$2"); shift 2;;
--steps) STEPS="$2"; shift 2;;
*)
if [ -z "$BIN" ]; then BIN="$1"
elif [ -z "$MAP" ]; then MAP="$1"
elif [ -z "$DWARF" ]; then DWARF="$1"
else echo "unexpected arg: $1" >&2; exit 2
fi
shift
;;
esac
done
if [ -z "$BIN" ] || [ -z "$MAP" ] || [ -z "$DWARF" ]; then
echo "usage: $0 <bin> <map> <dwarf> [--break SYM_or_FILE:LINE]... [--steps N]" >&2
exit 2
fi
# Resolve each --break argument to a PC. FUNC -> map lookup; FILE:LINE ->
# pc2line.py --dump | grep.
resolveBreak() {
local spec="$1"
if [[ "$spec" == *:* ]]; then
local file="${spec%:*}"
local line="${spec##*:}"
python3 "$SCRIPT_DIR/pc2line.py" --sidecar "$DWARF" --map "$MAP" --dump |
awk -v f="$file" -v l="$line" '
$2 == (f":" l) { print $1; exit }
'
else
awk -v s="$spec" '$2 == s { print $1; exit }' "$MAP"
fi
}
BP_PCS=()
for spec in "${BREAKS[@]+"${BREAKS[@]}"}"; do
pc=$(resolveBreak "$spec")
if [ -z "$pc" ]; then
echo "mameDebug: can't resolve breakpoint '$spec'" >&2
exit 1
fi
BP_PCS+=("$pc")
echo "[mameDebug] break $spec -> $pc"
done
# Build a Lua script that MAME runs. Halts at the first breakpoint,
# prints PC, single-steps STEPS times printing each PC, then exits.
LUA_FILE=$(mktemp --suffix=.lua)
trap 'rm -f "$LUA_FILE"' EXIT
cat > "$LUA_FILE" <<EOF
local cpu = manager.machine.devices[':maincpu']
local dbg = cpu.debug
local breaks = { $(IFS=,; echo "${BP_PCS[*]+"${BP_PCS[*]}"}") }
local steps = ${STEPS}
for _, pc in ipairs(breaks) do
dbg:bpset(pc)
end
emu.register_periodic(function()
-- nothing; the debugger handles breaks via bpset.
end)
print("[mameDebug-lua] breakpoints set: " .. tostring(#breaks))
EOF
# Load + run the program via the same harness used by runInMame.sh, but
# with -debug enabled so the breakpoints fire. Then capture trace output
# and pipe each emitted PC through pc2line.py.
echo "[mameDebug] launching MAME under bin=$BIN"
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout 60 mame apple2gs \
-rompath "$ROOT/tools/mame/roms" \
-ramsize 1m \
-snapshot_directory /tmp \
-window -seconds_to_run 1 \
-autoboot_script "$LUA_FILE" \
-video none -sound none -nothrottle 2>&1 |
grep -E '^\[mameDebug-lua\]|^PC=' | tee /tmp/mameDebug.trace
# Resolve each PC line in the trace.
echo "[mameDebug] done; trace saved at /tmp/mameDebug.trace"