Microsoft Windows DbgHelp 库提供了三个用于遍历堆栈的函数:
StackWalk
功能StackWalk64
功能StackWalkEx
功能不幸的是,微软的文档并没有很好地解释这三个函数之间的区别,以及新函数相对于旧函数有哪些优势。
在
StackWalk
与 StackWalk64
的情况下,差异很明显:StackWalk
使用 STACKFRAME
结构,它使用 ADDRESS
结构表示内存地址,该结构使用 DWORD
来表示内存地址。存储内存地址——因此,StackWalk
只能处理32位内存地址。相比之下,StackWalk64
使用STACKFRAME64
结构,使用ADDRESS64
结构表示内存地址,该结构使用64位DWORD64
来存储内存地址。因此, StackWalk64
可以支持 64 位代码,StackWalk
只能支持 32 位代码(该 API 似乎也对旧版 16 位 Windows 代码有一些支持,但这在今天基本上无关紧要。)区别在于就在名字里。
但是,不太清楚
StackWalk64
和 StackWalkEx
之间的区别是什么。我可以看到的主要区别是 StackWalkEx
使用较新的 STACKFRAME_EX
数据结构,它添加了 InlineFrameContext
成员 - 但我找不到任何解释其用途的文档。它还添加了一个 StackFrameSize
成员,其目的相当明显:允许将来添加到 STACKFRAME_EX
结构,而无需创建全新的 API(StackWalkEx2
或其他)。除了对结构的补充之外,StackWalkEx
还添加了一个Flags
参数,该参数以及默认值都支持SYM_STKWALK_FORCE_FRAMEPTR
——但文档没有解释该标志的作用。
所以,总结一下:
InlineFrameContext
有什么用?SYM_STKWALK_FORCE_FRAMEPTR
有什么作用?StackWalkEx
会比 StackWalk64
提供切实的好处吗?就像您指出的那样,没有很多这方面的文档。
所以这只是从使用这些 api 的经验以及了解 VS c++ 编译器的作用推导出来的。
答案的评论链接看起来很好地说明了调用堆栈。
VS C++ 编译器优化器可以在使用内联函数扩展进行编译/链接时进行内联代码。这意味着不再有调用堆栈调用内联方法/函数。
发生这种情况并且您调试应用程序时,您可能会注意到您仍然可以单步执行这些内联方法/函数。 发生这种情况是因为 PDB 数据中存在额外的内联信息。
在遍历堆栈时,您可以使用 InlineFrameContext 来使用 SymFromInlineContextW/SymGetLineFromInlineContextW/SymSetScopeFromInlineContext 获取内联信息,例如文件名/行号/变量信息。
例如
if (frame.StackFrameSize >= sizeof(STACKFRAME_EX) && frame.InlineFrameContext != INLINE_FRAME_CONTEXT_IGNORE && frame.InlineFrameContext != INLINE_FRAME_CONTEXT_INIT)
{
if (!SymFromInlineContextW(process_, address, frame.InlineFrameContext, &info.symbol_displacement, symbol_.get()))
{
return std::move(info);
}
info.frame_content_found = true;
info.symbol_name = symbol_->Name;
info.in_line = static_cast<sym_tag_enum>(symbol_->Tag) == sym_tag_enum::Inlinee;
if (SymGetLineFromInlineContextW(process_, address, frame.InlineFrameContext, it->second.base, &info.line_displacement, &line_))
{
info.line_number = line_.LineNumber;
info.file_name = line_.FileName;
}
if (SymSetScopeFromInlineContext(process_, address, frame.InlineFrameContext))
{
local_variables_walk(info.local_variables, info.parameters, type, frame.AddrFrame.Offset, thread_context, {}, symbol_walk_options::inline_variables);
}
}
SYM_STKWALK_FORCE_FRAMEPTR 我从未使用过,但我猜想它迫使 StackWalkEx 假设您指向的线程上下文使用帧指针。 使用 C++ 编译器Frame-Pointer Omission 选项意味着它可以生成在所有情况下都不使用帧指针的代码。 这使得 StackWalkEx 更难弄清楚该怎么做。 我相信 StackWalkEx 使用来自任何给定上下文的某种启发式方法来找出函数返回调用设置的位置,这可能会失败。
所以我唯一能想到何时使用此开关的情况是:
然后你可以尝试一下,看看它是否返回更好的堆栈跟踪。
我现在总是使用 StackWalkEx 而不是 StackWalk64,因为它较新,并且它允许您从调用堆栈遍历中提取比 StackWalk64 结果更多的信息(包含内联符号数据和参数以及局部变量数据等信息)。 我还注意到,在您没有 pdb 信息的情况下,它可以提供更好的调用堆栈结果。