|
Table of Content | Chapter Seventeen
(Part 3) |
| CHAPTER
SEVENTEEN: INTERRUPTS TRAPS AND EXEPTIONS (Part 2) |
|
| 17.3 -
Exceptions 17.3.1 - Divide Error Exception (INT 0) 17.3.2 - Single Step (Trace) Exception (INT 1) 17.3.3 - Breakpoint Exception (INT 3) |
17.3.4 -
Overflow Exception (INT 4/INTO) 17.3.5 - Bounds Exception (INT 5/BOUND) 17.3.6 - Invalid Opcode Exception (INT 6) 17.3.7 - Coprocessor Not Available (INT 7) |
| 17.3 Exceptions | |
Exceptions occur (are raised) when an abnormal condition occurs during execution. There are fewer than eight possible exceptions on machines running in real mode. Protected mode execution provides many others but we will not consider those here we will only consider those exceptions interesting to those working in real mode[4].
Although exception handlers are user defined the 80x86 hardware defines the exceptions that can occur. The 80x86 also assigns a fixed interrupt number to each of the exceptions. The following sections describe each of these exceptions in detail.
In general
an exception handler should preserve all
registers. However
there are several special cases where you may want to tweak a register
value before returning. For example
if you get a bounds violation
you may want to modify
the value in the register specified by the bound instruction before
returning. Nevertheless
you should not arbitrarily modify registers in an exception
handling routine unless you intend to immediately abort the execution of your program.
17.3.1 Divide Error Exception (INT 0)
This exception occurs whenever you attempt to divide a
value by zero or the quotient does not fit in the destination register when using the div
or idiv instructions. Note that the FPU's fdiv and fdivr instructions do not
raise this exception.
MS-DOS provides a generic divide exception handler that prints a message like "divide error" and returns control to MS-DOS. If you want to handle division errors yourself you must write your own exception handler and patch the address of this routine into location 0:0.
On 8086
8088
80186
and 80188 processors
the return
address on the stack points at the next instruction after the divide instruction. On the
80286 and later processors
the return address points at the beginning of the divide
instruction (include any prefix bytes that appear). When a divide exception occurs
the
80x86 registers are unmodified; that is
they contain the values they held when the 80x86
first executed the div or idiv instruction.
When a divide exception occurs there are three reasonable things you can attempt: abort the program (the easy way out) jump to a section of code that attempts to continue program execution in view of the error (e.g. as the user to reenter a value) or attempt to figure out why the error occurred correct it and reexecute the division instruction. Few people choose this last alternative because it is so difficult.
17.3.2 Single Step (Trace) Exception (INT 1)
The single step exception occurs after every instruction if the trace bit in the flags register is equal to one. Debuggers and other programs will often set this flag so they can trace the execution of a program.
When this exception occurs the return address on the stack is the address of the next instruction to execute. The trap handler can decode this opcode and decide how to proceed. Most debuggers use the trace exception to check for watchpoints and other events that change dynamically during program execution. Debuggers that use the trace exception for single stepping often disassemble the next instruction using the return address on the stack as a pointer to that instruction's opcode bytes.
Generally a single step exception handler should preserve all 80x86 registers and other state information. However you will see an interesting use of the trace exception later in this text where we will purposely modify register values to make one instruction behave like another.
Interrupt one is also shared by the debugging exceptions capabilities of 80386 and later processors. These processors provide on-chip support via debugging registers. If some condition occurs that matches a value in one of the debugging registers the 80386 and later CPUs will generate a debugging exception that uses interrupt vector one.
17.3.3 Breakpoint Exception (INT 3)
The breakpoint exception is actually a trap
not an
exception. It occurs when the CPU executes an int 3 instruction. However
we
will consider it an exception since programmers rarely put int 3 instructions
directly into their programs. Instead
a debugger like Codeview often manages the
placement and removal of int 3 instructions.
When the 80x86 calls a breakpoint exception handling
routine
the return address on the stack is the address of the next instruction after the
breakpoint opcode. Note
however
that there are actually two int
instructions that transfer control through this vector. Generally
though
it is the
one-byte int 3 instruction whose opcode is 0cch; otherwise it is the two byte
equivalent: 0cdh
03h.
17.3.4 Overflow Exception (INT 4/INTO)
The overflow exception
like int 3
is
technically a trap. The CPU only raises this exception when you execute an into
instruction and the overflow flag is set. If the overflow flag is clear
the into
instruction is effectively a nop
if the overflow flag is set
into behaves
like an int 4 instruction. Programmers can insert an into
instruction after an integer computation to check for an arithmetic overflow. Using into
is equivalent to the following code sequence:
<< Some integer arithmetic code »
jno GoodCode
int 4
GoodCode:
One big advantage to the into instruction is
that it does not flush the pipeline or prefetch queue if the overflow flag is not set.
Therefore
using the into instruction is a good technique if you provide a
single overflow handler (that is
you don't have some special code for each sequence where
an overflow could occur).
The return address on the stack is the address of the next
instruction after into. Generally
an overflow handler does not return to
that address. Instead
it will usually abort the program or pop the return address and
flags off the stack and attempt the computation in a different way.
17.3.5 Bounds Exception (INT 5/BOUND)
Like into
the bound instruction
will cause a conditional exception. If the specified register is outside the specified
bounds
the bound instruction is equivalent to an int 5
instruction; if the register is within the specified bounds
the bound
instruction is effectively a nop.
The return address that bound pushes is the
address of the bound instruction itself
not the instruction following bound.
If you return from the exception without modifying the value in the register (or adjusting
the bounds)
you will generate an infinite loop because the code will reexecute the bound
instruction and repeat this process over and over again.
One sneaky trick with the bound instruction is
to generate a global minimum and maximum for an array of signed integers. The following
code demonstrates how you can do this:
; This program demonstrates how to compute the minimum and maximum values
; for an array of signed integers using the bound instruction
.xlist
.286
include stdlib.a
includelib stdlib.lib
.list
dseg segment para public 'data'
; The following two values contain the bounds for the BOUND instruction.
LowerBound word ?
UpperBound word ?
; Save the INT 5 address here:
OldInt5 dword ?
; Here is the array we want to compute the minimum and maximum for:
Array word 1
2
-5
345
-26
23
200
35
-100
20
45
word 62
-30
-1
21
85
400
-265
3
74
24
-2
word 1024
-7
1000
100
-1000
29
78
-87
60
ArraySize = ($-Array)/2
dseg ends
cseg segment para public 'code'
assume cs:cseg
ds:dseg
; Our interrupt 5 ISR. It compares the value in AX with the upper and
; lower bounds and stores AX in one of them (we know AX is out of range
; by virtue of the fact that we are in this ISR).
;
; Note: in this particular case
we know that DS points at dseg
so this
; ISR will get cheap and not bother reloading it.
;
; Warning: This code does not handle the conflict between bound/int5 and
; the print screen key. Pressing prtsc while executing this code may
; produce incorrect results (see the text).
BoundISR proc near
cmp ax
LowerBound
jl NewLower
; Must be an upper bound violation.
mov UpperBound
ax
iret
NewLower: mov LowerBound
ax
iret
BoundISR endp
Main proc
mov ax
dseg
mov ds
ax
meminit
; Begin by patching in the address of our ISR into int 5's vector.
mov ax
0
mov es
ax
mov ax
es:[5*4]
mov word ptr OldInt5
ax
mov ax
es:[5*4 + 2]
mov word ptr OldInt5+2
ax
mov word ptr es:[5*4]
offset BoundISR
mov es:[5*4 + 2]
cs
; Okay
process the array elements. Begin by initializing the upper
; and lower bounds values with the first element of the array.
mov ax
Array
mov LowerBound
ax
mov UpperBound
ax
; Now process each element of the array:
mov bx
2 ;Start with second element.
mov cx
ArraySize
GetMinMax: mov ax
Array[bx]
bound ax
LowerBound
add bx
2 ;Move on to next element.
loop GetMinMax ;Repeat for each element.
printf
byte "The minimum value is %d\n"
byte "The maximum value is %d\n"
0
dword LowerBound
UpperBound
; Okay
restore the interrupt vector:
mov ax
0
mov es
ax
mov ax
word ptr OldInt5
mov es:[5*4]
ax
mov ax
word ptr OldInt5+2
mov es:[5*4+2]
ax
Quit: ExitPgm ;DOS macro to quit program.
Main endp
cseg ends
sseg segment para stack 'stack'
stk db 1024 dup ("stack ")
sseg ends
zzzzzzseg segment para public 'zzzzzz'
LastBytes db 16 dup (?)
zzzzzzseg
ends
end Main
If the array is large and the values appearing in the array
are relatively random
this code demonstrates a fast way to determine the minimum and
maximum values in the array. The alternative
comparing each element against the upper and
lower bounds and storing the value if outside the range
is generally a slower approach.
True
if the bound instruction causes a trap
this is much slower than the
compare and store method. However
it a large array with random values
the bounds
violation will rarely occur. Most of the time the bound instruction will
execute in 7-13 clock cycles and it will not flush the pipeline or the prefetch queue[5].
Warning: IBM
in their infinite wisdom
decided to use int
5 as the print screen operation. The default int 5 handler will dump
the current contents of the screen to the printer. This has two implications for those who
would like to use the bound instruction in their programs. First
if you do
not install your own int 5 handler and you execute a bound
instruction that generates a bound exception
you will cause the machine to print the
contents of the screen. Second
if you press the PrtSc key with your int 5 handler
installed
BIOS will invoke your handler. The former case is a programming error
but this
latter case means you have to make your bounds exception handler a little smarter. It
should look at the byte pointed at by the return address. If this is an int 5
instruction opcode (0cdh)
then you need to call the original int 5 handler
or simply return from interrupt (do you want them pressing the PrtSc key at that point?).
If it is not an int 5 opcode
then this exception was probably raised by the bound
instruction. Note that when executing a bound instruction the return address
may not be pointing directly at a bound opcode (0c2h). It may be pointing at
a prefix byte to the bound instruction (e.g.
segment
addressing mode
or
size override). Therefore
it is best to check for the int 5 opcode.
17.3.6 Invalid Opcode Exception (INT 6)
The 80286 and later processors raise this exception if you attempt to execute an opcode that does not correspond to a legal 80x86 instruction. These processors also raise this exception if you attempt to execute a bound lds les lidt or other instruction that requires a memory operand but you specify a register operand in the mod/rm field of the mod/reg/rm byte.
The return address on the stack points at the illegal
opcode. By examining this opcode
you can extend the instruction set of the 80x86. For
example
you could run 80486 code on an 80386 processor by providing subroutines that
mimic the extra 80486 instructions (like bswap
cmpxchg
etc.).
17.3.7 Coprocessor Not Available (INT 7)
The 80286 and later processors raise this exception if you attempt to execute an FPU (or other coprocessor) instruction without having the coprocessor installed. You can use this exception to simulate the coprocessor in software.
On entry to the exception handler the return address points at the coprocessor opcode that generated the exception.
[4] For more details on exceptions in protected mode see the bibliography.
[5] Note that on the 80486 and later processors the bound instruction may actually be slower than the corresponding straight line code.
|
Table of Content | Chapter Seventeen
(Part 3) |
Chapter Seventeen: Interrupts
Traps
and Exeptions (Part 2)
29 SEP 1996