SetUnhandledExceptionFilter 与 C/C++ 运行时库






4.79/5 (26投票s)
本文介绍了一种修复方法,使 SetUnhandledExceptionFilter 能够与 CRT 协同工作。
引言
Windows 提供了一种方法,允许应用程序通过 SetUnhandledExceptionFilter
函数覆盖默认的应用程序“崩溃”处理功能。
通常,SetUnhandledExceptionFilter
函数与崩溃报告活动结合使用。能够精确定位导致崩溃的代码行,对于事后调试来说是无价的。
事后调试已经在 CodeProject 上的其他文章中讨论过,不属于本文的范围。
下面是一个简单的未处理异常过滤器(它只在控制台中显示 “Gotcha!”)的示例:
bool g_showCrashDialog = false;
LONG WINAPI OurCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
{
std::cout << "Gotcha!" << std::endl;
return g_showCrashDialog ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER;
}
如果崩溃处理函数返回 EXCEPTION_EXECUTE_HANDLER
,操作系统将显示默认的崩溃对话框,或者如果系统上安装了即时 (JIT) 调试器,则调用 JIT 调试器。
为了测试代码,我们将模拟一个空指针无效访问,如下所示:
int main()
{
::SetUnhandledExceptionFilter(OurCrashHandler);
std::cout << "Normal null pointer crash" << std::endl;
char *p = 0;
*p = 5;
}
程序应该显示:
Normal null pointer crash
Gotcha!
C/C++ 运行时库
在某些情况下,C/C++ 运行时库会移除任何自定义崩溃处理程序,并且我们的崩溃处理程序将永远不会被调用。
例如:
abort() 函数
void AbortCrash()
{
std::cout << "Calling Abort" << std::endl;
abort();
}
将显示:
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
在 CRT 内部被调用,我们也需要捕获所有这些情况。
越界向量访问
void OutOfBoundsVectorCrash()
{
std::cout << "std::vector out of bounds crash!" << std::endl;
std::vector<int> v;
v[0] = 5;
}
将显示:
std::vector out of bounds crash!
纯虚函数调用
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();
}
将显示:
Pure Virtual Function Call crash!
R6025
- pure virtual function call
修复方法
为了捕获上述情况,我们需要将 SetUnhandledExceptionFilter
函数重定向到一个虚拟函数,这样当 CRT 调用 SetUnhandledExceptionFilter(0)
以移除任何自定义崩溃处理程序时,它将调用我们的虚拟函数。
重定向是通过 Jeffrey Richter 和 Christophe Nasarre 撰写的 Microsoft Press 出版 (c) 2008 的书 Windows via C/C++,第五版 中 第 22 章:DLL 注入和 API 挂钩 中介绍的 CAPIHook
类完成的。
代码如下:
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 位程序
重定向过程适用于 32 位代码和 64 位代码。示例代码提供了 32 位和 64 位编译器目标。
静态与动态 CRT 链接
该代码仅适用于动态 CRT 链接(默认行为)。
历史
- 2011 年 02 月 07 日:初始发布。