例子
void crash_test_func() {
int* ptr = nullptr;
*ptr = 99;
}
void Net::initEngine(const char* data) {
crash_test_func();
}
以上程序,对一个空指针解引用做赋值操作,会抛出一个S异常。
抓取crash 日志
对于SIGSEGV等错误,安卓ndk都会对这些错误进行映射捕获,并且打印在logcat里。
adb logcat *:F
抓取全部 fault等级的日志也就是 crash的日志,自己可以按需自行添加tag 进行过滤。 抓取日志如下(部分)
--------- beginning of system
--------- beginning of main
--------- beginning of crash
12-02 16:55:40.806 4714 4714 F libc : Fatal signal 4 (SIGILL), code 1 (ILL_ILLOPC), fault addr 0x81f98308 in tid 4714 (posetracksample), pid 4714 (posetracksample)
12-02 16:55:40.871 4983 4983 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-02 16:55:40.871 4983 4983 F DEBUG : Build fingerprint: 'Droidlogic/w400/w400:9/PPR1.180610.011/20210407:userdebug/test-keys'
12-02 16:55:40.871 4983 4983 F DEBUG : Revision: '0'
12-02 16:55:40.871 4983 4983 F DEBUG : ABI: 'arm'
12-02 16:55:40.871 4983 4983 F DEBUG : pid: 4714, tid: 4714, name: posetracksample >>> com.orbbec.posetracksample <<<
12-02 16:55:40.871 4983 4983 F DEBUG : signal 4 (SIGILL), code 1 (ILL_ILLOPC), fault addr 0x81f98308
12-02 16:55:40.871 4983 4983 F DEBUG : r0 00000061 r1 006fe152 r2 00000011 r3 00000000
12-02 16:55:40.871 4983 4983 F DEBUG : r4 bbc2c0e8 r5 bbc2c0b0 r6 a1e28d8c r7 8584abc0
12-02 16:55:40.871 4983 4983 F DEBUG : r8 8584ab30 r9 a1e28d8c r10 00000030 r11 bbc2c0d0
12-02 16:55:40.871 4983 4983 F DEBUG : ip 80b00000 sp bbc2c0a0 lr 81f8bf50 pc 81f98308
12-02 16:55:40.999 4983 4983 F DEBUG :
12-02 16:55:40.999 4983 4983 F DEBUG : backtrace:
12-02 16:55:40.999 4983 4983 F DEBUG : #00 pc 000be308 /data/app/com.tt.posetracksample-eOLYiNeTZs8obQCsIZ24Ow==/lib/arm/libobt.so
12-02 16:55:40.999 4983 4983 F DEBUG : #01 pc 000bab18 /data/app/com.tt.posetracksample-eOLYiNeTZs8obQCsIZ24Ow==/lib/arm/libobt.so
12-02 16:55:40.999 4983 4983 F DEBUG : #02 pc 000aeba0 /data/app/com.tt.posetracksample-eOLYiNeTZs8obQCsIZ24Ow==/lib/arm/libobt.so (obt_initialize+580)
日志分析
在日志里面,有价值的是backtrace 部分,他会将崩溃现场的函数调用堆栈保存下来了。 由日志可知,执行到libobt.so 的 0x000be308处抛出了一个SIGILL错误,由#01 处的 地址0x000bab18进行调用,依次类推。
根据这个地址我们通过addr2line(linux 自带)就可以知道具体对应代码在哪。
addr2line -e libobt.so 000be308
/mnt/library/shanrx/src/net.cpp:174
这里有几点需要注意:
- 一般来说我们的so库都是release版本的,他将调试信息去除了,根据这些调试信息,这些调试信息存储着地址及代码行号的对应关系;直接addr2line无法显示
addr2line -e libobt.so 000be308
??:?
对于这个问题也很好解决,在cmakelist添加编译选项-g即可
add_compile_options(-g)
再对这些新生成的动态库进行addr2line即可,-g 并不会修改代码地址,只是附加上了一些调试信息。
- 由于是release版本,编译器优化会让函数的调用并不是一定按照代码进行,例如以上第二层栈针的地址为0x000bab18,但转出来的地址是并不是net.cpp:179,而是更上一层函数调用,也就是crash_test_func并没有做函数调用,而是内联优化了。
addr2line -e libobt.so 000bab18
/mnt/library/shanrx/src/model.cpp:45
写到最后
虽然例子里面写的很简单,实际工作里面遇到的crash不会像例子这么一目了然,但知道crash在那一行多多少少能给开发人员解决bug给提供些许思路,方便定位问题。这也算是不需要另外额外成本的解决方法。
|