分析堆栈损坏的核心转储

问题描述 投票:0回答:3

我目前正在尝试调试我的 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

我对保密协议的局限性表示歉意。

显然,顶部框架是相当未知的。我的第一个猜测是

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
$5 = 0x1deb664
(gdb) print _listener
$6 = (jax::MyClass &) @0xf6dbf6c4: {_vptr.MyClass= 0x1deb664}
c++ segmentation-fault gdb stack-trace coredump
3个回答
5
投票

扩展 Hayt 的评论,由于堆栈的其余部分看起来不错,我怀疑第 1 帧中出了问题;考虑以下(显然不正确)程序,它生成类似的堆栈跟踪:

int main() {
    void (*foo)() = 0;
    foo();

    return 0;
}

堆栈跟踪:

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000040056a in main ()

3
投票

如果第 1 帧在源代码级别没有意义,您可以尝试查看第 1 帧的反汇编。选择该帧后,

disass $pc
应显示整个函数的反汇编,并用
=>
指示返回地址(调用帧 0 后立即执行的指令)。

在空函数指针取消引用的情况下,调用帧 0 的指令可能涉及简单的寄存器取消引用,在这种情况下,您需要了解该寄存器如何获得空值。在某些情况下,在

/m
命令中包含
disass
可能会有所帮助,但由于指令边界和源代码行边界之间的区别,它可能会导致混乱。省略
/m
更有可能显示有意义的退货地址。

更新的反汇编中的

=>
(没有
/m
)是有意义的。在除第 0 帧之外的任何帧中,
pc
值(反汇编中
=>
所指向的内容)指示当下一个编号最低的帧返回时将执行的指令(由于崩溃,该指令没有发生在这个案例)。第 1 帧中的
pc
值不是崩溃时
pc
寄存器的值,而是由
pc
指令压入堆栈的保存的
call
值。一种查看方法是比较帧 0 中
x/a $sp
与帧 1 中
x/i $pc
的输出。

解释此反汇编的一种方法是

edx
是某个对象,并且
[edx+0x14]
指向其 vtable。 vtable 可能会出现空指针的一种情况是内存分配问题,即对已被释放并随后被其合法所有者(分配该块的下一段代码)的内存块的陈旧引用。如果其中任何一个适用于此处,则它可以以任何方式工作(框架 1 中的代码可能是罪魁祸首,也可能是受害者)。还有其他原因可能会导致内存被不正确的内容覆盖,但双重分配可能是一个不错的起点。

检查第 1 帧中

edx
引用的对象的内容可能是有意义的,以查看除了可能是不正确的 vtable 之外是否存在任何其他异常。
print
命令和
x
命令(在 gdb 中)对此很有用。根据
edx
输出(在撰写本文时,仅在问题的编辑历史记录中可见),我对
disass/m
引用哪个对象的最佳猜测是
_listener
,但最好通过进一步确认反汇编的研究(此处的摘录似乎不包括确定
edx
值的指令)。


0
投票

另请参阅gdb无法访问内存地址错误的情况(在评论之一中),其中流氓为其他几个线程的堆栈取消映射未映射的内存,并因核心转储而崩溃,非常难以使用。

© www.soinside.com 2019 - 2024. All rights reserved.