Win32 Exception handling for assembler programmers
by Jeremy Gordon
e-mail: jorgon@compuserve.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. Exception handling in
multi-threaded applications
9. Except.Exe
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:-
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:-
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:-
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:-
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:
;program entry point
PUSH OFFSET FINAL_HANDLER
CALL SetUnhandledExceptionFilter
..
.. ;code covered by final
handler
..
CALL ExitProcess
;***************************************
FINAL_HANDLER:
..
.. ;code to provide a
polite exit
..
;eax=-1
reload context and continue
MOV EAX,1 ;eax=1 stops display of closure box
RET ;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] ;address of next ERR structure
MOV FS:[0],ESP ;give FS:[0] the ERR address just made
..
.. ;the code protected by this
.. ;handler goes here
..
POP FS:[0] ;restore next ERR structure
to FS:[0]
ADD ESP,4h ;throw away rest of ERR
structure
RET
;*******************************************
HANDLER:
..
..
;exception handler code goes here
..
MOV EAX,1 ;eax=1 go to next
handler
RET
;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.
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:-
|
|
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:-
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 ;make
the exception flag EH_UNWINDING
MOV EDI,FS:[0] ;get 1st per-thread handler address
L2:
CMP D[EDI],-1 ;see if it’s the last one
JZ >L3 ;yes,
so finish
PUSH EDI,EBX ;push ERR structure,
EXCEPTION_RECORD
CALL [EDI+4] ;call handler to run clear-up code
ADD ESP,8h ;remove the two
parameters pushed
MOV EDI,[EDI] ;get pointer to next ERR structure
JMP L2 ;and
do next if not at end
L3:
;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:- |
+4 |
Pointer to
structure:- |
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
C0000017h – No memory available exception when asking the system for more memory
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: |
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:- |
ESP+8 |
Pointer to own ERR structure |
ESP+C |
Pointer to
structure:- |
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 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 ;save ebp (used to address stack frame)
MOV EBP,ESP ;use EBP as stack frame pointer
SUB ESP,40h ;make 16 dwords on stack for local data
;**** local data now addressable as [EBP-4] to [EBP-40h]
;**************** install handler and its ERR structure
PUSH EBP ;ERR+14h save ebp (being ebp at
safe-place)
PUSH 0 ;ERR+10h area for flags
PUSH 0 ;ERR+0Ch information for
handler
PUSH OFFSET SAFE_PLACE ;ERR+8h new eip at safe-place
PUSH OFFSET HANDLER ;ERR+4h address of handler
PUSH FS:[0] ;ERR+0h keep next ERR up the chain
MOV FS:[0],ESP ;point to ERR just made on the stack
..
.. ;code which is protected goes here
..
JMP >L10 ;normal end if there is no exception
SAFE_PLACE: ;handler sets eip/esp/ebp for here
L10:
POP FS:[0] ;restore next ERR up the chain
MOV ESP,EBP
POP EBP
RET
;****************************************************
HANDLER:
RET
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 to EXCEPTION_RECORD
; [EBP+0Ch]=pointer to ERR structure
; [EBP+10h]=pointer to CONTEXT record
PUSH EBX,EDI,ESI ;save registers as required by windows
MOV EBX,[EBP+8] ;get exception record in ebx
TEST D[EBX+4],01h ;see if its a non-continuable exception
JNZ >L5
;yes, so
must not deal with it
TEST D[EBX+4],02h ;see if its EH_UNWINDING (from Unwind)
JZ >L2
;no
..
.. ;clear-up code when unwinding
..
JMP >L5
;must return
1 to go to next handler
L2:
PUSH 0
;return
value (not used)
PUSH [EBP+8h] ;pointer to this exception record
PUSH OFFSET UN23 ;code address for RtlUnwind to return
PUSH [EBP+0Ch] ;pointer to this ERR structure
CALL RtlUnwind
UN23:
MOV ESI,[EBP+10h] ;get context record in esi
MOV EDX,[EBP+0Ch] ;get pointer to ERR structure
MOV [ESI+0C4h],EDX ;use it as new esp
MOV EAX,[EDX+8] ;get safe place given in ERR structure
MOV [ESI+0B8h],EAX ;insert new eip
MOV EAX,[EDX+14h] ;get ebp at safe place given in ERR
MOV [ESI+0B4h],EAX ;insert new ebp
XOR EAX,EAX ;reload context &
return to system eax=0
JMP >L6
L5:
MOV EAX,1 ;go to next
handler - return eax=1
L6:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET
;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 ;keep address of previous handler
..
.. ;this is the code
.. ;being guarded
..
CALL SetUnhandledExceptionFilter
;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: ;called on uMsg=111h
(WM_COMMAND)
MOV EBPSAFE_PLACE,EBP ;keep ebp at safe-place
MOV ESPSAFE_PLACE,ESP ;keep esp at safe-place
..
.. ;protected code here
..
SAFE_PLACE: ;code-label for safe-place
XOR EAX,EAX ;return eax=0=message processed
RET
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.
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:-
MOV D[EBX+4],2h ;make
the exception flag EH_UNWINDING
L1:
PUSH ES
MOV AX,FS_VALUE ;get FS value of thread to unwind
MOV ES,AX
MOV EDI,ES:[0] ;get 1st per-thread handler address
POP ES
L2:
CMP D[EDI],-1 ;see if it’s the last one
JZ >L3
;yes, so
finish
PUSH EDI,EBX ;push ERR structure,
EXCEPTION_RECORD
CALL [EDI+4] ;call handler to run clear-up code
ADD ESP,8h ;remove the two
parameters pushed
MOV EDI,[EDI] ;get pointer to next ERR structure
JMP L2 ;and
do next if not at end
L3:
;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 you could use the following code to get a 32-bit linear address for the Thread Information Block. 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 ;see if failed
JZ >L300 ;yes so return
zero
MOV EAX,OFFSET LDT_ENTRY
MOV DH,[EAX+7] ;get base high
MOV DL,[EAX+4] ;get base mid
SHL EDX,16D ;shift to top of edx
MOV DX,[EAX+2] ;and get base low
OR EDX,EDX ;edx=linear 32 bit address)
L300:
;return nz
on success
The final handler would have to wait for all innocent threads to execute their termination code before returning to the system. The easiest way to do this is to arrange for all these threads to terminate after executing their termination code. Then the final handler could wait for the return from WaitForSingleObject or WaitForMultipleObjects. Alternatively each thread could use SetEvent, or as a further alternative you could make use of the Semaphore series of APIs.
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-8
[McDuck Software]
e-mail: jorgon@compuserve.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.