Introduction
Windows provides a way for applications to override the default application "crash" handling functionality by the means of the SetUnhandledExceptionFilter
function.
Usually, the SetUndhandledExceptionFilter
function is used in conjunction with the crash reporting activity. Having the ability of pinpointing the line of code which caused a crash is invaluable in post mortem debugging.
Post mortem debugging has been discussed in other articles on CodeProject and is not the scope of this article.
Here is how a simple unhandled exception filter (which displays only "Gotcha!" in the console) looks like:
bool g_showCrashDialog = false;
LONG WINAPI OurCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
{
std::cout << "Gotcha!" << std::endl;
return g_showCrashDialog ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER;
}
If the crash handling function returns EXCEPTION_EXECUTE_HANDLER
, the Operating System will display the default crash dialog or call the Just in time (JIT) debugger if such a debugger is installed on the system.
In order to test the code, we will simulate a null pointer invalid access like this:
int main()
{
::SetUnhandledExceptionFilter(OurCrashHandler);
std::cout << "Normal null pointer crash" << std::endl;
char *p = 0;
*p = 5;
}
The program should then display:
Normal null pointer crash
Gotcha!
The C/C++ Runtime Library
The C/C++ Runtime Library will remove any custom crash handler in certain circumstances, and our crash handler will never be called.
Circumstances such as:
abort() function
void AbortCrash()
{
std::cout << "Calling Abort" << std::endl;
abort();
}
will display:
Calling Abort
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
abort
is called internally in the CRT, we need to catch all those cases too.
Out of bounds vector access
void OutOfBoundsVectorCrash()
{
std::cout << "std::vector out of bounds crash!" << std::endl;
std::vector<int> v;
v[0] = 5;
}
Will display:
std::vector out of bounds crash!
Pure virtual function call
void VirtualFunctionCallCrash()
{
struct B
{
B()
{
std::cout << "Pure Virtual Function Call crash!" << std::endl;
Bar();
}
virtual void Foo() = 0;
void Bar()
{
Foo();
}
};
struct D: public B
{
void Foo()
{
}
};
B* b = new D;
// Just to silence the warning C4101:
// 'VirtualFunctionCallCrash::B::Foo' : unreferenced local variable
b->Foo();
}
Will display:
Pure Virtual Function Call crash!
R6025
- pure virtual function call
The fix
In order to have the above cases also caught, we need to redirect the SetUnhandledExceptionFilter
function to a dummy function so that when the CRT calls SetUnhandledExceptionFilter(0)
in order to remove any custom crash handlers, it will call our dummy function.
The redirection was done using the CAPIHook
class presented in Chapter 22: DLL Injection and API Hooking of the bookWindows via C/C++, Fifth Edition written by Jeffrey Richter and Christophe Nasarre, Microsoft Press (c) 2008.
The code looks like:
LONG WINAPI RedirectedSetUnhandledExceptionFilter(EXCEPTION_POINTERS * /*ExceptionInfo*/)
{
// When the CRT calls SetUnhandledExceptionFilter with NULL parameter
// our handler will not get removed.
return 0;
}
int main()
{
::SetUnhandledExceptionFilter(OurCrashHandler);
CAPIHook apiHook("kernel32.dll",
"SetUnhandledExceptionFilter",
(PROC)RedirectedSetUnhandledExceptionFilter);
// Code that crashes
}
64 bit programs
The redirection procedure works for 32 bit code as well as for 64 bit code. The sample code provides 32 bit and 64 bit compiler targets.
Static vs. dynamic CRT linking
The code works only with dynamic CRT linking (default behavior).
History
- 07.02.2011: Initial release.