GDB 损坏的堆栈帧 - 如何调试?
Posted
技术标签:
【中文标题】GDB 损坏的堆栈帧 - 如何调试?【英文标题】:GDB corrupted stack frame - How to debug? 【发布时间】:2012-04-06 06:40:39 【问题描述】:我有以下堆栈跟踪。是否有可能从中找出对调试有用的东西?
Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0 0x00000002 in ?? ()
#1 0x00000001 in ?? ()
#2 0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb)
当我们得到Segmentation fault
时从哪里开始查看代码,而堆栈跟踪不是那么有用?
注意:如果我发布代码,那么 SO 专家会给我答案。我想从 SO 那里得到指导并自己找到答案,所以我不会在这里发布代码。道歉。
【问题讨论】:
可能你的程序跳进了杂草 - 你能从堆栈指针中恢复任何东西吗? 另一件要考虑的事情是帧指针是否设置正确。您是在没有优化的情况下构建还是传递了-fno-omit-frame-pointer
之类的标志?此外,对于内存损坏,valgrind
可能是更合适的工具,如果您愿意的话。
【参考方案1】:
funny...我们在旧的 C 应用程序中使用驱动程序 exact 发生了同样的事情。十六进制的前 2 个堆栈跟踪值指针是从端口读取的数据字节。我只是碰巧注意到一个,因为它很熟悉。
【讨论】:
【参考方案2】:如果是堆栈覆盖,则这些值很可能对应于程序可识别的内容。
例如,我刚刚发现自己正在查看堆栈
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x000000000000342d in ?? ()
#2 0x0000000000000000 in ?? ()
而0x342d
是 13357,当我为它获取应用程序日志时,结果证明它是一个节点 ID。这立即帮助缩小了可能发生堆栈覆盖的候选站点。
【讨论】:
【参考方案3】:那些虚假地址(0x00000002 等)实际上是 PC 值,而不是 SP 值。现在,当你得到这种带有伪造(非常小的)PC 地址的 SEGV 时,99% 的时间是由于通过伪造的函数指针调用。请注意,C++ 中的虚调用是通过函数指针实现的,因此虚调用的任何问题都可以以同样的方式表现出来。
间接调用指令只是将调用后的 PC 压入堆栈,然后将 PC 设置为目标值(在这种情况下是假的),所以如果这 是 发生了什么,你可以很容易地通过手动将 PC 从堆栈中弹出来撤消它。在 32 位 x86 代码中,您只需:
(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4
使用您需要的 64 位 x86 代码
(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8
然后,您应该能够执行bt
并找出代码的真正位置。
另外 1% 的时间,错误将是由于覆盖堆栈,通常是溢出存储在堆栈上的数组。在这种情况下,您可以通过使用valgrind 之类的工具来更清楚地了解情况
【讨论】:
@George:gdb executable corefile
将使用可执行文件和核心文件打开gdb,此时您可以执行bt
(或上述命令后跟bt
)...
@mk.. ARM 不使用堆栈作为返回地址——而是使用链接寄存器。所以一般不会有这个问题,或者如果有,通常是由于其他一些堆栈损坏。
我认为即使在 ARM 中,所有通用寄存器和 LR 在被调用函数开始执行之前都存储在堆栈中。一旦函数完成,LR 的值就会弹出到 PC 中,因此函数返回。所以如果堆栈损坏,我们可以看到错误的值是 PC 对吗?在这种情况下,调整堆栈指针可能会导致适当的堆栈并有助于调试问题。你怎么看?请让我知道你的想法。谢谢。
什么是假的?
ARM 不是 x86 -- 它的堆栈指针被称为sp
,而不是esp
或rsp
,它的调用指令将返回地址存储在lr
寄存器中,而不是在堆。因此,对于 ARM,您真正需要撤消调用的只是 set $pc = $lr
。如果$lr
无效,您将面临更难解决的问题。【参考方案4】:
如果情况相当简单,Chris Dodd's answer 是最好的选择。它看起来确实像跳过了一个 NULL 指针。
但是,程序有可能在崩溃之前对自己的脚、膝盖、脖子和眼睛开枪——覆盖了堆栈,弄乱了帧指针,以及其他邪恶。如果是这样,那么解开哈希不太可能向您展示土豆和肉。
更有效的解决方案是在调试器下运行程序,并单步执行函数,直到程序崩溃。一旦识别出崩溃函数,重新开始并进入该函数并确定它调用的函数会导致崩溃。重复直到你找到一个有问题的代码行。 75% 的情况下,修复将是显而易见的。
在另外 25% 的情况下,所谓的违规代码行是一条红鲱鱼。它将对之前设置的许多行(可能是数千行)的(无效)条件做出反应。如果是这种情况,选择的最佳课程取决于许多因素:主要是您对代码的理解和使用它的经验:
也许设置调试器观察点或在关键变量上插入诊断printf
将导致必要的哈哈!
也许用不同的输入改变测试条件会比调试提供更多的洞察力。
也许第二双眼睛会迫使您检查假设或收集被忽视的证据。
有时,只需去吃晚饭并思考收集到的证据。
祝你好运!
【讨论】:
如果没有第二双眼睛,那么橡皮鸭是很好的替代品。 写出缓冲区的末尾也可以。它可能不会在你注销缓冲区末尾的地方崩溃,但是当你退出函数时,它就会死掉。 可能有用:GDB: Automatic 'Next'ing【参考方案5】:假设堆栈指针有效...
可能无法从回溯中准确知道 SEGV 发生的位置——我认为前两个堆栈帧已被完全覆盖。 0xbffff284 似乎是一个有效的地址,但接下来的两个不是。要仔细查看堆栈,您可以尝试以下操作:
gdb$ x/32ga $rsp
或变体(将 32 替换为另一个数字)。这将从巨型(g)大小的堆栈指针开始打印出一些字(32),格式为地址(a)。输入“help x”以获取有关格式的更多信息。
在这种情况下,使用一些哨兵“printf”来检测您的代码可能不是一个坏主意。
【讨论】:
非常有帮助,谢谢——我有一个堆栈只返回三帧然后点击“回溯停止:前一帧与本帧相同(损坏的堆栈?)”;我之前在 CPU 异常处理程序中的代码中做过类似的事情,但除了info symbol
之外,我不记得如何在 gdb 中执行此操作了。
32 位 ARM 设备上的 FWIW:x/256wa $sp
=)
@leander 你能告诉我什么是 X/256wa 吗?我需要它用于 64 位 ARM。一般来说,如果您能解释它是什么,将会很有帮助。
根据答案,'x'=检查内存位置;它打印出许多 'w'=words(在本例中为 256),并将它们解释为 'a'=addresses。在sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory 的 GDB 手册中有更多信息。【参考方案6】:
查看其他一些寄存器,看看其中一个是否缓存了堆栈指针。从那里,您也许可以检索堆栈。此外,如果这是嵌入的,堆栈通常定义在一个非常特定的地址。使用它,你有时也可以获得不错的筹码。这一切都假设当你跳到超空间时,你的程序并没有在整个过程中吐出所有的内存......
【讨论】:
以上是关于GDB 损坏的堆栈帧 - 如何调试?的主要内容,如果未能解决你的问题,请参考以下文章