Upcoming Events
ION Game Conference
5/13 - 5/15 @ Seattle, WA

Nordic Game 2008
5/14 - 5/15 @ Malmö, Sweden

CoGames 2008: The 1st International Workshop on Collaborative Games
5/19 - 5/23 @ Irvine, CA

Vancouver International Games Summit
5/21 - 5/22 @ Vancouver, Canada

More events...


Quick Stats
3942 people currently visiting GDNet.
2172 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

  search:   

The Visual C++ Exception Model



Contents
  Page 1
  Page 2
  Page 3
  Page 4

  Printable version
  Discuss this article

Implementing C++ Exceptions

This section presents a very high level overview of how C++ exceptions are implemented. 11) Let's start with what happens when a C++ exception is thrown: first a copy of the exception is placed on the stack, then the program calls to function named __CxxThrowException() with the address of the exception object and a pointer to a throw information structure placed in the executable image. __CxxThrowException() in turn calls RaiseException() with an exception code of 0xE06D7363 and three information arguments: a magic number, 0x19930520, and the two arguments passed to __CxxThrowException().

Given that RaiseException() is involved in throwing a C++ exception, the natural mechanism for implementing stack unwind for automatic objects is a termination handler. Basically, each function that requires stack unwind has an extra local variable that contains an ID that can be used to determine how far function execution has progressed. As objects are created and destroyed this ID is updated. When the stack frame is destroyed, and the termination handler is run, it uses the ID to destroy the objects in reverse order of construction. This is obviously an simplification of the process; there are a number of special cases. For example, for some calling conventions, function call arguments are constructed by the caller and destroyed by the callee. An unfortunate side effect of this is that if such a function is called via a function pointer and the function pointer has a bad address, causing an SEH exception, this means that the destructors for the arguments will not be run.

The throw information structure used is one of the interesting types that seem to be built into the compiler itself. Even if you include no headers, the compiler knows what the definition for the _s__ThrowInfo should be. Unfortunately, that means that there's no header that we can look at to see an official definition, but the relevant bits seem like they looks something like12):

typedef void (__cdecl * _PMFN)(void); // despite the static typing, function pointers of 
                                      //   type _PMFN are usually used as pointers to other
                                      //   function types
 
struct _PMD {
  int mdisp;  // member offset

  int pdisp;  // offset of the vtable 
  int vdisp;  // offset to displacment inside the vtable
};
 
struct _s__CatchableType {
  unsigned int      properties;       // bit 1: is a simple type

                                      //   bit 2: can be caught by reference only
                                      //   bit 3: has virtual base 
  _TypeDescriptor * pType             // pointer to std::type_info object.
  _PMD              thisDisplacement; // displacement of the object for this type
  int               sizeOrOffset;     // size of the type

  _PMFN             copyFunction;     // pointer to copy constructor or 0
};
typedef const _s__CatchableType _CatchableType;
 
struct _s__CatchableTypeArray {
  int              nCatchableTypes;         // number of entries in arrayOfCatchableTypes

  _CatchableType * arrayOfCatchableTypes[]; // array of pointers to all base types of the object
                                            //   including types that it can be implicitly cast to
};
typedef const _s__CatchableTypeArray _CatchableTypeArray;
 
struct _s__ThrowInfo {
  unsigned int          attributes;           // bit 1 for const, bit 2 for volatile

  _PMFN                 pmfnUnwind;           // function to destroy the exception object
  int        (__cdecl * pForwardCompat)(...);
  _CatchableTypeArray * pCatchableTypeArray;  // pointer to array of pointers to type information
};
typedef const _s__ThrowInfo _ThrowInfo;
So when the compiler runs into a catch (std::exception & e) it actually generates a SEH handler that checks the exception code for 0xE06D7363 then interprets the third exception information argument as a _s__ThrowInfo pointer, and checks the catchable types array for an entry for std::exception. If it finds it, it applies the address translation found in the _PMD record to the second exception information argument and transfers control to the handler block. If not, the exception handler continues the search for additional handlers until it hits one.13)

That brings us to the wonderful mess that is catch (...). Depending on your compiler catch (...) may turn into the equivalent of __except(EXCEPTION_EXECUTE_HANDLER) or __except(0xE06D7363 == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH). In other words, either always execute the handler no matter what kind of SEH exception was triggered or only execute the handler if it's a C++ exception. In MSVC 2005 and later, the compiler chooses the conditional execution if compiled with /EHs and the unconditional execution if compiled with /EHa. With MSVC 2003 and earlier the compiler chooses the always execute option no matter if you use /EHa or /EHs.

Well, sort of. Take this sample program:

#include <iostream>
#include <windows.h>
 
int main(int, char **) {
  try {
    RaiseException(0, 0, 0, 0);
  } catch (...) {
    std::cout << "Moo." << std::endl;
  }
  return 0;
}
In debug mode for MSVC 2003, this will print "Moo." In release mode, with /EHsc (the default enable exception handling flag), the compiler sees that RaiseException(), as an extern "C" function, can't raise a C++ exception. Since catch (...) is only supposed to catch C++ exceptions, the compiler then removes the catch handler as dead code. This can lead to wonderfully bizarre and inconsistent behavior between debug and release builds14). Using /EHs or /EHa without /EHc will prevent the compiler from removing the catch block.

So what is the difference between /EHs and /EHa? MSVC supports two exception models: the synchronous exception model and the asynchronous exception model. The asynchronous model generates code with the assumption that any expression can throw an exception. The synchronous model assumes that exceptions can only be thrown by throw statements or function calls. This means that functions compile with the asynchronous model tend to generate more function state IDs for stack unwind and the associated data structures to support the additional possible exception points. And, as mentioned, in MSVC 2005 and later, using /EHa will allow catch (...) to catch SEH exceptions while /EHs does not. /EHc, which makes sense only when applied with /EHs, additionally makes the assumption that extern "C" functions do not throw exceptions.

A case can be made either way for catch (...) handling SEH exceptions. One point against is the simple fact that C++ exception handlers aren't supposed to handle things like divide by zero or access violations. One point for is the fact that since C++ stack unwind is implemented in terms of termination handler semantics, unless catch (...) handles SEH exceptions, a SEH exception will cause stack unwind, but will not trigger a rethrow clean up catch block. In the following example, if f() raises a SEH exception, the_object will be destroyed, but the catch (...) block won't execute.

  try {
    A the_object;
    f();
  } catch (...) {
    // clean up stuff
    throw;
  }
Of course, this kind of inconsistency is less important if no SEH exceptions are caught by your program and the program ends when an exception is raised.

Unifying Exceptions in MSVC

Assuming you want to make sure that catch (...) handles SEH exceptions, the process is relatively simple in MSVC: use the /EHa flag instead of /EHsc for enabling exception handling information15). For MSVC 7.1 and earlier, this prevents catch blocks from being removed by the optimizer, and is a necessary condition for the catch blocks doing so in MSVC 2005 and later. It will also most likely increase the image size for the module and will probably increase the process working set as well.

You can also take an additional step: installing an exception translator, a function that translates SEH exceptions into C++ exceptions. One of the simplest examples of this would be:

struct SEHException {
  SEHException(const EXCEPTION_RECORD & record) : record(record) {}
  EXCEPTION_RECORD record;
};
 
void translator_function(unsigned int, EXCEPTION_POINTERS * eps) {
  throw SEHException(*eps->ExceptionRecord);
}

 
int main(int, char **) {
  try {
    _set_se_translator(&translator_function);
    // stuff
  } catch (SEHException & e) {
    // handle the exception

  }
  return 0;
}
This takes any SEH exception, no matter what it is and just makes a copy of the EXCEPTION_RECORD for the exception. This provides almost all the same information available to a __try/__except block to a C++ catch block. A translation function shouldn't be too complicated: if the translation function raises a SEH exception, terminate() will be called, just like if a C++ exception is thrown in a destructor during stack unwind. _set_se_translator() works on a per-thread basis, so if your program is multi-threaded, it needs to be called for each thread. This still requires that the /EHa flag is used when compiling your application.

If you don't want to install an exception translator or don't want to use /EHa, but want the equivalent of a catch (...) block that handles SEH exceptions, you can also use a __finally block that checks for abnormal termination. Of course, this isn't a direct translation. The limitation on objects with unwind semantics may make this technique annoying to apply in a large number of situations, and you need to pay attention to control flow statements that may generate an abnormal termination such as returning out of the __try block. In practice this translation often requires creation of two extra functions: the original function calls a function with just a __try/__finally block which in turn calls a third function that performs the work of the original try block (and sometimes a fourth function to perform the work of the catch block).16)

Sometimes you want the opposite effect: being able to handle C++ exceptions inside an __except block. This is a much steeper challenge since most C++ objects can't be used effectively within a function that sports SEH handling blocks. However, functions called from a function with SEH handling blocks don't have the same restrictions, including functions called from a filter expression. You might be wondering why you would want to do this, especially since you can translate SEH exceptions into normal C++ exceptions with MSVC. Well, one interesting aspect of a filter expression is that it's executed in the context of the point of the exception, before stack unwind occurs. That means you can obtain quite a bit of diagnostic information about the point where the exception was triggered in a filter expression, such as a stack trace, and this functionality has no analogue in C++ catch blocks.

Within an SEH handler, to get at a C++ object for an exception requires duplicating some of the work that the compiler does automatically in C++ catch blocks. You need to check the exception code, and then use the _s__ThrowInfo to figure out how to map the pointer in the exception information to a pointer to a desired type. To demonstrate this, let's develop an application that contains a global handler for SEH exceptions and reports a stack trace when it handles an exception.17)


11) Much more detailed explanations are available, see the references section.
12) Provided comments come from examining disassembly, so may not be entirely accurate, though the names and types should be accurate, modulo typedefs.
13) This description ignores a lot of the other work it needs to do, such as checking the magic number and initiating stack unwind.
14) Not that I'm bitter.
15) Since this isn't an option given in project properties for MSVC 2003 and earlier, you'll need to specify it as an additional command line option.
16) It's also important to remember to remove the throw; re-throw statement from the finally block when performing this kind of translation. Also, all this work has a secondary benefit: the debugger will no longer bug you about re-thrown exceptions.
17) This is far less information than a crash dump like that produced by Dr. Watson would include, so it isn't recommended for actual use in a complex program without heavy modification.




Page 3