我在我们的一个流程中遇到了间歇性的核心转储。所有线程的堆栈,除了崩溃的堆栈,看起来都不错,并且解析正确。
崩溃的线程有一个明显损坏的调用堆栈。堆栈有两个帧,它们都是 0x00000000。查看寄存器,PC和RA都是0(这解释了调用堆栈......)原因寄存器是00800008。
- 有没有办法获得有关崩溃线程的更多信息?
- 寄存器本身是如何损坏的?(或者相反,在核心转储中,调试器根据堆栈填充这些寄存器?)
谢谢!
首先回答(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):有没有办法获得有关崩溃线程的更多信息?
好吧,这有点困难,并且调试变得更像一门艺术而不是一门科学,但以下是要牢记的原则:
希望这可以帮助!