The Visual C++ Exception Model

Published March 27, 2008 by Howard Jeng, posted by Myopic Rhino
Do you see issues with this article? Let us know.
Advertisement
When programming in C++ with Microsoft Visual C++, your program has two exceptions models: the normal C++ exception model and the Structure Exception Handling (SEH) exception model.[sup]1)[/sup] MSVC's C++ exception system is, in fact, implemented with SEH's functionality. This implementation leads to some interesting behaviors. This article discusses some of the consequences of the MSVC exception system from the standpoint of a C++ programmer. It assumes familiarity with C++ exceptions, including exception aware programming techniques like RAII, and basic Windows programming.[sup]2)[/sup] It does not assume any background with SEH. The behavior described covers MSVC .NET 2002, 2003, 2005 and 2008.

An Introduction to Structured Exception Handling

Structured Exception Handling is a mechanism built into the operating system that provides facilities to detect and deal with exceptional circumstances, much as C++ exceptions do for C++ programs. SEH exceptions come in two varieties: hardware exceptions and software exceptions. Hardware exceptions are exceptions raised by the CPU such as access violations and divide by zero. Software exceptions are exceptions raised explicitly by programs or, on occasion, the operating system itself. For the purposes of this article, there isn't much difference between the two. When an exception is raised, two pieces of information are captured: one is the state of the processor at the time of the exception, which is stored in a CONTEXT structure[sup]3)[/sup]. The other is information about the exception, which is stored in an EXCEPTION_RECORD. The two parts of this structure we'll be concerned about are the ExceptionCode member and the ExceptionInformation array, which contain a numeric value for the exception type and additional information respectively. For example, when an access violation is detected, a hardware exception is raised with the code 0xC0000005. If ExceptionInformation[0] is zero the access violation was caused by a read and if ExceptionInformation[0] is non-zero the access violation was triggered by a write, and ExceptionInformation[1] is the address that was read from or written to. Not all exceptions generate additional information; in fact, the majority don't. To raise a software exception, code can call the RaiseException() function, which takes an exception code and other exception information, which will then be packaged into an EXCEPTION_RECORD. Windows exception codes follow the following format: Bits 31 and 30 represent the severity code. It should be 00 for success, 01 for informational, 10 for warning and 11 for an error. Bit 29 is set for user exception codes. Bit 28 is reserved and should not be set. Bits 27 to 16 represent a facility code. For example, Windows Update is assigned the facility code of 36, while 0 represents no particular facility. The bottom 16 bits then represent the actual error code. This is why exceptions like access violations start with 0xC: these codes represent non-user error exceptions. In theory, most user exception codes should start with 0xE to represent user defined error exceptions[sup]4)[/sup]. Actually dealing with SEH exceptions in MSVC takes two primary forms: frame based exception handling (__try/__except blocks) and termination handling (__try/__finally blocks).

Frame Based Exception Handling

Frame based exception handling is roughly analogous to C++'s try/catch blocks. Instead of try/catch, the blocks look like: __try { // code to attempt to execute } __except (filter_expression) { // handler code } The block after the __try is executed, and if an SEH exception is thrown the filter expression is evaluated. __try blocks have many of the restrictions, both de jure and de facto, that C++ try blocks have. For example, goto cannot be used to enter __try blocks and __try blocks inhibit inlining. Unlike C++ catch block, where the catch contains types, the filter expression is more like the contents of the parenthesis after an if[sup]5)[/sup]: it can be almost anything that eventually evaluates to a value. In particular, the filter expression must evaluate to one of three values: EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH or EXCEPTION_CONTINUE_EXECUTION. If the expression evaluates to EXCEPTION_EXECUTE_HANDLER, the code in the handler block is executed, much like when the type matches the catch for a C++ exception. If the expression evaluates to EXCEPTION_CONTINUE_SEARCH it looks for another __except block that might handle the exception, like when a C++ catch block doesn't contain a matching type [sup]6)[/sup]. EXCEPTION_CONTINUE_EXECUTION has no analogy in C++ exception handling: it causes program execution to resume from the point where the exception was raised [sup]7)[/sup]. Unlike C++ catch blocks, you can't chain __except blocks. That is to say this code is illegal: __try { // stuff } __except (filter expression 1) { // handler 1 } __except (filter expression 2) { // handler 2 } You can, instead, nest the blocks. __try { __try { // stuff } __except (filter expression 1) { // handler 1 } } __except (filter expression 2) { // handler 2 } Another, much bigger limitation of SEH blocks is that functions containing SEH blocks cannot contain C++ objects with unwind semantics. This translates into basically anything with a destructor is illegal to create as a auto or stack variable in a function with SEH blocks[sup]8)[/sup]. You also can't use C++ try/catch blocks. [sup]9)[/sup] So what does a typical filter expression look like? If there was such a thing as a typical filter expression, it would probably look something like this: EXCEPTION_POINTERS * eps = 0; __try { // stuff } __except (eps = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) { // handler } GetExceptionInformation() is a macro that obtains pointers to the CONTEXT and EXCEPTION_RECORD structures mentioned earlier. It can only be called inside a filter expression; it can't be called in the __except block. So this filter expression assigns the exception pointers and then uses the comma operator to have the filter expression take on the value of EXCEPTION_EXECUTE_HANDLER. This rapidly gets unwieldy if the expression needs to have any additional logic rather unconditionally returning a single value. Fortunately, it is legal to call functions in the filter expression.[sup]10)[/sup] DWORD filter(EXCEPTION_POINTERS * eps) { if (eps->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) return EXCEPTION_EXECUTE_HANDLER; return EXCEPTION_CONTINUE_SEARCH; } EXCEPTION_POINTERS * eps = 0; __try { } __except (eps = GetExceptionInformation(), filter(eps)) { } Another macro used in filter expressions is GetExceptionCode(). Like GetExceptionInformation() it can only be called inside a filter expression. As you might guess from the name, it returns the exception code of the exception being filtered. With that you could write the above filter expression like so: EXCEPTION_POINTERS * eps = 0; __try { } __except (eps = GetExceptionInformation(), ((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)) { }

Termination Handling

Termination handling is much simpler than frame based exception handling. Instead of __try/__except, we have __try/__finally. The __try block is executed, and barring the thread or process ending, the code in the __finally block is executed unconditionally when the __try block is exited, whether the function returns, an exception is thrown, if there's a goto out of the block or the last statement in the __try block finishes executing. __try { // try to execute this code } __finally { // execute this code afterwards (unless the thread or process ends) } Like __try/__except blocks, __try/__finally blocks can't be used in functions with objects with stack unwind semantics. You also cannot tag a __finally block on a __try/__except, though you can nest a __try/__finally block within a __try/__except (or vice versa). __try { __try { // try to execute this code } __finally { // execute this code afterwards (unless the thread or process ends) } } __except (filter_expression) { // exception handler } Termination handling brings one new concept to the table: abnormal termination. A __try block is said to end normally or abnormally. Normal termination means that program flow continued after the last statement in the __try block was executed and continued into the __finally block. Abnormal termination means that the control flow exited the block in any other way, including exceptions, but also include return, goto, continue or break statements; even if one of those control flow statements was the last statement in the __try block. Abnormal termination results in a performance penalty as the SEH code searches for all relevant __finally handlers. Abnormal termination can also be avoided with the __leave keyword, which immediately ends the __try block and starts the __finally block. Inside the __finally block, the AbnormalTermination() macro can be used to determine if the __try block was left normally or abnormally.

Implementing C++ Exceptions

This section presents a very high level overview of how C++ exceptions are implemented. [sup]11)[/sup] 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 like[sup]12)[/sup]: 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.[sup]13)[/sup] 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 #include 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 builds[sup]14)[/sup]. 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 information[sup]15)[/sup]. 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).[sup]16)[/sup] 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.[sup]17)[/sup]

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.[sup]18)[/sup] 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(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; const std::type_info & ti_i = *reinterpret_cast(type_i.pType); if (ti_i == ti) { char * base_address = reinterpret_cast(e.exception_object); base_address += type_i.thisDisplacement.mdisp; return base_address; } } return 0; } template T * exception_cast(const UntypedException & e) { const std::type_info & ti = typeid(T); return reinterpret_cast(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(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(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(ue)) { sstr << e->what(); } else if (exception_cast(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.[sup]19)[/sup] 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; const std::type_info & ti_i = *reinterpret_cast(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.[sup]20)[/sup] 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 [sup]21)[/sup]. 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.

Conclusion and References

This article only touches the surface of what can be done with customizing exception handling in MSVC, though it hopefully does cover the important consequences of the exception model if you don't intend on modifying the behavior. The following references include more detail on how SEH is implemented, how C++ exceptions are implemented with SEH and additional things you can do with exception handling with MSVC including instrumenting the _CxxThrowException() function and replacing the MSVC exception handler altogether.

The Complete Sample Application

The following is the complete code for running the application developed in the article. It suffers from many of the flaws of other article sample code examples: it exists to demonstrate techniques and points mentioned in the article and so does some things unnecessarily. For example, the buffer in the main() function exists to demonstrate a method to use an object with unwind semantics in a SEH handler rather than any actual need. Also, the code fails to properly handle exceptions resulting from stack overflows, only works on x86 processors, may cause cancer and the stack dump output is quite ugly. //Copyright (c) 2007 Howard Jeng // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following condition: // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. #include #include #include #include #include class SymInit { public: SymInit(void) : process_(GetCurrentProcess()) { SymInitialize(process_, 0, TRUE); DWORD options = SymGetOptions(); options |= SYMOPT_LOAD_LINES; SymSetOptions(options); } ~SymInit() { SymCleanup(process_); } private: HANDLE process_; SymInit(const SymInit &); SymInit & operator=(const SymInit &); }; std::string get_module_path(HMODULE module = 0) { char path_name[MAX_PATH] = {}; DWORD size = GetModuleFileNameA(module, path_name, MAX_PATH); return std::string(path_name, size); } void write_module_name(std::ostream & os, HANDLE process, DWORD64 program_counter) { DWORD64 module_base = SymGetModuleBase64(process, program_counter); if (module_base) { std::string module_name = get_module_path(reinterpret_cast(module_base)); if (!module_name.empty()) os << module_name << "|"; else os << "Unknown module|"; } else { os << "Unknown module|"; } } void write_function_name(std::ostream & os, HANDLE process, DWORD64 program_counter) { SYMBOL_INFO_PACKAGE sym = { sizeof(sym) }; sym.si.MaxNameLen = MAX_SYM_NAME; if (SymFromAddr(process, program_counter, 0, &sym.si)) { os << sym.si.Name << "()"; } else { os << "Unknown function"; } } void write_file_and_line(std::ostream & os, HANDLE process, DWORD64 program_counter) { IMAGEHLP_LINE64 ih_line = { sizeof(IMAGEHLP_LINE64) }; DWORD dummy = 0; if (SymGetLineFromAddr64(process, program_counter, &dummy, &ih_line)) { os << "|" << ih_line.FileName << ":" << ih_line.LineNumber; } } void generate_stack_trace(std::ostream & os, CONTEXT ctx, int skip) { STACKFRAME64 sf = {}; sf.AddrPC.Offset = ctx.Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = ctx.Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = ctx.Ebp; sf.AddrFrame.Mode = AddrModeFlat; HANDLE process = GetCurrentProcess(); HANDLE thread = GetCurrentThread(); os << std::uppercase; for (;;) { SetLastError(0); BOOL stack_walk_ok = StackWalk64(IMAGE_FILE_MACHINE_I386, process, thread, &sf, &ctx, 0, &SymFunctionTableAccess64, &SymGetModuleBase64, 0); if (!stack_walk_ok || !sf.AddrFrame.Offset) return; if (skip) { --skip; } else { // write the address os << std::hex << reinterpret_cast(sf.AddrPC.Offset) << "|" << std::dec; write_module_name(os, process, sf.AddrPC.Offset); write_function_name(os, process, sf.AddrPC.Offset); write_file_and_line(os, process, sf.AddrPC.Offset); os << "\n"; } } } struct UntypedException { UntypedException(const EXCEPTION_RECORD & er) : exception_object(reinterpret_cast(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; const std::type_info & ti_i = *reinterpret_cast(type_i.pType); if (ti_i == ti) { char * base_address = reinterpret_cast(e.exception_object); base_address += type_i.thisDisplacement.mdisp; return base_address; } } return 0; } 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; const std::type_info & ti_i = *reinterpret_cast(type_i.pType); os << ti_i.name() << "\n"; } } template T * exception_cast(const UntypedException & e) { const std::type_info & ti = typeid(T); return reinterpret_cast(exception_cast_worker(e, ti)); } 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(ue)) { const std::type_info & ti = typeid(*e); sstr << ti.name() << ":" << e->what(); } else { sstr << "Unknown C++ exception thrown.\n"; get_exception_types(sstr, ue); } 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(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; } DWORD filter(EXCEPTION_POINTERS * eps, std::string & buffer) { __try { return do_filter(eps, buffer); } __except (EXCEPTION_EXECUTE_HANDLER) { return EXCEPTION_CONTINUE_SEARCH; } } int actual_main(int, char **) { // do stuff // cause an access violation //char * ptr = 0; *ptr = 0; // divide by zero //int x = 5; x = x / (x - x); // C++ exception //throw std::runtime_error("I'm an exception!"); throw 5; return 0; } void save_buffer(const std::string & buffer) { std::ofstream ofs("err_log.txt"); if (ofs) ofs << 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; } } int main(int argc, char ** argv) { SymInit sym; std::string buffer; return seh_helper(argc, argv, buffer); } [sup]1)[/sup] You can disable the C++ exception model by changing your project properties and avoiding exception code, but you're stuck with SEH no matter what you do. Even if you never put a single __try block in your code, the operating system wraps your program in a SEH handler that produces the standard dialogs for access violations and the like. [sup]2)[/sup] Such as recognizing what an access violation is, the basic typedefs and the difference between the ANSI and Unicode versions of systems calls. [sup]3)[/sup] This is highly processor dependent information such as the contents of registers and the state of the floating point unit. [sup]4)[/sup] In practice, user defined exception codes range across the board, since the RaiseException() documentation only mentions the effect of bit 28 and the rest of the Platform SDK documentation on SEH doesn't mention the bit information for exception codes. These guidelines are actually mentioned in winerror.h and the Visual C++ language reference for SEH and not many other places. [sup]5)[/sup] With some additional restrictions such as not being able to define a new variable in the filter expression. [sup]6)[/sup] If there is no enclosing __except block in the executable code, the exception handler installed by the operating system triggers, which generally displays an unhandled exception dialog and then ends the program. [sup]7)[/sup] Unless the exception was raised with the EXCEPTION_NONCONTINUABLE flag, in which case if the filter expression returns EXCEPTION_CONTINUE_EXECUTION this causes an EXCEPTION_NONCONTINUABLE_EXCEPTION exception. [sup]8)[/sup] However, pointers and references to such objects can be used. [sup]9)[/sup] It may help to think of functions with SEH blocks to be C only, though this isn't strictly true. [sup]10)[/sup] This is not necessarily a good thing in all instances. Since filter expressions are evaluated in the context of the exception, calling a function during a stack overflow could cause additional problems. [sup]11)[/sup] Much more detailed explanations are available, see the references section. [sup]12)[/sup] Provided comments come from examining disassembly, so may not be entirely accurate, though the names and types should be accurate, modulo typedefs. [sup]13)[/sup] This description ignores a lot of the other work it needs to do, such as checking the magic number and initiating stack unwind. [sup]14)[/sup] Not that I'm bitter. [sup]15)[/sup] 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. [sup]16)[/sup] 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. [sup]17)[/sup] 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. [sup]18)[/sup] 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. [sup]19)[/sup] 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. [sup]20)[/sup] Though, obviously, this is firmly in the realm of undocumented behavior land, and I wouldn't rely on that ordering. [sup]21)[/sup] 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.
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!

A brief introduction to SEH, a high level overview of how MSVC implements C++ exceptions on top of SEH, some implications of this implementation and some ways to exploit the internals of the exception handling model.

Advertisement
Advertisement