Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
71 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 Termination
 Handlers

 Exception Handlers

 Printable version

 


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 (intristic) 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).

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.