最有效的一种方法,可以在程序挂断之后通过core文件定位程序错误的地方,而且不影响程序运行。需要以下三个步骤。
如何判断一个可执行是否加了参数-g编译出来的?
readelf -S sonodebug |grep debug,若有输出则是加了-g编译的
ps -ef|grep a.out#查询到进程号
cat /proc/pid/limits|grep "Max core file size"#为0则不运行
Max core file size 字段的Soft Limit和Hard Limit值,才是标明了该进程是否真的能生成core文件。
gdb ./aout core.file
gdb调试core文件的一些常用命令:
(gdb)bt //这个命令会列出程序崩溃时的堆栈信息,一层一层会有标号 #0 #1 #2 ....
(gdb)f N //其中N是项查看的层数,在bt命令里会有打印
(gdb)info args//打印当前函数的参数以及其值
(gdb)info locals//打印所有的局部变量
(gdb)info catch//打印当前函数的异常处理信息
(gdb)p var//打印具体的某一个变量值
gdb+core文件的方法,可以定位到常见的绝大数错误。但也存在一些不好解决的问题:
gdb ./a.out core.out
……………… ………………
Program terminalted with signal 11, segmentation fault.
#0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:1658
1658 movdqu -0x40(%rsi),%xmm4
(gdb) bt
#0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:1658
#1 0x0000000000000000 in ?? ()
(gdb) quit
/var/log/message 中记录了程序的异常退出,dmesg也可以列出程序的段错误。dmesg + grep a.out 可以查出程序是否发生了segment fault。
dmesg中有哪些有用信息呢?它可以告诉我们程序的内存错误类型和错误位置。以下是可执行文件sowithdebug段错误之后,用dmesg |grep sowithdebug获得的错误信息。
sowithdebug[6847]: segfault at 28 ip 00000000004008cc sp 00007fff016d9400 error 4 in sowithdebug[400000+2000]
其中的ip后面的数字是错误的行数(4008cc), in 后面的是错误的文件(sowithdebug)。然后用objdump可以查错误的汇编代码,用addr2line可以定位到具体的行数。
dmesg |grep sowithdebug #查询错误文件及行数
objdump -d sowithdebug |grep 4008cc #查询汇编指令
addr2line -e sowithdebug 4008c #定位行数
error 4是错误的类型,表示用户态程序读操作访问越界。
error后边数字的含义:
bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
dmesg的不足之处:
要想清楚进程此刻正在忙什么,pstack是一个很好的工具。它可以告诉我们各线程的函数调用情况。
pstack PID #PID既可以是进程号也可以是线程号
当pstack加进程号的时候,它会打印出所有的线程运行情况。如果加线程号,就打印此线程的函数调用情况。
其中的LWP就是线程号。
pstack的弊端:
只是进程的一个静态照片,不能把握程序的整体运行流程。多次执行pstack,或者结合watch -n 0.1,通过“高频率的快照近似看视频”的方式,掌握程序的运行情况。但watch的最高频率是0.1s。
如果说pstack是给各线程照了一张照片,那么strace就是给各线程录视频。
strace可以实时动态的查看程序的运行情况,使用起来也很方便。
ps -ef|grep a.out #查出进程的PID
strace -p PID #更踪此进程的运行(既可以跟踪进程PID,也可以是线程PID)
strace 也可以直接运行程序:
strace -f -F -o out.log a.out #a.out是可执行文件,out.log是运行结果
其中的 -f , -F 告诉strace同时跟踪fork和vfork出来的进程,否则strace只会跟踪主线程。
strace的弊端:
它打印的都是一些系统调用,需要对这些系统调用有比较熟悉的掌握,才能清楚strace告诉我们的到底是什么。
这篇博文很精彩的介绍了strace的一些使用方法,很巧妙的解决一些问题:《strace命令详解》
valgriind是一个代码动态检测工具,它是一个大的工具集,有几个小组件。其中比较常用的是memcheck和helgrind。
valgrind --leak-check=full --show-reachable=yes --trace-children=yes --log-file=./valgrind.log ./a.out
–leak-check=full指的是完全检查内存泄漏,–show-reachable=yes是显示内存泄漏的地点,–trace-children=yes是跟入子进程。
以上介绍的我遇到的gdb+core出现的不能定位问题,gdb只仍给我一行又一行的问号问题,就是用valgrind的找到原因的。用以上参数运行程序,最后的日志会非常庞大而杂乱,可以重点关注其中的内存写(write)错误,程序断掉一般都是这些错误导致的。
valgrind --tool=helgrind --trace-children=yes ./a.out
它可以检测到大部分的共享资源竞争。错误类型也分的比较细致。
killall memcheck- #先用top看一下valgrind实际运行的进程名称,killall杀死
killall 默认发送SIGTERM(15),如果用kill -9 直接杀死 memcheck是不能生成内存报告的
用valgrind定位程序问题的弊端:
c++程序的异常定位,是一个非常复杂的问题,写出没有bug的c++代码,也是一项几乎不可能完成的任务。掌握排错工具和方法,就是一个非常重要的技能。后续实践中又好的排错工具和方法,会继续更新本文。