ZERODIV, an Example Interrupt Handler

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.