Introduction
One of the widely used and not well documented feature of 32-bit Windows Operating Systems is Structured Exception Handling. This common exception service provided by the Operating Systems is used by C++ compilers.
This article describes the Win32 exception layering, and discusses structured exceptions handling (SEH) on OS level and on C++ compiler level.
Describing exceptions from the OS level point of view helps to understand the functionality and the performance cost of exceptions. Then, it will not be difficult to reveal how it is possible to mix SEH and C++ exceptions, and how it is possible to catch SEH in C++ exceptions, which could be the main motivation to read this article.
The best way to understand the topic is to play with the attached examples.
Win32 Exception Layering
The following diagram shows how the common exception service provided by the OS (OS Level SEH) is used by C++ compilers for structured exceptions handling (C++ Compiler Level SEH) and for well-known C++ exceptions (C++ Exceptions Handling).
It is important to understand that both C++ Compiler Level SEH and C++ Exception Handling use the same OS Level SEH.
OS Level SEH
The common exception service provided by OS handles:
- Software (Synchronous) Exceptions - explicitly passes control to the Operating System through software interrupt.
- Hardware (Asynchronous) Exceptions - e.g., access violation, integer division by 0, illegal instruction, etc...
Exception Callback Function
Whenever some exception occurs, the OS calls the Exception Callback Function within the current thread context.
This function can be defined by the user and the prototype is as follows (defined in the excpt.h header):
EXCEPTION_DISPOSITION __cdecl _except_handler
(
struct _EXCEPTION_RECORD* _ExceptionRecord,
void* _EstablisherFrame,
struct _CONTEXT* _ContextRecord,
void* _DispatcherContext
);
According to the returned value, the OS will perform a certain action (defined in excpt.h):
typedef enum _EXCEPTION_DISPOSITION
{
ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException,
ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;
The first parameter _ExceptionRecord
describes the exception (defined in WinNT.h):
typedef struct _EXCEPTION_RECORD
{
DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress; DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
The third parameter _ContextRecord
is a pointer to the CONTEXT
structure which represents the register values of a particular thread at the time of the exception. This structure is defined in WinNT.h, and it is the same structure which is used for the Get/SetThreadContext
API methods.
Exception Callback Function Registration
The Operating System stores the registered Exception Callback Functions in a linked list of EXCEPTION_REGISTRATION_RECORD
structures:
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD* prev;
DWORD handler;
} _EXCEPTION_REGISTRATION_RECORD;
The element prev
points to the previous record in the linked list, or is 0xFFFFFFFF for the last record. The element handler is a pointer to an _except_handler
callback function.
The linked list of Exception Callback Functions is registered in the Thread Information Block (TIB) defined in WinNT.h:
typedef struct _NT_TIB
{
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
...
} NT_TIB;
On the Intel Win32 platform, the register FS always points to the TIB of the current thread. Thus, at FS:[0] is the pointer ExceptionList
(see the following diagram).
Exception Callback Function Searching
When an exception occurs, the Operating System walks the linked list of registered exception callback functions, and searches for the callback which agrees to handle the exception (which does not return ExceptionContinueSearch
).
The Operating System stores at the end of the linked list the default callback which displays the general-protection-fault message box. Thus, if the user does not change the end of the linked list, the searching stops on the default callback.
If the Operating System does not find any callback for the exception, it terminates the application.
Example 1
Example 1 registers its own Exception Callback Functions using only the common exception service provided by the Operating System. The first one does not handle any exception, and always returns the value which tells the OS to go to the next exception handler. The second one handles an access violation exception and corrects bad writing address and tells the OS to restart the failing instruction.
This example further shows searching of the callback which agrees to handle the exception, and shows the continuation of the execution after the exception correction.
C++ Compiler Level SEH
The C++ Compiler Level SEH is a compiler wrapper around the OS Level SEH.
The Microsoft C++ compilers define the new keywords __try
, __finally
, and __except
to handle SEH. These keywords work for both C and C++ source files.
A very important thing to note for C++ code with compiler level SEH is that the compiler does not care about C++ object destructors.
C++ SEH Exception Callback
The C++ SEH Exception Callback is the C++ compiler implementation of the Exception Callback Function for SEH. It calls the C/C++ user code (called exception filter) to check if the exception should be handled or not. If an exception should not be handled, it returns the processing back to the OS. If an exception should be handled, it never returns back to the OS.
If the user code decides that the exception should be handled, the C++ SEH Exception Callback Function:
- executes exception unwind
- calls the C/C++ user code (called except block) to handle the exception
- cleans up the stack and sets the frame pointers
- transfers control back to the C/C++ user code immediately after the except block
Exception Unwind
During exception unwind, the C++ SEH Exception Callback Function:
- walks the linked list of registered callbacks again, up to itself
- calls each callback the second time with different exception flags (
EXCEPTION_UNWINDING
)
During this second traversal, the callbacks call the C/C++ user code (called finally block) allowing to do cleanup (e.g., releasing of resources).
Try-except Block
To handle C++ Compiler Level SEH exceptions, the try-except block is used:
__try
{
}
__except (filter_expression)
{
}
The __try
block is protected by the C++ SEH Exception Callback Function which is registered by EXCEPTION_REGISTRATION_RECORD
allocated on the stack.
__except
defines an except block (called exception handler as well).
filter_expression
defines an exception filter which is any C expression or even function call having the following integer result:
EXCEPTION_CONTINUE_EXECUTION
- continue execution where exception occurredEXCEPTION_CONTINUE_SEARCH
- continue searching in the callback linked listEXCEPTION_EXECUTE_HANDLER
- exception has been recognized and continue with the execution of the exception handler
Try-finally Block
To handle user cleanup when an exception occurs, the try-finally block is used:
__try
{
}
__finally
{
}
__finally
defines a finally block (called termination handler as well).
In case of no exception, the code in the finally block will be executed as well.
SEH API Functions
The Win32 supports API functions for SEH. The following is a quick overview of the most used functions:
GetExceptionCode()
- Returns a code that identifies the exception. This function can be called only from the filter expression or the exception handler block.GetExceptionInformation()
- Returns a pointer to the exception record structure and a pointer to the exception context structure. This function can be called only from the filter expression.AbnormalTermination()
- Indicates whether the try-block of a termination handler terminated normally. This function can be called only from the termination handler.RaiseException()
- Raises an exception in the calling thread (creates a software synchronous exception).
Example 2
Example 2 demonstrates:
- the handling of access violation exception by the C++ compiler level SEH
- usage of the try-except block
- sample implementation of the exception filter
- usage of the try-finally block
C++ Exception handling (C++ EH)
C++ Exception Handling is the C++ compiler wrapper around the OS Level SEH. C++ compilers defined the new keywords try
, catch
, and throw
to handle C++ exceptions.
The big advantage of C++ code compared to the compiler level SEH is that C++ EH properly calls the destructors.
The C++ EH is very well described in many C++ language books. Therefore, we will not describe C++ EH in more details here.
Mixing SEH and C++ EH
It is important to understand that it is not possible to mix SEH and C++ EH in one C++ method, because both register their own Exception Callback Function. However, it is possible to mix SEH and C++ EH in different C++ methods. But be very careful about C++ object destructors, SEH does not call them!
The second important thing is that SEH cannot handle C++ exceptions because SEH comes from the C world. However, C++ EH can handle SEH. But again, be very careful because this feature is compiler dependent, and it is usually supported by a special compiler switch. Furthermore, this feature has a performance impact because the compiler must trace each C++ object to call its destructor.
Consider the following C++ construction:
try
{
...
}
catch (...)
{
}
The question is: does this C++ block catch an SEH exception? The answer depends on the compiler:
- VC++ 6.0 - yes (by default)
- VC++ 8.0 - no (by default), yes with /EHa
- EVC 4.0 - no (by default), yes with /EHa
But, a C++ try-catch block cannot distinguish a Structured Exception type. To do so, you must use the try-except block from SEH, or transform the Structured Exception to a C++ typed exception using the _set_se_translator()
API method (not supported by WinCE).
Conclusion
This article shows how layers of exception handling implementation are stacked one upon others, and what are the common points between OS Level SEH, C++ Compiler Level SEH, and C++ EH.
Understanding the low level details is a key point for proper judging of program behaviour and its performance during exception raising and during exception mixing.
Acknowledgement and Links