# 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.
• "A Crash Course on the Depths of Win32 Structured Exception Handling" by Matt Pietrek http://www.microsoft.com/msj/0197/exception/exception.aspx . This is one of the most frequently referenced articles explaining how SEH works on the Win32 platform.
• "The Exception Model" http://blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx . A blog entry by one of the CLR developers explaining updates to the SEH mechanisms that have occurred since Matt Pietrek's article was published, as well as how the Common Language Runtime implements its exception handling system.
• "Structured Exception Handling" http://msdn2.microsoft.com/en-us/library/ms680657.aspx . MSDN's Platfrom SDK section on using SEH.
• "Reversing Microsoft Visual C++ Part I: Exception Handling" http://www.openrce.org/articles/full_view/21 . A detailed examination of the mechanics of exactly how the data structures and functions of the C++ exception handling mechanism works for MSVC.
• "How a C++ compiler implements exception handling" by Vishal Kochhar http://www.codeproject.com/KB/exception/exceptionhandlerByVishal%20Kochhar.aspx . Another detailed examination of the the MSVC C++ exception handling implementation.
• "Visual C++ Exception-Handling Instrumentation" by Eugene Gershnik http://www.ddj.com/windows/184416600 . A detailed explanation of how MSVC implements throwing C++ exceptions, including how to hook the exception handling implementation.
• "How to trap stack overflow in a Visual C++ application" http://support.microsoft.com/kb/315937 . A description of the minimum work necessary to handle stack overflow properly in a SEH exception handler.
• "Exceptions and Error Codes" by Kyle Wilson http://gamearchitect.net/Articles/ExceptionsAndErrorCodes.html . A game programmer's analysis of the relative costs of exception handling vs. error codes with MSVC 8.

# 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.

Report Article

## User Feedback

There are no comments to display.

## Create an account

Register a new account

• 2
• 0
• 0
• 1
• 1

• 12
• 15
• 9
• 14
• 10