The listing ZERODIV.ASM (Figure 13-4) illustrates some of the principles and guidelines on the previous pages. It is an interrupt handler for the divide-by-zero internal interrupt (type 0). ZERODIV is loaded as a .COM file (usually by a command in the system's AUTOEXEC file) but makes itself permanently resident in memory as long as the system is running.
The ZERODIV program has two major portions: the initialization portion and the interrupt handler.
The initialization procedure (called init in the program listing) is executed only once, when the ZERODIV program is executed from the MS-DOS level. The init procedure takes over the type 0 interrupt vector, prints a sign-on message, then performs a terminate-and-stay-resident exit to MS-DOS. This special exit reserves the memory occupied by the ZERODIV program, so that it is not overwritten by subsequent application programs.
The interrupt handler (called zdiv in the program listing) receives control when a divide-by-zero interrupt occurs. The handler preserves all registers and then prints a message to the user asking whether to continue or to abort the program. We can use the MS-DOS console I/O functions within this particular interrupt handler because we can safely presume that the application was in control when the interrupt occurred; thus, there should be no chance of accidentally making overlapping calls upon the operating system.
If the user enters a C to continue, the handler simply restores all the registers and performs an IRET (INTERRUPT RETURN) to return control to the application. (Of course, the results of the divide operation will be useless.) If the user enters Q to quit, the handler exits to MS-DOS. Int 21H Function 4CH is particularly convenient in this case because it allows the program to pass a return code and at the same time is the only termination function that does not rely on the contents of any of the segment registers.
For an example of an interrupt handler for external (communications port) interrupts, see the TALK terminal-emulator program in Chapter 7. You may also want to look again at the discussions of Ctrl-C and critical-error exception handlers in Chapters 5 and 8.
name zdivide
page 55,132
title ZERODIV--Divide-by-zero handler
;
; ZERODIV.ASM--Terminate-and-stay-resident handler
; for divide-by-zero interrupts
;
; Copyright 1988 Ray Duncan
;
; Build: C>MASM ZERODIV;
; C>LINK ZERODIV;
; C>EXE2BIN ZERODIV.EXE ZERODIV.COM
; C>DEL ZERODIV.EXE
;
; Usage: C>ZERODIV
;
cr equ 0dh ; ASCII carriage return
lf equ 0ah ; ASCII linefeed
beep equ 07h ; ASCII bell code
backsp equ 08h ; ASCII backspace code
_TEXT segment word public 'CODE'
org 100H
assume cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT
init proc near ; entry point at load time
; capture vector for
; interrupt zero...
mov dx,offset zdiv ; DS:DX = handler address
mov ax,2500h ; function 25h = set vector
; interrupt type = 0
int 21h ; transfer to MS-DOS
; print sign-on message
mov dx,offset msg1 ; DS:DX = message address
mov ah,9 ; function 09h = display string
int 21h ; transfer to MS-DOS
; DX = paragraphs to reserve
mov dx,((offset pgm_len+15)/16)+10h
mov ax,3100h ; function 31h = terminate and
; stay resident
int 21h ; transfer to MS-DOS
init endp
zdiv proc far ; this is the divide-by-
; zero interrupt handler
sti ; enable interrupts
push ax ; save registers
push bx
push cx
push dx
push si
push di
push bp
push ds
push es
mov ax,cs ; make data addressable
mov ds,ax
; display message
; "Continue or Quit?"
mov dx,offset msg2 ; DS:DX = message address
mov ah,9 ; function 09h = display string
int 21h ; transfer to MS-DOS
zdiv1: mov ah,1 ; function 01h = read keyboard
int 21h ; transfer to MS-DOS
or al,20h ; fold char to lowercase
cmp al,'c' ; is it C or Q?
je zdiv3 ; jump, it's a C
cmp al,'q'
je zdiv2 ; jump, it's a Q
; illegal entry, send beep
; and erase the character
mov dx,offset msg3 ; DS:DX = message address
mov ah,9 ; function 09h = display string
int 21h ; transfer to MS-DOS
jmp zdiv1 ; try again
zdiv2: ; user chose "Quit"
mov ax,4cffh ; terminate current program
int 21h ; with return code = 255
zdiv3: ; user chose "Continue"
; send CR-LF pair
mov dx,offset msg4 ; DS:DX = message address
mov ah,9 ; function 09h = print string
int 21h ; transfer to MS-DOS
; what CPU type is this?
xor ax,ax ; to find out, we'll put
push ax ; zero in the CPU flags
popf ; and see what happens
pushf
pop ax
and ax,0f000h ; 8086/8088 forces
cmp ax,0f000h ; bits 12-15 true
je zdiv5 ; jump if 8086/8088
; otherwise we must adjust
; return address to bypass
; the divide instruction...
mov bp,sp ; make stack addressable
lds bx,[bp+18] ; get address of the
; faulting instruction
mov bl,[bx+1] ; get addressing byte
and bx,0c7h ; isolate mod & r/m fields
cmp bl,6 ; mod 0, r/m 6 = direct
jne zdiv4 ; not direct, jump
add word ptr [bp+18],4
jmp zdiv5
zdiv4: mov cl,6 ; otherwise isolate mod
shr bx,cl ; field and get instruction
mov bl,cs:[bx+itab] ; size from table
add [bp+18],bx
zdiv5: pop es ; restore registers
pop ds
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
iret ; return from interrupt
zdiv endp
msg1 db cr,lf ; load-time sign-on message
db 'Divide by Zero Interrupt '
db 'Handler installed.'
db cr,lf,'$'
msg2 db cr,lf,lf ; interrupt-time message
db 'Divide by Zero detected: '
db cr,lf,'Continue or Quit (C/Q) ? '
db '$'
msg3 db beep ; used if bad entry
db backsp,' ',backsp,'$'
msg4 db cr,lf,'$' ; carriage return-linefeed
; instruction size table
itab db 2 ; mod = 0
db 3 ; mod = 1
db 4 ; mod = 2
db 2 ; mod = 3
pgm_len equ $-init ; program length
_TEXT ends
end init
Figure 13-4. A simple example of an interrrupt handler for use within the MS-DOS environment. ZERODIV makes itself permanently resident in memory and handles the CPU's internal divide-by-zero interrupt.