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,13 @@
|
||||
; bp_target.asm -- a flat 16-bit routine used by the Unicorn breakpoint experiment.
|
||||
; Three one-byte INC CX instructions, so a two-byte CD 03 patch on the first one
|
||||
; overwrites the first byte of the second one too.
|
||||
bits 16
|
||||
org 0x7C00
|
||||
start:
|
||||
xor cx, cx
|
||||
site:
|
||||
inc cx ; 41 <- breakpoint goes here
|
||||
inc cx ; 41
|
||||
inc cx ; 41
|
||||
done:
|
||||
nop
|
||||
@@ -0,0 +1,64 @@
|
||||
; common.inc -- tiny output helpers shared by the demo programs (DOS .COM, NASM syntax)
|
||||
|
||||
; print the '$'-terminated string at DS:DX (INT 21h, AH=09h)
|
||||
puts:
|
||||
mov ah, 09h
|
||||
int 21h
|
||||
ret
|
||||
|
||||
; print CRLF
|
||||
crlf:
|
||||
mov dx, s_crlf
|
||||
jmp puts
|
||||
s_crlf db 0Dh, 0Ah, '$'
|
||||
|
||||
; print AX as four hex digits using INT 21h AH=02h
|
||||
puthex16:
|
||||
push ax
|
||||
mov al, ah
|
||||
call puthex8
|
||||
pop ax
|
||||
puthex8:
|
||||
push ax
|
||||
shr al, 1
|
||||
shr al, 1
|
||||
shr al, 1
|
||||
shr al, 1
|
||||
call nib
|
||||
pop ax
|
||||
and al, 0Fh
|
||||
nib:
|
||||
add al, '0'
|
||||
cmp al, '9'
|
||||
jbe .ok
|
||||
add al, 7
|
||||
.ok:
|
||||
mov dl, al
|
||||
mov ah, 02h
|
||||
int 21h
|
||||
ret
|
||||
|
||||
; print AX as unsigned decimal
|
||||
putdec:
|
||||
push bx
|
||||
push cx
|
||||
push dx
|
||||
xor cx, cx
|
||||
mov bx, 10
|
||||
.d1:
|
||||
xor dx, dx
|
||||
div bx
|
||||
push dx
|
||||
inc cx
|
||||
test ax, ax
|
||||
jnz .d1
|
||||
.d2:
|
||||
pop dx
|
||||
add dl, '0'
|
||||
mov ah, 02h
|
||||
int 21h
|
||||
loop .d2
|
||||
pop dx
|
||||
pop cx
|
||||
pop bx
|
||||
ret
|
||||
@@ -0,0 +1,8 @@
|
||||
; dbg_cc.asm -- target for a DEBUG session: a real one-byte breakpoint (CC) between two MOVs.
|
||||
bits 16
|
||||
org 0x100
|
||||
mov ax, 1111h
|
||||
int3 ; CC
|
||||
mov ax, 2222h
|
||||
mov ax, 4C00h
|
||||
int 21h
|
||||
@@ -0,0 +1,8 @@
|
||||
; dbg_cd03.asm -- the same program, but the breakpoint is the two-byte CD 03.
|
||||
bits 16
|
||||
org 0x100
|
||||
mov ax, 1111h
|
||||
db 0CDh, 03h ; CD 03 (NASM's `int 3` would emit exactly this)
|
||||
mov ax, 2222h
|
||||
mov ax, 4C00h
|
||||
int 21h
|
||||
@@ -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"
|
||||
@@ -0,0 +1,9 @@
|
||||
; hello21.asm -- the smallest useful INT 21h program: AH=09h then AH=4Ch with a return code
|
||||
bits 16
|
||||
org 0x100 ; .COM program
|
||||
mov ah, 09h ; AH selects the DOS function: write '$'-terminated string
|
||||
mov dx, msg ; DS:DX -> string (DS = CS in a .COM file)
|
||||
int 21h ; CD 21 -- the DOS entry point
|
||||
mov ax, 4C07h ; AH=4Ch terminate, AL=7 return code (ERRORLEVEL)
|
||||
int 21h
|
||||
msg db 'Hello from INT 21h, AH=09h', 0Dh, 0Ah, '$'
|
||||
@@ -0,0 +1,13 @@
|
||||
; int3_default.asm -- execute INT 3 with NO handler of our own and see whether the program survives.
|
||||
bits 16
|
||||
org 0x100
|
||||
mov dx, m1
|
||||
call puts
|
||||
int3
|
||||
mov dx, m2
|
||||
call puts
|
||||
mov ax, 4C00h
|
||||
int 21h
|
||||
m1 db 'before INT3', 0Dh, 0Ah, '$'
|
||||
m2 db 'after INT3 (program survived)', 0Dh, 0Ah, '$'
|
||||
%include "common.inc"
|
||||
@@ -0,0 +1,123 @@
|
||||
; int3_handler.asm -- install our own INT 3 handler (AH=25h), then trigger it twice:
|
||||
; once with the one-byte CC and once with the two-byte CD 03.
|
||||
; Prints how far past the trapping instruction the pushed return IP points, and the
|
||||
; state of IF (interrupt flag) inside the handler versus after IRET.
|
||||
bits 16
|
||||
org 0x100
|
||||
; --- save the old vector with AH=35h, install ours with AH=25h ---
|
||||
mov ax, 3503h
|
||||
int 21h ; ES:BX = current INT 3 handler
|
||||
mov [old_off], bx
|
||||
mov [old_seg], es
|
||||
mov ax, 2503h
|
||||
mov dx, handler
|
||||
int 21h ; DS:DX = new handler for vector 3
|
||||
|
||||
sti ; make sure IF=1 before we trap, so the handler's IF=0 is visible
|
||||
mov word [target], trap_cc
|
||||
mov dx, m_cc
|
||||
call puts
|
||||
trap_cc:
|
||||
int3 ; CC
|
||||
call report
|
||||
|
||||
mov word [target], trap_cd
|
||||
mov dx, m_cd
|
||||
call puts
|
||||
trap_cd:
|
||||
db 0CDh, 03h ; CD 03
|
||||
call report
|
||||
|
||||
; --- restore the old vector and exit ---
|
||||
push ds
|
||||
mov dx, [old_off]
|
||||
mov ds, [old_seg]
|
||||
mov ax, 2503h
|
||||
int 21h
|
||||
pop ds
|
||||
mov ax, 4C00h
|
||||
int 21h
|
||||
|
||||
; ------------------------------------------------------------------
|
||||
; The handler runs with IF=0 and TF=0 (the CPU cleared them when it took the interrupt).
|
||||
handler:
|
||||
push bp
|
||||
mov bp, sp
|
||||
push ax
|
||||
; stack now: [bp+0]=saved BP, [bp+2]=return IP, [bp+4]=return CS, [bp+6]=FLAGS
|
||||
mov ax, [bp+2]
|
||||
mov [ret_ip], ax
|
||||
mov ax, [bp+6]
|
||||
mov [flags_pushed], ax
|
||||
pushf
|
||||
pop ax ; FLAGS as the handler itself sees them
|
||||
mov [flags_inside], ax
|
||||
pop ax
|
||||
pop bp
|
||||
iret
|
||||
|
||||
report:
|
||||
mov dx, m_delta
|
||||
call puts
|
||||
mov ax, [ret_ip]
|
||||
sub ax, [target] ; how many bytes past the trapping instruction?
|
||||
call putdec
|
||||
mov dx, m_ifpush
|
||||
call puts
|
||||
mov ax, [flags_pushed]
|
||||
mov cl, 9
|
||||
shr ax, cl
|
||||
and al, 1
|
||||
add al, '0'
|
||||
mov dl, al
|
||||
mov ah, 02h
|
||||
int 21h
|
||||
mov dx, m_ifin
|
||||
call puts
|
||||
mov ax, [flags_inside]
|
||||
mov cl, 9
|
||||
shr ax, cl
|
||||
and al, 1
|
||||
add al, '0'
|
||||
mov dl, al
|
||||
mov ah, 02h
|
||||
int 21h
|
||||
mov dx, m_tfin
|
||||
call puts
|
||||
mov ax, [flags_inside]
|
||||
mov cl, 8
|
||||
shr ax, cl
|
||||
and al, 1
|
||||
add al, '0'
|
||||
mov dl, al
|
||||
mov ah, 02h
|
||||
int 21h
|
||||
mov dx, m_ifafter
|
||||
call puts
|
||||
pushf
|
||||
pop ax
|
||||
mov cl, 9
|
||||
shr ax, cl
|
||||
and al, 1
|
||||
add al, '0'
|
||||
mov dl, al
|
||||
mov ah, 02h
|
||||
int 21h
|
||||
jmp crlf
|
||||
|
||||
m_cc db 'CC (INT3): $'
|
||||
m_cd db 'CD 03 (INT 3): $'
|
||||
m_delta db 'return IP = trap address + $'
|
||||
m_ifpush db ', IF pushed = $'
|
||||
m_ifin db ', IF in handler = $'
|
||||
m_tfin db ', TF in handler = $'
|
||||
m_ifafter db ', IF after IRET = $'
|
||||
|
||||
old_off dw 0
|
||||
old_seg dw 0
|
||||
target dw 0
|
||||
ret_ip dw 0
|
||||
flags_pushed dw 0
|
||||
flags_inside dw 0
|
||||
|
||||
%include "common.inc"
|
||||
@@ -0,0 +1,9 @@
|
||||
; opcodes.asm -- which bytes does each spelling assemble to? (read the listing, never run)
|
||||
bits 16
|
||||
org 0x100
|
||||
int3 ; the one-byte breakpoint -> CC
|
||||
int 3 ; "INT n" with n = 3 in NASM -> CD 03
|
||||
db 0CDh, 03h ; the same two bytes, written out -> CD 03
|
||||
int 21h ; DOS entry point -> CD 21
|
||||
int 10h ; BIOS video -> CD 10
|
||||
into ; one-byte INTO -> CE
|
||||
Reference in New Issue
Block a user