目录标题
- 前言
- PIE
- core dump
- 开启core dump
- backtrace
- 静态库
- 动态库
- 最后补充几句
前言
linux 程序崩溃,如果能根据已有的插桩日志能排查出来自然好,但是往往日志未全覆盖,这时候大海捞针排查起来还是比较麻烦的。
一般来说有以下这几种方法获取崩溃现场数据。
PIE
PIE (position-independent executable) 是一种生成地址无关可执行程序的技术。如果编译器在生成可执行程序的过程中使用了PIE,那么当可执行程序被加载到内存中时其加载地址存在不可预知性。
需要注意PIE的存在,一些嵌入式板子gcc版本低的话默认不开PIE,但是新版本的gcc默认开PIE的话,那backtrace也好,coredump也好生成出来的地址都是随机的,无法直接通过addr2line去解析,必须使用map函数地址+偏移量的形式去获取正常代码行号。
或者禁用pie,添加-no-pie选项。
core dump
core dump是linux原生自带的一个异常分析工具,当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中。注意程序编译 需要带 -g。
开启core dump
linux 默认是关闭的。
开启
ulimit -c unlimited
此时,运行程序发生sigsegv之类的异常时,自动生成core文件在执行文件目录下。
随便写一个简单错误代码,做测试
957: int *a = NULL;
958: *a = 6; //非法,段错误
生成core文件后,执行
gdb <可执行程序> core
随后你就可以看到,如下提示,指名在那个文件那个函数的那一行触发异常,如上为958行
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/a7_softfp_neon-vfpv4/libthread_db.so.1".
Core was generated by `./JanusFace'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0xacdd0d0c in test::testFunc (this=0x877f8550, stInfo=...) at testServer.cpp:958
958 testServer.cpp: No such file or directory.
这里的测试代码比较简单,如果是具体业务中的可能就要利用上gdb的那些功能,bt full 、查看栈帧,查看局部变量值之类的,方便具体问题具体分析。
(gdb) bt full
#0 0xacdd0d0c in test::testFunc (this=0x877f8550, stVideoViewReqInfo=...) at testServer.cpp:958
iRet = -1
a = 0x0
__PRETTY_FUNCTION__ = "void test::testFunc(VIDEO_VIEW_REQ_INFO&)"
如上所示,局部变量a = 0 为空指针 。
backtrace
有时候不方便gdb的时候,那就只能用backtrace之类的函数来辅助分析排查了
注册如sigsegv之类异常信号的异常处理函数,在处理函数中用backtrace查看调用堆栈。
void SigSegv_handler(int signo)
{
int j, nptrs;
void *buffer[BT_BUF_SIZE];
char **strings;
nptrs = backtrace(buffer, BT_BUF_SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
would produce similar output to the following: */
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);
free(strings);
exit(-1);
}
如上即可获取调用堆栈
静态库
静态库打印的信息大致如下
Dump stack start...
backtrace() returned 8 addresses
[00] ./backtrace(dump+0x1f) [0x400a9b]
[01] ./backtrace(signal_handler+0x31) [0x400b63]
[02] /lib/x86_64-linux-gnu/libc.so.6(+0x36150) [0x7f86afc7e150]
[03] ./backtrace(testFunc+0x1a) [0x400a3e]
Dump stack end...
比较简单,根据addr2line命令
addr2line -e backtrace 0x400a3e
testServer.cpp:958
动态库
动态库则略微麻烦,动态库每次加载的内存地址都不一样,所以不能跟静态库一样,不过可以根据根据map文件找到对应实际地址,编译的时候加上-Wl,-Map,entry.map。即可生成map文件。需要带 -g
动态库backtrace 打印如下。
Dump stack start...
backtrace() returned 8 addresses
[00] ./backtrace(dump+0x1f) [0x400a53]
[01] ./backtrace(signal_handler+0x31) [0x400b1b]
[02] /lib/x86_64-linux-gnu/libc.so.6(+0x36150) [0x7f8583672150]
[03]./libTest.so(_ZN13Testt11testFuncER20_VIDEO_VIEW_REQ_INFO+**0x44**) [0xace45d0c]
Dump stack end...
段错误 (核心已转储)
在map文件中搜索testFunc
0x0007fcc8 test::testFunc(_VIDEO_VIEW_REQ_INFO&)
可知在代码 0x0x0007fcc8 + 0x44处发生异常。
addr2line -e libTest.so 0x7FD0C
testServer.cpp:958
最后补充几句
mcu和linux这些处理crash基本思路都是一样的,linux里面的sigsegv这些诸如此类的异常,基本等同于mcu里面的那些hardfault,只不过handler在linux里面需要程序取做注册挂载,mcu里面只需要按照启动文件提供函数实现即可。处理逻辑也大同小异,mcu里面根据发生hardfault时的寄存器获取当时fault的现场环境数据,然后再根据ide生成的map获取现场环境做分析,linux只不过工具上更多样复杂了,gdb 、core dump 、addr2line 和backtrace这些,提供了比mcu更高级的crash现场分析。