GDB损坏了堆栈帧 - 如何调试?

San*_*raj 111 c recursion gdb

我有以下堆栈跟踪.是否可以从中进行任何有用的调试?

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) 
Run Code Online (Sandbox Code Playgroud)

当我们得到一个时,从哪里开始查看代码Segmentation fault,并且堆栈跟踪不是那么有用?

注意:如果我发布代码,那么SO专家会给我答案.我想从SO那里得到指导并自己找到答案,所以我不会在这里发布代码.道歉.

Chr*_*odd 154

那些伪造的地址(0x00000002等)实际上是PC值,而不是SP值.现在,当你得到这种SEGV时,伪造的(非常小的)PC地址,99%的时间是由于通过虚假功能指针调用.请注意,C++中的虚拟调用是通过函数指针实现的,因此虚拟调用的任何问题都可以以相同的方式显示.

间接调用指令只是在调用堆栈后将PC推送到PC,然后将PC设置为目标值(在这种情况下假的),所以如果发生了这种情况,您可以通过手动将PC从堆栈中弹出来轻松撤消它.在32位x86代码中,您只需:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4
Run Code Online (Sandbox Code Playgroud)

使用64位x86代码

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8
Run Code Online (Sandbox Code Playgroud)

然后,你应该能够做一个bt并找出代码的真正位置.

另外1%的时间,错误将是由于覆盖堆栈,通常是通过溢出存储在堆栈上的数组.在这种情况下,您可以通过使用valgrind之类的工具来更清楚地了解情况

  • @George:`gdb executable corefile`将打开带有可执行文件和核心文件的gdb,此时你可以执行`bt`(或上面的命令后跟`bt`)... (4认同)
  • @mk .. ARM 不使用堆栈作为返回地址——而是使用链接寄存器。所以它通常没有这个问题,或者如果有,通常是由于其他一些堆栈损坏。 (2认同)
  • 我认为,即使在 ARM 中,所有通用寄存器和 LR 都在被调用函数开始执行之前存储在堆栈中。一旦函数完成,LR 的值就会被弹出到 PC 中,因此函数返回。所以如果堆栈损坏,我们可以看到错误的值是 PC 对吗?在这种情况下,调整堆栈指针可能会导致适当的堆栈并有助于调试问题。你怎么认为?请让我知道你的想法。谢谢你。 (2认同)
  • ARM并非x86,其堆栈指针称为“ sp”,而不是“ esp”或“ rsp”,其调用指令将返回地址存储在“ lr”寄存器中,而不是堆栈中。因此,对于ARM来说,您真正需要撤消的呼叫就是`set $ pc = $ lr`。如果$ lr无效,则要解决的问题要困难得多。 (2认同)

wal*_*lyk 42

如果情况相当简单,Chris Dodd的回答是最好的.它看起来像跳过一个NULL指针.

然而,程序有可能在撞击之前在脚,膝盖,颈部和眼睛中射击 - 覆盖叠加,弄乱框架指针和其他邪恶.如果是这样,那么解开散列不太可能向你展示土豆和肉.

更有效的解决方案是在调试器下运行程序,并逐步执行函数直到程序崩溃.一旦识别出崩溃功能,再次启动并进入该功能并确定它调用哪个功能导致崩溃.重复,直到找到单个有问题的代码行.75%的时间,修复将是显而易见的.

在其他25%的情况下,所谓的违规行代码是红鲱鱼.它将对之前设置许多行的(无效)条件做出反应 - 可能之前有数千行.如果是这种情况,选择的最佳课程取决于许多因素:主要是您对代码的理解和经验:

  • 也许设置调试器观察点或printf在关键变量上插入诊断将导致必要的A ha!
  • 也许用不同的输入改变测试条件将提供比调试更多的洞察力.
  • 也许第二双眼睛会迫使你检查你的假设或收集被忽视的证据.
  • 有时候,只需要吃晚餐并思考所收集的证据.

祝好运!

  • 如果第二副眼睛不可用,那么橡胶鸭已被证明是替代品. (12认同)
  • 写掉缓冲区的结尾也可以做到。它可能不会在您注销缓冲区末尾的地方崩溃,但是当您退出该函数时,它就会死掉。 (2认同)

man*_*ear 25

假设堆栈指针有效......

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

gdb $ x/32ga $ rsp

或变体(用另一个数字替换32).这将从巨型(g)大小的堆栈指针开始打印出一些字(32),格式为地址(a).输入"help x"以获取有关格式的更多信息.

在这种情况下,使用一些sentinel'printf'检测代码可能不是一个坏主意.

  • 32位ARM设备上的FWIW:`x/256wa $ sp` =) (21认同)
  • 根据答案,'x'=检查内存位置; 它打印出一些'w'=单词(在本例中为256),并将它们解释为'a'=地址.有关GDB手册的更多信息,请访问https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory. (4认同)
  • @leander你能告诉我什么是X/256wa?我需要它用于64位ARM.一般来说,如果你能解释它是什么会有所帮助. (2认同)

Mic*_*gan 6

查看一些其他寄存器,看看其中一个寄存器是否有堆栈指针缓存在其中.从那里,您可以检索堆栈.此外,如果嵌入了这种情况,通常会在非常特定的地址定义堆栈.使用它,你有时也可以得到一个体面的堆栈.这一切都假设当你跳到超空间时,你的程序并没有在整个记忆中呕吐...