C++ Exception Handling low-level
By Gabriele Trombetti

This article will explain a bit of how the the low-level handling of exceptions works in the Win32/C++ environment.
I will not be very technical here so that reading this will not be too difficult, while still useful for the average programmer. You can find more detailed information on the Internet, and I will also give you some links at the bottom of the page.

Disclaimer: Don't take the things written here as 100% exact. The principles are correct but I'm still not an exception guru and I don't know some details. It's possible that I will update this page in the future when I discover something more or find mistakes.



Interrupts, exceptions and throw statements:

Exceptions can be generated by some software fault or with a throw statement.
Some software faults are e.g. DivideByZero error and GeneralProtectionFault (read/write where you couldn't). All software faults trigger an interrupt on the CPU. [Actually, they are called "exceptions" even at CPU level, but you can think to them like if they where interrupts]

The Operating System has installed its own exception handler in the interrupt descritptor table (IDT) of the CPU, so that one gets called when your software errs. Your exception handler will be called by that one, as described below.
Your exception handler WILL NEVER go into the IDT of the CPU because that one is unique for all the applicatons in the system, so forget it. User-mode code cannot even access IDT.

If you dig into the assembler you will find that a throw statement is finally mapped by the compiler into an INT 3 assembler instruction, which calls the exception handler exactly as if it was a software failure. So software-failure exceptions and throw statements are finally the same thing.



The Exception Handlers linked list:

As you know, you can put a try{} (and corresponding catch) statement in your code, and call a function from inside it. That's in facts the normal way to operate. The called function can set an exception handler itself with try{} and catch() {} statements, and do things inside the try block itself.
Every exception handler can decide to handle some types of exception and to leave the other exceptions to the more external handlers. If no handlers accept that exception, the process gets terminated.

So you can begin to think that there is a linked list of exception handlers somewhere, which the OS interrupt exception handler will browse asking "Do you want to handle this kind of exception?" "Do you want...?" "Do you...?" until one of them accepts.

The linked list works as a stack: every time the compilers finds a try/catch statement it will add one exception handler at the top, and as soon as the try block ends it will remove it.
Actually the linked list is made in the usual way: every block contains the address of the exception handler and then a pointer to the next block. So how does the Operating System know where is the first block?
The 386 segment register called FS points to a segment which contains the information associated to the current thread. The first DWORD points to the first exception block of the list (the top of the "Exception Handlers Stack").

The gate in the IDT for exception handling by the OS is CallGate-like and not TaskGate-like, so the CPU registers are mantained when the exception is raised, and the OS exception handler is executed in the address space of the process which has raised the exception. So the OS procedure for handling exception can easily read FS:[0] and find the list of custom exception handlers.

When the compiler wants to add an exception handler it creates a new block, sets the second field pointing to the previous block (= copies the address from FS:[0]), sets the first field so that it points to the new exception handling procedure, and sets FS:[0] to the address of the new block. The compiler will remove the exception handler when the execution goes out from the try block. The technique for removing the last added exception handler is exactly inverse.

Where should we create this new exception handler block? The simplest place is pushing it on the stack (the normal stack, I mean SS:ESP). And this is what is done in facts.

You can find the pointer to the first block in FS:[0]: the pointed DWORD is the address of the previous exception handler block, and the one above (pushed first, but the stack grows down) is the address of the exception handler function. In C++ notation we would write:

struct ExceptionHandlerBlock
{
    ExceptionHandlerBlock *pPrevBlock;
    EXCEPTIONHANDLERFUNCTION pfnExceptionHandler;
};



Restoring the stack pointer:

So, when a nested function raises an exception the OS begins to call all the handlers from the innermost (most recently installed) to the outermost (older) until one of them accepts to handle that type of exception.

The exception handler which accepts to handle the raised exception will resume the execution at its corresponding catch block. Now you can be wondering how could the stack pointer be OK now, since the program was executing a nested function.
The answer is that the compiler helps you by writing the "REAL" exception handler: a function which sets the stack back and then jumps to your catch block.

And how can the compiler-made exception handler (which is just a procedure, it's written statically) know what is the correct value of ESP to execute the catch block? Not every time the function is entered the value of ESP will be the same, because it depends on the exact sequence of calls which can vary according to external events.

[I'm only 80% sure here] I think the OS helps the handler by telling where it found the handler address. Yep! If the handler gets informed on where its exception-handler-block is located on the stack it can calculate the value of ESP at the moment the function was entered and so it can calculate the correct value of ESP required to execute the catch block.

This is the way VisualC++6 operates. Another compiler might choose to store larger exception blocks where there could be a third field with custom data (e.g. value of ESP and other stuff). This always requires that the OS informs the handler about where it found its address (= position of the block).



Objects and stack unwinding:

There is one more thing to investigate: The C++ specifications tell that the classes must be exception safe. What does it mean? It means that if a class was created on the stack (= stack space allocated and constructor called) and then an exception is raised, if the handler is out of scope (e.g. in the caller function) the destructor must get called.

Since exceptions can be raised by most ASM instruction, the compiler can never be sure that an exception handler is not needed if a class is instantiated. And the situation is even more dangerous when a function is called.

So every time you use a class (with a nonempty destructor) as a local variable the compiler generates a half exception handler (my word) which is not able to handle any sort of exception but is able to do the stack unwinding for that function and will call the destructor for every class instance which was in-scope at the moment of the exception.

So the real sequence of things the OS does is this: it scans the exception handlers a first time by asking "Can you handle this kind of exception?". Then when one has been found the handlers get scanned a second time, up to that one, and they are told "Please now do your stack unwinding".

So now there is another problem: how can the half-exception-handler know for which classes the constructor was called and the destructor wasn't, and which classes are on the contrary ok? (Ok = both constructor and destructor called, or none of them). Remember that there can be many levels of { } in the code, so some objects can enter in scope (->be constructed) after others are already destroyed.

Well, VisualC++6 allocates a local DWORD variable (the first one in the local stack area: SS:[EBP-4] ) which uses as a flag to trace the execution. Every time a constructor or destructor returns, the value is changed (usually incremented). So by looking that local variable the half-handler is able to understand for which classes the destructor is to be called.

VisualC++6 decides not to use the half-exception handler if no functions are called, even if a class is instantiated. It means it trusts the code you write "directly there" while the object is in-scope. If you generate e.g. a GPF fault while an object is in scope you will see that the destructor doesn't get called. This is against the C++ specifications but I suppose it is done for optimization purposes (a function which doesn't call another function it's often time-critical) but if you want to change this behaviour you can easily set a try block around your dangerous code and just set the catch statement like this
catch (...) {throw;}
rethrowing the exception. This will force the compiler to write an exception handler, and now the stack unwinding will be handled correctly.



Catching a structure:

We are nearly done.
One last thing to investigate: thrown exception can throw a structure or class, and that is catched by the catch statement.
[the class or structure is allocated on the heap, and its address is stored in a register before calling int3, there is no magic in this]

So you could wonder: how do the throw and catch statement agree on the type of custom structs/classes ?

Maybe when I compile the throw I have a definition of what is structure A

struct A {//Substitute class for struct as you like
 int i;
};

and when I compile the catch I have another definition for the structure
called "A"

struct A {
 float i;
};

maybe because the function with the throw and the function with the catch
are in different files .cpp with different #includes.

so when the function with the throw does
......
A a;
throw a;
......
};

and the function with the catch does:

......
try{
......
};
catch (A &a) {
......
};

they mean two different things.
Well, I  have tried this example and it seems this time the compiler makes the mistake. The catch CATCHES the structure A even if it shouldn't.
Since the compiler always creates a code which is name-wise probably the linker is involved in all this. My supposition is that the linker generates exception identifying numbers based on names.
There are some fixed numbers which belong to the software faults and they are defined by Intel I think (you can see them in win32 manuals), but as far as throwing is concerned I think custom numbers to identify datatypes are defined at the linking step.

Ok, We end here :) I hope it was useful.
If you are interested you can see more technical Win32-related exception handling information here.

Regards,
Gabriele Trombetti

Back to Trombettworks Software