214 lines
7.2 KiB
Bash
Executable file
214 lines
7.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# probeLocals.sh - end-to-end validation harness for pc2line.py --locals.
|
|
#
|
|
# Phase 3.2 slice 2 smoke probe:
|
|
# 1. Compile a probe C file (4 i16 locals + a sentinel store at $025000)
|
|
# with -O0 -g.
|
|
# 2. Link with crt0+libgcc, produce DWARF sidecar + map.
|
|
# 3. Load the .bin into MAME, run until the sentinel store fires
|
|
# (poll bank-2 $5000 for 0xC0DE).
|
|
# 4. Snapshot the S register at that point and the stack memory
|
|
# around it.
|
|
# 5. Call pc2line.py --locals with the captured S; verify each
|
|
# reported ADDR= holds the expected constant (0xABCD/0x1234/0x5678).
|
|
#
|
|
# Exit 0 if at least the first variable's resolved ADDR yields the
|
|
# expected value (matches the smoke gate the plan asks for: "asserts
|
|
# at least one of x/y/z resolves correctly"). Exit non-zero on any
|
|
# build/link failure or MAME read mismatch.
|
|
#
|
|
# Usage: probeLocals.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 "probeLocals: missing toolchain (clang/llvm-mc/link816)" >&2
|
|
exit 2
|
|
fi
|
|
if ! command -v mame >/dev/null 2>&1; then
|
|
echo "probeLocals: mame not on PATH; skipping" >&2
|
|
exit 77 # autotools-style "skip"
|
|
fi
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
CFILE="$WORK/loctest.c"
|
|
OFILE="$WORK/loctest.o"
|
|
OCRT0="$WORK/crt0.o"
|
|
OLIBGCC="$WORK/libgcc.o"
|
|
BIN="$WORK/loctest.bin"
|
|
MAP="$WORK/loctest.map"
|
|
DWARF="$WORK/loctest.dwarf"
|
|
LUA="$WORK/loctest.lua"
|
|
OUT="$WORK/loctest.out"
|
|
|
|
cat > "$CFILE" <<'EOF'
|
|
int main(void) {
|
|
int x = 0xABCD;
|
|
int y = 0x1234;
|
|
int z = 0x5678;
|
|
*(volatile unsigned short *)0x025000 = 0xC0DE;
|
|
while (1) { }
|
|
return 0;
|
|
}
|
|
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 "probeLocals: link produced empty .bin"; exit 1; }
|
|
[ -s "$DWARF" ] || { echo "probeLocals: link produced empty DWARF sidecar"; exit 1; }
|
|
|
|
MAIN_PC=$(awk '$2 == "main" { print $1; exit }' "$MAP")
|
|
[ -n "$MAIN_PC" ] || { echo "probeLocals: no 'main' symbol in map"; exit 1; }
|
|
|
|
# Lua: load .bin at $001000, kick PC, then poll bank-2 $5000 for the
|
|
# sentinel value 0xC0DE. When the sentinel fires, snapshot S + PC and
|
|
# the stack memory in the surrounding 64-byte window. Print everything
|
|
# on MAME- prefixed lines so the host script can grep them.
|
|
cat > "$LUA" <<EOF
|
|
local frame = 0
|
|
local loaded = false
|
|
local captured = false
|
|
emu.register_frame_done(function()
|
|
frame = frame + 1
|
|
if frame == 30 and not loaded then
|
|
local cpu = manager.machine.devices[":maincpu"]
|
|
local mem = cpu.spaces["program"]
|
|
local f = io.open("$BIN", "rb"); local data = f:read("*all"); f:close()
|
|
for i = 1, #data do
|
|
local addr = 0x001000 + i - 1
|
|
if not (addr >= 0x00C000 and addr < 0x00D000) then
|
|
mem:write_u8(addr, data:byte(i))
|
|
end
|
|
end
|
|
cpu.state["PC"].value = 0x1000
|
|
cpu.state["PB"].value = 0
|
|
cpu.state["DB"].value = 0
|
|
cpu.state["D"].value = 0
|
|
cpu.state["P"].value = 0x34
|
|
cpu.state["E"].value = 0
|
|
cpu.state["S"].value = 0x01FF
|
|
loaded = true
|
|
print("MAME-LOADED bytes=" .. #data)
|
|
end
|
|
if loaded and not captured and frame > 35 then
|
|
local cpu = manager.machine.devices[":maincpu"]
|
|
local mem = cpu.spaces["program"]
|
|
local sentinel = mem:read_u16(0x025000)
|
|
if sentinel == 0xC0DE then
|
|
local sp = cpu.state["S"].value
|
|
local pc = cpu.state["PC"].value
|
|
print(string.format("MAME-SENTINEL val=0x%04x", sentinel))
|
|
print(string.format("MAME-S val=0x%04x", sp))
|
|
print(string.format("MAME-PC val=0x%06x", pc))
|
|
-- Dump 64 bytes of stack around S (sp+0 .. sp+63) as u16
|
|
-- words. pc2line.py addresses we'll evaluate land in this
|
|
-- window (fbreg offsets are at most 24 for this probe).
|
|
for ofs = 0, 32 do
|
|
local addr = sp + ofs
|
|
local v = mem:read_u16(addr)
|
|
print(string.format("MAME-STACK addr=0x%06x val=0x%04x",
|
|
addr, v))
|
|
end
|
|
captured = true
|
|
manager.machine:exit()
|
|
end
|
|
end
|
|
if frame >= 240 then
|
|
print("MAME-TIMEOUT")
|
|
manager.machine:exit()
|
|
end
|
|
end)
|
|
EOF
|
|
|
|
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout 30 \
|
|
mame apple2gs -rompath "$ROOT/tools/mame/roms" \
|
|
-plugins -autoboot_script "$LUA" \
|
|
-video none -sound none -nothrottle -seconds_to_run 6 2>&1 \
|
|
| grep "^MAME-" > "$OUT" || true
|
|
|
|
if [ "$VERBOSE" -eq 1 ]; then
|
|
cat "$OUT" >&2
|
|
fi
|
|
|
|
if grep -q "^MAME-TIMEOUT$" "$OUT"; then
|
|
echo "probeLocals: timed out before sentinel fired" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SP_HEX=$(awk -F= '/^MAME-S val=/ {print $NF; exit}' "$OUT")
|
|
PC_HEX=$(awk -F= '/^MAME-PC val=/ {print $NF; exit}' "$OUT")
|
|
SENTINEL=$(awk -F= '/^MAME-SENTINEL val=/ {print $NF; exit}' "$OUT")
|
|
|
|
if [ -z "$SP_HEX" ] || [ -z "$PC_HEX" ]; then
|
|
echo "probeLocals: no S/PC snapshot captured" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$SENTINEL" != "0xc0de" ]; then
|
|
echo "probeLocals: sentinel mismatch ($SENTINEL)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "probeLocals: sentinel fired, S=$SP_HEX PC=$PC_HEX"
|
|
|
|
# Call pc2line.py --locals with the captured S. Expect three variables
|
|
# (the DW_TAG_variable DIEs for x, y, z) each with an ADDR= field.
|
|
LOCALS=$(python3 "$HERE/pc2line.py" --sidecar "$DWARF" --map "$MAP" \
|
|
--locals --sp "$SP_HEX" "$PC_HEX")
|
|
echo "$LOCALS"
|
|
|
|
# For each ADDR= line, read the stored value from the snapshot and
|
|
# compare against the set of expected constants. At least one must
|
|
# match (the slice gate).
|
|
EXPECTED=(abcd 1234 5678)
|
|
hits=0
|
|
while IFS= read -r line; do
|
|
addr_hex=$(echo "$line" | sed -nE 's/.* ADDR=0x([0-9a-fA-F]+).*/\1/p')
|
|
if [ -z "$addr_hex" ]; then
|
|
continue
|
|
fi
|
|
addr_norm=$(printf "0x%06x" "0x$addr_hex" 2>/dev/null || echo "")
|
|
# Find that addr in MAME-STACK lines.
|
|
snap=$(awk -F= -v want="$addr_norm" '
|
|
/^MAME-STACK addr=/ {
|
|
split($2, parts, " ")
|
|
a = parts[1]
|
|
v = $NF
|
|
if (a == want) { print v; exit }
|
|
}' "$OUT")
|
|
if [ -z "$snap" ]; then
|
|
continue
|
|
fi
|
|
snap_lc=$(echo "$snap" | tr 'A-Z' 'a-z' | sed 's/^0x//')
|
|
for exp in "${EXPECTED[@]}"; do
|
|
if [ "$snap_lc" = "$exp" ]; then
|
|
echo "probeLocals: HIT addr=$addr_norm value=0x$snap_lc"
|
|
hits=$((hits + 1))
|
|
break
|
|
fi
|
|
done
|
|
done <<< "$LOCALS"
|
|
|
|
if [ "$hits" -lt 1 ]; then
|
|
echo "probeLocals: FAIL: no variable's --locals ADDR resolved to a known constant" >&2
|
|
exit 1
|
|
fi
|
|
echo "probeLocals: OK ($hits/3 variables resolved correctly)"
|
|
exit 0
|