【问题标题】:GDB corrupted stack frame - How to debug?GDB 损坏的堆栈帧 - 如何调试?
【发布时间】: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 可能是更合适的工具,如果您愿意的话。

标签: c recursion gdb


【解决方案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】:

      查看其他一些寄存器,看看其中一个是否缓存了堆栈指针。从那里,您也许可以检索堆栈。此外,如果这是嵌入的,堆栈通常定义在一个非常特定的地址。使用它,你有时也可以获得不错的筹码。这一切都假设当你跳到超空间时,你的程序并没有在整个过程中吐出所有的内存......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-03-02
        • 2017-06-07
        • 2019-03-30
        • 1970-01-01
        • 2015-07-03
        • 2013-01-27
        • 2015-11-07
        • 1970-01-01
        相关资源
        最近更新 更多