465 lines
17 KiB
Bash
Executable File
465 lines
17 KiB
Bash
Executable File
#!/bin/sh
|
|
# M3 dis34 round-trip.
|
|
#
|
|
# scripts/m3/dis-roundtrip.sh
|
|
#
|
|
# Parses build/m3/m3.coff, disassembles its linked .text at 018A0000 with the
|
|
# VT1200 tree's dis34, and checks that GSPA encoded the compiler output:
|
|
#
|
|
# _XOpenDisplay/_XCreateSimpleWindow/_XMapWindow/_XSync JAUC <ROM bit addr>
|
|
# (XMapWindow is 3FD64F60, MapWindow opcode 8; 3FD645B0 is XRaiseWindow)
|
|
# _xhello: 1 push -> CALLA _XOpenDisplay, 9 pushes -> CALLA
|
|
# _XCreateSimpleWindow (arguments 1,0,2,200,400,100,100,root,
|
|
# display in that order, display pushed last), 2 -> _XMapWindow,
|
|
# 2 -> _XSync, each followed by the ABI's SETF 16/32 pair
|
|
# branch code: CMP, JALT, JAGT, JAEQ, JAUC and LBB… labels (never .LBB)
|
|
# .data: 30 3A 3A 30 00 ("0::0")
|
|
# old-STK restore offsets: *SP((pushes+1)*32),A14
|
|
#
|
|
# Overridable: M3_COFF M3_ASM DIS34
|
|
set -eu
|
|
|
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
|
WORK=${M3_WORK:-$ROOT/build/m3}
|
|
COFF=${M3_COFF:-$WORK/m3.coff}
|
|
ASM=${M3_ASM:-$WORK/m3.asm}
|
|
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
|
|
|
|
fail() {
|
|
echo "M3 dis34 round-trip: FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
[ -f "$COFF" ] || fail "linked COFF not found: $COFF (run scripts/m3/run.sh)"
|
|
[ -f "$ASM" ] || fail "linked assembly not found: $ASM (run scripts/m3/run.sh)"
|
|
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
|
|
|
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-dis34.XXXXXX")
|
|
FAIL_LOG=${M3_DIS_FAIL_LOG:-${TMPDIR:-/tmp}/m3-dis34-failure.txt}
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
|
|
# Read little-endian fields out of the COFF. od keeps this dependency-free.
|
|
read_u8s() {
|
|
od -An -v -tu1 -j "$1" -N "$2" "$COFF"
|
|
}
|
|
u16() {
|
|
read_u8s "$1" 2 | awk '{ print $1 + 256 * $2 }'
|
|
}
|
|
u32() {
|
|
read_u8s "$1" 4 | awk '{ print $1 + 256 * $2 + 65536 * $3 + 16777216 * $4 }'
|
|
}
|
|
|
|
echo "== parse the COFF section table ($COFF)"
|
|
nscns=$(u16 2)
|
|
opthdr=$(u16 16)
|
|
[ "$nscns" -gt 0 ] 2>/dev/null || fail "COFF has no sections (nscns='$nscns')"
|
|
[ "$opthdr" -gt 0 ] 2>/dev/null || fail "COFF has no optional header (opthdr='$opthdr')"
|
|
|
|
TEXT_ADDR=
|
|
TEXT_BITS=
|
|
TEXT_PTR=
|
|
DATA_ADDR=
|
|
DATA_BITS=
|
|
DATA_PTR=
|
|
sh_base=$((20 + opthdr))
|
|
i=0
|
|
while [ "$i" -lt "$nscns" ]; do
|
|
o=$((sh_base + i * 40))
|
|
name=$(dd if="$COFF" bs=1 skip="$o" count=8 2>/dev/null | tr -d '\000')
|
|
if [ "$name" = ".text" ]; then
|
|
TEXT_ADDR=$(u32 $((o + 8)))
|
|
TEXT_BITS=$(u32 $((o + 16)))
|
|
TEXT_PTR=$(u32 $((o + 20)))
|
|
elif [ "$name" = ".data" ]; then
|
|
DATA_ADDR=$(u32 $((o + 8)))
|
|
DATA_BITS=$(u32 $((o + 16)))
|
|
DATA_PTR=$(u32 $((o + 20)))
|
|
fi
|
|
i=$((i + 1))
|
|
done
|
|
[ -n "$TEXT_ADDR" ] || fail "COFF has no .text section"
|
|
[ "$TEXT_BITS" -gt 0 ] 2>/dev/null || fail ".text is empty"
|
|
[ $((TEXT_BITS % 8)) -eq 0 ] || fail ".text size $TEXT_BITS bits is not whole bytes"
|
|
[ -n "$DATA_ADDR" ] || fail "COFF has no .data section"
|
|
[ "$DATA_BITS" -gt 0 ] 2>/dev/null || fail ".data is empty"
|
|
[ $((DATA_BITS % 8)) -eq 0 ] || fail ".data size $DATA_BITS bits is not whole bytes"
|
|
|
|
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
|
|
ENTRY=$(u32 $((20 + 16)))
|
|
ENTRY_HEX=$(printf '%08X' "$ENTRY")
|
|
TEXT_BYTES=$((TEXT_BITS / 8))
|
|
DATA_BYTES=$((DATA_BITS / 8))
|
|
|
|
[ "$TEXT_HEX" = "018A0000" ] \
|
|
|| fail ".text is linked at $TEXT_HEX, want 018A0000"
|
|
[ "$ENTRY_HEX" = "018A0000" ] \
|
|
|| fail "COFF entry point is $ENTRY_HEX, want 018A0000"
|
|
[ "$TEXT_PTR" -gt 0 ] || fail ".text has no raw data (scnptr=0)"
|
|
[ "$DATA_PTR" -gt 0 ] || fail ".data has no raw data (scnptr=0)"
|
|
|
|
echo " .text at $TEXT_HEX: $TEXT_BITS bits ($TEXT_BYTES bytes), scnptr=$TEXT_PTR"
|
|
echo " .data at $(printf '%08X' "$DATA_ADDR"): $DATA_BITS bits ($DATA_BYTES bytes), scnptr=$DATA_PTR"
|
|
echo " entry point $ENTRY_HEX"
|
|
|
|
dd if="$COFF" of="$TMP/text.bin" bs=1 skip="$TEXT_PTR" count="$TEXT_BYTES" 2>/dev/null
|
|
[ "$(wc -c < "$TMP/text.bin")" -eq "$TEXT_BYTES" ] \
|
|
|| fail "extracted $(wc -c < "$TMP/text.bin") .text bytes, want $TEXT_BYTES"
|
|
dd if="$COFF" of="$TMP/data.bin" bs=1 skip="$DATA_PTR" count="$DATA_BYTES" 2>/dev/null
|
|
[ "$(wc -c < "$TMP/data.bin")" -eq "$DATA_BYTES" ] \
|
|
|| fail "extracted $(wc -c < "$TMP/data.bin") .data bytes, want $DATA_BYTES"
|
|
|
|
echo "== resolve the linked symbols from the COFF symbol table"
|
|
symptr=$(u32 8)
|
|
nsyms=$(u32 12)
|
|
[ "$symptr" -gt 0 ] 2>/dev/null || fail "COFF has no symbol table"
|
|
[ "$nsyms" -gt 0 ] 2>/dev/null || fail "COFF symbol table is empty"
|
|
|
|
sym_addr() {
|
|
_want=$1
|
|
_strtab=$((symptr + nsyms * 18))
|
|
_i=0
|
|
while [ "$_i" -lt "$nsyms" ]; do
|
|
_o=$((symptr + _i * 18))
|
|
# COFF inline names are 8 bytes; longer names are 0,0,0,0 + a
|
|
# string-table offset (the table starts with its own 4-byte size).
|
|
if [ "$(u32 "$_o")" -eq 0 ]; then
|
|
_soff=$(u32 $((_o + 4)))
|
|
_n=$(dd if="$COFF" bs=1 skip=$((_strtab + _soff)) count=64 2>/dev/null |
|
|
tr '\000' '\n' | head -1)
|
|
else
|
|
_n=$(dd if="$COFF" bs=1 skip="$_o" count=8 2>/dev/null | tr -d '\000')
|
|
fi
|
|
if [ "$_n" = "$_want" ]; then
|
|
u32 $((_o + 8))
|
|
return 0
|
|
fi
|
|
_aux=$(od -An -v -tu1 -j $((_o + 17)) -N 1 "$COFF" | awk '{ print $1 }')
|
|
_i=$((_i + 1 + _aux))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
get_sym() {
|
|
_name=$1
|
|
_addr=$(sym_addr "$_name") || fail "COFF symbol table has no $_name"
|
|
[ -n "$_addr" ] || fail "COFF symbol table has no $_name"
|
|
printf '%s' "$_addr"
|
|
}
|
|
|
|
XOPEN=$(get_sym _XOpenDisplay)
|
|
XCREATE=$(get_sym _XCreateSimpleWindow)
|
|
XMAP=$(get_sym _XMapWindow)
|
|
XSYNC=$(get_sym _XSync)
|
|
XHELLO=$(get_sym _xhello)
|
|
SUMTO=$(get_sym _sumto)
|
|
MAX2=$(get_sym _max2)
|
|
LT=$(get_sym _lt)
|
|
BRANCH=$(get_sym _branch_test)
|
|
ROOTPROBE=$(get_sym _root_probe)
|
|
XOPEN_HEX=$(printf '%08X' "$XOPEN")
|
|
XCREATE_HEX=$(printf '%08X' "$XCREATE")
|
|
XMAP_HEX=$(printf '%08X' "$XMAP")
|
|
XSYNC_HEX=$(printf '%08X' "$XSYNC")
|
|
XHELLO_HEX=$(printf '%08X' "$XHELLO")
|
|
XOPEN_L=$(printf '%x' "$XOPEN")
|
|
XCREATE_L=$(printf '%x' "$XCREATE")
|
|
XMAP_L=$(printf '%x' "$XMAP")
|
|
XSYNC_L=$(printf '%x' "$XSYNC")
|
|
echo " _XOpenDisplay=$XOPEN_HEX _XCreateSimpleWindow=$XCREATE_HEX"
|
|
echo " _XMapWindow=$XMAP_HEX _XSync=$XSYNC_HEX"
|
|
echo " _xhello=$XHELLO_HEX _sumto=$(printf '%08X' "$SUMTO") _max2=$(printf '%08X' "$MAX2") _lt=$(printf '%08X' "$LT")"
|
|
echo " _branch_test=$(printf '%08X' "$BRANCH") _root_probe=$(printf '%08X' "$ROOTPROBE")"
|
|
|
|
echo "== disassemble the emitted .text with dis34"
|
|
"$DIS34" -b "$TEXT_HEX" -l -o "$TMP/text.bin" > "$TMP/dis34.txt" 2> "$TMP/dis34.err" \
|
|
|| { cat "$TMP/dis34.err" >&2; fail "dis34 failed"; }
|
|
[ -s "$TMP/dis34.txt" ] || fail "dis34 produced no output"
|
|
|
|
# match_at FILE DECIMAL_ADDR ERE
|
|
match_at() {
|
|
_file=$1
|
|
_addr=$2
|
|
_pat=$3
|
|
_laddr=$(printf '%08x' "$_addr")
|
|
grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]*(;|$))" "$_file"
|
|
}
|
|
|
|
expect_at() {
|
|
_label=$1
|
|
_addr=$2
|
|
_want=$3
|
|
_pat=$4
|
|
_laddr=$(printf '%08x' "$_addr")
|
|
if match_at "$TMP/dis34.txt" "$_addr" "$_pat"; then
|
|
echo " ok $_label @ 0x$_laddr: $_want"
|
|
return 0
|
|
fi
|
|
{
|
|
echo "M3 dis34 round-trip: FAIL"
|
|
echo " $_label at bit 0x$_laddr: expected $_want"
|
|
echo " dis34 output near 0x$_laddr:"
|
|
awk -v a="$_laddr" '
|
|
{ low = tolower($0) }
|
|
low ~ ("^" a ":") { seen = 1 }
|
|
seen { print " " $0; n++ }
|
|
seen && n >= 4 { exit }
|
|
' "$TMP/dis34.txt"
|
|
if cp "$TMP/dis34.txt" "$FAIL_LOG" 2>/dev/null; then
|
|
echo " full disassembly preserved at: $FAIL_LOG"
|
|
else
|
|
echo " full disassembly: $TMP/dis34.txt (temporary; removed on exit)"
|
|
fi
|
|
} >&2
|
|
exit 1
|
|
}
|
|
|
|
func_body() {
|
|
_laddr=$(printf '%08x' "$1")
|
|
awk -v a="$_laddr" '
|
|
{ low = tolower($0) }
|
|
low ~ ("^" a ":") { on = 1 }
|
|
on { print }
|
|
on && low ~ /^[0-9a-f]+:[[:space:]]+rets([[:space:]]|;|$)/ { exit }
|
|
' "$TMP/dis34.txt"
|
|
}
|
|
|
|
expect_in() {
|
|
_file=$1
|
|
_want=$2
|
|
_pat=$3
|
|
if grep -Eiq "$_pat" "$_file"; then
|
|
echo " ok $_want"
|
|
return 0
|
|
fi
|
|
echo "M3 dis34 round-trip: FAIL: missing $_want" >&2
|
|
sed 's/^/ /' "$_file" >&2
|
|
exit 1
|
|
}
|
|
|
|
# count_between FILE START_RE END_RE PUSH_RE: count push encodings after the
|
|
# START_RE line up to (not including) the END_RE line.
|
|
count_between() {
|
|
_file=$1
|
|
_start=$2
|
|
_end=$3
|
|
_push=$4
|
|
awk -v s="$_start" -v e="$_end" -v p="$_push" '
|
|
{ low = tolower($0) }
|
|
on && low ~ e { exit }
|
|
on && low ~ p { n++ }
|
|
low ~ s { on = 1 }
|
|
END { print n + 0 }
|
|
' "$_file"
|
|
}
|
|
|
|
echo "== check the ROM import stubs"
|
|
expect_at "_XOpenDisplay" "$XOPEN" "JAUC 3F9C2570" \
|
|
'jauc[[:space:]]+0x3f9c2570'
|
|
expect_at "_XCreateSimpleWindow" "$XCREATE" "JAUC 3FD41B90" \
|
|
'jauc[[:space:]]+0x3fd41b90'
|
|
expect_at "_XMapWindow" "$XMAP" "JAUC 3FD64F60" \
|
|
'jauc[[:space:]]+0x3fd64f60'
|
|
expect_at "_XSync" "$XSYNC" "JAUC 3FD9F6B0" \
|
|
'jauc[[:space:]]+0x3fd9f6b0'
|
|
|
|
echo "== check the compiled Xlib call sequence"
|
|
func_body "$XHELLO" > "$TMP/xhello.txt"
|
|
expect_in "$TMP/xhello.txt" "CALLA _XOpenDisplay ($XOPEN_HEX)" \
|
|
"calla[[:space:]]+l${XOPEN_L}"
|
|
expect_in "$TMP/xhello.txt" "CALLA _XCreateSimpleWindow ($XCREATE_HEX)" \
|
|
"calla[[:space:]]+l${XCREATE_L}"
|
|
expect_in "$TMP/xhello.txt" "CALLA _XMapWindow ($XMAP_HEX)" \
|
|
"calla[[:space:]]+l${XMAP_L}"
|
|
expect_in "$TMP/xhello.txt" "CALLA _XSync ($XSYNC_HEX)" \
|
|
"calla[[:space:]]+l${XSYNC_L}"
|
|
|
|
# The arguments are pushed last-first onto the program stack; the first
|
|
# argument (background=1 for XCreateSimpleWindow) is pushed last.
|
|
P1=$(count_between "$TMP/xhello.txt" '^[0-9a-f]+:[[:space:]]+setf[[:space:]]+16' \
|
|
"calla[[:space:]]+l${XOPEN_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
|
P2=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XOPEN_L}" \
|
|
"calla[[:space:]]+l${XCREATE_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
|
P3=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XCREATE_L}" \
|
|
"calla[[:space:]]+l${XMAP_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
|
P4=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XMAP_L}" \
|
|
"calla[[:space:]]+l${XSYNC_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
|
[ "$P1" -eq 1 ] || fail "XOpenDisplay has $P1 argument pushes, want 1"
|
|
[ "$P2" -eq 9 ] || fail "XCreateSimpleWindow has $P2 argument pushes, want 9"
|
|
[ "$P3" -eq 2 ] || fail "XMapWindow has $P3 argument pushes, want 2"
|
|
[ "$P4" -eq 2 ] || fail "XSync has $P4 argument pushes, want 2"
|
|
echo " ok argument pushes: XOpenDisplay=1 XCreateSimpleWindow=9 XMapWindow=2 XSync=2"
|
|
|
|
# Counting pushes is not enough: the nine words must reach the callee in the
|
|
# ABI order. build/m3/m3.lst records background 1, border 0, border width 2,
|
|
# height 200, width 400, y 100, then x 100 (x reuses y's register, so there are
|
|
# six MOVIs for the seven constants), followed by the root (A2) and display
|
|
# (A0) pushes. Assert the immediates and push registers as one sequence, so a
|
|
# swapped pair fails even though the count stays nine, and require the final
|
|
# push before CALLA to be the display register.
|
|
awk '
|
|
tolower($0) ~ /calla[[:space:]]+l'"$XOPEN_L"'/ { on = 1; next }
|
|
tolower($0) ~ /calla[[:space:]]+l'"$XCREATE_L"'/ { exit }
|
|
!on { next }
|
|
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*-\*sp,[[:space:]]*1/ { started = 1; next }
|
|
!started { next }
|
|
tolower($0) ~ /movi[[:space:]]+/ {
|
|
line = $0
|
|
sub(/^[^:]*:[[:space:]]+movi[[:space:]]+/, "", line)
|
|
sub(/[[:space:]]*;.*/, "", line)
|
|
sub(/[[:space:]]+/, " ", line)
|
|
split(line, p, /,[[:space:]]*/)
|
|
printf "IMM %s %s\n", tolower(p[1]), tolower(p[2])
|
|
next
|
|
}
|
|
tolower($0) ~ /move[[:space:]]+a[0-9]+,[[:space:]]*\*a14\+,[[:space:]]*1/ {
|
|
line = $0
|
|
sub(/^[^:]*:[[:space:]]+move[[:space:]]+/, "", line)
|
|
sub(/,.*/, "", line)
|
|
printf "PUSH %s\n", tolower(line)
|
|
next
|
|
}
|
|
' "$TMP/xhello.txt" > "$TMP/xcreate.seq"
|
|
cat > "$TMP/xcreate.want" <<'EOF'
|
|
IMM 0x1 a1
|
|
PUSH a1
|
|
IMM 0x0 a1
|
|
PUSH a1
|
|
IMM 0x2 a3
|
|
PUSH a3
|
|
IMM 0xc8 a3
|
|
PUSH a3
|
|
IMM 0x190 a3
|
|
PUSH a3
|
|
IMM 0x64 a3
|
|
PUSH a3
|
|
PUSH a3
|
|
PUSH a2
|
|
PUSH a0
|
|
EOF
|
|
if ! cmp -s "$TMP/xcreate.want" "$TMP/xcreate.seq"; then
|
|
{
|
|
echo "M3 dis34 round-trip: FAIL: XCreateSimpleWindow argument order"
|
|
echo " expected (background,border,border_width,height,width,y,x,root,display):"
|
|
sed 's/^/ /' "$TMP/xcreate.want"
|
|
echo " got:"
|
|
sed 's/^/ /' "$TMP/xcreate.seq"
|
|
} >&2
|
|
exit 1
|
|
fi
|
|
echo " ok XCreateSimpleWindow argument order: 1,0,2,200,400,100,100,root,display (display pushed last)"
|
|
|
|
# Xlib clobbers SETF; the compiler must re-establish 16/FS0 and 32/FS1 after
|
|
# every call. Check in the linked assembly, where CALLA symbols survive.
|
|
awk '
|
|
/^_xhello:/ { on = 1; next }
|
|
on && /-- End function/ { exit }
|
|
on && /^[[:space:]]*CALLA/ { pending = 1; next }
|
|
on && pending && /SETF[[:space:]]+16,[[:space:]]*0,[[:space:]]*0/ { saw16 = 1; next }
|
|
on && pending && saw16 && /SETF[[:space:]]+32,[[:space:]]*0,[[:space:]]*1/ {
|
|
n++; pending = 0; saw16 = 0; next
|
|
}
|
|
END { print n + 0 }
|
|
' "$ASM" > "$TMP/setf_count"
|
|
SETF_COUNT=$(cat "$TMP/setf_count")
|
|
[ "$SETF_COUNT" -eq 4 ] \
|
|
|| fail "found $SETF_COUNT post-call SETF 16/32 pairs in _xhello, want 4"
|
|
echo " ok four post-call SETF 16,0,0 + SETF 32,0,1 pairs"
|
|
|
|
# _xhello takes no arguments of its own (the nine-argument call is inside it),
|
|
# so it returns with RETS 0; the argument-taking branch functions use RETS 2.
|
|
func_body "$XHELLO" > "$TMP/xhello.txt"
|
|
expect_in "$TMP/xhello.txt" "_xhello RETS 0" 'rets([[:space:]]+0)?([[:space:]]*(;|$))'
|
|
for pair in "$SUMTO:_sumto" "$MAX2:_max2" "$LT:_lt"; do
|
|
addr=${pair%%:*}
|
|
label=${pair##*:}
|
|
func_body "$addr" > "$TMP/fn.txt"
|
|
expect_in "$TMP/fn.txt" "$label RETS 2" 'rets[[:space:]]+2([[:space:]]*(;|$))'
|
|
done
|
|
|
|
echo "== check the branch code"
|
|
for pair in "$SUMTO:_sumto" "$MAX2:_max2" "$LT:_lt"; do
|
|
addr=${pair%%:*}
|
|
label=${pair##*:}
|
|
func_body "$addr" > "$TMP/fn.txt"
|
|
if ! grep -Eiq 'cmp' "$TMP/fn.txt"; then
|
|
sed 's/^/ /' "$TMP/fn.txt" >&2
|
|
fail "$label has no CMP"
|
|
fi
|
|
if ! grep -Eiq '\bjauc\b' "$TMP/fn.txt"; then
|
|
sed 's/^/ /' "$TMP/fn.txt" >&2
|
|
fail "$label has no JAUC"
|
|
fi
|
|
echo " ok $label: CMP + JAUC"
|
|
done
|
|
for pair in "$XHELLO:_xhello"; do
|
|
addr=${pair%%:*}
|
|
label=${pair##*:}
|
|
func_body "$addr" > "$TMP/fn.txt"
|
|
if ! grep -Eiq '\bjauc\b' "$TMP/fn.txt"; then
|
|
sed 's/^/ /' "$TMP/fn.txt" >&2
|
|
fail "$label has no JAUC"
|
|
fi
|
|
echo " ok $label: JAUC"
|
|
done
|
|
func_body "$BRANCH" > "$TMP/branch.txt"
|
|
for callee in _sumto _max2 _lt _layout_screens _layout_root; do
|
|
grep -Eiq "calla[[:space:]]+L$(get_sym "$callee" | xargs printf '%X')" "$TMP/branch.txt" \
|
|
|| fail "_branch_test does not call $callee"
|
|
done
|
|
echo " ok _branch_test calls sumto, max2, lt, layout_screens, layout_root"
|
|
# Conditional branch opcodes emitted by this compiled image. JALE/JALO/JAHI
|
|
# coverage lives in the m3-loop/m3-unsigned/m3-cmpi lit tests; this image has
|
|
# the signed compare, loop-exit and smax branches.
|
|
for op in jalt jagt jaeq; do
|
|
grep -Eiq "^[0-9a-f]+:[[:space:]]+$op([[:space:]]|;|$)" "$TMP/dis34.txt" \
|
|
|| fail "no $op instruction in the linked .text"
|
|
echo " ok $op present"
|
|
done
|
|
|
|
# GSPA rejects dotted .L labels; the harness gives each TU an LBB prefix.
|
|
grep -q 'LBB' "$ASM" || fail "linked assembly has no LBB block labels"
|
|
if grep -q '\.LBB' "$ASM"; then
|
|
fail "linked assembly contains dotted .LBB labels (GSPA rejects them)"
|
|
fi
|
|
echo " ok LBB… block labels (no dot)"
|
|
|
|
echo "== check the old-STK restore offsets"
|
|
check_stk() {
|
|
_label=$1
|
|
_addr=$2
|
|
func_body "$_addr" > "$TMP/func.txt"
|
|
_pushes=$(awk '
|
|
tolower($0) ~ /move[[:space:]]+[a-z0-9]+,[[:space:]]*-\*sp,[[:space:]]*1/ { n++ }
|
|
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*a13/ { print n; exit }
|
|
' "$TMP/func.txt")
|
|
[ -n "$_pushes" ] || fail "$_label has no SETFP (MOVE A14,A13)"
|
|
_operand=$(grep -Eio '\*sp\(0x[0-9a-f]+\)' "$TMP/func.txt" | head -1 \
|
|
| sed 's/.*0x//; s/).*//')
|
|
[ -n "$_operand" ] || fail "$_label has no LOADSTK (*SP(n),A14)"
|
|
_want=$(( (_pushes + 1) * 32 ))
|
|
_got=$((0x$_operand))
|
|
[ "$_got" -eq "$_want" ] \
|
|
|| fail "$_label restores A14 from *SP(0x$_operand) ($_got bits) but $_pushes pushes need $_want bits"
|
|
echo " ok $_label: $_pushes pushes -> *SP($(printf '0x%x' "$_want")), A14, 1"
|
|
}
|
|
check_stk "_sumto" "$SUMTO"
|
|
check_stk "_max2" "$MAX2"
|
|
check_stk "_lt" "$LT"
|
|
check_stk "_root_probe" "$ROOTPROBE"
|
|
|
|
# Negative control for the RETS operand boundary: "rets 1" must not match.
|
|
printf '%08x:\trets\t1\t; 0961\n' "$SUMTO" > "$TMP/rets-neg.txt"
|
|
if match_at "$TMP/rets-neg.txt" "$SUMTO" 'rets([[:space:]]+0)?'; then
|
|
fail "negative control: the RETS pattern accepted 'rets 1'"
|
|
fi
|
|
echo " ok negative control @ 0x$(printf '%08x' "$SUMTO"): 'rets 1' rejected"
|
|
|
|
echo "== check the copied .data bytes"
|
|
DATA_HEX=$(od -An -v -tx1 "$TMP/data.bin" | tr -d ' \n')
|
|
case "$DATA_HEX" in
|
|
*303a3a3000*) echo " ok .data contains 30 3A 3A 30 00 (\"0::0\")" ;;
|
|
*) fail ".data does not contain 30 3A 3A 30 00 (got $DATA_HEX)" ;;
|
|
esac
|
|
|
|
echo "M3 dis34 round-trip: PASS"
|