How to use the Invalid Opcode Exception interrupt.
Contents of this document
Introduction
The interrupt handler
The code
Installing and removing the interrupt handler
The complete program
Introduction.
Here is a simple way to use the Invalid Opcode Exception interrupt (06h) supported
on Intels 80286+ processors. The interrupt is called whenever an invalid opcode
is executed (most protected-mode instructions are considered invalid in real mode).
The return address is pointing to the beginning of the invalid instruction.
With proper programming, this interrupt may be used to emulate instructions
which do not exist; many 386 BIOSes emulate the undocumented 80286 instruction
LOADALL which was removed from the 80386+.
The Processor Extension Not Available interrupt (07h) can be used in
a similar manner. It is automatically called if a coprocessor instruction is
encountered when no coprocessor is installed. Therefore it can be used to
emulate a numeric coprocessor in software.
The interrupt handler.
I will use a simple IOE to explain how you use this technique:
; Input: DX = Return address
OurINT6 proc far
pop ax ; get IP from stack
mov ax,dx ; point to return address
push ax ; save it
iret ; go split
OurINT6 endp
It actually is as simple as it can be. When the interrupt is called the stack
contains the CS:IP of the invalid instruction. We don't need the IP (instruction pointer),
but we want to return to the offset in DX, therefore we first remove the IP
from the stack, then we push the return address, and return (iret).
The code.
The code with the invalid instruction could look like this:
.586
mov dx,offset @NoCPUID ; set destination location
xor eax,eax ; real dirty way to detect 80386+
cpuid ; try CPUID instruction
;additional cpuid processing
@NoCPUID:
.286
PRINT_MSG CPUIDMsg ; print CPUID message
PRINT_MSG CRLFMsg ; print
First we set the return address where we want the execution to continue if we
run into an invalid instruction. Then we try the first possible problem:
xor eax,eax this will trash a 286 cpu since it is a 32-bit instruction.
Therefore our exception-handler will be called, and we will jump to the NoCPUID
label.
If we're running on a 386 cpu, we aren't safe yet. The cpuid instruction
were first introduced in 1993, long after the first i486 cpus. Thus we will
trash in the cpuid, and again jump to the NoCPUID label.
If, however, we're running on a cpu supporting cpuid we'll continue running
the additional cpuid processing, or at least until we encounter another
invalid instruction!
Installing and removing the interrupt handler.
This really is easy. Let's install it first:
push es ; save es
mov ax,seg INTSEG
mov es,ax
mov ax,offset OurINT6 ; get pointer to our INT6 handler
mov dx,cs ; get our code segment
xchg ax,word ptr es:INT06 ; swap 'em
xchg dx,word ptr es:INT06[2] ; swap vector
mov word ptr OrigINT06,ax ; save original vector
mov word ptr OrigINT06[2],dx; vector now saved
pop es ; restore original segment
We now have the old int 6 handler in the OrigINT06 variable, so we can reinstall
it when we're done. Let's do that:
push es ; save es
mov ax,seg INTSEG
mov es,ax
mov ax,word ptr OrigINT06[0]; get pointer to our INT6 handler
mov dx,word ptr OrigINT06[2]; get our code segment
xchg ax,word ptr es:INT06 ; swap 'em
xchg dx,word ptr es:INT06[2] ; swap vector
pop es ; restore original segment
Quite easy, right?
The complete program.
Here goes the complete program, just in case you don't believe me. I used
TASM to compile it, but MASM should work too.
;-----------------------------------------------------------------------------
; Assembler directives
;-----------------------------------------------------------------------------
.xlist ; disable list file
.286
;-----------------------------------------------------------------------------
; Macros
;-----------------------------------------------------------------------------
CPUID MACRO
db 0Fh,0A2h
ENDM
PRINT_MSG MACRO MSG
mov ah,9
mov dx,offset MSG
int 21h
ENDM
.list
;-----------------------------------------------------------------------------
; Dummy segments
;-----------------------------------------------------------------------------
INTSEG segment at 0
org 6*4
INT06 dd ?
INTSEG ends
;-----------------------------------------------------------------------------
; Data segment
;-----------------------------------------------------------------------------
_DATA segment use16 para public 'DATA'
;-----------------------------------------------------------------------------
; Misc data storage.
;-----------------------------------------------------------------------------
CPUIDVal dd 0 ; Results of CPUID instruction
OrigINT06 dd 0 ; Temp holding spot for INT06 vector
;-----------------------------------------------------------------------------
; String messages used for formatting the screen output
;-----------------------------------------------------------------------------
CRLFMsg db 0dh,0ah,24h
CPUIDMsg db "Microprocessor ID: "
FamilyString db "Unknown "
db " Vendor String: "
IDString db "Not Detected",0dh,0ah,24h
_DATA ENDS
;-----------------------------------------------------------------------------
; Beginning of main code segment
;-----------------------------------------------------------------------------
_TEXT segment para public use16 'CODE'
ASSUME CS:_TEXT, DS:_DATA, ES:_DATA, SS:STACKSEG
;-----------------------------------------------------------------------------
; Code starts here
; * Set up stack
;-----------------------------------------------------------------------------
OPCODE proc far
mov ax,seg STACKSEG ; setup stack segment
mov ss,ax
mov sp,size StackPtr
xor ax,ax ; clear it
pushf
push ds ; save far return on stack
push ax
;-----------------------------------------------------------------------------
; * Disable interrupts during this test
; * Set up data segments
;-----------------------------------------------------------------------------
cli ; disable interrupts
mov ax,seg _DATA ; get data segment
mov ds,ax
mov es,ax
;-----------------------------------------------------------------------------
; Install INT06 (Invalid opcode) exception handler
;-----------------------------------------------------------------------------
@@: push es ; save
mov ax,seg INTSEG
mov es,ax
mov ax,offset OurINT6 ; get pointer to our INT6 handler
mov dx,cs ; get our code segment
xchg ax,word ptr es:INT06 ; swap 'em
xchg dx,word ptr es:INT06[2] ; swap vector
mov word ptr OrigINT06,ax ; save original vector
mov word ptr OrigINT06[2],dx; vector now saved
pop es ; restore original segment
;-----------------------------------------------------------------------------
; Execute CPUID instruction and print results on string
;
; This is a real down-and-dirty way to detect CPUID. I've installed an
; invalid exception handler, and pointed DX to a return address in the case
; that an invalid opcode exception occurs. If we're an 80286, then the
; 'xor eax,eax' instruction will cause the invalid opcode fault. If we're
; running on a processor that doesn't support CPUID, the 'cpuid' instruction
; will cause the invalid opcode fault. In either case, if the fault occurs,
; execution continues beyond the processor detection code. At that point,
; the processor stepping information is printed anyways with a default
; response. If CPUID does work, then the default results are filled in with
; the appropriate CPUID return values.
;-----------------------------------------------------------------------------
.386
mov dx,offset @NoCPUID ; set destination location
xor eax,eax ; real dirty way to detect 80386+
cpuid ; try CPUID instruction
mov dword ptr IDString[0],ebx
mov dword ptr IDString[4],edx
mov dword ptr IDString[8],ecx
mov eax,1 ; do next level of CPUID
cpuid ; get processor stepping
mov CPUIDVal,eax ; save it
mov si,offset CPUIDVal ; get source of CPUID stepping
mov di,offset FamilyString ; get destination of string
mov cx,4 ; # of bytes to convert
call hex_string ; convert data
@NoCPUID:
.286
PRINT_MSG CPUIDMsg ; print CPUID message
PRINT_MSG CRLFMsg ; print
;-----------------------------------------------------------------------------
; Restore invalid opcode interrupt handler
;-----------------------------------------------------------------------------
push es ; save
mov ax,seg INTSEG
mov es,ax
mov ax,word ptr OrigINT06[0]; get pointer to our INT6 handler
mov dx,word ptr OrigINT06[2]; get our code segment
xchg ax,word ptr es:INT06 ; swap 'em
xchg dx,word ptr es:INT06[2] ; swap vector
pop es ; restore original segment
;-----------------------------------------------------------------------------
; Terminate and return to DOS.
;-----------------------------------------------------------------------------
iret ; return to DOS
OPCODE endp
;-----------------------------------------------------------------------------
; HEX_STRING: Convert a string of 8-bit hex numbers to ASCII.
; Input: DS:SI = Pointer to hex data
; ES:DI = Buffer to get output
; CX = # of bytes to convert
; Output: ES:DI = Filled in w/ ASCII hex#
;-----------------------------------------------------------------------------
Hex_String proc near
;-----------------------------------------------------------------------------
jcxz @Hex_str_exit ; go split
push ax ; [bp][0ah]
push cx ; [bp][8]
push dx ; [bp][6]
push si ; [bp][4]
push di ; [bp][2]
push bp ; [bp]
mov bp,sp
add si,cx
@B: dec si
mov al,ds:[si] ; get hex digit
mov dl,al
mov cl,4 ; shift count
rol dl,cl
mov al,dl ; save it
and al,0fh ; keep low nibble
daa
add al,0f0h
adc al,40h ; here is the ASCII
stosb ; save it
mov cl,4 ; shift count
rol dl,cl
mov al,dl ; save it
and al,0fh ; keep low nibble
daa
add al,0f0h
adc al,40h ; here is the ASCII
stosb ; save it
dec word ptr [bp][8] ; are we done yet?
jnz @B
pop bp
pop di
pop si
pop dx
pop cx
pop ax
@Hex_str_exit:
ret
Hex_String endp
;-----------------------------------------------------------------------------
; This is a real down-and-dirty invalid opcode exception handler. All this
; handler does, is take the value in DX and use it as the return address.
;-----------------------------------------------------------------------------
; Input: DX = Return address
; Output: None
;-----------------------------------------------------------------------------
OurINT6 proc far
pop ax ; get IP from stack
mov ax,dx ; point to return address
push ax ; save it
iret ; go split
OurINT6 endp
_TEXT ENDS
STACKSEG segment para public STACK
;-----------------------------------------------------------------------------
; Stack segment
;-----------------------------------------------------------------------------
StackPtr db 400h dup (?)
STACKSEG ends
end OPCODE
|