rax和pc等于0000000的MIPS核心转储

时间:2012-11-27 16:52:35

标签: gdb mips coredump

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

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

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

1 个答案:

答案 0 :(得分:3)

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

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

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

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

  

寄存地址存储在寄存器$ 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))然后跳转返回到({{ 1}})。我展示了foo(),因为它更短,但是对于bar()完全相同的结构 - 除了在bar()中还有中间的memset(),当它在堆栈上时覆盖RA(它是将50个字节写入大小为10的数组中;然后恢复到寄存器的是0,最终导致崩溃。

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

嗯,这有点困难,而且调试变得更像是一门艺术而非科学,但这里要记住的原则是:

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

希望这有帮助!