分析堆栈损坏的核心转储
Analyzing core dump with stack corrupted
我目前正在尝试调试我的 C++ 应用程序中的核心。客户报告了一个 SEGFAULT
核心,其线程列表如下:
...Other threads go above here
3 Thread 0xf73a2b70 (LWP 2120) 0x006fa430 in __kernel_vsyscall ()
2 Thread 0x2291b70 (LWP 2212) 0x006fa430 in __kernel_vsyscall ()
* 1 Thread 0x218fb70 (LWP 2210) 0x00000000 in ?? ()
让我困惑的是崩溃的线程指向0x00000000
。如果我尝试检查回溯,我会得到:
Thread 1 (Thread 0x1eeeb70 (LWP 27156)):
#0 0x00000000 in ?? ()
#1 0x00281da7 in SomeClass1::_someKnownMethod1 (this=..., elem=...) at path_to_cpp_file:line_number
#2 0x0028484d in SomeClass2::_someKnownMethod2 (this=..., stream=..., stanza=...) at path_to_cpp_file:line_number
#3 0x002958b2 in SomeClass3::_someKnownMethod3 (this=..., stream=..., elem=...) at path_to_cpp_file:line_number
我对密文表示歉意 - NDA 的局限性。
很显然,顶框是很不为人知的。我的第一个猜测是 PC
寄存器被一些堆栈覆盖损坏了。
我已尝试通过提供在 Frame #1
中看到的相同调用在我的本地部署中重现该问题,但崩溃从未发生。
众所周知,这些内核很难调试?但是有人对尝试什么有一些提示吗?
更新
0x00281d8b <+171>: mov edx,DWORD PTR [ebp+0x8]
0x00281d8e <+174>: mov ecx,DWORD PTR [ebp+0xc]
0x00281d91 <+177>: mov eax,DWORD PTR [edx+0x8]
0x00281d94 <+180>: mov edx,DWORD PTR [eax]
0x00281d96 <+182>: mov DWORD PTR [esp+0x8],ecx
0x00281d9a <+186>: mov ecx,DWORD PTR [ebp+0x8]
0x00281d9d <+189>: mov DWORD PTR [esp],eax
0x00281da0 <+192>: mov DWORD PTR [esp+0x4],ecx
0x00281da4 <+196>: call DWORD PTR [edx+0x14]
=> 0x00281da7 <+199>: mov ebx,DWORD PTR [ebp-0xc]
0x00281daa <+202>: mov esi,DWORD PTR [ebp-0x8]
0x00281dad <+205>: mov edi,DWORD PTR [ebp-0x4]
0x00281db0 <+208>: mov esp,ebp
0x00281db2 <+210>: pop ebp
0x00281db3 <+211>: ret
0x00281db4 <+212>: lea esi,[esi+eiz*1+0x0]
... 应该是 Frame #0
中的那个,但从反汇编来看这没什么意义。好像程序在从 Frame #1
返回时崩溃了,但为什么我看到无效的 Frame #0
?或者这个框架拆解部分属于函数onPacket
?
更新#2:
(gdb) p/x $edx
= 0x1deb664
(gdb) print _listener
= (jax::MyClass &) @0xf6dbf6c4: {_vptr.MyClass= 0x1deb664}
扩展 Hayt 的评论,因为堆栈的其余部分看起来不错,我怀疑第 1 帧中出现问题;考虑以下(显然不正确的)程序,它生成类似的堆栈跟踪:
int main() {
void (*foo)() = 0;
foo();
return 0;
}
堆栈跟踪:
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x000000000040056a in main ()
如果第 1 帧在源代码级别没有意义,您可以尝试查看第 1 帧的反汇编。选择该帧后,disass $pc
应该向您展示整个函数的反汇编,=>
表示 return 地址(调用帧 0 之后的指令)。
在空函数指针取消引用的情况下,调用帧 0 的指令可能涉及简单的寄存器取消引用,在这种情况下,您需要了解该寄存器如何获得空值。在某些情况下,在 disass
命令中包含 /m
可能会有所帮助,尽管由于指令边界和源代码行边界之间的区别,它可能会导致混淆。省略 /m
更有可能显示有意义的 return 地址。
更新后的反汇编(没有 /m
)中的 =>
是有道理的。在除第 0 帧之外的任何帧中,pc
值(=>
在反汇编中指向的内容)指示将在下一个编号最低的帧 return 时执行的指令(其中,由于崩溃,在这种情况下没有发生)。第 1 帧中的 pc
值不是崩溃时 pc
寄存器的值,而是 call
压入堆栈的已保存 pc
值操作说明。一种查看方式是将第 0 帧中的 x/a $sp
的输出与第 1 帧中的 x/i $pc
的输出进行比较。
解释此反汇编的一种方法是 edx
是某个对象,而 [edx+0x14]
指向其 vtable。 vtable 可能以空指针结束的一种方式是内存分配问题,其中引用了一块内存,该内存块已被释放并随后被其合法所有者(分配该块的下一段代码)覆盖。如果其中任何一个适用于此,它可以以任何一种方式工作(框架 1 中的代码可能是罪魁祸首,也可能是受害者)。内存可能被不正确的内容覆盖还有其他原因,但双重分配可能是一个不错的起点。
检查第 1 帧中 edx
引用的对象的内容可能是有意义的,看看除了可能是不正确的 vtable 之外是否还有任何其他异常。 print
命令和 x
命令(在 gdb 中)都对此很有用。根据 disass/m
输出(在撰写本文时,仅在问题的编辑历史记录中可见),我对 [=25= 引用了哪个对象的最佳猜测是 _listener
,但它将是最好通过反汇编的进一步研究来确认这一点(此处提供的摘录似乎不包括确定 edx
值的指令)。
另请参阅 的情况(在其中一条评论中),其中流氓未映射未映射的内存用于其他几个线程的堆栈,并且因核心转储而崩溃,非常难以使用。
我目前正在尝试调试我的 C++ 应用程序中的核心。客户报告了一个 SEGFAULT
核心,其线程列表如下:
...Other threads go above here
3 Thread 0xf73a2b70 (LWP 2120) 0x006fa430 in __kernel_vsyscall ()
2 Thread 0x2291b70 (LWP 2212) 0x006fa430 in __kernel_vsyscall ()
* 1 Thread 0x218fb70 (LWP 2210) 0x00000000 in ?? ()
让我困惑的是崩溃的线程指向0x00000000
。如果我尝试检查回溯,我会得到:
Thread 1 (Thread 0x1eeeb70 (LWP 27156)):
#0 0x00000000 in ?? ()
#1 0x00281da7 in SomeClass1::_someKnownMethod1 (this=..., elem=...) at path_to_cpp_file:line_number
#2 0x0028484d in SomeClass2::_someKnownMethod2 (this=..., stream=..., stanza=...) at path_to_cpp_file:line_number
#3 0x002958b2 in SomeClass3::_someKnownMethod3 (this=..., stream=..., elem=...) at path_to_cpp_file:line_number
我对密文表示歉意 - NDA 的局限性。
很显然,顶框是很不为人知的。我的第一个猜测是 PC
寄存器被一些堆栈覆盖损坏了。
我已尝试通过提供在 Frame #1
中看到的相同调用在我的本地部署中重现该问题,但崩溃从未发生。
众所周知,这些内核很难调试?但是有人对尝试什么有一些提示吗?
更新
0x00281d8b <+171>: mov edx,DWORD PTR [ebp+0x8]
0x00281d8e <+174>: mov ecx,DWORD PTR [ebp+0xc]
0x00281d91 <+177>: mov eax,DWORD PTR [edx+0x8]
0x00281d94 <+180>: mov edx,DWORD PTR [eax]
0x00281d96 <+182>: mov DWORD PTR [esp+0x8],ecx
0x00281d9a <+186>: mov ecx,DWORD PTR [ebp+0x8]
0x00281d9d <+189>: mov DWORD PTR [esp],eax
0x00281da0 <+192>: mov DWORD PTR [esp+0x4],ecx
0x00281da4 <+196>: call DWORD PTR [edx+0x14]
=> 0x00281da7 <+199>: mov ebx,DWORD PTR [ebp-0xc]
0x00281daa <+202>: mov esi,DWORD PTR [ebp-0x8]
0x00281dad <+205>: mov edi,DWORD PTR [ebp-0x4]
0x00281db0 <+208>: mov esp,ebp
0x00281db2 <+210>: pop ebp
0x00281db3 <+211>: ret
0x00281db4 <+212>: lea esi,[esi+eiz*1+0x0]
... 应该是 Frame #0
中的那个,但从反汇编来看这没什么意义。好像程序在从 Frame #1
返回时崩溃了,但为什么我看到无效的 Frame #0
?或者这个框架拆解部分属于函数onPacket
?
更新#2:
(gdb) p/x $edx
= 0x1deb664
(gdb) print _listener
= (jax::MyClass &) @0xf6dbf6c4: {_vptr.MyClass= 0x1deb664}
扩展 Hayt 的评论,因为堆栈的其余部分看起来不错,我怀疑第 1 帧中出现问题;考虑以下(显然不正确的)程序,它生成类似的堆栈跟踪:
int main() {
void (*foo)() = 0;
foo();
return 0;
}
堆栈跟踪:
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x000000000040056a in main ()
如果第 1 帧在源代码级别没有意义,您可以尝试查看第 1 帧的反汇编。选择该帧后,disass $pc
应该向您展示整个函数的反汇编,=>
表示 return 地址(调用帧 0 之后的指令)。
在空函数指针取消引用的情况下,调用帧 0 的指令可能涉及简单的寄存器取消引用,在这种情况下,您需要了解该寄存器如何获得空值。在某些情况下,在 disass
命令中包含 /m
可能会有所帮助,尽管由于指令边界和源代码行边界之间的区别,它可能会导致混淆。省略 /m
更有可能显示有意义的 return 地址。
更新后的反汇编(没有 /m
)中的 =>
是有道理的。在除第 0 帧之外的任何帧中,pc
值(=>
在反汇编中指向的内容)指示将在下一个编号最低的帧 return 时执行的指令(其中,由于崩溃,在这种情况下没有发生)。第 1 帧中的 pc
值不是崩溃时 pc
寄存器的值,而是 call
压入堆栈的已保存 pc
值操作说明。一种查看方式是将第 0 帧中的 x/a $sp
的输出与第 1 帧中的 x/i $pc
的输出进行比较。
解释此反汇编的一种方法是 edx
是某个对象,而 [edx+0x14]
指向其 vtable。 vtable 可能以空指针结束的一种方式是内存分配问题,其中引用了一块内存,该内存块已被释放并随后被其合法所有者(分配该块的下一段代码)覆盖。如果其中任何一个适用于此,它可以以任何一种方式工作(框架 1 中的代码可能是罪魁祸首,也可能是受害者)。内存可能被不正确的内容覆盖还有其他原因,但双重分配可能是一个不错的起点。
检查第 1 帧中 edx
引用的对象的内容可能是有意义的,看看除了可能是不正确的 vtable 之外是否还有任何其他异常。 print
命令和 x
命令(在 gdb 中)都对此很有用。根据 disass/m
输出(在撰写本文时,仅在问题的编辑历史记录中可见),我对 [=25= 引用了哪个对象的最佳猜测是 _listener
,但它将是最好通过反汇编的进一步研究来确认这一点(此处提供的摘录似乎不包括确定 edx
值的指令)。
另请参阅