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
4123 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

Bringing it all Together

Because SEH handler blocks can't be used in functions with C++ objects that have unwind semantics, normal RAII techniques don't work without a little finesse. For example, let's say I want two C++ objects in my main() function. One is an RAII object to initialize and cleanup the symbol handler for the stack walk and the for the other I want my exception handler's filter expression to write to a buffer that the will be displayed and saved in the __except, but I don't want to manually manage the memory, so I want to use a std::string. One way to handle this is to have the object created in one function and then have that function call a function that uses SEH handlers like so:

int main(int argc, char ** argv) {
  SymInit sym;
  std::string buffer;
  return seh_helper(argc, argv, buffer);
}

 
int seh_helper(int argc, char ** argv, std::string & buffer) {
  __try {
    return actual_main(argc, argv);
  } __except (filter(GetExceptionInformation(), buffer)) {
    if (!buffer.empty()) {
      save_buffer(buffer);
      MessageBoxA(0, buffer.c_str(), "Abnormal Termination", MB_OK);
    }
    return -1;
  }
}
Here, since the symbol handler doesn't need to do anything but be constructed and later destroyed, nothing special needs to happen to it. The string buffer, on the other hand, gets passed as a reference to the helper function, which in turn passes it to the filter() function which will fill the buffer. Also, the exception handler can get a pointer to the buffer's data with the c_str() member function. However, if instead of a std::string buffer had been defined as a std::stringstream, the similar call buffer.str().c_str() would not be valid for use in the seh_helper() function, since std::stringstream::str() returns a std::string by value.

Similarly, if we try to perform a stack walk inside a filter expression, things could go wrong because of the reason of the exception. An exception can result because of heap corruption, stack corruption or any number of other states that would make performing the stack walk impossible. So to address this, you can break the filter function in two parts:

DWORD filter(EXCEPTION_POINTERS * eps, std::string & buffer) {
  __try {
    return do_filter(eps, buffer);
  } __except (EXCEPTION_EXECUTE_HANDLER) {
    return EXCEPTION_CONTINUE_SEARCH;
  }
}
This catches any exceptions raised by the actual filter function and returns EXCEPTION_CONTINUE_SEARCH to cause the next exception handler to attempt to handle the original exception. In the case of this sample application, this should be the operating system's handler, which will give applications like Dr. Watson a chance to get a crash dump.18)

For converting the SEH exception information to C++ exception information, I use this implementation:

struct UntypedException {
  UntypedException(const EXCEPTION_RECORD & er)
    : exception_object(reinterpret_cast<void *>(er.ExceptionInformation[1])),
      type_array(reinterpret_cast<_ThrowInfo *>(er.ExceptionInformation[2])->pCatchableTypeArray)
  {}
  void * exception_object;
  _CatchableTypeArray * type_array;
};

 
void * exception_cast_worker(const UntypedException & e, const type_info & ti) {
  for (int i = 0; i < e.type_array->nCatchableTypes; ++i) {
    _CatchableType & type_i = *e.type_array->arrayOfCatchableTypes[i];
    const std::type_info & ti_i = *reinterpret_cast<std::type_info *>(type_i.pType);
    if (ti_i == ti) {
      char * base_address = reinterpret_cast<char *>(e.exception_object);
      base_address += type_i.thisDisplacement.mdisp;
      return base_address;
    }
  }
  return 0;
}

 
template <typename T>
T * exception_cast(const UntypedException & e) {
  const std::type_info & ti = typeid(T);
  return reinterpret_cast<T *>(exception_cast_worker(e, ti));
}
UntypedException simply performs some casts and stores the results, while exception_cast_worker() performs the check for type_info equality and the exception_cast() template supplies the correct type_info. The code that actually uses these building blocks looks like:
DWORD do_filter(EXCEPTION_POINTERS * eps, std::string & buffer) {
  std::stringstream sstr;
  const EXCEPTION_RECORD & er = *eps->ExceptionRecord;
  int skip = 0;
 
  switch (er.ExceptionCode) {
    case 0xE06D7363: { // C++ exception

      UntypedException ue(er);
      if (std::exception * e = exception_cast<std::exception>(ue)) {
        const std::type_info & ti = typeid(*e);
        sstr << ti.name() << ":" << e->what();
      } else {
        sstr << "Unknown C++ exception thrown.";
      }
      skip = 2; // skip RaiseException and _CxxThrowException

    } break;
    case EXCEPTION_ACCESS_VIOLATION: {
      sstr << "Access violation. Illegal "
           << (er.ExceptionInformation[0] ? "write" : "read")
           << " by "
           << er.ExceptionAddress
           << " at "
           << reinterpret_cast<void *>(er.ExceptionInformation[1]);
    } break;
    default: {
      sstr << "SEH exception thrown. Exception code: "
           << std::hex << std::uppercase << er.ExceptionCode
           << " at "
           << er.ExceptionAddress;
    }
  }
  sstr << "\n\nStack Trace:\n";
  generate_stack_trace(sstr, *eps->ContextRecord, skip);
  buffer = sstr.str();
  
  return EXCEPTION_EXECUTE_HANDLER;
}
Here the intent is that the C++ exception handling block can be extended in the same way that adding catch blocks can be done. For example, you could extend it like so:
      if (std::exception * e = exception_cast<std::exception>(ue)) {
        sstr << e->what();
      } else if (exception_cast<boost::python::error_already_set>(ue)) {
        PyErr_Print();
        // magically extract the contents of sys.stderr and put it in the stream

      } else {
        sstr << "Unknown C++ exception thrown.";
      }
This results in the same caveat in the if chain as for catch blocks in C++ exception handlers: put more derived classes earlier in the chain than base classes or the base classes will consume the exception.19) Of course, since we have access to the _ThrowInfo pointer in the exception handler, we can also extract some type information from an unknown exception type.
void get_exception_types(std::ostream & os, const UntypedException & e) {
  for (int i = 0; i < e.type_array->nCatchableTypes; ++i) {
    _CatchableType & type_i = *e.type_array->arrayOfCatchableTypes[i];
    const std::type_info & ti_i = *reinterpret_cast<std::type_info *>(type_i.pType);
    os << ti_i.name() << "\n";
  }
}
This simply inserts the name of all the types in the catchable type array into a stream. It seems that the first entry is the actual type of the exception and the rest of the entries are base classes or types with implicit conversions.20) For example, throwing a string literal might produce "char *" and "void *" as output.

Exception Handling Overhead

The word "overhead" in the context of exception handling is something of a loaded term, but no discussion of an specific implementation of exception handling is really complete without at least a minimal discussion of the performance consequences.

First off, when exception handling is enabled, every function that needs unwind semantics needs to execute additional prologue and epilogue code, as well as additional space on the stack to hold exception handling information. This depends on the function in question, the optimization settings, processor targeted and MSVC version, but works out to be about five instructions for the prologue and about three instructions for the epilogue in the common case and the additional space on the stack works out to about five pointer widths. The prologue and epilogue code may also be invoked via a function call rather than inlined; this will increase the cost of executing the prologue and epilogue code to gain some reductions in overall executable size. Additionally, quite a bit of code and data is added to the executable in order to actually process the exception and perform stack unwind.

Fortunately, these sources of overhead are significantly reduced in the presence of aggressive inlining, especially link time cross-module inlining. On the other hand, while MSVC is very good about inlining code, it's not so good at separating out redundant code from multiple functions. As mentioned earlier, when throwing a C++ exception, the compiler generates code to place a copy of the exception on the stack, then calls _CxxThrowException() with a pointer to the exception and a pointer to the _ThrowInfo for the exception type. On 32-bit x86, throw std::exception() could compile to something like:

  ; 6 : throw std::exception();
 
  lea   ecx, DWORD PTR $T5541[esp+12]
  call  DWORD PTR __imp_??0exception@@QAE@XZ
  push  OFFSET FLAT:__TI1?AVexception@@
  lea   eax, DWORD PTR $T5541[esp+16]
  push  eax
  call  __CxxThrowException@8
This code should rarely be called, since exceptions should rarely occur, so on the face of it the number of instructions is not a large issue; however, because of the aggressive inlining required to reduce the stack frame overhead, the throw code can suddenly explode in a code base, especially if it occurs in template code. Therefore, if you have multiple identical throw statements (or a single throw in a template with multiple instantiations), you should strongly consider factoring them into a single function 21).

The exception model is implemented in such a way that translation units compiled with /EHs, /EHa or without exception handling enabled at all can coexist in an application. This means that another way to fight exception handling overhead is if a profiler shows a function to be a bottleneck, it can be separated into its own source file and compiled with a different exception handling switch to eliminate prologue and epilogue generation.

Finally there's the cost incurred of throwing and catching an exception versus returning an error code and checking and propagating the value. The relative cost of these situations are highly dependent on a number of situation specific modifiers such as how far nested the point of failure is compared to the location of the error handler, the number and type of objects that need to be unwound, optimization settings and so on. However, even the most optimistic estimates place throwing an exception as hundreds of times more expensive than returning an error code. The cost is probably closer to several thousands of times, if not more. Of course, this shouldn't be news to anyone experienced with C++ exceptions; exceptions should only be used for exceptional circumstances. This does, however, supply a metric for exception use: if the error condition is expected to occur more often than once out of every 10,000 times the function is run, then use an error code; otherwise use an exception.


18) In some cases the process of executing the filter expression may make the crash dump less useful, so for this kind of exception handler, it's a good idea to include an option to disable the filter from executing at all. This can be via a command line argument or configuration file option. In general, it should be simple for the end user to toggle, which reduces the viability of mechanisms like environment variables or registry entries.
19) This is not the most efficient implementation for this task, especially if multiple exception types need to be differentiated. Of course, if program execution has reached this point then efficiency is not so much a prime concern.
20) Though, obviously, this is firmly in the realm of undocumented behavior land, and I wouldn't rely on that ordering.
21) Preferably one __declspec'd noinline and noreturn. Of course, for template code, factoring common code into non-template helpers is a good idea for things other than throwing exceptions.




Page 4