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

How to generate stack trace from SEH exception

本文关键字:堆栈 跟踪 异常 SEH      更新时间:2023-10-16

我正在使用Win32 SEH:捕获异常

try
{
    // illegal operation that causes access violation
}
__except( seh_filter_func(GetExceptionInformation()) )
{
    // abort
}

过滤函数如下所示:

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

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

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

根据EXCEPTION_POINTERS中的信息或其他信息,我是否可以使用任何代码来生成通往EIP现在所在位置的函数调用链的跟踪?(在外部调试器下运行程序不是一个选项)。

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

我在这个项目中使用C++Builder 2006,尽管MSVC++解决方案可能无论如何都能工作。

我认为你可以使用Boost.Stacktrace:

#include <boost/stacktrace.hpp>
int seh_filter_func(EXCEPTION_POINTERS *xp)
{
    const auto stack = to_string( boost::stacktrace::stacktrace() );
    LOG( "%s", stack.c_str() );
    return 1;
}

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

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

以下代码适用于我的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;
}

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