65.9K
CodeProject 正在变化。 阅读更多。
Home

捕获异常时的调用堆栈

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (7投票s)

2009 年 8 月 31 日

CPOL

3分钟阅读

viewsIcon

52060

downloadIcon

2256

提供一个实用程序类,使用 Windows SEH 返回捕获异常时的调用堆栈。

StackTraceExample

引言

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。然后,SymFromAddrSymGetLineFromAddr64将获取帧中的函数名称和行号。

历史

  • 2009年8月30日:初始版本。
© . All rights reserved.