Structured Exception Handling Basics

Published January 06, 2001 by Vadim Kokielov, posted by Myopic Rhino
Do you see issues with this article? Let us know.
Advertisement
[size="3"]Abstract

This article discusses briefly the Windows technology known as Structured Exception Handling, including its advantages and disadvantages, its capabilities, and various technical aspects of its use.

[size="3"]Credit

Much of the technical information is based on Jeffrey Richter's last two chapters of Programming Applications for Microsoft Windows. I would like to thank him for writing and publishing a great book on Windows kernel programming.

[size="3"]Prerequisite

Familiarity with C++ exception handling will help you understand this article, though it is not strictly necessary.

[size="3"]About This Article

This article explains the fundamentals of using structured exception handling, a Windows-specific technology that offers more power and flexibility and better performance than the C++ exception handling system. The reasons for this framework's advantages are clear. For one, SEH supports exception filtering. It is possible, using SEH, to resolve a problem inside an exception filter function, or alternatively force the system to ignore this particular instance of an exception and skip it. In addition, SEH in Visual C++ offers an extremely valuable asset - termination handling. With termination handling, you can forget annoying cleanup code and painstaking, repeated return statements. Finally, the technology is available in C code - quite a blessing, really, considering that many applications use that language for fast processing. Moreover, C++ exception handling under Visual C++ carries speed and size overhead due to the immense amount of functions that can get called in the process and the data structures required by compilers to pull it off, while SEH, despite some additional functions that the compiler must generate and the OS kernel must call, is fast and reliable because the kernel is responsible for all the cleanup chores and because SEH refrains from making things more complex than they need to be. There is no doubt that in Windows, SEH is faster than C++ EH. Finally, SEH carries with it cross-language compatibility; I can write a DLL that raises a software exception in 2000, and my coworker's great-grandchildren in 2100, using the magical language called X-Plus, can write a program that uses my DLL and catching the exception it raises. Try that with C++ EH, and you'll find that it works only in C++ - and even then with possible headaches because the DLL attaches at runtime.

Quite dismayingly, using C++ exception handling is preferred over SEH in C++ unless SEH is used in a system-level routine or the programmer knows exactly what he is doing. The reason for this is that SEH fails to call object destructors after a block of code is abandoned due to a raised exception. It is possible to integrate SEH into C++, however. You are encouraged to experiment with various ways that SEH can be used in C++. From the concepts presented here, it should be relatively easy to deduce the side-effects of using SEH on a C++ compiler, and most compilers warn their users about inappropriate utilization of SEH in C++.

It's time to make your next application as robust as it can get.


[size="5"]Fundamentals of SEH

Most current compilers provide an interface to SEH using three keywords and one API call, although there is a lot more to it than that. There are two types of exceptions: hardware exceptions, raised by the OS in response to processor or virtual machine events (e.g. divide by zero or invalid memory access), and software exceptions, raised by you or the Windows API or a component you are using. SEH is used to implement C++ exception handling in Microsoft Visual C++. The gory details of SEH's own implementation are not required knowledge. This information is, however, available (see Microsoft Systems Journal article "A Crash Course on the Depths of Win32 Structured Exception Handling").

The three keywords used to interface with SEH are __try (preceded by two underscores), __except, and __finally. __try begins a code block and is paired with either __except or __finally. In both cases, it forms two blocks: code after the __try keyword, enclosed in braces, and code after the second keyword, also usually enclosed in braces. When it is paired with __except, it is called a try-except block. When it is paired with __finally, it produces a try-finally block. The API function RaiseException can be used to generate a software exception, akin to the C++ throw keyword. Please see the documentation for RaiseException in the Windows Platform SDK for more details

An important rule to remember in all cases when dealing with SEH is that you cannot jump into a try block. If such behavior were allowed, it would disturb the very principle upon which SEH (or C++ EH, for that matter) is based: structure. A similar rule is that jumps out of try blocks, although permitted, are best avoided because the compiler must go to pains to make it work. However, jumps within a try block are acceptable because no special code must be generated in that situation.


[size="5"]Termination Handlers - Basics

A termination handler is a block of code that gets called on early exit from a function or any of the functions it calls. This can be useful for providing dynamic clean-up, similar to C++ destructors (with the exception that C++ destructors do not get called when a hardware exception is raised).

To build a termination handler, simply enclose the code you want to guard in a try block and follow it by clean-up code inside the finally block, like so:

__try
{
// Guarded code
}
__finally {
// Termination handler
}
The code above will execute as follows: unless there is some interruption - officially termed a premature exit, the code in the try block will execute, followed by code in the finally block. If there is a premature exit, the instruction pointer jumps from the current machine instruction to the first instruction inside the finally block. There are two types of premature exits: those invoked by an early jump from the try block, such as returning from the function, and those that are caused by SEH exceptions being raised.

Whenever there is a change in control that results in the instruction pointer of the CPU leaving the guarded code, such as a return or a goto, the system considers this a premature exit and the instruction pointer is moved to the finally block. The confusing aspect of this is the question, "what does it mean for a jump to leave the guarded block?" Generally, if a statement makes some kind of jump that is not a function call, it will leave the guarded block and cause finally code to begin executing. This happens because function calls push their return address on the stack, and the system recognizes that very important fact, important because as long as the system can be assured that code can return to the finally block at any point, it does not consider a call to be a premature exit from the try block. This mechanism gives the termination handler technology a serious advantage over "fail gotos".

What happens when a software or hardware exception is raised? Before the code in an exception handler can execute (as described below), the system ensures that any finally code it encounters that is on the stack below the handler is run. Compare it to a fatal wound. If the finally code starts running early, you may not be quite dead yet, but you know that there isn't anything you can do about it (probably the reason for the keyword itself).

If a premature return happens in a termination handler's try block locally (in other words, the system executes a jump instruction), the system must do a local unwind. This means that whenever such a return happens, the system must save the variable on the stack and force execution to continue in the finally block. This is not very efficient on some processors and should be avoided, specifically by using the __leave keyword. The second kind of unwind (a global unwind) has a slightly more complex role. It is more appropriate to discuss it when I get to exception handling.

As you can see, termination handling is an interesting compiler extension. However, it is not meaningful until seen applied in some context.


[size="5"]Applying Termination Handlers

So, what is the practical benefit of termination handlers? In a situation where a function could fail before termination, you may find it wise to use a termination handler even if you can see no reason in the world for an exception to be raised. In fact, the blessing of THs is that they are applicable to almost any task, if

// Termination handler example with summation program:
// read data from file and accumulate the sum

int CalcSum (const char* pszfn, int iCount, int* pfOK)
{
// These variables are local to the function
int fSuccess = 0, iSum;
FILE* pf;
int* pMem = NULL;



__try {
// Some variables needn't be accessed in the finally block
int i;

// Begin the try block by opening a file to read the numbers from
pf = fopen (pszfn);

// If that fails, we bail out
if (!pf) __leave;

// Allocate some memory
pMem = malloc (iCount * sizeof(int));
if (!pMem) __leave;

// Read from it
if (fread (pMem, sizeof(int), iCount, pf) < sizeof(iPrvNumber)
__leave;

// Call Summate
for (i = 0; i < iCount; i+=2) {
Summate (pMem, &iSum);
}

// Signify that all has gone OK
fSuccess = 1;

} __finally {
// Take care of the cleanup
fclose (pf);
free (pMem);
}

// Set up the success out parameter
*pfOK = fSuccess;
// And the actual return value, too
return iSum;
}

void Summate (const int* pSrcArray, int* pDest)
{
// For example. sum two numbers or 1/2 chance crash the entire system
if (rand () % 2) {
// Randomly crash the scene (don't try this at home)
short *p = NULL;
*p = 0xBEEF; // Random value
} else {
*pDest = pSrcArray[0] + pSrcArray[1]; // Correct action.
}
}
used correctly. The listing above should provide you with a good example of a function that makes good use of a TH. There is one keyword you will not recognize, and that is __leave. This keyword is described in detail below.

In this code, we have several conditions for early termination. The unfamiliar __leave keyword is actually quite simple: it is an alias for a goto statement that jumps to the closing brace of the try block, essentially skipping any code still left in it. This method should sound familiar if you have worked in BASIC: it is the same ON ERROR GOTO principle applied there. In effect, we are saying that we want the lines of code in the finally block to execute no matter what happens in the try block. If you examine the Summation function, you will notice that there is a 50% chance it will attempt an invalid memory access and thus raise an exception. If that happens, the system will see that the program failed to handle the exception and will handle the exception on its own, displaying the Illegal Operation dialog box on Windows 9x. However, prior to that the statements in the finally block are executed. Both of these occurrences are normal, since exceptions, akin to their name, occur quite rarely (I can't imagine anyone writing a Summation function as in the example unless they want to give their marketing manager a heart attack).

Why use __leave and not return? As said before, use of __leave prevents local unwinds from occurring and slowing down the entire setup. In general, Jeffrey Richter recommends you to avoid putting any flow-of-execution modifiers in the try block. When you wish to use a loop, you should be absolutely sure that the loop you are writing is entirely inside the try block and that your jump will remain within the block. This is demonstrated by the continue in the for loop.

Why not simply use the "fail goto" method? You can use such a structure for simple code setups, of course, but this is going to start to fail you when you are talking about more than one function or even nested try-finally blocks. If Summation had called another function, say, GetDC, and GetDC failed, how would the failure be reported? One way is to use the infamous return-value-chain, but its complexity was one of the reasons exception handling was invented. In reality, all the inner function has to do to report failure in a try-finally situation is raise a software exception. A function somewhere at the top will have a try-except block that will handle the exception. While the system walks up the stack to run the code in the except block, all the finally blocks execute, cleaning up after whatever was in the try block. Termination handling is clean and effective, and has no easy alternative in either ANSI C or standard C++.


[size="5"]Exception Handlers

We are entering the realm of exception handling in SEH. The topic is not as complex as some would have you think, however. This section describes in detail how try-except blocks behave, what global unwinds are, what exception filter functions are, how the system acts on unhandled exceptions, and raising software exceptions. The basic format for exception handlers is this:

__try {
// Guarded code
} __except (EXCEPTION_EXECUTE_HANDLER) { // Exception filter
// Exception handling code
}
Let's begin by understanding what a try-except block does. Simply put, a try-except block is a try block matched with an except block. You already know what a try block is. An except block is code that executes only if an exception was raised. In C++ EH, this block would be comparable to a catch, though the syntax is not identical. The main difference between the two exception handling mechanisms is that an except block is followed by a standard C expression called the exception filter. Although there is a filter in catch blocks, it is merely a variable declaration - rather limiting. When a Win32 compiler generates code for C++ EH, it replaces its catch with an except whose filter expression is a primitive type check. This is how C++ EH works: I "throw" a value and the handler generated by the compiler verifies my value's type against the one in its own filter expression. If they match, the handler executes. If the system encounters a throw statement in the catch block, it raises another exception - something that is not at all necessary when using SEH. Ironically, the data-type checking expression that is generated is most likely larger than needed because when an SEH exception receives its data it does not care what the data type is, and therefore to support C++ EH a structure must be passed with both the data and data type inside it. This is getting complex, isn't it? The overhead involved with all C++ exceptions on Windows is starting to surface.

When an except block is reached by means of a raised software or hardware exception, the compiler checks the value of the filter expression. The filter expression can evaluate to one of three constants defined in excpt.h and can perform as many operations as it needs to get this done; for filtering tasks above simple exception code checking it is usually best to write a filter function that returns one of the three values and call it from the filter. Let's discuss them in order. The first is EXCEPTION_CONTINUE_SEARCH. If the parenthesized expression before the except block returns this value, the system skips the except block as if it weren't there and continues up the stack as though no handler was there. This is very important for several reasons. First, if your exception handler can't handle this particular exception, you must pass it up to the higher powers for handling. Second, if you find that even though the exception can be handled by you, but the parameters passed to it are out of your jurisdiction, you have no choice but to pass it up, as well. You may ask, "What happens when an exception is ignored by every function searched as the stack is traversed?" Then it becomes an unhandled exception for which the system is directly responsible. Unhandled exceptions will be explained after the description of the two remaining filter return values.

The second value that can be returned by the except filter is EXCEPTION_CONTINUE_EXECUTION. This is by far the most dangerous (and powerful) of the three, because it instructs the processor to retry the last machine instruction that caused the exception by assuming that your code "fixed" the cause of this exception in the filter function so the program may continue normally. However, imagine the implications of this: even if your scenario is a simple case of invalid memory being accessed or divide-by-zero, the machine instruction that caused the exception is probably one that deals with registers rather than variables, in which case you would have to go to some pretty drastic measures to make it work like you want to. However, in several scenarios, EXCEPTION_CONTINUE_EXECUTION is appropriate, and even recommended. One such case is in deciding when to commit virtual memory. Instead of keeping track of committed memory, it is much cleaner to react to an access violation that occurred due to a program attempting to access reserved, non-committed storage and respond by allocating that page of storage. Using EXCEPTION_CONTINUE_EXECUTION safely is a topic beyond the scope of this document.

The simplest of the three values is EXCEPTION_EXECUTE_HANDLER. Returning this value causes the code in the except block to begin executing just after a global unwind happens (see below). There is no way at that point to return to the instruction that raised the exception other than by saving the value of the EIP register and attempting a jump to that location - not a viable solution, mainly because if the code was in another function when the exception occurred, this will not work properly. Therefore, you should return this value after you have ascertained that you can handle the given exception and that you cannot continue where you left off. In fact, sometimes your except block will be empty because your filter function took care of most of the work involved with handling the exception, and other times it will contain a call to a message reporting function that will explain to the user exactly what went wrong and how to fix it, or trace the error for debugging purposes (most IDEs on the market probably support debugging exceptions, so this may not be necessary). To force the system to look higher up the stack for another exception handler, your except block can call RaiseException and pass it the same information that was passed to you by the system. For program style reasons, it is better to make this decision in your exception filter function.

As mentioned before, an exception filter function is a function called from an exception filter to decide what to do with the exception and (potentially) correct the situation. This function is aided by two built-in (intrinsic) functions that are expanded directly into your code and most likely access registers that the system uses to store this information. The first, GetExceptionCode, returns a DWORD that contains the exception code (a preset value from excpt.h or a value you defined for software exceptions). This function can be called both inside the exception filter expression and inside the except handler block. The second, GetExceptionInformation, is more thorough. It can be called from within the exception filter expression only (because the data is removed from the stack after that) and returns a pointer to an EXCEPTION_POINTERS structure, which in turn contains pointers to an EXCEPTION_RECORD structure that describes the exception and a CONTEXT structure which represents the contents of this thread's registers at the time the exception was raised. Quite interesting is that if you know which CPU your code is running on, you can modify this CONTEXT structure to change where EXCEPTION_CONTINUE_EXECUTION returns or to change the register which has the invalid address (if you wrote your function in assembly and know the correct one) - a very powerful trick and a justification for the system's invalidation of the EXCEPTION_POINTERS and CONTEXT structures as soon as the exception filter is executed. In many cases with hard exceptions you will want to pass other variables to your exception filter function. Below is an example of an exception frame with GetExceptionInformation a parameter for the filter. The code for the filter function is omitted for clarity.

DWORD AV_Filter (PEXCEPTION_POINTERS pPtrs, BYTE* pErr);

DWORD OrBytes (const BYTE** ppBytes, BYTE orByte) {
BYTE* pCurByte; pCurByte = ppBytes[0];
__try {
while(pCurByte++)
*pCurByte |= orByte;
} __except (AV_Filter (GetExceptionInformation(), pCurByte) ) {
// End of list or first byte is null
}
}
Putting aside the fact that the code assumes a null-terminated array of bytes as input, this listing demonstrates calling an exception filter function. In this concrete example, the filter function would be designed to attempt to correct the problem and return EXCEPTION_CONTINUE_EXECUTION if pCurByte happened to be, say, protected memory not equal to NULL. Although this is overkill for a ridiculously simple task like the one presented, it will become more apparent with each time it is used that SEH can cleanly report to its user a great variety of errors and present an interesting alternative to the classic programming problems that have plagued developers for years, such as overstepping array bounds without intention.

Global unwinds are directly related to the exception handling process. A global unwind occurs when an exception filter returns EXCEPTION_EXECUTE_HANDLER. In essence, every try that is associated with a finally nested under the except block that handles the exception must be run. This process, in contrast to local unwinds described above, is desirable; it is one of the main reasons termination handlers exist. The more confusing aspect of this is that you can halt a global unwind and prevent an exception handler from executing. If you place a return statement in your finally block, and that block happens to be in the way of a global unwind, the system will exit the function that contained the statement and continue execution normally. This behavior is intended, since you may occasionally find it useful.

When the program fails to handle an exception, the system exception-handling frame receives control and Kernel32.dll takes care of terminating the process or attaching a debugger to it. The infamous illegal operation / unhandled exception message box is the result of the system's own exception filter function, named UnhandledExceptionFilter. This function can be discussed in very great detail, but for the purpose of this article such depth is unnecessary. The function is actually part of an SEH frame set up for every thread in every process that catches all user-mode (application) exceptions. It is possible to force it to call a custom filter function that takes the same parameters that it does. It is also possible to call it from within one's program. You are encouraged to research the topic of unhandled exceptions further. It presents a powerful method of preventing unexpected crashes. For example, in the Maxis game SimCopter, an unhandled exception filter had to have been used, since the program would offer the user a chance to attempt saving his game. Whether this actually succeeded was dependent on the cause of the error, but this approach was nevertheless more useful than the ubiquitous "this program has performed an illegal operation, you lose your work, tough luck, bye" attitude taken by Windows (only because Windows does not know your program; you do).

[size="5"]
Conclusion

SEH is a mixed blessing. Although it is faster and more efficient than C++ EH, it has the limitation of being confined to the Windows platform at this point in time. However, for professional development that should not be a hindrance since Windows is quite a popular platform. DirectX is compatible with SEH - in fact, the technology may be used in quite interesting ways in conjunction with DirectX. Although the MSDN documentation recommends that C++ users do not utilize SEH, that part of the library was written by the developers of Visual C++ and therefore is more for show than weight. I urge you to consult the Microsoft Systems Journal (also on the MSDN library CD) and Jeffrey Richter's Programming Applications for Windows for additional - and more insightful - information on structured exception handling.
Cancel Save
0 Likes 0 Comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

This article discusses briefly the Windows technology known as Structured Exception Handling, including its advantages and disadvantages, its capabilities, and various technical aspects of its use.

Advertisement
Advertisement