Win32
Exception handling for assembler programmers by
Jeremy Gordon
JGJorg@cs.com
http://www.GoDevTool.com
CONTENTS (click to go there)
1. Background
2. Exception handling in practice
3. Setting up simple exception handlers
4. Stack unwinds
5. The information sent to the handlers
6. Recovering from and Repairing an exception
7. Continuing execution after final handler called
8. Single-stepping
by setting the trap flag within the handler
9. Exception handling in multi-threaded applications
10. Except.Exe
Background
We're going to examine how to make an application more robust by handling
its own exceptions, rather than permitting the system to do so. An "exception"
is an offence committed by the program, which would otherwise result in
the embarrassing appearance of the dreaded closure message box:-
or its more elaborate counterpart in Windows NT.
What exception handling does
...
The idea of exception handling (often called "Structured Exception Handling")
is that your application instals one or more callback routines called "exception
handlers" at run-time and then, if an exception occurs, the system will
call the routine to let the application deal with the exception. The hope
would be that the exception handler may be able to repair the exception
and continue running either from the same area of code where the exception
occurred, or from a "safe place" in the code as if nothing had happened.
No closure message box would then be displayed and the user would be done
the wiser. As part of this repair it may be necessary to close handles,
close temporary files, free device contexts, free memory areas, inform
other threads, then unwind the stack or close down the offending thread.
During this process the exception handler may make a record of what it
is doing and save this to a file for later analysis.
If a repair cannot be achieved, exception handling allows your application
to close gracefully, having done as much clearing up, saving of data, and
apologising as it can.
Planned exceptions
The Windows SDK suggests another use for exception handling. It is suggested
as a way to keep track of memory usage. The idea is that an exception will
occur if you need to commit more memory: you intercept it and carry out
the memory allocation. This can be done by intercepting a memory access
violation [exception number 0C0000005h], which would occur if your code
tries to read from, or write to, memory which had not been committed.
Another way suggested to keep track of memory usage is to set the guard
page flag in a call to VirtualAlloc when committing the memory, or later
using VirtualProtect. This causes a guard page exception [080000001h] if
an attempt was made to read to, or write from a guarded area of memory,
after which the guard page flag is released. The exception handler would
therefore be kept informed of the memory requirements and could reset the
flag if required.
These methods are widely used throughout the system, for example, as
more stack is required by a thread, it is automatically enlarged.
An application, however, usually knows what it hopes to do next, so
it is much simpler and quicker to keep track of memory requirements by
keeping the top of the memory area as a data variable, and to check before
the start of each series of memory read/write operations whether the memory
area needs to be enlarged or diminished.
This works even if more than one thread uses the same area of memory,
since the same data variable can be used by each thread. In that case,
handling the 0C0000005h exception might only be a backup in case your code
went wrong.
And what exception handling
cannot do ...
Apart from divide by zero [exception code 0C0000094h] which can easily
be avoided by protective coding, the most common type of exception is an
attempt to read from, or write to, an illegal memory address [0C0000005h].
There are several ways that the second (illegal address) can arise. For
example:-
-
wrong index register values when addressing memory
-
unexpected continuous loops involving memory access
-
mismatch of PUSHes and POPs so execution continues from the wrong place
after return from a CALL
-
unforeseen corruption in input data files
It can be seen from this list that exceptions may occur in unexpected circumstances
for a variety of reasons. And it will be precisely this type of exception
which may terminate your program despite the best efforts of your exception
handler. In these circumstances at the very least, the exception handler
should try to save important data which would otherwise be lost, and then
retire gracefully, with suitable apologies.
Other program failures
Your program may fail for other reasons which will not result in an exception
at all.
The usual cause of this is:-
-
insufficient system resources
-
continuous loops in your program which do not involve memory access
The result is that your program will not be able to respond to system messages
it will appear to the user simply to have stopped. Luckily, however, because
it runs in its own virtual address space other programs will not be affected,
although the whole system may appear to run a little more slowly.
Utterly fatal exceptions
Some errors are so bad that the system cannot even manage to call your
exception handler. Then only if the user is lucky will the system's closure
message box appear, or the devastating bright blue error screen will appear,
showing that a "fatal" error has occurred. Almost inevitably this is a
result of a total crash of the system and a reboot is the only remedy.
Fortunately in Win32 you have to try quite hard to produce such errors,
but they can still occur.
... and where exception handling
really scores
Having spent some time on what exception handling cannot do, let’s
review the instances where it is invaluable:-
-
During program development, to catch and report on errors as an alternative
to debug control.
-
When using code written by others which may not be fully trusted.
-
When reading from, or writing to, memory areas which may be moved without
notice. For example, while spelunking around system memory areas (which
would be under system control) or memory areas which could possibly be
closed by other processes or threads.
-
Using pointers from files which may be corrupted or of the wrong format.
Here exception handling would be much quicker than using the IsBadReadPtr
or IsBadWritePtr APIs to check each pointer immediately prior to its use.
-
As a general catch-all for all unforeseen bugs.
Exception
handling in practice
The Windows sequence
In order to understand what your code can or should do when handling exceptions,
you need to know in some more detail what the system does when an exception
occurs. If you are new to the subject, the following may not yet be clear.
However it is necessary to know these steps to understand the subject.
The steps are as follows:-
-
Windows decides first whether it is an exception which it is willing to
send to the program's exception handler. If so, if the program is being
debugged, Windows will notify the debugger of the exception by suspending
the program and sending EXCEPTION_DEBUG_EVENT (value 1h) to the debugger.
-
If the program is not being debugged or if the exception is not dealt with
by the debugger, the system sends the exception to your per-thread exception
handler if you have installed one. A per-thread handler is installed at
run-time and is pointed to by the first dword in the Thread Information
Block whose address is at FS:[0].
-
The per-thread exception handler can try to deal with the exception, or
it may not do so, leaving it for handlers further up the chain, if there
are any more handlers installed.
-
Eventually if none of the per-thread handlers deal with the exception,
if the program is being debugged the system will again suspend the program
and notify the debugger.
-
If the program is not being debugged or if the exception is still not dealt
with by the debugger, the system will call your final handler if one is
installed. This will be a final handler installed at run-time by the application
using the API SetUnhandledExceptionFilter.
-
If your final handler does not deal with the exception after it returns,
the system final handler will be called. Optionally it will show the system’s
closure message box. Depending on the registry settings, this box may give
the user a chance to attach a debugger to the program. If no debugger can
be attached or if the debugger is powerless to assist, the program is doomed
and the system will call ExitProcess to terminate the program.
-
Before finally terminating the program, though, the system will cause a
"final unwind" of the stack for the thread in which the exception occurred.
Advantages of using assembler
for exception handling
Win32 provides only the framework for exception handling, using a handful
of APIs. So most of the code required for exception handling has to be
coded by hand.
"C" programmers will use various shortcuts provided by their compilers
by including in their source code statements such as _try, _except, _finally,
_catch and _throw.
One real disadvantage in relying on the compiler’s code is that it can
enlarge the final exe file enormously.
Also most C programmers would have no idea what code is produced by
the compiler when exception handling is used, and this is a real disadvantage
because to handle exceptions properly you need flexibility, understanding
and control. This is because exceptions can be intercepted and handled
in various ways and at various different levels in your code. Using assembler
you can produce tight, reliable and flexible code which you can tailor
closely to your own application.
Multi-threaded applications need particularly careful treatment and
assembler provides a simple and versatile way to add exception handling
to such programs.
Information about exception handling at a low level is hard to get hold
of, and the samples in the Win32 Software Development Kit (SDK) concentrate
on how to use the "C" compiler statements rather than how to hard-wire
a program to use the Win32 framework itself.
The information in this article was obtained using a test program and
a debugger, and by disassembling code produced by "C" compilers. The accompanying
program, Except.Exe, demonstrates the techniques described here.
Setting
up simple exception handlers
I hope you will be pleasantly surprised to see in practice how easy it
is in assembler to add exception handling to your programs.
The two types of exception
handlers
As you have seen above, there are two types of exception handlers.
Type 1 – the "final" exception handler
The "final" exception handler is called by the system if your program is
doomed to close. Because this handler is process-specific it is called
irrespective of which thread caused the exception.
Establishing a final exception handler
Typically, this is established in the main thread as soon as possible after
the program entry point by calling the API SetUnhandledExceptionFilter.
It therefore covers the whole program from that point until termination.
There is no need to remove the handler on termination - this is done automatically
by windows.
Example
|
MAIN:
PUSH OFFSET FINAL_HANDLER
CALL SetUnhandledExceptionFilter
; ...
; ...
; ...
CALL ExitProcess
;************************************
FINAL_HANDLER:
; ...
; ...
; ...
;(eax=-1 reload context and continue)
MOV EAX,1
RET |
;program entry point
;
;
;
;code covered by final handler
;
;
;
;
;
;code to provide a polite exit
;
;
;eax=1 stops display of closure box
;eax=0 enables display of the box |
No chaining of final exception handlers
There can only be one application-defined final exception handler in the
process at any one time. If SetUnhandledExceptionFilter is called a second
time in your code the address of the final exception handler is simply
changed to the new value, and the previous one is discarded.
Type 2 – the "per-thread" exception handler
This type of handler is typically used to guard certain areas of code and
is established by altering the value held by the system at FS:[0]. Each
thread in your program has a different value for the segment register FS,
so this exception handler will be thread specific. It will be called if
an exception occurs during the execution of code protected by the handler.
The value in FS is a 16-bit selector which points to the "Thread Information
Block", a structure which contains important information about each thread.
The very first dword in the Thread Information Block points to a structure
which we are going to call an "ERR" structure.
The "ERR" structure is at least 2 dwords as follows:-
1st
dword +0 |
Pointer to next ERR structure
|
2nd
dword +4 |
Pointer to own exception handler
|
Establishing a "per-thread" exception handler
So now we can see how easy it is to establish this type of exception handler:-
Example
|
PUSH OFFSET HANDLER
PUSH FS:[0]
MOV FS:[0],ESP
...
...
...
POP FS:[0]
ADD ESP,4h
RET
;***********************
HANDLER:
...
...
...
MOV EAX,1
RET |
;
;address of next ERR structure
;give FS:[0] the ERR address just made
;
;the code protected by the handler goes here
;
;restore next ERR structure to FS:[0]
;throw away rest of ERR structure
;
;
;
;
;exception handler code goes here
;
;eax=1 go to next handler
;eax=0 reload context & continue execution |
Chaining of per-thread exception handlers
In the above code we can see that the 2nd dword of the ERR structure,
which is the address of your handler, is put on the stack first, then the
1st dword of the next ERR structure is put on the stack by the
instruction PUSH FS:[0]. Suppose the code which was then protected by this
handler called other functions which needed their own individual protection.
Then you may create another ERR structure and handler to protect that code
in exactly the same way. This is called chaining. In practice this
means that when an exception occurs the system will walk the handler
chain by first calling the exception handler most recently established
before the code where the exception occurred. If that handler does not
deal with the exception (returning EAX=1), then the system calls the next
handler up the chain. Since each ERR structure contains the address of
the next handler up the chain, any number of such handlers can be established
in this way. Each handler might guard against or deal with particular types
of exceptions depending on what is foreseeable in your code. The stack
is used to keep the ERR structure, to avoid write-overs. However there
is nothing to stop you using other parts of memory for the ERR structures
if you prefer.
Stack
unwinds
We’re going to look at with stack unwinds at this point because they shouldn’t
keep their mystery any longer! A "stack unwind" sounds very dramatic, but
in practice it’s simply all about calling the exception handlers whose
local data is held further down the stack and then (probably) continuing
execution from another stack frame. In other words the program gets ready
to ignore the stack contents between these two positions.
Suppose you have a chain of per-thread handlers established as in this
arrangement, where Function A calls Function B which calls Function C:-
Then the stack will look something like this:-
|
stackâ
+ve
|
3rd
Stack
Frame |
Use of stack by Function C
|
Handler 3
|
Local Data Function C
|
2nd
Stack
Frame |
Return address Function C
|
Use of stack by Function B
|
Handler 2
|
Local Data Function B
|
1st
Stack
Frame |
Return address Function B
|
Use of stack by Function A
|
Handler 1
|
Local Data Function A
|
|
Return address Function A
|
|
Stackâ +ve
|
Here as each function is called things are PUSHed onto the stack: firstly
the return address, then local data, and then the exception handler (this
is the "ERR" structure referred to earlier).
Then suppose that an exception occurs in Function C. As we have seen,
the system will cause a walk of the handler chain. Handler 3 will
be called first. Suppose Handler 3 does not deal with the exception (returning
EAX=1), then Handler 2 will be called. Suppose Handler 2 also returns EAX=1
so that Handler 1 is called. If Handler 1 deals with the exception, it
may need to cause a clear-up using local data in the stack frames created
by Functions B and C.
It can do so by causing an Unwind.
This simply repeats the walk of the handler chain again, causing
first Handler 3 then Handler 2, then Handler 1 to be called in turn.
The differences between this type of handler chain walk and the walk
initiated by the system when the exception first occurred are as follows:-
-
This handler walk is initiated by your handler rather than by the system
-
The exception flag in the EXCEPTION_RECORD should be set to 2h (EH_UNWINDING).
This indicates to the per-thread handler that it is being called by another
handler higher in the chain to clear-up using local data. It should not
attempt to do any more than that and it must return EAX=1.
-
The handler walk stops at the handler immediately before the caller. For
example in the diagram, if Handler 1 initiates the unwind, the last Handler
to be called during the unwind is Handler 2. There is no need for Handler
1 to be called from within itself because it has access to its own local
data to clear-up.
You can see below ("Providing access to local data") how the handler is
able to find local data during the handler walk.
How the unwind is done
The handler can initiate an unwind using the API RtlUnwind or, as we shall
see, it can also easily be done using your own code. This API can be called
as follows:-
PUSH Return value>
PUSH pExceptionRecord
PUSH OFFSET CodeLabel
PUSH LastStackFrame
CALL RtlUnwind
Where:-
Return value is said to give a return value after the unwind
(you would probably not use this)
pExceptionRecord is a pointer to the exception record, which
is one of the structures sent to the handler when an exception occurs
CodeLabel is a place from which execution should continue after
the unwind and is typically the code address immediately after the call
to RtlUnwind. If this is not specified the API appears to return in the
normal way, however the SDK suggests that it should be used and it is better
to play safe with this type of API
LastStackFrame is the stack frame at which the unwind should
stop. Typically this will be the stack address of the ERR structure which
contains the address of the handler which is initiating the unwind
Unlike other APIs you cannot
rely on RtlUnwind saving the EBX, ESI or EDI registers – if you are using
these in your code you should ensure that they are saved prior to PUSHing
the first parameter and restored after the CodeLabel |
Own-code Unwind
The following code simulates the unwind (where ebx holds the address of
the EXCEPTION_RECORD structure sent to the handler):-
|
MOV D[EBX+4],2h
MOV EDI,FS:[0]
>L2:
CMP D[EDI],-1
JZ >L3
PUSH EDI,EBX
CALL [EDI+4]
ADD ESP,8h
MOV EDI,[EDI]
JMP L2
L3: |
;make the exception flag EH_UNWINDING
;get 1st per-thread handler address
;
;see if it’s the last one
;yes, so finish
;push ERR structure, EXCEPTION_RECORD
;call handler to run clear-up code
;remove the two parameters pushed
;get pointer to next ERR structure
;and do next if not at end
;code label when finished |
Here each handler is called in turn with the ExceptionFlag set to 2h until
the last handler is reached (the system has a value of –1 in the last ERR
structure).
The above code does not check for corruption of the values at [EDI]
and at [EDI+4]. The first is a stack address and could be checked by ensuring
that it is above the thread’s stack base given by FS:[8] and below the
thread’s stack top given by FS:[4]. The second is a code address and so
you could check that it lies within two code labels, one at the start of
your code and one at the end of it. Alternatively you could check that
[EDI] and [EDI+4] could be read by calling the API IsBadReadPtr.
Unwind by final handler then continue
It is not just a per-thread handler which can initiate a stack unwind.
It can also be done in your final handler by calling either RtlUnwind or
an own-code unwind and then returning EAX= –1. (See "Continuing execution
after final handler called").
Final unwind then terminate
If a final handler is installed and it returns either EAX=0 or EAX=1, the
system will cause the process to terminate. However, before final termination
something interesting happens. The system does a final unwind by
going back to the very first handler in the chain (that is to say, the
handler guarding the code in which the exception occurred). This is the
very last opportunity for your handler to execute the clear-up code necessary
within each stack frame. You can see this final unwind clearly occurring
if you set the accompanying demo program Except.Exe to allow the exception
to go to the final handler and press either F3 or F5 when there.
The
information sent to the handlers
Clearly sufficient information must be sent to the handlers for them to
be able to try to repair the exception, make error logs, or report to the
user. As we shall see, this information is sent by the system itself on
the stack, when the handlers are called. In addition to this you can send
your own information to the handlers by enlarging the ERR structure so
that it contains more information.
The information sent to the final handler
The final handler is documented in the Windows Software Development Kit
("SDK") as the API "UnhandledExceptionFilter". It receives one parameter
only, a pointer to the structure EXCEPTION_POINTERS. This structure is
as follows:-
EXCEPTION_POINTERS +0
|
Pointer to structure:-
EXCEPTION_RECORD
|
+4
|
Pointer to structure:-
CONTEXT record
|
The structure EXCEPTION_RECORD has these fields:-
EXCEPTION_RECORD +0
|
ExceptionCode
|
+4
|
ExceptionFlag
|
+8
|
NestedExceptionRecord
|
+C
|
ExceptionAddress
|
+10
|
NumberParameters
|
+14
|
AdditionalData
|
Where
ExceptionCode gives the type of exception which has occurred.
There are a number of these listed in the SDK and header files, but in
practice, the types which you may come across are:-
C0000005h – Read or write memory violation
C0000094h – Divide by zero
C00000FDh – The stack went beyond the maximum available size
80000001h – Violation of a guard page in memory set up using
Virtual Alloc
The following only occur whilst dealing with exceptions:-
C0000025h – A non-continuable exception – the handler should
not try to deal with it
C0000026h – Exception code used the by system during exception
handling. This code might be used if the system encounters an unexpected
return from a handler. It is also used if no Exception Record is supplied
when calling RtlUnwind.
The following are used in debugging:-
80000003h – Breakpoint occurred because there was an INT3 in
the code
80000004h – Single step during debugging
The exception codes follow
these rules:
Bits 31-30 Bit
29
Bit
28
Bits 27-0
0=success 0=Microsoft
Reserved For exception
1=information 1=Application Must be zero code
2=warning
3=error
A typical own exception code sent by RaiseException might therefore
be E0000100h (error, application, code=100h). |
Own user code – this would be sent by your own application
by calling the API RaiseException. This is a quick way to exit code directly
into your handler if required.
Exception flag which gives instructions to the handler. The values
can be:-
0 – a continuable exception (can be repaired)
1 – a non-continuable exception (cannot be repaired)
2 – the stack is unwinding - do not try to repair
Nested exception record pointing to another EXCEPTION_RECORD
structure if the handler itself has caused another exception
Exception address – the address in code where the exception occurred
NumberParameters – number of dwords to follow in Additional
information
Additional information – array of dwords with further information
This can either be information sent by the application itself when calling
RaiseException, or, if the exception code is C0000005h it will be
as follows:-
1st dword - 0=a read violation, 1=a write violation.
2nd dword – address of access violation
The second part of the EXCEPTION_POINTERS structure which is sent to the
final handler points to the CONTEXT record structure which contains the
processor-specific values of all the registers at the time of the exception.
WINNT.H contains the CONTEXT structures for various processors. Your program
can find out what sort of processor is being used by calling GetSystemInfo.
CONTEXT is as follows for IA32 (Intel 386 and upwards):-
+0 context flags
(used when calling GetThreadContext)
DEBUG REGISTERS
+4 debug register #0
+8 debug register #1
+C debug register #2
+10 debug register #3
+14 debug register #6
+18 debug register #7
FLOATING POINT / MMX registers
+1C ControlWord
+20 StatusWord
+24 TagWord
+28 ErrorOffset
+2C ErrorSelector
+30 DataOffset
+34 DataSelector
+38 FP registers x 8 (10 bytes each)
+88 Cr0NpxState
SEGMENT REGISTERS
+8C gs register
+90 fs register
+94 es register
+98 ds register
ORDINARY REGISTERS
+9C edi register
+A0 esi register
+A4 ebx register
+A8 edx register
+AC ecx register
+B0 eax register
CONTROL REGISTERS
+B4 ebp register
+B8 eip register
+BC cs register
+C0 eflags register
+C4 esp register
+C8 ss register
The information sent to the per-thread handlers
At the time of the call to the per-thread handler, ESP points to three
structures as follows:-
ESP+4
|
Pointer to structure:-
EXCEPTION_RECORD
|
ESP+8
|
Pointer to own ERR structure
|
ESP+C
|
Pointer to structure:-
CONTEXT record
|
Unlike usual CALLBACKs
in Windows, when the per-thread handler is called, the C calling convention
is used (caller to remove the arguments from the stack) not the PASCAL
convention (function to do so). This can be seen from the actual Kernel32
code used to make the call:-
PUSH Param, CONTEXT record, ERR, EXCEPTION_RECORD
CALL HANDLER
ADD ESP,10h
In practice the first argument, Param, was not found to contain meaningful
information |
The EXCEPTION_RECORD and CONTEXT record structures have already been
described above.
The ERR structure is the structure you created on the stack when the
handler was established and it must contain the pointer to the next ERR
structure and the code address of the handler now being installed (see
"Setting up simple exception handlers", above). The pointer to the ERR
structure passed to the per-thread handler is to the top of this
structure. It is possible, therefore, to enlarge the ERR structure so that
the handler can receive additional information.
In a typical arrangement the ERR structure might look like this, where
[ESP+8h] points to the top of this structure when the handler is called:-
ERR +0
|
Pointer to next ERR structure
|
+4
|
Pointer to own exception handler
|
+8
|
Code address of "safe-place" for handler
|
+C
|
Information for handler
|
+10
|
Area for flags
|
+14
|
Value of EBP at safe-place
|
As we shall see below ("Continuing execution from a safe-place"), the fields
at +8 and +14 may be used by the handler to recover from the exception.
Providing access to local data
Let’s now consider the best position of the ERR structure on the stack
relative to the stack frame, which may well hold local data variables.
This is important because the handler may well need access to this local
data in order to clear-up properly. Here is some typical code which may
be used to establish a per-thread handler where there is local data:-
|
MYFUNCTION:
PUSH EBP
MOV EBP,ESP
SUB ESP,40h
;******** local data now at
;********** install handler
PUSH EBP
PUSH 0
PUSH 0
PUSH OFFSET SAFE_PLACE
PUSH OFFSET HANDLER
PUSH FS:[0]
MOV FS:[0],ESP
...
...
...
JMP >L10
SAFE_PLACE:
L10:
POP FS:[0]
MOV ESP,EBP
POP EBP
RET
;*****************
HANDLER:
RET |
;procedure entry point
;save ebp (used to address stack frame)
;use EBP as stack frame pointer
;make 16 dwords on stack for local data
[EBP-4] to [EBP-40h]
and its ERR structure
;ERR+14h save ebp (being ebp at safe-place)
;ERR+10h area for flags
;ERR+0Ch information for handler
;ERR+8h new eip at safe-place
;ERR+4h address of handler
;ERR+0h keep next ERR up the chain
;point to ERR just made on the stack
;
;code which is protected goes here
;
;normal end if there is no exception
;handler sets eip/esp/ebp for here
;
;restore next ERR up the chain |
Using this code, when the handler is called, the following is on the
stack, and with [ESP+8h] pointing to the top of the ERR structure (ie.
ERR+0):-
|
stackâ +ve
|
ERR +0 |
Pointer to next ERR structure
|
ERR +4 |
Pointer to own exception handler
|
ERR +8 |
Code address of "safe-place" for handler
|
ERR +C |
Information for handler
|
ERR +10 |
Area for flags
|
ERR +14 |
Value of EBP at safe-place
|
+18 |
Local Data
|
+1C |
Local Data
|
+20 |
Local Data
|
|
more local data â
|
You can see from this that since the handler is given a pointer to the
ERR structure it can also find the address of local data on the stack.
This is because the handler knows the size of the ERR structure and also
the position of the local data on the stack. If the EBP field is used at
ERR+14h as in the above example, that could also be used as a pointer to
the local data.
Recovering
from and Repairing an exception
Continuing execution from a safe-place
Choosing the safe-place
You need to continue execution from a place in the code which will not
cause further problems. The main thing you must bear in mind is that since
your program is designed to work within the Windows framework, your aim
is to return to the system as soon as possible in a controlled manner,
so that you can wait for the next system event. If the exception has occurred
during the call by the system to a window procedure, then often a good
safe-place will be near the exit point of the window procedure so that
control passes back to the system cleanly. In this case it will simply
appear to the system that your application has returned from the window
procedure in the usual way.
If the exception has occurred, however, in code where there is no window
procedure, then you may need to exercise more control. For example, a thread
established to do certain tasks will probably need to be terminated, reporting
to the main thread that it could not complete the task.
Another major consideration is how easy it is to get the correct EIP,
ESP and EBP values at the safe-place. As we can see below, this may not
be at all difficult.
There are so many possible permutations here it is probably pointless
to postulate them. The precise safe-place will depend on the nature of
your code and the use you are making of exception handling.
Example of how to get to safe-place
As an example, though, look again at the code example above in MYFUNCTION.
You can see the code label "SAFE-PLACE". This is a code address from which
execution could continue safely, the handler having done all necessary
clearing up.
In the code example, in order to continue execution successfully, it
must be borne in mind that although SAFE-PLACE is within the same stack
frame as the exception occurred, the values of ESP and EBP need carefully
to be set by the handler before execution continues from EIP.
These 3 registers therefore need to be set and for the following reasons:-
ESP – to enable the POP FS:[0] instruction to work and to POP other
values if necessary
EBP – to ensure that local data can be addressed within the handler
and to restore the correct ESP value to return from MYFUNCTION
EIP – to cause execution to continue from SAFE-PLACE
Now you can see that each of these values is readily available from within
the handler function. The correct ESP value is, in fact, exactly the same
as the top of the ERR structure itself (given by [ESP+8h] when the handler
is called). The correct EBP value is available from ERR+14h, because this
was PUSHed onto the stack when the ERR structure was made. And the correct
code address of SAFE-PLACE to give to EIP is at ERR+8h.
Now we are ready to see how the handler can ensure that execution continues
from a safe-place, instead of allowing the process to close, should an
exception occur.
|
HANDLER:
PUSH EBP
MOV EBP,ESP
;** now [EBP+8]=pointer
;** [EBP+0Ch]=pointer to
;** [EBP+10h]=pointer to
PUSH EBX,EDI,ESI
MOV EBX,[EBP+8]
TEST D[EBX+4],01h
JNZ >L5
TEST D[EBX+4],02h
JZ >L2
...
...
...
JMP >L5
L2:
PUSH 0
PUSH [EBP+8h]
PUSH OFFSET UN23
PUSH [EBP+0Ch]
CALL RtlUnwind
UN23:
MOV ESI,[EBP+10h]
MOV EDX,[EBP+0Ch]
MOV [ESI+0C4h],EDX
MOV EAX,[EDX+8]
MOV [ESI+0B8h],EAX
MOV EAX,[EDX+14h]
MOV [ESI+0B4h],EAX
XOR EAX,EAX
JMP >L6
L5:
MOV EAX,1
L6:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET |
;
;
;
to EXCEPTION_RECORD
ERR structure
CONTEXT record
;save registers as required by windows
;get exception record in ebx
;see if its a non-continuable exception
;yes, so must not deal with it
;see if its EH_UNWINDING (from Unwind)
;no
;
;clear-up code when unwinding
;
;must return 1 to go to next handler
;
;return value (not used)
;pointer to this exception record
;code address for RtlUnwind to return
;pointer to this ERR structure
;
;
;get context record in esi
;get pointer to ERR structure
;use it as new esp
;get safe place given in ERR structure
;insert new eip
;get ebp at safe place given in ERR
;insert new ebp
;reload context & return to system eax=0
;
;
;go to next handler - return eax=1
;ordinary return (no actual arguments) |
Repairing the exception
In the above example you saw the context being loaded with the new eip,
ebp and esp to cause execution to continue from a safe-place. It may be
possible using the same method of replacing the values for some of the
registers in the context, to "repair" the exception, permitting execution
to continue from near the offending code, so that the current task can
be continued.
An obvious example would be a divide by zero, which can be repaired
by the handler by substituting the value 1 for the divisor, and then a
return with EAX=0 (if a "per-thread" handler) causing the system to reload
the context and continue execution.
In the case of memory violations, you can make use of the fact that
the address of the memory violation is passed as the second dword in the
additional
information field of the exception record. The handler can use this
very same value to pass to VirtualAlloc to commit more memory starting
at that place. If this is successful, the handler can then reload the context
(unchanged) and return EAX=0 to continue execution (in the case of a "per-thread"
handler).
Continuing
execution after final handler called
If you wish you can deal with exceptions in the final handler. You recall
that at the beginning of this article I said that the final handler is
called by the system when the process is about to be terminated.
This is true.
The returns in EAX from the final handler are not the same as those
from the per-thread handler. If the return is EAX=1 the process terminates
without showing the system’s closure message box, and if EAX=0 the box
is shown.
However, there is also a third return code, EAX= –1 which is properly
described in the SDK as "EXCEPTION_CONTINUE_EXECUTION". This return has
the same effect as returning EAX=0 from a per-thread handler, that is,
it reloads the context record into the processor and continues execution
from the eip given in the context. Of course, the final handler may change
the context record before returning to the system, in the same way as a
per-thread handler might do so. In this way the final handler can recover
from the exception by continuing execution from a suitable safe-place or
it may try to repair the exception.
If you use the final handler to deal with all exceptions instead of
using per-thread handlers you do lose some flexibility, though.
Firstly, you cannot nest final handlers. You can only have one working
final handler established by SetUnhandledExceptionFilter in your code at
any one time. You could, if you wished, change the address of the final
handler as different parts of your code are being processed. SetUnhandledExceptionFilter
returns the address of the final handler being replaced so you could make
use of this as follows:-
|
PUSH OFFSET FINAL_HANDLER
CALL SetUnhandledExceptionFilter
PUSH EAX
...
...
...
...
CALL SetUnhandledExceptionFilter |
;
;
;keep address of previous handler
;
;this is the code
;being guarded
;
;restore previous handler |
Note here that at the time of the second call to SetUnhandledExceptionFilter
the address of the previous handler is already on the stack because of
the earlier PUSH EAX instruction.
Another difficulty with using the final handler is that the information
sent to it is limited to the exception record and the context record. Therefore
you will need to keep the code address of the safe-place, and the values
of ESP and EBP at that safe-place, in static memory. This can be done easily
at run time. For example, when dealing with the WM_COMMAND message within
a window procedure,
|
PROCESS_COMMAND:
MOV EBPSAFE_PLACE,EBP
MOV ESPSAFE_PLACE,ESP
...
...
...
SAFE_PLACE:
XOR EAX,EAX
RET |
;called on uMsg=111h (WM_COMMAND)
;keep ebp at safe-place
;keep esp at safe-place
;
;protected code here
;
;code-label for safe-place
;return eax=0=message processed |
In the above example, in order to repair the exception by continuing execution
from the safe-place, the handler would insert the values of EBPSAFE_PLACE
at CONTEXT+0B4h (ebp), ESPSAFE_PLACE at CONTEXT+0C4h (esp), and OFFSET
SAFE_PLACE into CONTEXT+0B8h (eip) and then return -1.
Note that in a stack unwind forced by the system because of a fatal
exit, only the "per-thread" handlers (if any) and not the final handler
are called. If there are no "per-thread" handlers, the final handler would
have to deal with all clearing-up itself before returning to the system.
Single-stepping
by setting the trap flag within the handler
You can make a simple single-step tester for your program while it is under
development by using the handler's ability to set the trap flag in the
register context before returning to the system. You can arrange
for the handler to display the results on the screen, or to dump them to
a file. This may be useful if you suspect that results are
being altered under debugger control, or if you need to see quickly how
a particular piece of code responds to various inputs. Insert the
following code fragment where you want single-stepping to begin:-
MOV SSCOUNT,5
INT 3
SSCOUNT is a data symbol and is set to the number of steps the handler
should do before returning to normal operation. The INT 3 causes
a 80000003h exception, so your handler is called.
The code in your development program should be protected by a per-thread
handler using code like this:-.
|
SS_HANDLER:
PUSH EBP
MOV EBP,ESP
PUSH EBX,EDI,ESI
MOV EBX,[EBP+8]
TEST D[EBX+4],01h
JNZ >L14
TEST D[EBX+4],02h
JNZ >L14
MOV ESI,[EBP+10h]
MOV EAX,[EBX]
CMP EAX,80000004h
JZ >L10
CMP EAX,80000003h
JNZ >L14
L10:
DEC SSCOUNT
JZ >L12
OR D[ESI+0C0h],100h
L12:
...
...
...
XOR EAX,EAX
JMP >L17
L14:
MOV EAX,1
L17:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET |
;
;
;
;save registers as required by Windows
;get exception record in ebx
;see if its a non-continuable exception
;yes
;see if EH_UNWINDING
;yes
;get context record in esi
;get ExceptionCode
;see if here because trap flag set
;yes
;see if its own INT 3 inserted to single-step
;no
;
;stop when correct number done
;
;set trap flag in context
;
;
;code here to display results to screen
;
;eax=0 reload context and return to system
;
;
;eax=1 system to go to next handler |
Here the first call to the handler is caused by the INT 3 (the system objected
strongly to the use of INT 1 when I tried it). On receipt of this
exception, which could only come from the code fragment inserted in the
code-to-test, the handler sets the trap flag in the context before returning.
This causes a 80000004h exception to come back to the handler upon the
next instruction. Note that with these exceptions, eip is already
at the next instruction ie. one past the INT 3, or past the instruction
executed with the trap flag set. Accordingly all you have to do in
the handler to continue single-stepping is to set the trap flag again and
return to the system.
* Thanks to G.W.Wilhelm, Jr of IBM for this idea
Exception
handling in multi-threaded applications
When it comes to exception handling in multi-threaded applications there
is little or no help from the system. You will need to plan for likely
faults and organise your threads accordingly.
The rules applying to the exception handling provided by the system
(in the context of a multi-threaded application) are:-
-
Only one type 1 (final handler) can be in existence at any one time for
each process. If a new thread calls SetUnhandledExceptionFilter, this will
simply replace the final handler – there is no chain of final handlers
as there is for the type 2 (per-thread) handlers. Therefore the simplest
way of using the final handler is still probably the best way in a multi-threaded
application – establish it in the main thread as soon as possible after
the program start point.
-
The final handler will be called by the system if the process will be terminating,
regardless of which thread caused the exception.
-
However, there will only be a final unwind (immediately prior to termination)
in the per-thread handlers established
for the thread which caused the
exception. Even if any other (innocent) threads have a window
and a message loop, the system will not warn them that the process is about
to terminate (no special message will be sent to them other than usual
messages arising from the loss of focus of other windows).
-
Therefore the other (innocent) threads cannot expect a final unwind if
the process is to terminate. And they will remain ignorant of the
imminent termination.
-
If, as is likely, these other innocent threads will also need to clear-up
on such termination you will need to inform them from the final handler.
The final handler will need to wait until these other threads have completed
clearing up before returning to the system.
-
The way in which the innocent threads are informed of the expected termination
of the program depends on the precise make-up of your code. If the innocent
thread has a window and message loop, then the final handler can use SendMessage
to that window to send an application defined message (must be 400h or
above), to inform that thread to terminate gracefully.
If there is no window and message loop, the final handler could set a public
variable flag, polled from time to time by the other thread. Alternatively
you could use SetThreadContext to force the thread to execute certain termination
code, by setting the value of eip to point to that code. This method would
not work if the thread is in an API, for example, waiting for the return
from GetMessage. In that case you would need to send a message as well,
to make sure the thread returned from the API, so that the new context
is set.
-
RaiseException only works on the calling thread, so this cannot be used
as a means of communication between threads to make an innocent thread
execute its own exception handler code.
-
How does the final handler know when it may proceed after informing the
other threads that the program is about to terminate? SendMessage
will not return until the recipient has returned from its window procedure
and the final handler could wait for that return. Alternatively it
could poll a flag waiting for a response from the other thread that it
has finished clearing up (note you must call the API Sleep in the polling
loop to avoid over-using the system). Or better still, the final
handler could wait until the other thread has terminated (this can be done
using the API WaitForSingleObject or WaitForMultipleObjects if there is
more than one thread). Alternatively use could be made of the Event
or Semaphore APIs.
-
For an example of how these procedures could work in practice, suppose
a secondary thread has the job of re-organising a database and then writing
it to disk. It may be in the middle of this task when the main thread
causes an exception which enters your final handler. Here you could
either cause the secondary thread to abort its job, by causing it to unwind
and terminate gracefully, leaving the original data on disk or alternatively
you could permit it to complete the task, and then inform the handler that
it had finished so that the handler could then return to the system.
You would need to stop the secondary thread starting any further such jobs
if your handler had been called. This could be achieved by the handler
setting a flag tested by the secondary thread before it started any job,
or by using the Event APIs.
-
If communication between threads is difficult, there is another way for
one thread to access the stack of another thread, and thereby cause an
unwind. This makes use of the fact that whereas each thread has its
own stack, the memory reserved for that stack is within the address space
for the process itself. You can check this yourself if you watch a multi-threaded
application using a debugger. As you move between threads the values of
ESP and EBP will change, but they are all kept within the address space
of the process itself. The value of FS will also be different between threads
and will point to the Thread Information Block for each thread. So if you
take the following steps one thread can access the stack and cause an unwind
of another:-
a. As each thread is created record in a static variable the value
of its FS register.
b. As each thread closes it returns the static variables to zero.
c. The handler which needs to unwind other threads should take all
the static variables in turn and for those which have a non-zero value
(ie. thread was running at the time of the exception) the handlers should
be called with the exception flag of 2 (EH_UNWINDING) and, a user flag
of say, 400h to show that the per-thread handler is being called by your
final handler. You cannot call a per-thread handler in a different thread
using RtlUnwind (which is thread-specific) but it can be done using the
following code (where ebx holds the address of the EXCEPTION_RECORD):-
|
MOV D[EBX+4],402h
L1:
PUSH ES
MOV AX,FS_VALUE
MOV ES,AX
MOV EDI,ES:[0]
POP ES
L2:
CMP D[EDI],-1
JZ >L3
PUSH EDI,EBX
CALL [EDI+4]
ADD ESP,8h
MOV EDI,[EDI]
JMP L2
L3: |
;make the exception flag EH_UNWINDING
+ 400h
;
;
;get FS value of thread to unwind
;
;get 1st per-thread handler address
;
;
;see if it’s the last one
;yes, so finish
;push ERR structure, EXCEPTION_RECORD
;call handler to run clear-up code
;remove the two parameters pushed
;get pointer to next ERR structure
;and do next if not at end
;code label when finished |
;now loop back to L1 with a new FS_VALUE until all threads done
Here you see that the Thread Information Block of each innocent thread
is read using the ES register, which is temporarily given the value of
the thread’s FS register.
Instead of using FS to find the Thread Information Block you could use
the following code to get a 32-bit linear address for it. In this code
LDT_ENTRY is a structure of 2 dwords, ax holds the 16-bit selector value
(FS_VALUE) to be converted and hThread is any valid thread handle:-
|
AND EAX,0FFFFh
PUSH OFFSET LDT_ENTRY,EAX,hThread
CALL GetThreadSelectorEntry
OR EAX,EAX
JZ >L300
MOV EAX,OFFSET LDT_ENTRY
MOV DH,[EAX+7]
MOV DL,[EAX+4]
SHL EDX,16D
MOV DX,[EAX+2]
OR EDX,EDX
L300: |
;
;
;
;see if failed
;yes so return zero
;
;get base high
;get base mid
;shift to top of edx
;and get base low
;edx now=linear 32 bit address)
;return nz on success |
The reason why it is important (using the flag 400h) to inform the handler
being called that it is being called by another thread (the final handler)
is that the thread being called is still running because the exception
occurred in a different thread. The handler may well need to suspend
the thread in these circumstances, so that the clear-up job can be achieved
by the calling thread. The innocent thread would then be given a
safe-place to go to before calling ResumeThread. All this must be
done before the final handler is allowed to return to the system because
on return the system will simply terminate all threads by brute force.
Except.Exe
This is the accompanying program which is intended to demonstrate the above
explanation of exception handling for assembler programmers.
The source code for Except.Exe (Except.asm and Except.RC) is also provided.
There is a modal dialog for the main window and the final handler is
set up very early in the process. When the "Cause Exception" button is
clicked, first the dialog procedure is called with the command, then 2
further routines are called, the third routine causing an exception of
the type chosen by the radiobuttons. As execution passes through this code,
3 per-thread exception handlers are created.
The exception is either repaired in situ if possible, or the program recovers
in the chosen handler from a safe-place. If the exception is allowed to
go to the final handler you can either exit by pressing F3 or F5, or if
you press F7 the final handler will try to recover from the exception.
You can follow events as they occur because each handler displays various
messages in the listbox. There is a slight delay between each message so
that you can follow more easily what is happening, or you can scroll the
messages to get them back into view.
When the program is about to terminate, something interesting happens.
The system causes a final unwind with the exception flag set to 2h. The
messages sent to the listbox are slowed down even further because the program
will be terminating soon!
You will see that the same type of unwind occurs if you specify that
execution should continue from a "safe-place" or if F7 is pressed from
the final handler. This unwind is initiating by the handler itself.
COPYRIGHT
NOTE - this article, Except.ASM, Except.RC and Except.Exe are all
Copyright © Jeremy Gordon 1996-2000
[McDuck Software]
e-mail: JGJorg@cs.com
http://www.GoDevTool.com
LEGAL NOTICE - The author accepts no responsibility for losses of any
type arising from this article. Whereas the author has used his best endeavours
to ensure that the contents of this article are correct, you should not
rely on this and you should do your own tests.