Add interrupts module: INT 3 (CC breakpoint) vs INT 21h (DOS services)
NASM sources, DOSBox/FreeDOS Debug/Unicorn harness, captured output and 31 assertions backing the ankurm.com article "INT 3 vs INT 21h in 8086 Assembly". Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KqJyCidz3ZgRyHABv2GVJh
This commit is contained in:
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Assemble the .COM demos with NASM and run them inside DOSBox (headless).
|
||||
|
||||
DOSBox provides a real MS-DOS-compatible INT 21h implementation (and its own INT 3
|
||||
default vector), so this is where the INT 21h and INT 3 behaviour is actually executed.
|
||||
"""
|
||||
import os, pathlib, re, shutil, subprocess, sys, tempfile
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
ASM, BUILD, OUT = ROOT / "asm", ROOT / "build", ROOT / "output"
|
||||
BUILD.mkdir(exist_ok=True); OUT.mkdir(exist_ok=True)
|
||||
|
||||
PROGRAMS = { # source -> DOS 8.3 name
|
||||
"hello21": "HELLO21.COM", "fn_tour": "TOUR.COM", "int3_handler": "I3H.COM",
|
||||
"int3_default": "I3D.COM", "dbg_cc": "DBGCC.COM", "dbg_cd03": "DBGCD.COM",
|
||||
}
|
||||
|
||||
def nasm(src, out, listing=None):
|
||||
cmd = ["nasm", "-f", "bin", str(ASM / (src + ".asm")), "-o", str(out)]
|
||||
if listing:
|
||||
cmd += ["-l", str(listing)]
|
||||
subprocess.run(cmd, check=True, cwd=ASM)
|
||||
|
||||
def clean(text):
|
||||
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
||||
return "\n".join(l.rstrip() for l in text.split("\n")).rstrip("\n") + "\n"
|
||||
|
||||
def main():
|
||||
# 1. opcode listing (assembled, never executed)
|
||||
nasm("opcodes", BUILD / "opcodes.bin", OUT / "01-opcodes.lst")
|
||||
lst = (OUT / "01-opcodes.lst").read_text()
|
||||
lst = "\n".join(l for l in lst.split("\n") if l.strip())
|
||||
(OUT / "01-opcodes.lst").write_text(lst + "\n")
|
||||
|
||||
work = pathlib.Path(tempfile.mkdtemp(prefix="dosbox_"))
|
||||
for src, dos in PROGRAMS.items():
|
||||
nasm(src, work / dos)
|
||||
have_debug = (BUILD / "DEBUG.COM").exists()
|
||||
if have_debug:
|
||||
shutil.copy(BUILD / "DEBUG.COM", work / "DEBUG.COM")
|
||||
(work / "IN.TXT").write_bytes(b"ZAnkur\r\n") # stdin for fn_tour: AH=01h reads Z, AH=0Ah reads Ankur
|
||||
# DEBUG script: list code, run to the breakpoint, show registers, single-step once, show registers, run on, quit
|
||||
(work / "DC.TXT").write_bytes(b"u 100 L9\r\ng\r\nr\r\nt\r\nr\r\ng\r\nq\r\n")
|
||||
|
||||
steps = [
|
||||
("HELLO21.COM > O1.TXT", None),
|
||||
("if errorlevel 7 echo ERRORLEVEL is at least 7 >> O1.TXT", None),
|
||||
("TOUR.COM < IN.TXT > O2.TXT", None),
|
||||
("I3H.COM > O3.TXT", None),
|
||||
("I3D.COM > O4.TXT", None),
|
||||
]
|
||||
if have_debug:
|
||||
steps += [("DEBUG DBGCC.COM < DC.TXT > O5.TXT", None), ("DEBUG DBGCD.COM < DC.TXT > O6.TXT", None)]
|
||||
conf = "[sdl]\nfullscreen=false\n[autoexec]\nmount c %s\nc:\n%s\nexit\n" % (work, "\n".join(s for s, _ in steps))
|
||||
(work / "run.conf").write_text(conf)
|
||||
env = dict(os.environ, SDL_VIDEODRIVER="dummy", SDL_AUDIODRIVER="dummy")
|
||||
subprocess.run(["dosbox", "-conf", str(work / "run.conf"), "-noconsole"], env=env, timeout=180,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
names = {"O1.TXT": "02-hello21.txt", "O2.TXT": "03-fn-tour.txt", "O3.TXT": "04-int3-handler.txt",
|
||||
"O4.TXT": "05-int3-default.txt", "O5.TXT": "06-debug-cc.txt", "O6.TXT": "07-debug-cd03.txt"}
|
||||
for src, dst in names.items():
|
||||
p = work / src
|
||||
if p.exists():
|
||||
(OUT / dst).write_text(clean(p.read_text(errors="replace")))
|
||||
else:
|
||||
print("missing", src, "(DEBUG.COM not fetched? run scripts/fetch-debug.sh)", file=sys.stderr)
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user