为啥调试器需要符号来重建堆栈?

Posted

技术标签:

【中文标题】为啥调试器需要符号来重建堆栈?【英文标题】:Why does the debugger need symbols to reconstruct the stack?为什么调试器需要符号来重建堆栈? 【发布时间】:2010-09-15 13:21:46 【问题描述】:

在 Visual Studio 中调试时,如果缺少调用堆栈的符号,例如:

00 > HelloWorld.exe!my_function(int y=42)  Line 291
01   dynlib2.dll!10011435()  
  [Frames below may be incorrect and/or missing, no symbols loaded for dynlib2.dll] 
02   dynlib2.dll!10011497()  
03   HelloWorld.exe!wmain(int __formal=1, int __formal=1)  Line 297 + 0xd bytes
04   HelloWorld.exe!__tmainCRTStartup()  Line 594 + 0x19 bytes
05   HelloWorld.exe!wmainCRTStartup()  Line 414
06   kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes 

调试器将显示警告Frames below may be incorrect and/or missing

(请注意,只有第 01 行和第 02 行没有符号。第 00 行,我设置了一个断点,所有其他行都加载了符号。)

现在,我知道如何修复警告(->get pdb 文件),但我不太明白为什么会显示它!我上面贴的栈完全没问题,只是我没有dynlib2.dll模块的pdb文件。

为什么调试器需要符号文件来确保堆栈正确?

【问题讨论】:

【参考方案1】:

我认为这是因为并非所有函数都遵循“标准”堆栈布局。通常每个函数都以:

push        ebp  
mov         ebp,esp 

结尾
pop         ebp  
ret

由此,每个函数都会创建其所谓的堆栈帧。 EBP 始终指向顶部堆栈帧的开头。在每一帧中,前两个值是指向前一个堆栈帧的指针,以及函数返回地址。

使用此信息可以轻松地重构堆栈。然而:

    此堆栈信息不包括函数名称和参数信息。 并非所有函数都遵循此堆栈框架布局。如果启用了某些优化(例如 /Oy,省略堆栈帧指针) - 堆栈布局会有所不同。

【讨论】:

【参考方案2】:

I tried to understand this myself a while ago.

截至 2013 年,FPO 不在 MSFT 中使用,而且通常不受欢迎。我确实遇到过一种内部使用的不同 MS 二进制技术,这可能会阻碍简单的 EBP 链遍历:Basic Block Tools。

如帖子中所述,PDB 确实包含“StackFrameTypeEnum”,并且在其他地方暗示它们包含堆栈帧的“展开程序”。所以总而言之,它们仍然是需要的,而且关于为什么 - 的血腥细节没有记录在案。

【讨论】:

FPO 代表帧点省略吗?谢谢。 @Sabuncu 是:docs.microsoft.com/en-us/cpp/build/reference/…【参考方案3】:

符号与相关的二进制代码分离,以减少传送二进制文件的大小。检查您的 PDB 文件有多大——特别是与匹配的二进制文件 (EXE/DLL) 相比。每次交付、安装和使用二进制文件时,您都不希望有这种开销。这在加载时尤其重要。符号信息毕竟只是用于调试,而不是正确运行代码所必需的。只要您保留与您交付的二进制文件匹配的符号,您仍然可以在加载所有符号后调试问题。

【讨论】:

这个答案与这个问题到底有什么关系? (除了大致匹配相同的标签?) 我的意思是,如果没有符号文件,您将不会在重构的调用堆栈中看到完整的符号调试信息,因为二进制文件中不包含符号调试信息。我的猜测是@valdo 暗示符号文件中有信息可以始终生成完整的调用堆栈 - 我相信情况并非如此,但很高兴知道我错了。

以上是关于为啥调试器需要符号来重建堆栈?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 gcc 在非调试版本中添加符号?

DLR - 为啥我的堆栈跟踪中没有显示调试信息?

使用 GDB 在堆栈上打印符号

[转]Visual Studio调试之符号文件

由于调试符号,巨大的可执行文件,为啥?

Minidumps 和 modules匹配