#!/bin/bash # Build and run the .COM linker harness (tests/ComTest.mod), then verify every # .COM it produced with an INDEPENDENT checker. # # The independent pass matters: ComTest computes the expectations from the same # Compiler state it is testing, so a bug in the compiler would be invisible to # it. The Python pass re-derives what the file must contain - the runtime's # first bytes, a zero gap, a size that covers the data area - from the # constants only, and cross-checks. set -u D=/home/eric/Projets/Projets-Modula2/MyWork/TP3-comp/shell GM2=/home/eric/bin/Modula2/Gm2/bin/gm2 cd "$D" || exit 9 FLAGS="-fiso" echo "== support modules ==" [ -f Posix.o ] || cc -c Posix.c || exit 1 for m in TextBuf Compiler Runtime Linker; do $GM2 $FLAGS -c $m.mod >/tmp/cm_c_$m 2>&1 \ || { echo "COMPILE_FAIL $m"; grep -m5 "error:" /tmp/cm_c_$m; exit 1; } done $GM2 $FLAGS -c tests/ComTest.mod >/tmp/cm_c_ComTest 2>&1 \ || { echo "COMPILE_FAIL ComTest"; grep -m5 "error:" /tmp/cm_c_ComTest; exit 1; } rm -f tests/ct.lst comtest $GM2 $FLAGS -fgen-module-list=tests/ct.lst -o /dev/null \ tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \ >/tmp/cm_p1 2>&1 p1=$? $GM2 $FLAGS -fuse-list=tests/ct.lst -o comtest \ tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \ >/tmp/cm_p2 2>&1 p2=$? if [ $p2 -ne 0 ]; then echo "LINK_FAIL p1_rc=$p1 p2_rc=$p2" grep -E "error:|undefined" /tmp/cm_p2 | head -10 exit 1 fi echo "comtest built (p1_rc=$p1, phase 1 rc=1 is the expected rollup)" # .COM files are written beside the harness, so run it in a scratch dir OUT=$(mktemp -d) || exit 9 trap 'rm -rf "$OUT"' EXIT cd "$OUT" || exit 9 ls "$D"/tests/fixtures/*.pas | "$D"/comtest > "$OUT/raw.txt" 2>&1 sed 's/^.*fixtures\///' "$OUT/raw.txt" echo "----------------------------------------------------------------" nok=$(grep -c " OK com=" "$OUT/raw.txt") nerr=$(grep -c " ERROR " "$OUT/raw.txt") nbad=$(grep -cE "WRITE_COM_FAILED|CANNOT" "$OUT/raw.txt") echo "linked: $nok .COM files, $nerr fixtures rejected at compile time, $nbad harness failures" if [ "$nbad" -ne 0 ]; then echo "RESULT: FAIL (harness could not link every compiling fixture)" exit 1 fi # ---- independent verification of the bytes on disk ------------------------ python3 - "$OUT" <<'PYEOF' import sys, os, re, glob out = sys.argv[1] # Compiler layout constants, restated here on purpose: the checker must not # ask the code under test what the answer is. # # Re-baselined 385 -> 391 with the runtime bug fixes (see the re-baseline # rationale in tests/runtime.golden and the ModRM table in Runtime.mod). The # size is what a change to the runtime shows up in first; if this constant # ever needs changing again, find out why the runtime moved rather than just # editing the number, and check tests/check_runtime.py for the reason. RTSZ = 391 # Runtime.RT_Size() DATAB = RTSZ + 0x1000 # Compiler: data base = rtSz + 1000H # initmem's prologue, which is the runtime's only reader of the program # header. The displacements +4 and +6 below are the whole point of this # constant: the header word checks further down read hdrDS at +4 and hdrHeap # at +6, and initmem has to read the SAME two words or it clears the wrong # range. It used to read +8 (hdrMax, which the compiler patches to 0), so it # zeroed nothing at all, and nothing here noticed -- the emitted loop was # perfectly well formed, it just never ran. Asserting the bytes and the # header offsets together is what closes that gap. HEAD = '8B F0 8B 54 04 8B 4C 06' # 11 bytes of initmem, see below HDR_DS_WORD = 4 # header word holding the data base HDR_HEAP_WORD = 6 # header word holding the data end # Byte 4 of HEAD is the displacement of initmem's MOV DX,[SI+?], and byte 7 # the displacement of its MOV CX,[SI+?]. The checks below read the header # words at HDR_DS_WORD and HDR_HEAP_WORD, so tying those two displacements to # the same two constants is what makes the runtime and the compiler agree by # construction rather than by coincidence. assert [int(HEAD.split()[4], 16), int(HEAD.split()[7], 16)] == \ [HDR_DS_WORD, HDR_HEAP_WORD], \ 'initmem no longer reads the two header words this checker verifies' raw = open(os.path.join(out, 'raw.txt')).read() rows = re.findall(r'(\S+\.pas)\s+OK\s+com=(\d+)\s+image=(\d+)\s+data=(\d+)\s+nonzeroInGap=(\d+)', raw) if not rows: print('RESULT: FAIL (no linked fixtures found in output)') sys.exit(1) bad = 0 for name, com, image, data, nzg in rows: com, image, data, nzg = int(com), int(image), int(data), int(nzg) # ComTest writes the .COM by BASENAME beside itself (it cannot graft a # directory onto a source path), so the checker must look for the bare # name, not the full source path the fixture was read from. path = os.path.join(out, os.path.basename(name)[:-4] + '.COM') errs = [] if not os.path.exists(path): errs.append('no .COM file') d = b'' else: d = open(path, 'rb').read() if d[:len(HEAD.split())].hex(' ').upper() != HEAD: errs.append('runtime not at offset 0 (first bytes %s, want %s)' % (d[:len(HEAD.split())].hex(' ').upper(), HEAD)) if len(d) != com: errs.append('file is %d bytes, harness said %d' % (len(d), com)) if DATAB + data != len(d): errs.append('size %d != dataBase+data %d' % (len(d), DATAB + data)) # the gap between the image and the data area must be entirely zero gap = d[image:DATAB] if any(gap): errs.append('%d non-zero bytes in the code/data gap' % sum(1 for b in gap if b)) if nzg != 0: errs.append('harness itself reported %d non-zero gap bytes' % nzg) # the program must start exactly where the runtime ends if image < RTSZ: errs.append('image %d shorter than the runtime %d' % (image, RTSZ)) # the program header sits at offset rtSz, and its words must describe the # image that is actually in the file if len(d) > RTSZ + 8: hdr = d[RTSZ:RTSZ+16] flag = int.from_bytes(hdr[0:2], 'little') cs = int.from_bytes(hdr[2:4], 'little') ds = int.from_bytes(hdr[HDR_DS_WORD:HDR_DS_WORD+2], 'little') heap = int.from_bytes(hdr[HDR_HEAP_WORD:HDR_HEAP_WORD+2], 'little') if flag != 1: errs.append('hdrFlag=%d' % flag) # hdrCS is pc, the end of the code, and the harness's `image` is # rtSz + code ALREADY - so the two are the same number. Adding # RTSZ here counted the runtime twice. if cs != image: errs.append('hdrCS=%d, want %d (end of image)' % (cs, image)) if ds != DATAB: errs.append('hdrDS=%d, want %d' % (ds, DATAB)) if heap != DATAB + data: errs.append('hdrHeap=%d, want %d' % (heap, DATAB + data)) # initmem must read hdrDS and hdrHeap, not some other pair of header # words. (It read +8, hdrMax, which the compiler patches to 0, so # the range it cleared was empty and every global kept whatever the # loader left in it.) if [d[4], d[7]] != [HDR_DS_WORD, HDR_HEAP_WORD]: errs.append('initmem reads header words +%d/+%d, but the data ' 'base and data end are at +%d/+%d' % (d[4], d[7], HDR_DS_WORD, HDR_HEAP_WORD)) if errs: bad += 1 print(' %-24s FAIL %s' % (os.path.basename(name)[:-4], '; '.join(errs))) else: print(' %-24s PASS %d bytes' % (os.path.basename(name)[:-4], com)) print('----------------------------------------------------------------') print('independent .COM check: %d checked, %d failed' % (len(rows), bad)) sys.exit(1 if bad else 0) PYEOF rc=$? [ "$rc" -eq 0 ] || exit 1 echo "RESULT: ALL PASS"