Files
8086-Programs/interrupts/scripts/dosbox_run.py
T
asmhatreandClaude Sonnet 5.5 09af523f8c 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
2026-09-30 19:10:56 +00:00

72 lines
3.2 KiB
Python
Executable File

#!/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()