目录标题

  • 前言
  • 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 默认是关闭的。

centos crash 日志 linux crash日志路径_c++

开启

ulimit -c unlimited

centos crash 日志 linux crash日志路径_centos crash 日志_02

此时,运行程序发生sigsegv之类的异常时,自动生成core文件在执行文件目录下。

centos crash 日志 linux crash日志路径_c++_03

随便写一个简单错误代码,做测试

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现场分析。