我目前正在尝试在我的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
?
(gdb) p/x $edx
$5 = 0x1deb664
(gdb) print _listener
$6 = (jax::MyClass &) @0xf6dbf6c4: {_vptr.MyClass= 0x1deb664}
答案 0 :(得分:4)
扩展Hayt的评论,因为堆栈的其余部分看起来很好,我怀疑第1帧中出现了问题;考虑以下(显然是不正确的)程序,它生成类似的堆栈跟踪:
int main() {
void (*foo)() = 0;
foo();
return 0;
}
堆栈追踪:
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x000000000040056a in main ()
答案 1 :(得分:2)
如果第1帧在源级别没有意义,您可以尝试查看第1帧的反汇编。选择该帧后,disass $pc
应该显示整个函数的反汇编,{{1}指示返回地址(调用第0帧后的指令)。
在空函数指针取消引用的情况下,对帧0的调用的指令可能涉及简单的寄存器取消引用,在这种情况下,您想要了解该寄存器如何获得空值。在某些情况下,在=>
命令中包含/m
可能会有所帮助,尽管由于指令边界和源行边界之间的区别,它可能会导致混淆。省略disass
更有可能显示有意义的返回地址。
更新的反汇编中的/m
(没有=>
)是有道理的。在除第0帧之外的任何帧中,/m
值(反汇编中pc
指向的值)表示当下一个最低编号帧返回时将执行的指令(由于崩溃,在这种情况下没有发生)。第1帧中的=>
值不是崩溃时pc
寄存器的值,而是pc
在堆栈上推送的已保存pc
值指令。一种方法是将第0帧中call
的输出与第1帧中的x/a $sp
进行比较。
解释此反汇编的一种方法是x/i $pc
是某个对象,而edx
指向其vtable。 vtable可能用空指针结束的一种方式是一个内存分配问题,它对一块内存的陈旧引用已被解除分配并随后被其合法所有者覆盖(下一段代码用于分配该块)。如果其中任何一个适用于此,它可以以任何方式工作(第1帧中的代码可能是罪魁祸首,或者它可能是受害者)。还有其他原因可能会用不正确的内容覆盖内存,但双重分配可能是一个很好的起点。
检查第1帧中[edx+0x14]
引用的对象的内容可能是有意义的,看看除了可能是不正确的vtable之外是否还有其他任何异常。 edx
命令和print
命令(在gdb中)都可能对此有用。基于x
输出(在撰写本文时,仅在问题的编辑历史中可见),edx
引用哪个对象的最佳猜测是disass/m
,但它会是最好通过进一步研究反汇编来确认(这里的摘录似乎不包括确定_listener
的值的指令。)
答案 2 :(得分:0)
另请参阅gdb can't access memory address error(在其中一项注释中),其中流氓将未映射的未映射内存映射到其他几个线程的堆栈,并且由于核心转储而崩溃,这很难使用。