Files
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

367 lines
8.3 KiB
NASM

; fn_tour.asm -- exercises the important INT 21h functions, one labelled line of output each.
; Run it under DOS (the harness uses DOSBox) with stdin redirected from IN.TXT.
bits 16
org 0x100
; ---- shrink our memory block: a .COM owns all conventional memory (AH=4Ah) ----
mov bx, 1000h ; keep 64 KB = 1000h paragraphs
mov ah, 4Ah ; ES = our PSP segment already
int 21h
; ---- AH=30h get DOS version ----
mov dx, l_ver
call puts
mov ah, 30h
int 21h ; AL = major, AH = minor
push ax
xor ah, ah
call putdec
mov dl, '.'
mov ah, 02h
int 21h
pop ax
mov al, ah
xor ah, ah
call putdec
call crlf
; ---- AH=2Ah / AH=2Ch date and time (values change every run, so we only range-check) ----
mov dx, l_date
call puts
mov ah, 2Ah ; CX = year, DH = month, DL = day
int 21h
cmp cx, 1980
jb .baddate
cmp dh, 12
ja .baddate
mov dx, s_ok
jmp .dateout
.baddate:
mov dx, s_bad
.dateout:
call puts
mov ah, 2Ch ; CH = hour, CL = minute, DH = second
int 21h
mov dx, s_ok2
cmp ch, 23
jbe .timeok
mov dx, s_bad
.timeok:
call puts
call crlf
; ---- AH=02h / AH=06h character output ----
mov dx, l_chr
call puts
mov dl, 'A'
mov ah, 02h
int 21h
mov dl, 'B'
mov ah, 06h ; direct console output (no ^C check)
int 21h
call crlf
; ---- AH=01h then AH=0Ah stdin: one echoed char, then a buffered line ----
mov dx, l_in
call puts
mov ah, 01h ; AL = char read (and echoed)
int 21h
mov [ch1], al
mov dx, buf
mov ah, 0Ah ; buf[0] = max, buf[1] = count read, buf[2..] = text
int 21h
call crlf
mov dx, l_got
call puts
mov al, [ch1]
call puthex8 ; the character AH=01h returned
mov dx, l_cnt
call puts
xor ah, ah
mov al, [buf+1] ; number of characters AH=0Ah stored (CR not counted)
mov bx, ax
call putdec
mov byte [buf+2+bx], '$'
mov dx, l_txt
call puts
mov dx, buf+2
call puts
call crlf
; ---- AH=25h / AH=35h set and get an interrupt vector ----
mov dx, l_vec
call puts
mov ax, 2560h
mov dx, dummy_isr
int 21h
mov ax, 3560h
int 21h ; ES:BX = vector 60h
mov dx, s_bad
cmp bx, dummy_isr
jne .vecout
mov ax, es
mov cx, cs
cmp ax, cx
jne .vecout
mov dx, s_ok
.vecout:
call puts
call crlf
; ---- AH=48h / AH=49h allocate and free memory; failure returns CF=1, AX=8, BX=largest ----
mov dx, l_mem
call puts
mov bx, 0100h ; 100h paragraphs = 4 KB
mov ah, 48h
int 21h
jc .memfail
mov es, ax ; ES = segment of the new block
mov ah, 49h
int 21h
mov dx, s_ok
jmp .memout
.memfail:
mov dx, s_bad
.memout:
call puts
call crlf
mov bx, 0FFFFh ; ask for 1 MB: must fail
mov ah, 48h
int 21h
mov [err], ax ; save the results before printing clobbers them
mov [big], bx
mov byte [cfch], '0'
jnc .memcf
mov byte [cfch], '1'
.memcf:
mov dx, l_memfail
call puts
mov dl, [cfch]
mov ah, 02h
int 21h
mov dx, l_ax
call puts
mov ax, [err] ; 0008h = insufficient memory
call puthex16
mov dx, l_big
call puts
mov ax, [big] ; BX = largest block available, in paragraphs
call puthex16
call crlf
; ---- file I/O: AH=3Ch create, 40h write, 3Eh close, 3Dh open, 3Fh read, 42h lseek ----
mov dx, l_file
call puts
mov dx, fname
xor cx, cx ; normal attributes
mov ah, 3Ch
int 21h
jc .fileerr
mov bx, ax ; BX = handle
mov dx, payload
mov cx, payload_len
mov ah, 40h
int 21h
mov ah, 3Eh
int 21h
mov dx, fname
mov ax, 3D00h ; open read-only
int 21h
jc .fileerr
mov bx, ax
mov dx, rdbuf
mov cx, 64
mov ah, 3Fh
int 21h ; AX = bytes actually read
call putdec ; bytes actually read
mov dx, l_read
call puts
mov ax, 4202h ; LSEEK from end, offset 0
xor cx, cx
xor dx, dx
int 21h ; DX:AX = file size
mov [fsize], ax
mov dx, l_seek
call puts
mov ax, [fsize]
call putdec
mov ah, 3Eh
int 21h
call crlf
jmp .fileok
.fileerr:
mov dx, s_bad
call puts
call crlf
.fileok:
; ---- AH=56h rename, AH=4Eh findfirst (DTA defaults to PSP:0080h), AH=41h delete ----
mov dx, l_ff
call puts
mov dx, fname
mov di, fname2
push ds
pop es
mov ah, 56h
int 21h
mov dx, fname2
xor cx, cx
mov ah, 4Eh
int 21h
jc .ffbad
mov dx, 80h+1Eh ; DTA+1Eh = ASCIZ name found
mov si, dx ; copy it to a '$'-terminated buffer so AH=09h can print it
mov di, ffname
.cp:
lodsb
test al, al
jz .cpend
stosb
jmp .cp
.cpend:
mov al, '$'
stosb
mov dx, ffname
call puts
mov dx, l_size
call puts
mov ax, [80h+1Ah] ; DTA+1Ah = file size (low word)
call putdec
jmp .ffend
.ffbad:
mov dx, s_bad
call puts
.ffend:
call crlf
mov dx, l_del
call puts
mov dx, fname2
mov ah, 41h ; delete
int 21h
mov dx, fname2
mov ax, 3D00h ; open it again: must fail
int 21h
mov [err], ax ; save AX and CF before printing clobbers them
mov byte [cfch], '0'
jnc .cfout
mov byte [cfch], '1'
.cfout:
mov dl, [cfch]
mov ah, 02h
int 21h
mov dx, l_ax
call puts
mov ax, [err]
call puthex16
call crlf
; ---- AH=39h mkdir, AH=3Ah rmdir, AH=19h current drive, AH=47h current directory ----
mov dx, l_dir
call puts
mov dx, dname
mov ah, 39h
int 21h
mov dx, dname
mov ah, 3Ah
int 21h
mov dx, s_ok
jnc .dirout
mov dx, s_bad
.dirout:
call puts
mov dx, l_drv
call puts
mov ah, 19h ; AL = 0 for A:, 1 for B:, 2 for C:
int 21h
xor ah, ah
call putdec
call crlf
; ---- AH=4Bh EXEC a child, AH=4Dh read its return code ----
mov dx, l_exec
call puts
mov ax, cs
mov [pb+4], ax ; command-tail segment
mov [pb+8], ax ; FCB1 segment
mov [pb+12], ax ; FCB2 segment
mov dx, child
mov bx, pb
mov ax, 4B00h ; load and execute
int 21h
jc .execbad
mov ah, 4Dh ; AL = child's return code, AH = termination type
int 21h
push ax
mov dx, l_rc
call puts
pop ax
push ax
xor ah, ah
call putdec
mov dx, l_tt
call puts
pop ax
mov al, ah
xor ah, ah
call putdec
call crlf
jmp .done
.execbad:
mov dx, s_bad
call puts
call crlf
.done:
mov ax, 4C00h ; AH=4Ch: exit, return code 0
int 21h
dummy_isr:
iret
l_ver db 'AH=30h DOS version : $'
l_date db 'AH=2Ah/2Ch date+time ranges : $'
l_chr db 'AH=02h + AH=06h : $'
l_in db 'AH=01h + AH=0Ah (stdin) : echo -> $'
l_got db ' AH=01h got 0x$'
l_txt db ', text = $'
l_cnt db ' ; AH=0Ah count = $'
l_vec db 'AH=25h/35h vector 60h : $'
l_mem db 'AH=48h/49h alloc+free 4 KB : $'
l_memfail db 'AH=48h ask for 1 MB (fails) : CF=$'
l_big db ' ; largest block (paragraphs) = $'
l_file db 'AH=3Ch/40h/3Dh/3Fh/42h : $'
l_read db ' bytes read ; $'
l_ff db 'AH=56h + AH=4Eh findfirst : $'
l_size db ' size = $'
l_del db 'AH=41h delete, reopen fails : CF=$'
l_seek db 'LSEEK(end) says size = $'
l_ax db ' AX=$'
l_dir db 'AH=39h/3Ah mkdir+rmdir : $'
l_drv db ' ; AH=19h drive = $'
l_exec db 'AH=4Bh exec child : $'
l_rc db 'AH=4Dh return code = $'
l_tt db ' type = $'
s_ok db 'ok$'
s_ok2 db ' ok$'
s_bad db 'FAILED$'
fname db 'T.TXT', 0
fname2 db 'U.TXT', 0
dname db 'SUBD', 0
child db 'HELLO21.COM', 0
payload db 'DOS file I/O'
payload_len equ $ - payload
ch1 db 0
err dw 0
fsize dw 0
big dw 0
cfch db 0
buf db 20, 0
times 22 db 0
ffname times 16 db 0
rdbuf times 64 db 0
; EXEC parameter block: env seg (0 = inherit), cmd tail ptr, FCB1 ptr, FCB2 ptr
pb dw 0, tail, 0, fcb1, 0, fcb2, 0
tail db 0, 0Dh
fcb1 times 16 db 0
fcb2 times 16 db 0
%include "common.inc"