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:
@@ -0,0 +1,366 @@
|
||||
; 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"
|
||||
Reference in New Issue
Block a user