【问题标题】:Problems getting a call stack in a release build在发布版本中获取调用堆栈的问题
【发布时间】:2011-06-26 22:56:28
【问题描述】:

我一直在努力在 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);
}

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

我不明白当调试器一直在执行此操作时,要在所有构建中进行这项工作是多么困难。我可以明确地说,在代码生成中没有省略帧指针。我先构建调试,然后只将优化从无更改为完全优化并重建以重现调用堆栈故障。

任何解决方案的提示将不胜感激。

/乔纳斯

【问题讨论】:

  • 您没有在打开帧指针优化的情况下进行编译,是吗?
  • 在代码生成中不会省略帧指针,如果这就是你的意思的话。

标签: winapi callstack


【解决方案1】:

这不是一个答案,而只是一个“我也是”的报告,以澄清特定版本:

我在RtlCaptureContext 中看到零星的崩溃,但仅在 32 位调试版本上而不是在 32 位发布版本上,在调试或发布 64 位版本上都没有。我正在使用 VS2008 SP1 和 dbghelp.dll fileVersion 6.12.2.633 从 2011 年 4 月 25 日下载的 Windows 调试工具中下载,并且在调用之前将 dbghelp.dll 复制到与我的 EXE 相同的目录中。

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

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

【讨论】:

    【解决方案2】:

    我现在使用“兼容方式”进行了这项工作。我使用以下代码来初始化上下文:

    #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);
    }
    

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

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

    现在 RtlCaptureContext 崩溃了,所以我重新使用 GET_CURRENT_CONTEXT 宏。

    【讨论】:

    • 嗨 Jonas,是否可以将检索到的条目 [] 映射到优化构建中的实际函数名称?我得到不正确的结果。你有什么例子吗?
    猜你喜欢
    • 2023-02-04
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 2011-05-03
    • 2020-04-05
    • 2020-07-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多