Structured Exception Handling is a powerful feature of all Win32 platforms
that allows a program to recover from any critical errors like BOUND, divide
overflow, page missing or general protection fault. It is documented only for
C-level usage (try-except/finally syntax), and no documentation for low level
languages exists. Therefore I will try to show how to use it.
The starting point for Structured Exception Handling, SEH, is the Thread
Info Block. TIB, as almost all the other structures, is described in winnt.h
file that comes with PlatformSDK.
struc NT_TIB
ExceptionList dd ? ; Used by SEH
StackBase dd ? ; Used by functions to check for
StackLimit dd ? ; stack overflow
SubSystemTib dd ? ; ?
FiberDataOrVersion dd ? ; ?
ArbitraryUserPointer dd ? ; ?
Self dd ? ; Linear address of the TIB
ends
TIB is accessible at address fs:0. NT_TIB.Self contains linear address of TIB,
base of FS segment.
When an exception occurs, the system uses (dword)fs:0, NT_TIB.ExceptionList
to find an exception handler and execute it. The exception list entry is very
simple:
struc ELENTRY
Next dd ? ; Points to next entry in the list
ExceptionHandler dd ? ; User callback - exception hook
Optional db X dup (?) ; Exception Handler data
EntryTerminator dd -1 ; Optional
ends
C compilers usually keep some additional information in ELENTRY.Optional
field of varying size and usually terminated with (dword)-1. Both .Optional
and .EntryTerminator fields are not required.
Before calling an exception handler, the exception manager pushes
ExceptionRecord and ContextRecord onto the stack. These structures identify an
exception and processor state before it. The exception manager adds also its
own entry to the exception list.
Exception handler is in fact a typical callback. It is not however
installed by any API function, but appended in ELENTRY into the exception
list.
EXCEPTION_DISPOSITION __cdecl excepthandler (
struct EXCEPTIONRECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext
);
The exception handler uses C-style calling convention, it does not release
arguments while returning. The most important parameters are ExceptionRecord
and ContextRecord, described at the end of this text, that point to the pushed
corresponding structures. I do not have yet any idea what is the purpose of
EstablisherFrame and DispatcherContext.
struc EXCEPTION_RECORD
ExceptionCode dd ? ; See at the end of this text
ExceptionFlags dd ?
ExceptionRecord dd ? ; ?
ExceptionAddress dd ? ; Linear address of faulty instruction
NumberParameters dd ? ; Corresponds to the field below
ExceptionInformation dd 15 dup (?) ; ?
ends
Exception flags are:
EXCEPTION_NONCONTINUABLE = 1
EXCEPTION_UNWINDING = 2
EXCEPTION_UNWINDING_FOR_EXIT = 4
The exception handler has two possible ways of proceeding. It can return to
the exception manager, or it can unwind the stack and continue the program. In
the first case it has to return one of the following values:
enum EXCEPTION_DISPOSITION \
ExceptionContinueExecution = 0,\
ExceptionContinueSearch = 1,\
ExceptionNestedException = 2,\
ExceptionCollidedUnwind = 3
The value of zero forces the exception manager to continue the program
at saved in context cs:eip, which may be altered by the exception handler. The
value of 1 causes the exception manager to call another exception handler in
the exception list. Values 2 and 3 inform the exception manager that an error
occured - an exception-in-exception happened, or the handler wanted to unwind
the stack during another handler of higher instance was doing this already.
The other case can be determined if one of .ExceptionFlags is
EXCEPTION_UNWINDING or EXCEPTION_UNWINDING_FOR_EXIT.
While appending a new exception handler to the exception list, a common
practice is to push new ELENTRY onto the stack. This way unwinding the stack
can be done simply by skipping the exception manager's entry and restoring the
stack pointer.
Here is an example of exception handling.
----Start-of-file-------------------------------------------------------------
ideal
p686n
model flat, stdcall
O equ <offset>
struc EXCEPTION_RECORD
ExceptionCode dd ?
ExceptionFlags dd ?
ExceptionRecord dd ?
ExceptionAddress dd ?
NumberParameters dd ?
ExceptionInformation dd 15 dup (?)
ends
procdesc wsprintfA c :dword, :dword, :dword:?
procdesc MessageBoxA :dword, :dword, :dword, :dword
procdesc ExitProcess :dword
udataseg
ExCode dd ?
szCode db 12 dup (?)
dataseg
szWindowTitle db 'Exception code', 0
szFormat db '%0X', 0
codeseg
proc main
; Install exception handler
push O ExceptionHandler
push [dword fs:0] ; ELENTRY.Next
mov [fs:0], esp ; Append new ELENTRY
; Cause Invalid Opcode exception
ud2
; Display exception code and quit
_Continue: call wsprintfA, O szCode, O szFormat, [ExCode]
call MessageBoxA, 0, O szCode, O szWindowTitle, 0
call ExitProcess, 0
endp
proc ExceptionHandler c ExceptionRecord, EF, ContextRecord, DC
; Save exception code
mov eax, [ExceptionRecord]
mov ecx, [(EXCEPTION_RECORD eax).ExceptionCode]
mov [ExCode], ecx
; Unwind the stack
mov eax, [fs:0] ; Exception Manager's entry
mov esp, [eax] ; Our entry
pop [dword fs:0] ; Restore fs:0
add esp, 4 ; Skip ExHandler address
jmp _Continue
endp
end main
----End-of-file---------------------------------------------------------------
The above source should be compiled with TASM 5.0r or later like this:
tasm32 /ml except.asm
tlink32 /x /Tpe /aa /c /V4.0 except.obj,,, LIBPATH\import32.lib
And here are other important constants and structures, all defined in
winnt.h PlatformSDK file.
Exception codes:
STATUS_SEGMENT_NOTIFICATION = 040000005h
STATUS_GUARD_PAGE_VIOLATION = 080000001h
STATUS_DATATYPE_MISALIGNMENT = 080000002h
STATUS_BREAKPOINT = 080000003h
STATUS_SINGLE_STEP = 080000004h
STATUS_ACCESS_VIOLATION = 0C0000005h
STATUS_IN_PAGE_ERROR = 0C0000006h
STATUS_INVALID_HANDLE = 0C0000008h
STATUS_NO_MEMORY = 0C0000017h
STATUS_ILLEGAL_INSTRUCTION = 0C000001Dh
STATUS_NONCONTINUABLE_EXCEPTION = 0C0000025h
STATUS_INVALID_DISPOSITION = 0C0000026h
STATUS_ARRAY_BOUNDS_EXCEEDED = 0C000008Ch
STATUS_FLOAT_DENORMAL_OPERAND = 0C000008Dh
STATUS_FLOAT_DIVIDE_BY_ZERO = 0C000008Eh
STATUS_FLOAT_INEXACT_RESULT = 0C000008Fh
STATUS_FLOAT_INVALID_OPERATION = 0C0000090h
STATUS_FLOAT_OVERFLOW = 0C0000091h
STATUS_FLOAT_STACK_CHECK = 0C0000092h
STATUS_FLOAT_UNDERFLOW = 0C0000093h
STATUS_INTEGER_DIVIDE_BY_ZERO = 0C0000094h
STATUS_INTEGER_OVERFLOW = 0C0000095h
STATUS_PRIVILEGED_INSTRUCTION = 0C0000096h
STATUS_STACK_OVERFLOW = 0C00000FDh
STATUS_CONTROLCEXIT = 0C000013Ah
STATUS_FLOAT_MULTIPLE_FAULTS = 0C00002B4h
STATUS_FLOAT_MULTIPLE_TRAPS = 0C00002B5h
STATUS_ILLEGAL_VLM_REFERENCE = 0C00002C0h
Context flags:
CONTEXT_i386 = 000010000h
CONTEXT_i486 = 000010000h
CONTEXT_CONTROL = (CONTEXT_i386 or 1) ; SS:ESP, CS:EIP, EFLAGS, EBP
CONTEXT_INTEGER = (CONTEXT_i386 or 2) ; EAX, EBX,..., ESI, EDI
CONTEXT_SEGMENTS = (CONTEXT_i386 or 4) ; DS, ES, FS, GS
CONTEXT_FLOATING_POINT = (CONTEXT_i386 or 8) ; 387 state
CONTEXT_DEBUG_REGISTERS = (CONTEXT_i386 or 16); DB 0-3,6,7
CONTEXT_EXTENDED_REGISTERS = (CONTEXT_i386 or 32); cpu specific extensions
CONTEXT_FULL = (CONTEXT_CONTROL or CONTEXT_INTEGER or\
CONTEXT_SEGMENTS)
Context structure:
struc CONTEXT
ContextFlags dd ? ; CONTEXT_??? flags
Dr0 dd ? ; Debug registers
Dr1 dd ?
Dr2 dd ?
Dr3 dd ?
Dr6 dd ?
Dr7 dd ?
ControlWord dd ? ; FPU context
StatusWord dd ?
TagWord dd ?
ErrorOffset dd ?
ErrorSelector dd ?
DataOffset dd ?
DataSelector dd ?
RegisterArea dt 8 dup (?)
Cr0NpxState dd ?
SegGs dd ? ; Segment registers
SegFs dd ?
SegEs dd ?
SegDs dd ?
Edi dd ? ; Integer registers
Esi dd ?
Ebx dd ?
Edx dd ?
Ecx dd ?
Eax dd ?
Ebp dd ? ; Control registers
Eip dd ?
SegCs dd ?
EFlags dd ?
Esp dd ?
SegSs dd ?
ExtendedRegisters db 512 dup (?)
ends
Additional word
This article was posted on comp.lang.asm.x86.
Especially thanks to Michael Tippach for pointing out some exception flags.
My web page is at {http://ams.ampr.org/cdragan/}
|