记录一次线上进程异常崩溃的排查过程

        7月5日晚上11点收到运维同学的消息,说有一个游戏服进程崩溃了,有产生corefile文件,原本以为gdb bt一下就能很清晰地找到这个问题的原因,但当进行问题排查时,发现事情并没有那么简单。先看一下堆栈图。记录一次线上进程异常崩溃的排查过程_第1张图片

        红框部分为逻辑层的最后的函数调用,红框以上部分是c++库函数调用,再怎么怀疑也不能怀疑库函数出问题了吧,于是着重排查红框部分是否有访问非法内存地址。

记录一次线上进程异常崩溃的排查过程_第2张图片

        回去查看源码,该函数的实现非常简单,就是传入一个根据key在std::map里查找对应的value,那么按照以往经验,情况只有两种可能,一是传入的key是个非法指针(NULL或者野指针),二是类成员变量m_BuffParams所在的地址被写坏了

        虽然gdb中,逻辑层调用栈中的key被优化掉无法打印,但是从传入至map的参数可以看到,该key是有效字符串,并不是NULL或者野指针,因此可以排除这种情况。那么接下来就要怀疑m_BuffParams是不是被写坏了,于是打印该成员变量:

记录一次线上进程异常崩溃的排查过程_第3张图片

发现该成员变量也是完整的,说明内存没有被写坏。这就有点出乎我的意料了,内存既然没有被写坏,那为何会产生宕机呢?于是我去查看系统的syslog关于宕机的信息:

这条日志有这么几个信息点:

1.系统抛出的错误码error值为0(后面会讲这个error值是什么意思)

2.错误关键信息为trap invalid op code,并非segfault。

3.导致宕机的地方libc-2.23.so这个动态链接库中,崩溃代码相对地址为0x16fa4a(0x7fa01036da41-0x7fa0101fe0000)

信息收集到了,那么接下来我们可以做进一步分析了。先看error值,一般如果是我们在逻辑函数中将内存写坏,error值通常为4或者6,那么这个error值是什么意思呢,这里直贴一个结论:

记录一次线上进程异常崩溃的排查过程_第4张图片具体关于error值的讨论可以参考这篇文章:

https://worthsen.blog.csdn.net/article/details/106896795

之前我们之所以逻辑层写坏内存导致宕机error为4(00000010)或者6(00000110),是由于我们写坏或者读取的是用户态内存段。而这一次报的错误,首先并非通常碰到的segfault,而是内核态读操作trap invalid opcode导致。百度搜了一下关于trap invalid opcode的信息,少之又少。只有一篇与我们这次宕机非常相似的文章:

https://cloud.tencent.com/developer/article/1356935

大致的意思是,由于指令集不一致导致宕机。那么我们接下来分析的重点就是指令集这一部分了,我们首先要去查看,宕机的地方到底是用来什么汇编指令。于是我们需要将lib-2.23.so这个动态链接库进行反汇编处理:

objdump -D /lib/x86_64-linux-gnu/libc-2.23.so > libc.objdump

查看生成后的汇编文件,并定位到出错的那个地址(0x16fa4a)。

记录一次线上进程异常崩溃的排查过程_第5张图片

我们发现,该地址的指令非常简单,就是cmp,即对两个寄存器中的内容进行比较,因此肯定不是因为无法识别指令导致。但此时我又怀疑是不是由于我找错了地方,为了验证程序就是在此处崩溃,我又回头去看了下堆栈信息的最顶部,当即的代码是发生在memcmp-sse4.S文件的1577行:

我决定将libso的源码下载下来,再与我们的反汇编文件进行比较,如果二者一致,说明一定是在这个地方崩溃的。

记录一次线上进程异常崩溃的排查过程_第6张图片

在github上很容易搜到源码,于是我们定位到1577行,并查看上下文,发现就是反汇编后在0x16fa4a地址的那段代码,因此可以确定的确是在cmp %cl,%al这个地方崩的,gdb查看这两个寄存器中的内容,发现也是合法值。此时我有些没有头绪了,既然全部合法(地址合法,指令合法),程序为何会崩溃?无奈只能求助我们的CTO,经过一番讨论后,开始怀疑是云服务商的提供的Linux版本内核有问题(毕竟不是真实物理机,而是云服务虚拟机),并且之前也碰到过类似的问题,最终通过升级内核版本解决的问题。如果真是该问题,那么显然已经超过了我们能够解决的范畴了,我们只能收集信息并反馈给云服务商。

        此次问题记录,不光是为了收集信息,更多其实是想要总结一下进程崩溃时,排查崩溃原因的基本思路(通过coredump文件查看堆栈,通过syslog查看崩溃地址与error值,通过反汇编文件查看崩溃程序段汇编源码)。

        很多时候,书本上的知识只能为我们提供理论依据,真正能够帮助我们提高问题解决能力的,还是要通过不断的进行实践才行,因此每当遇到问题时,我们都需要好好把握住这次解决问题的机会。

你可能感兴趣的:(c++,c语言)