GDB损坏的堆栈框架-如何调试?


113

我有以下堆栈跟踪。可以从中得出一些有用的调试信息吗?

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那里获得指导,自己找到答案,所以我不在这里发布代码。道歉。


可能您的程序跳入了杂草-您可以从堆栈指针中恢复任何东西吗?
卡尔·诺鲁姆

1
要考虑的另一件事是是否正确设置了帧指针。您是在没有优化的情况下进行构建还是传递类似这样的标志-fno-omit-frame-pointer?另外,valgrind如果您可以选择使用内存损坏,则可能是更合适的工具。
FatalError 2012年

Answers:


155

这些伪造的地址(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之类的工具来更清楚地了解情况


5
@George:gdb executable corefile将使用可执行文件和核心文件打开gdb,这时您可以执行bt(或上述命令,然后执行bt...)
克里斯·多德2014年

2
@mk .. ARM不使用堆栈作为返回地址,而是使用链接寄存器。因此,它通常不会出现此问题,或者如果出现问题,通常是由于其他一些堆栈损坏。
克里斯·多德

2
我认为,即使在ARM中,所有通用寄存器和LR都在调用函数开始执行之前存储在堆栈中。函数完成后,LR的值会弹出到PC中,因此函数会返回。因此,如果堆栈损坏,我们可以看到错误的值是PC正确吗?在这种情况下,可能会调整堆栈指针将导致适当的堆栈并有助于调试问题。你怎么看?请让我知道您的想法。谢谢。
mk..15

1
什么是假的?
Danny Lo

5
ARM不是x86,其堆栈指针称为spesprsp,其调用指令将返回地址存储在lr寄存器中,而不是堆栈中。因此,对于ARM来说,您真正需要撤消的调用是set $pc = $lr。如果$lr无效,则您要解决的问题要困难得多。
克里斯·多德

44

如果情况很简单,克里斯·多德(Chris Dodd)的答案就是最好的答案。看起来确实像是跳过了NULL指针。

但是,程序有可能在崩溃前用自己的脚,膝盖,脖子和眼睛开枪—覆盖堆栈,弄乱了帧指针和其他弊端。如果是这样,那么拆开哈希表就不太可能显示土豆和肉。

更有效的解决方案是在调试器下运行程序,并逐步执行功能,直到程序崩溃为止。一旦识别出崩溃的功能,请重新启动并进入该功能,并确定调用哪个功能会导致崩溃。重复直到找到唯一令人反感的代码行。75%的时间,修复将显而易见。

在其他25%的情况下,所谓的冒犯性代码是一条红色鲱鱼。它将对之前很多行(可能之前几千行)设置的(无效)条件做出反应。在这种情况下,选择的最佳课程取决于许多因素:主要是您对代码的了解和使用经验:

  • 也许printf在关键变量上设置调试器观察点或插入诊断将导致必要的操作!
  • 也许使用不同的输入来更改测试条件将比调试提供更多的见解。
  • 也许第二双眼睛会迫使您检查您的假设或收集被忽视的证据。
  • 有时,所需要做的只是吃晚饭并考虑收集的证据。

祝好运!


13
如果没有第二只眼睛,那么橡皮鸭已被证明是替代品。
马特2012年

2
注销缓冲区的末尾也可以做到这一点。注销缓冲区的末尾可能不会崩溃,但是当您退出该函数时,它就会死掉。
phyatt

可能有用:GDB:自动“
下一步

28

假设堆栈指针有效...

从回溯可能无法确切知道SEGV发生在哪里-我认为前两个堆栈帧已被完全覆盖。0xbffff284似乎是有效地址,但接下来的两个不是。要仔细查看堆栈,可以尝试以下操作:

gdb $ x / 32ga $ rsp

或变体(用另一个数字替换32)。从巨型(g)大小的堆栈指针开始,将打印出一定数量的单词(32),其格式为地址(a)。输入“帮助x”以获取有关格式的更多信息。

在这种情况下,使用一些标记为'printf'的代码来检测代码可能不是一个坏主意。


很有帮助,谢谢-我的堆栈仅返回了三帧,然后单击了“回溯停止:上一帧与此帧相同(损坏的堆栈?)”;之前,我已经在CPU异常处理程序中的代码中完成了与此完全相同的操作,但是除了info symbol在gdb中如何执行此操作之外,我已经不记得了。
利安德

22
在32位ARM设备上的FWIW:x/256wa $sp =)
Leander

2
@leander能告诉我X / 256wa是什么吗?对于64位ARM,我需要它。通常,如果您可以解释它的内容,将很有帮助。
mk..15-4-17

5
根据答案,“ x” =检查内存位置;它打印出多个“ w” =单词(在本例中为256),并将它们解释为“ a” =地址。GDB手册中有更多信息,位于sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory
leander 2015年

7

查看其他一些寄存器,看其中是否有一个缓存在其中的堆栈指针。从那里,您也许可以检索堆栈。同样,如果是嵌入式的,则通常在非常特定的地址处定义堆栈。使用它,有时还可以得到不错的堆栈。所有这些都假定当您跳到超空间时,您的程序并不会在整个过程中都在内存中呕吐...


3

如果是堆栈覆​​盖,则这些值很可能对应于程序中可识别的值。

例如,我只是发现自己正在查看堆栈

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

并且0x342d是13357,当我为它添加应用程序日志时,结果是一个节点ID。这立即帮助缩小了可能发生堆栈覆盖的候选站点的范围。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.