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,而不是esprsp,它的调用指令将返回地址存储在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 损坏的堆栈帧 - 如何调试?的主要内容,如果未能解决你的问题,请参考以下文章

使用 GDB 在堆栈上打印符号

如何使用 GDB 检查堆栈帧?

调试堆栈损坏

如何修复“调试错误!,变量'x'周围的堆栈已损坏”?

如何获取 gdb 调用堆栈跟踪?

如何使用 gdb 探索堆栈/堆?