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
367 lines
8.3 KiB
NASM
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"
|