Introduction
One of the widely used and not well documented feature of 32-bit Windows operating system is structured exceptions handling. This common exception service provided by operating system 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 OS level point of view helps to understand functionality and the performance cost of exceptions. Then, it will be not 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 why to read this article.
The best way how to understand the topic is to play with attached examples.
Win32 Exception Layering
The following diagram shows how the common exception service provided by OS (OS Level SEH) is used by C++ compilers for structured exceptions handling (C++ Compiler Level SEH) and for well-known C++ exceptions (C++ Exception 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, 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 excpt.h header):
EXCEPTION_DISPOSITION __cdecl _except_handler
(
struct _EXCEPTION_RECORD* _ExceptionRecord,
void* _EstablisherFrame,
struct _CONTEXT* _ContextRecord,
void* _DispatcherContext
);
According to the returned value, 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;
Element prev
points to the previous record in the linked list or is 0xFFFFFFFF for the last record. Element handler is a pointer to an _except_handler
callback function.
The linked list of Exception Callback Functions is registered in Thread Information Block (TIB) defined in WinNT.h:
typedef struct _NT_TIB
{
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
...
} NT_TIB;
On Intel Win-32 platform, 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
).
Operating system stores at the end of linked list the default callback which display general-protection-fault message box. Thus, if user does not change the end of linked list, the searching stops on the default callback.
If operating system does not find any callback for the exception, it terminates the application.
Example 1
The Example 1 registers own Exception Callback Functions using only common exception service provided by operating system. The first one does not handle any exception and always returns value which tells OS to go to the next exception handler. The second one handles an access violation exception and corrects bad writing address and tells OS to restart the failing instruction.
This example further shows searching of the callback which agree to handle exception and shows continuation of execution after exception correction.
C++ Compiler Level SEH
The C++ Compiler Level SEH is compiler wrapper around OS Level SEH.
The Microsoft C++ compilers define new keywords __try
, __finally
and __except
to handle SEH. These keywords work for both C and C++ source files.
Very important thing for C++ code with compiler level SEH is that compiler does not care about C++ object destructors.
C++ SEH Exception Callback
The C++ SEH Exception Callback is the C++ compiler implementation of Exception Callback Function for SEH.
It calls 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 processing back to OS. If an exception should be handled, it never returns back to OS.
If user code decides that the exception should be handled, the C++ SEH Exception Callback Function
- executes exception unwind
- calls C/C++ user code (called except block) to handle exception
- cleans up stack and sets frame pointers
- transfer control back to the C/C++ user code immediately after except block
Exception Unwind
During exception unwind, the C++ SEH Exception Callback Function
- walks the linked list of registered callbacks again, up to the itself
- calls each callback the second time with different exception flags (
EXCEPTION_UNWINDING
)
During this second traversal, the callbacks call 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 exception, the try-except block is used:
__try
{
}
__except (filter_expression)
{
}
__try
block is protected by 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 exception filter which is any C expression or even function call having the following integer result:
EXCEPTION_CONTINUE_EXECUTION
- continue execution where exception occurred
EXCEPTION_CONTINUE_SEARCH
- continue searching in callback linked list
EXCEPTION_EXECUTE_HANDLER
- exception has been recognized and continue with execution of exception handler
Try-finally Block
To handle user cleanup when exception occurs, 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 the quick overview of the most used functions:
- GetExceptionCode() - Returns a code that identifies the exception. This function can be called only from filter expression or 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 filter expression.
- AbnormalTermination() - Indicates whether try-block of a termination handler terminated normally. This function can be called only from termination handler.
- RaiseException() - Raises an exception in the calling thread (creates software synchronous exception)
Example 2
The Example 2 demonstrates
- the handling of access violation exception by C++ compiler level SEH
- usage of try-except block
- sample implementation of exception filter
- usage of try-finally block
C++ Exception handling (C++ EH)
The C++ Exception handling is C++ compiler wrapper around OS Level SEH.
The C++ compilers define new keywords try, catch and throw to handle C++ exceptions.
Big advantage for C++ code comparing to the compiler level SEH is that C++ EH properly calls 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 its 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 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 special compiler switch. Furthermore this feature has performance impact because 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 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 the C++ try-catch block cannot distinguish Structured Exception type. To do so, you must use try-except block from SEH or transform Structured Exception to C++ typed exception by _set_se_translator() API method (not supported by WinCE).
Conclusion
This article shows how layers of exception handling implementation are stacked on 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 it's performance during exception raising and during exception mixing.
Acknowledgement and Links