Native Crash是指,在用户空间的C/C++代码发生的abort、段错误、指令异常等crash问题。如果Native Crash发生在应用APK,则导致应用异常崩溃闪退,如果发生在系统关键进程则导致Android 系统重启。
Native crash问题相对比java crash问题更难分析和定位。Native Crash问题的分析主要依赖Android tombstone 和corefile 等日志信息
Native Crash问题分类
根据产生native crash的信号分类,常见native crash可分为以下几类:
信号类型 |
说明 |
产生原因 |
SIGSEGV (signal 11) |
段错误,非法内存访问 |
越界访问、写只读的内存块、野指针、double free、空指针 |
SIGABRT (signal 6) |
主动终止程序运行,通常是检查失败后主动的退出执行的程序 |
程序主动判断 |
SIGBUS (signal 7) |
非法地址, 包括内存地址对齐(alignment)出错 |
内存未对齐 |
SIGILL (signal 4) |
指令异常 |
ddr/cache不一致或指令兼容性问题 |
SIGFPE (signal 8) |
算术运算错误(除0错误) |
除0操作 |
SIGTRAP (signal 5) |
断点指令或其它trap指令产生,用于debuger |
通常是gdb在线调试时产生。部分app使用brk 指令产生。 |
Native Crash的分析依赖Android tombstone 和corefile 日志。
对于Userdebug版本,发生native crash时会生成tombstone和corefile。分别在/data/tombstones/和/data/corefile/目录。而如果是user版本仅生成tombstone,不会生成corefile日志。
分析tombstone和corefile都需要有发生问题对应版本的symbols。Android项目工程在编译过程中产生的symbols存放在:out/target/product/sc***/symbols目录。
在分析tombstone和corefile时会用到以下命令行工具(注意:32位和64位使用的工具是不一样的):
Log 文件 |
工具名 |
常用命令参数 |
tombstone |
arm-linux-eabi-add2line aarch64-linux-android-add2line arm-linux-eabi-objdump aarch64-linux-android-objdump |
arm-linux-androideabi-add2line–f –e /symbols/system/lib/libc.so arm-linux-androideabi-objdump -D /symbols/system/lib/libc.so |
corefile |
arm-linux-eabi-gdb aarch64-linux-android-gdb |
见gdb常用命令及coredump案例分析 |
上述工具在工程目录:
32位工具:prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin
64位工具:prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin
得到symbol库的位置解析:arm-linux-androideabi-addr2line -C -f -e symbols/system/lib/libcampm.so 00011248
实例讲解,最近出我分析的一个列子,现了一个客户的crash,具体log如下:
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xac0bf77a
r0 ab68fff0 r1 ab57f6ac r2 ab8e55ec r3 ac0bf74a
r4 00000000 r5 00004028 r6 ab8e5010 r7 ab8e5610
r8 ab8e5010 r9 00000025 r10 ab86e95c r11 ab8e5000
ip adb571bc sp abe08cd0 lr adb4aa95 pc adb51248
backtrace:
#00 pc 00011248 /vendor/lib/libcampm.so (_pm_saturation_init+20) (BuildId: 0d7cf1bba471f9510a6aba0c2d2783c7)
#01 pc 0000aa93 /vendor/lib/libcampm.so (isp_pm_set_mode+430) (BuildId: 0d7cf1bba471f9510a6aba0c2d2783c7)
#02 pc 0000981b /vendor/lib/libcampm.so (isp_pm_param_init_and_update+2422) (BuildId: 0d7cf1bba471f9510a6aba0c2d2783c7)
#03 pc 00008ddd /vendor/lib/libcampm.so (isp_pm_init+144) (BuildId: 0d7cf1bba471f9510a6aba0c2d2783c7)
#04 pc 0001d75b /vendor/lib/libcamdrv.so (isp_alg_fw_init+226) (BuildId: 106305b58b0c45e9b423271baa7b2d6f)
#05 pc 000261e5 /vendor/lib/libcamdrv.so (isp_init+108) (BuildId: 106305b58b0c45e9b423271baa7b2d6f)
#06 pc 0002e283 /vendor/lib/libcamoem.so (camera_local_int+2766) (BuildId: 25597d5ee348da4a9a05d6e431616398)
#07 pc 0002505b /vendor/lib/libcamoem.so (camera_init+70) (BuildId: 25597d5ee348da4a9a05d6e431616398)
首先signal 11肯定是内存相关的错误,然后通过命令解析
./arm-linux-androideabi-objdump -S libcampm.so > libcampm.txt
00011234 <_pm_saturation_init@@Base>:
11234: b570 push {r4, r5, r6, lr}
11236: b086 sub sp, #24
11238: 6993 ldr r3, [r2, #24]
1123a: 2400 movs r4, #0
1123c: 6003 str r3, [r0, #0]
1123e: 6e0b ldr r3, [r1, #96] ; 0x60
11240: 5ccb ldrb r3, [r1, r3]
11242: 6043 str r3, [r0, #4]
11244: 6e4b ldr r3, [r1, #100] ; 0x64
11246: 440b add r3, r1
11248: f893 3030 ldrb.w r3, [r3, #48] ; 0x30
1124c: 6083 str r3, [r0, #8]
1124e: 6e0b ldr r3, [r1, #96] ; 0x60
11250: 60c3 str r3, [r0, #12]
11252: 6e4b ldr r3, [r1, #100] ; 0x64
11254: 6103 str r3, [r0, #16]
11256: f811 c004 ldrb.w ip, [r1, r4]
1125a: eb00 0e04 add.w lr, r0, r4
1125e: 190b adds r3, r1, r4
11260: f88e c014 strb.w ip, [lr, #20]
11264: 3401 adds r4, #1
11266: f893 3030 ldrb.w r3, [r3, #48] ; 0x30
1126a: 2c30 cmp r4, #48 ; 0x30
1126c: f88e 3044 strb.w r3, [lr, #68] ; 0x44
11270: d1f1 bne.n 11256 <_pm_saturation_init@@Base+0x22>
然后,很可惜,使用aarch64-linux-android-addr2line解析地址C语言出不来,那没办法只能分析
汇编语句,_pm_saturation_init+20的地址应该为0x11234+0x14,这样出问题的地方应该在
11248: f893 3030 ldrb.w r3, [r3, #48] ; 0x30,而这句的对应该的代码应该通过函数分析
cmr_s32 _pm_saturation_init(void *dst_csa_param, void *src_csa_param, void *param1, void *param_ptr2)
{
cmr_s32 rtn = ISP_SUCCESS;
cmr_u32 i = 0, j = 0;
struct isp_chrom_saturation_param *dst_csa_ptr = (struct isp_chrom_saturation_param *)dst_csa_param;
struct sensor_saturation_param *src_csa_ptr = (struct sensor_saturation_param *)src_csa_param;
struct isp_pm_block_header *csa_header_ptr = (struct isp_pm_block_header *)param1;
UNUSED(param_ptr2);
dst_csa_ptr->cur.bypass = csa_header_ptr->bypass;
dst_csa_ptr->cur.factor_u = src_csa_ptr->csa_factor_u[src_csa_ptr->index_u];
dst_csa_ptr->cur.factor_v = src_csa_ptr->csa_factor_v[src_csa_ptr->index_v];
dst_csa_ptr->cur_u_idx = src_csa_ptr->index_u;
dst_csa_ptr->cur_v_idx = src_csa_ptr->index_v;
for (i = 0; i < SENSOR_LEVEL_NUM; i++) {
dst_csa_ptr->tab[0][i] = src_csa_ptr->csa_factor_u[i];
dst_csa_ptr->tab[1][i] = src_csa_ptr->csa_factor_v[i];
}
for (i = 0; i < MAX_SCENEMODE_NUM; i++) {
dst_csa_ptr->scene_mode_tab[0][i] = src_csa_ptr->scenemode[0][i];
dst_csa_ptr->scene_mode_tab[1][i] = src_csa_ptr->scenemode[1][i];
}
csa_header_ptr->is_update = ISP_ONE;
return rtn;
}
开始分析汇编和C语言对应起来,首先,有两个for循环,那肯定有调整指令,
1125a: eb00 0e04 add.w lr, r0, r4,///显然这个是第一个跳转指令,
那这个问题发生在第一个for循环之前,然后函数前面都是赋值,汇编没有操作,那这个
11238: 6993 ldr r3, [r2, #24]对于的C语言语句为csa_header_ptr->bypass;
1123c: 6003 str r3, [r0, #0] 对于的C语言语句为dst_csa_ptr->cur.bypass =
1123e: 6e0b ldr r3, [r1, #96] ; 0x60对于的C语言语句为src_csa_ptr->index_u
11240: 5ccb ldrb r3, [r1, r3] 对于的C语言语句为src_csa_ptr->csa_factor_u[src_csa_ptr->index_u];
11242: 6043 str r3, [r0, #4] 对于的C语言语句为dst_csa_ptr->cur.factor_u
11244: 6e4b ldr r3, [r1, #100] ; 0x64对于的C语言语句为src_csa_ptr->index_v
11246: 440b add r3, r1对于的C语言语句为src_csa_ptr->csa_factor_v[src_csa_ptr->index_v];
11248: f893 3030 ldrb.w r3, [r3, #48] ; 0x30这行语句显然就挂了,也就是r3再偏移48存在地址越界。
所以其实在11244: 6e4b ldr r3, [r1, #100] ; 0x64对于的C语言语句为src_csa_ptr->index_v这里就已经是野指针了。所以最后通过代码发现是参数的数据太少了数据导致crash.