Welcome to denMike's tiny page!
Home
Guestbook
Contact
 
Husky
 
Corona virus
Electronics
Fantasy
mtech.dk
Programming
  Resources in .exe Files

  Pascal Runtime Error 200

  The Pentium F0 bug

  Number Printing Routing

  Using the IOE Interrupt

 
Software
Space
Tilbud
Various Links
 
USA 2001
ISU MSS03
USA 2006
South Africa 2008


Last updated: May 26th, 1997
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