捕获异常时的调用堆栈
提供一个实用程序类,使用 Windows SEH 返回捕获异常时的调用堆栈。
引言
StackTracer
是一个 C++ 类,它允许您使用 Windows SEH 方便地获取调用堆栈,然后您可以显示异常消息或将其记录下来以帮助您诊断问题。只要您拥有 PDB 文件,它就可以在调试和发布模式下工作。(在发布模式下,由于函数可能会被优化,因此可能不太准确。)
背景
我们在应用程序中使用 Windows 的 SEH(结构化异常处理)来处理异常,使用__try
、__except
语句。我们这样做是为了保护我们的应用程序免于意外崩溃。但这也会隐藏问题的细节,因为我们只能看到失败的外部操作,而不知道内部发生了什么,除非调试代码。
通过将此StackTracer
类与__try
、__except
语句集成,我们现在可以快速查明调用堆栈中的问题。每当出现异常时,都会记录详细信息,我们只需要打开日志文件,一切就一目了然了。
使用代码
我们将StackTracer
用作静态类;它有四个公共成员。
static LONG ExceptionFilter(LPEXCEPTION_POINTERS e);
此函数应在__except
子句中调用;它将EXCEPTION_POINTERS
结构作为上下文,并遍历堆栈帧以获取调用堆栈。然后,它将只返回EXCEPTION_EXECUTE_HANDLER
以执行异常处理程序 - __except
块。
static std::string GetExceptionMsg();
以本类提供的格式返回异常信息(异常代码、调用堆栈):如页面开头所示。
static DWORD GetExceptionCode(); static std::vector<FunctionCall> GetExceptionCallStack();
有两个函数返回异常数据。如果您不喜欢GetExceptionMsg
提供的默认格式,您可以自己创建。
调用堆栈深度设置为 6,因为我相信这将为我们提供足够的信息来诊断问题,但您也可以更改以下行以获得更深的深度。
const int CALLSTACK_DEPTH = 6;
要使用此类,您可以将其与您要监视的任何重要函数集成,或者简单地只包装Main
函数,这样所有函数都将被检测到。
以下是使用方法
假设您有一个执行大型操作的函数
void LargeOperation() { // Large Operations }
您想使用 SEH 保护此函数,但当出现异常时也希望获取调用堆栈。您可以将其与StackTracer
集成。
// Pop up the exception information
void PopupCallStack()
{
std::string str = StackTracer::GetExceptionMsg();
AfxMessageBox(CString(str.c_str()));
}
// Log the exception information
void LogCallStack()
{
std::string str = StackTracer::GetExceptionMsg();
Log(str.c_str());
}
void LargeOperation()
{
__try
{
// Large Operations
}
__except(StackTracer::ExceptionFilter(GetExceptionInformation()))
{
PopupCallStack();
//LogCallStack();
}
}
解释
此类获取异常引发时生成的EXCEPTION_POINTERS
结构,然后遍历异常上下文中的堆栈帧以获取调用堆栈。它需要符号文件才能获取详细信息,例如函数名称和行号。以下是核心思想的框架
SymInitialize(...); STACKFRAME64 sf; While(StackWalk64(...)) { SYMBOL_INFO symbolInfo; SymFromAddr(...); IMAGEHLP_LINE64 lineInfo; SymGetLineFromAddr64(...); } SymCleanup();
首先,它初始化符号处理器,加载必要的符号文件,然后它向上遍历堆栈帧,从最新的到较旧的。StackWalk64
如果成功获取上一个堆栈帧,则返回true
。然后,SymFromAddr
和SymGetLineFromAddr64
将获取帧中的函数名称和行号。
历史
- 2009年8月30日:初始版本。