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

7

我正在使用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函数中调用,因此我仍然不知道哪段代码负责使用虚假参数调用该函数。
是否有任何代码可以生成一条函数调用链的跟踪,以便查看EIP现在所在的位置,基于EXCEPTION_POINTERS中的信息或其他方式?(在外部调试器下运行程序不是一个选择)。
只有EIP值就可以,因为我可以在链接器映射和符号表中查找它们,但如果有一种自动将它们映射到符号名称的方法,那就更好了。
我正在使用C++Builder 2006进行这个项目,但MSVC++解决方案可能仍然可行。

1
请尝试阅读这篇文章:http://www.codeproject.com/Articles/11132/Walking-the-callstack - PaulMcKenzie
dbghelp.dll是一个很棒的库。我们使用它,但是有一些需要考虑的事情。如果您的进程因为堆损坏而崩溃,dbghelp可能无法为对象文件符号表分配内存。在崩溃之前初始化dbghelp。您需要随软件一起提供调试信息。对于某些安全敏感的软件来说,这可能是一个问题。 - brian beuning
1
@PaulMcKenzie 正是我正在寻找的,谢谢。我希望这篇文章能够以某种方式被存档,以防链接失效后其他人也想要同样的东西。 - M.M
1
@brianbeuning 谢谢,minidump 也生成了一些有用的信息,尽管我似乎无法从中获取调用堆栈,它只显示我的代码为“外部代码”。使用 VS2013 Express 查看转储。 - M.M
1
还有,《Walking the callstack》文章已迁移到https://github.com/JochenKalmbach/StackWalker。 - yeputons
显示剩余2条评论
2个回答

3
我认为您可以使用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;
}

2
在64位模式下,似乎SEH过滤函数在同一堆栈上执行而不进行展开,因此您确实可以查看boost::stacktrace::stacktrace()的后缀来查看错误发生的位置,如另一个答案中所示
然而,在32位模式下,这对我来说不起作用。我必须使用DbgHelp.h/DbgHelp.lib中的StackWalk64函数遍历堆栈。虽然它需要STACKFRAME64来开始,但可以使用从xp->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;
}

由于某种原因,即使我将32位寄存器替换为相应的64位寄存器,在64位模式下仍然无法正常工作。它会正确打印第一帧,但后面的内容不清晰。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接