Win32 Exception handling for assembler programmers

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

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 offense 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 installs 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 apologizing 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

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

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:-

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:-

Own-code Unwind

The following code simulates the unwind (where ebx holds the address of the EXCEPTION_RECORD structure sent to the handler):-

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

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):-

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:-

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:-

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.

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:-

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,

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:-

  1. 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.
  2. The final handler will be called by the system if the process will be terminating, regardless of which thread caused the exception.
  3. However, there will only be a final unwind (immediately prior to termination) in per-thread handlers established for the thread which caused the exception.
  4. Therefore the other (innocent) threads cannot expect a final unwind if the process is to terminate.
  5. If, as is likely, these other innocent threads will also need to clear-up on such termination they must be told by the final handler to do so. The final handler will need to wait until this has been done before returning to the system.
  6. 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 above 400h), 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 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.
  7. 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.
  8. There is a much easier way, however, for one thread to cause an unwind in another thread, and 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 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 variable 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). You cannot do this 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],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.

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-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.