如何从 SEH 异常生成堆栈跟踪

M.M*_*M.M 5 c++ windows seh stack-trace

我使用 Win32 SEH 捕获异常:

try
{
    // illegal operation that causes access violation
}
__except( seh_filter_func(GetExceptionInformation()) )
{
    // abort
}
Run Code Online (Sandbox Code Playgroud)

过滤器函数如下所示:

int seh_filter_func(EXCEPTION_POINTERS *xp)
{
    // log EIP, other registers etc. to file

    return 1;
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这是有效的,并且中的值xp->ContextRecord->Eip告诉我哪个函数导致了访问冲突(实际上 - ntdll.dll!RtlEnterCriticalSection ,并且 EDX 的值告诉我这个函数是用虚假参数调用的)。

但是,这个函数在很多地方都被调用了,包括从其他WinAPI函数中调用的,所以我仍然不知道是哪个代码负责使用假参数调用这个函数。

是否有任何代码可以根据输入EXCEPTION_POINTERS或其他信息来生成通向 EIP 现在所在位置的函数调用链的跟踪?(在外部调试器下运行程序不是一种选择)。

仅 EIP 值就可以了,因为我可以在链接器映射和符号表中查找它们,尽管如果有一种方法可以将它们自动映射到符号名称,那就更好了。

我在这个项目中使用了 C++Builder 2006,尽管 MSVC++ 解决方案可能会起作用。

yep*_*ons 2

看来在 64 位模式下,SEH 过滤函数是在同一个堆栈上执行的,而不展开它,因此您确实可以查看后缀来boost::stacktrace::stacktrace()查看错误发生的位置,如另一个答案所示。

但是,它在 32 位模式下对我不起作用。我必须使用/StackWalk64中的函数遍历堆栈。尽管它需要来启动,但可以使用从struct at获得的相应寄存器来填充它。DbgHelp.hDbgHelp.libSTACKFRAME64CONTEXTxp->ContextRecord

以下代码在 32 位模式下适用于我:

#include <boost/stacktrace.hpp>  // For symbols only
#include <DbgHelp.h>

#pragma comment(lib, "DbgHelp.lib")

int seh_filter_func(EXCEPTION_POINTERS* xp) {
    CONTEXT context = *xp->ContextRecord;
    STACKFRAME64 s;
    ZeroMemory(&s, sizeof(s));
    s.AddrPC.Offset = context.Eip;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = context.Ebp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrStack.Offset = context.Esp;
    s.AddrStack.Mode = AddrModeFlat;

    // Not thread-safe!
    for (int i = 0;
        StackWalk64(
            IMAGE_FILE_MACHINE_I386, 
            GetCurrentProcess(), 
            GetCurrentThread(), 
            &s, 
            &context, 
            NULL, 
            NULL, 
            NULL, 
            NULL);
        i++) {
        std::cout << i << ": " << boost::stacktrace::frame((void*)s.AddrPC.Offset) << "\n";
    }
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

由于某种原因,即使我用相应的 64 位寄存器替换 32 位寄存器,这也无法在 64 位模式下工作。它正确打印第一帧,然后打印一些不清楚的内容。