1

我在我们的一个流程中遇到了间歇性的核心转储。所有线程的堆栈,除了崩溃的堆栈,看起来都不错,并且解析正确。

崩溃的线程有一个明显损坏的调用堆栈。堆栈有两个帧,它们都是 0x00000000。查看寄存器,PC和RA都是0(这解释了调用堆栈......)原因寄存器是00800008。

  1. 有没有办法获得有关崩溃线程的更多信息?
  2. 寄存器本身是如何损坏的?(或者相反,在核心转储中,调试器根据堆栈填充这些寄存器?)

谢谢!

4

1 回答 1

3

首先回答(2)——因为了解实际发生的事情对于找出有关崩溃根本原因的更多信息很重要:

真正是寄存器本身,在运行时的机器中,是 0;但这并不是寄存器本身损坏了;相反,内存损坏了,然后损坏的内存被复制回寄存器,最终导致崩溃。

正在发生的事情是这样的:堆栈被损坏,包括(a)特别是 RA,当它存储在堆栈内存中时,被清零。然后,当函数准备好返回时,它 (b) 从堆栈中恢复 RA 寄存器——因此 RA寄存器现在为 0——然后 (c) 跳转返回到 RA,从而将 PC 设置为指向0;下一条指令将导致崩溃,而 RA 和 PC 都为 0。

例如,在http://logos.cs.uic.edu/366/notes/mips%20quick%20tutorial.htm(强调我的)中解释了关于 RA 存储在堆栈上然后从中恢复的业务:

返回地址存储在寄存器 $ra 中;如果子程序将调用其他子程序,或者是递归的,返回地址应该从 $ra 复制到堆栈上以保存它,因为 jal 总是将返回地址放在这个寄存器中,因此会覆盖以前的值。

这是一个示例程序,它在 PC 和 RA 都为 0 时崩溃,它很好地说明了上述序列(可能需要调整确切的数字,具体取决于系统):

#include <string.h>

int bar(void)
{
    char buf[10] = "ABCDEFGHI";
    memset(buf, 0, 50);
    return 0;
}

int foo(void)
{
    return bar();
}

int main(int argc, char *argv[])
{
    return foo();
}

如果我们看一下 foo() 的反汇编:

(gdb) disas foo
Dump of assembler code for function foo:
   0x00400408 <+0>:     addiu   sp,sp,-32
   0x0040040c <+4>:     sw      ra,28(sp)
   0x00400410 <+8>:     sw      s8,24(sp)
   0x00400414 <+12>:    move    s8,sp
   0x00400418 <+16>:    jal     0x4003a0 <bar>
   0x0040041c <+20>:    nop
   0x00400420 <+24>:    move    sp,s8
   0x00400424 <+28>:    lw      ra,28(sp)
   0x00400428 <+32>:    lw      s8,24(sp)
   0x0040042c <+36>:    addiu   sp,sp,32
   0x00400430 <+40>:    jr      ra
   0x00400434 <+44>:    nop
End of assembler dump.        

我们很好地看到 RA 在函数开始时存储在堆栈中(<+4> sw ra,28(sp)),然后在结束时恢复(<+28> lw ra,28(sp)),然后跳转返回到(<+40> jr ra)。我展示了 foo() 因为它更短,但是 bar() 的结构完全相同——除了在 bar() 中间还有 memset() ,它在堆栈上覆盖 RA(它是将 50 个字节写入大小为 10 的数组中);然后恢复到寄存器中的是0,最终导致崩溃。

所以,现在我们知道崩溃的根本原因是某种堆栈损坏,这让我们回到问题(1):有没有办法获得有关崩溃线程的更多信息?

好吧,这有点困难,并且调试变得更像一门艺术而不是一门科学,但以下是要牢记的原则:

  • 基本思想是找出导致堆栈损坏的原因——很可能是对某个本地缓冲区的写入,如上例所示。
  • 尽量将腐败发生在流程中的位置归零。日志在这里可以提供很大帮助:您看到的最后一个日志显然发生在崩溃之前(尽管不一定在损坏之前!) - 在可疑区域添加更多日志以在崩溃位置归零。当然,如果您可以访问调试器,您也可以单步执行代码以找出崩溃的位置。
  • 一旦找到崩溃位置,从那里向后工作就容易多了:首先,在崩溃之前,PC 尚未设置为 0,因此您应该能够看到回溯(不过,请注意回溯本身是使用存储在堆栈中的值“计算”的——一旦它们被损坏,就无法计算超出损坏的回溯。但这在这种情况下实际上很有帮助:这可以非常准确地告诉您损坏在内存中的位置是:回溯被截断的点是被损坏的 RA(在堆栈上)。)
  • 一旦你发现了什么被破坏了,但你仍然不知道是什么导致了破坏,使用观察点:一旦你进入将最终被覆盖的 RA 放置在堆栈上的函数,就在它上面设置一个观察点。一旦发生腐败,这应该会导致中断......

希望这可以帮助!

于 2013-02-05T00:51:37.317 回答