在发布版本中获取调用堆栈时出现问题

时间:2011-02-14 14:04:41

标签: winapi callstack

我一直在努力在Windows可执行文件中获取调用堆栈。我已经尝试了几种不同的方法来获取调用堆栈。以下是一些例子。请注意,我稍微修改了它们并删除了错误处理以使它们易于理解,因此它们可能无法按原样编译。我想你明白了。

简单的方法:

const int max_entries = 10;
void *entries[max_entries];
return CaptureStackBackTrace(0, max_entries, entries, 0);

低级方式:

const int max_entries = 10;
void *entries[max_entries];

void **frame = 0;
__asm { mov frame, ebp }
unsigned int i = 0;
while(frame && i < max_entries) {
    entries[i++] = frame[1];
    frame = (void **)frame[0];
}

兼容方式:

void *entries[max_entries];
CONTEXT context;
RtlCaptureContext(&context);
STACKFRAME64 stack_frame;
ZeroMemory(&stack_frame, sizeof(STACKFRAME64));
stack_frame.AddrPC.Offset    = context.Eip;
stack_frame.AddrPC.Mode      = AddrModeFlat;
stack_frame.AddrFrame.Offset = context.Ebp;
stack_frame.AddrFrame.Mode   = AddrModeFlat;
stack_frame.AddrStack.Offset = context.Esp;
stack_frame.AddrStack.Mode   = AddrModeFlat;

unsigned int num_frames = 0;
while (true) {
    if (!StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(),
        GetCurrentThread(), &stack_frame, &context, NULL,
        SymFunctionTableAccess64, SymGetModuleBase64, NULL))
        break;

    if (stack_frame.AddrPC.Offset == 0)
        break;

    entries[num_frames++] = reinterpret_cast<void *>(stack_frame.AddrPC.Offset);
}

我的问题是他们在未经优化的构建中工作,但没有完全优化。会发生什么事情,我得到一个破碎的条目,然后他们退出他们的循环。在调试中,我得到完整的调用堆栈,当我稍后查找符号时,它都是正确的。

我不明白当调试器一直这样做时,如何在所有版本中使这项工作变得困难。我可以具体说在代码生成中不会省略帧指针。我首先构建调试,然后只将优化从none更改为完全优化,并重建以重现调用堆栈失败。

非常感谢任何有关解决方案的提示。

/纳斯

2 个答案:

答案 0 :(得分:3)

我现在正在使用“兼容方式”。我使用以下代码初始化上下文:

#define GET_CURRENT_CONTEXT(c, contextFlags) \
    do { \
        memset(&c, 0, sizeof(CONTEXT)); \
        c.ContextFlags = contextFlags; \
        __asm    call x \
        __asm x: pop eax \
        __asm    mov c.Eip, eax \
        __asm    mov c.Ebp, ebp \
        __asm    mov c.Esp, esp \
    } while(0);

CONTEXT context;
GET_CURRENT_CONTEXT(context, CONTEXT_FULL);

然后继续像以前一样使用StackWalk64获取堆栈。

void *entries[max_entries];
STACKFRAME64 stack_frame;
ZeroMemory(&stack_frame, sizeof(STACKFRAME64));
stack_frame.AddrPC.Offset    = context.Eip;
stack_frame.AddrPC.Mode      = AddrModeFlat;
stack_frame.AddrFrame.Offset = context.Ebp;
stack_frame.AddrFrame.Mode   = AddrModeFlat;
stack_frame.AddrStack.Offset = context.Esp;
stack_frame.AddrStack.Mode   = AddrModeFlat;

unsigned int num_frames = 0;
while (true) {
    if (!StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(),
        GetCurrentThread(), &stack_frame, &context, NULL,
        SymFunctionTableAccess64, SymGetModuleBase64, NULL))
        break;

    if (stack_frame.AddrPC.Offset == 0)
        break;

    entries[num_frames++] = reinterpret_cast<void *>(stack_frame.AddrPC.Offset);
}

我注意到在将其发送到RtlCaptureContext之前忘记清除CONTEXT结构,所以我尝试这样做(因为我更喜欢使用RtlCaptureContext函数)。

CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);

现在RtlCaptureContext崩溃了,所以我回到了使用GET_CURRENT_CONTEXT宏。

答案 1 :(得分:0)

这不是一个答案,只是一个“我也是”报告来澄清具体版本:

我看到RtlCaptureContext内部偶然发生崩溃,但只发生在32位Debug版本而不是32位Release版本上,而不是在Debug或Release 64位版本上。我在2011年4月25日下载的Debugging Tools for Windows中使用VS2008 SP1和dbghelp.dll fileVersion 6.12.2.633,并且在调用之前将dbghelp.dll复制到与我的EXE相同的目录中。

这是在同一台Windows XP 64位SP2计算机上使用完全相同的VS2008 SP1编译器进行编译(编译32位和64位本机应用程序,根本没有.NET托管代码) )。

上面的关键是零星的性质。我还没有确定它崩溃的条件。