最近遇到一个棘手的问题,用在现场的程序崩溃了,但是却无法复现,隔个一个月左右出一次,已经出现了三次,各种测试并没有能复现问题。现在只好把目光聚焦在能够获得的仅有的信息,系统日志上了,查看/var/log/message,找到了关键的一套信息:
kernel: myapp[1427]: segfault at 494d1f84 ip 00d73e54 sp b17fac20 error 6 in libThdDllAlg.so[d63000+1e000]
这条信息太宝贵了,这是唯一能够获得的信息。但是怎么用呢?一头雾水。
经过查找资料终于有了一点眉目。
1.myapp是崩溃的程序名,后面中括号中的是PID号,对我没什么用。
2.后面的at 494d1f84 ip 00d73e54 sp b17fac20 ,at后面是发生段错误时访问的地址,是错误当时的地址,好像对我的调试用处不大,ip后面是指令地址,这个就非常重要了,配合后面库的基地址就可以找到出错的代码了。sp后面的stack pointer栈顶指针,貌似我也没用上。
3.error后面的6,这个还是比较重要的,但是得到的信息不多,只能得到信息:由于用户态程序写操作访问越界造成的。
error number是由三个字位组成的,从高到底分别为bit2 bit1和bit0,所以它的取值范围是0~7.
bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
4. libThdDllAlg.so[d63000+1e000]这个是崩在了哪个库上,这个非常重要,方框号里第一个是运行时库的基地址,跟ip后面的一块用,用ip后面的值减去基地址就可以得到出错在这个库里的哪个位置了。在这里是00d73e54-d63000=10e54.至于+号后面是什么意思,我还没搞懂。希望以后能补充
下面知道了出错的库和出错的指令地址,就可以看看是出在了哪条指令上了
还好这个库是我自己 写的库,不是系统库。用objdump -d libThdDllAlg.so>dumpcode 把文件反汇编写进dumpcode文件。这个libThdDllAlg.so是从现场拷贝回来了,而不是自己重新编译的。
打开dumpcode文件,找到10e54地址,往上翻能够找到是在哪个函数里,再结合源文件,找到上下文,大概就能知道出错在哪里了。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
前面讲到了把崩溃点定位到了函数级别,那么怎么找到是那一行代码出了问题呢?
经过后来的实践,总结了定位到行的方法。
之前的方法使用的前提是获得的release版程序的崩溃信息,由于时release版,没有调试信息,所以没法直接使用objdump来显示出源代码行号等信息。不过,我们仍然可以重新编译一个debug版本的汇编代码,与release版的反汇编来对照,也可以找到出错代码所在的行。
debug版objdump使用(引自objdump反汇编用法示例)
-d:将代码段反汇编
-S:将代码段反汇编的同时,将反汇编代码和源代码交替显示,编译时需要给出-g,即需要调试信息。
-C:将C++符号名逆向解析。
-l:反汇编代码中插入源代码的文件名和行号。
-j section:仅反汇编指定的section。可以有多个-j参数来选择多个section。
怎样通过现有代码生成带有源代码和行号的汇编代码呢?
1.使用g++ -S -g -fverbose-asm来生成汇编代码,当然可以直接在原有的编译命令基础上增加这几个参数,由于已经定位到了函数,所以可以直接只编译函数所在的cpp文件,将cpp编译为s
例如:
g++ -Wall -L./lib -DLINUX -O2 -fPIC -S -g -fverbose-asm -o xxx.s xxx.cpp
2使用as来查看,当然也可以打印到文件中来与objdump出来的对比
例如:
as -alhnd xxx.s>dump.txt
3.现在我们有了两个汇编版本,一个是release版,也就是实际运行的版本反汇编出来的(以下简称release版),另一个是带调试信息和源代码的(以下简称debug版)。下面就是对比这两个文件了。
首先找到release版中崩溃发生的代码,从上下寻找特征代码,一般可以找特征数字
例如
274da: 0f b7 4f 06 movzwl 0x6(%edi),%ecx
274de: 0f b7 c0 movzwl %ax,%eax
274e1: 0f b7 57 02 movzwl 0x2(%edi),%edx
274e5: 89 85 38 fe ff ff mov %eax,-0x1c8(%ebp)
274eb: 66 89 8d 28 fe ff ff mov %cx,-0x1d8(%ebp)
274f2: 89 95 30 fe ff ff mov %edx,-0x1d0(%ebp)
274f8: 0f b7 85 28 fe ff ff movzwl -0x1d8(%ebp),%eax
274ff: 8b 8d 30 fe ff ff mov -0x1d0(%ebp),%ecx
27505: 8b 95 10 fe ff ff mov -0x1f0(%ebp),%edx
2750b: 89 44 24 10 mov %eax,0x10(%esp)
2750f: 8b 85 38 fe ff ff mov -0x1c8(%ebp),%eax
27515: 89 4c 24 0c mov %ecx,0xc(%esp)
27519: 89 44 24 08 mov %eax,0x8(%esp)
2751d: e9 2c ff ff ff jmp 2744e <_ZN9SlClass3RunEtt+0xdde>
27522: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
27528: 8b 95 34 fe ff ff mov -0x1cc(%ebp),%edx
2752e: 0f b7 c0 movzwl %ax,%eax
27531: 89 85 30 fe ff ff mov %eax,-0x1d0(%ebp)
27537: 8b 42 04 mov 0x4(%edx),%eax
2753a: 8b 95 30 fe ff ff mov -0x1d0(%ebp),%edx
27540: 8b 04 90 mov (%eax,%edx,4),%eax
27543: 85 c0 test %eax,%eax
27545: 0f 84 99 02 00 00 je 277e4 <_ZN9SlClass3RunEtt+0x1174>
2754b: 0f b7 57 02 movzwl 0x2(%edi),%edx
2754f: 3b 50 0c cmp 0xc(%eax),%edx
27552: 89 95 04 fe ff ff mov %edx,-0x1fc(%ebp)
27558: 0f 83 6a 01 00 00 jae 276c8 <_ZN9SlClass3RunEtt+0x1058>
2755e: 8b 40 08 mov 0x8(%eax),%eax
27561: 8b 95 04 fe ff ff mov -0x1fc(%ebp),%edx
27567: 8b 04 90 mov (%eax,%edx,4),%eax
2756a: 85 c0 test %eax,%eax
2756c: 89 85 28 fe ff ff mov %eax,-0x1d8(%ebp)
27572: 0f 84 50 01 00 00 je 276c8 <_ZN9SlClass3RunEtt+0x1058>
27578: 8b 95 28 fe ff ff mov -0x1d8(%ebp),%edx
2757e: 0f b7 47 06 movzwl 0x6(%edi),%eax
27582: 66 3b 42 06 cmp 0x6(%edx),%ax
这是我的relase版汇编代码,程序崩溃在27582行,我找的特征代码是274e5行,之所以选这个,是因为后面连续的几个代码都带有不同的数字,这个重复的概率就比较小了。这里有个地方要注意,这里是16进制,debug版文件里是10进制,需要转换一下。
-0x1c8转为-456,直接在debug版中搜索-456,找到movl %eax, -456,再搜索%eax, -456,还好,只搜到4处,然后结合下一行代码,可以定位到是哪一个。看看上下文,就定位到了源文件所在的行。
-----------------------------------------------------------------------------------------------
引用以下文章
1.一次segfault错误的排查过程,引用地址https://blog.csdn.net/zhaohaijie600/article/details/45246569
一次segfault错误的排查过程
正常运行了几年的程序忽然崩溃了,由于机器没有设置CORE文件,无法从CORE中取得错误信息,程序运行在centOS 7上, 本来对centOS用的也是不熟,只能边查资料边查问题。
首先、我需要确认程序是否真的崩溃了,还是别人误操作关闭了。如果程序真的崩溃了,会在系统中留下痕迹,我查了一下,在messages文件中发现了一条信息:
xxxxx.o[2374]: segfault at7f0ed0bfbf70 ip 00007f0edd646fe7 sp 00007f0ed3603978 error 4 inlibc-2.17.so[7f0edd514000+1b6000]
由上面信息看出,系统确实是崩溃了,发生了段错误。
查看messages需要root权限,用命令:cat /var/log/messages 就可以了,还有一个命令dmesg也可以查到上面的信息。
从上面的信息,我们可以得到以下信息:
1、从libc-2.17.so[7f0edd514000+1b6000]可以看出错误发生在libc上,libc在此程序中映射的内存基址为7f0edd514000,这个信息是个坏消息,这个so上的东西太多了;
2、segfault at和error 4这两条信息可以得出是内存