|
The Visual C++ Exception Model
Implementing C++ ExceptionsThis 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 Given that 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 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 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 A case can be made either way for 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 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 Sometimes you want the opposite effect: being able to handle C++ exceptions inside an 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 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. |