1. Crash后 logcat中输出绿色信息:
05-02 10:14:37.130: I/DEBUG(1890): backtrace: 05-02 10:14:37.130: I/DEBUG(1890): #00 pc 00033fda /data/data/com.XXXXX.map/lib/libmapengine.so (TextureCache::_touchListNode(TextureCacheItem*)+25) 05-02 10:14:37.130: I/DEBUG(1890): #01 pc 0003407d /data/data/com.XXXXX.map/lib/libmapengine.so (TextureCache::getTexItem(char, char, int, int)+32) 05-02 10:14:37.130: I/DEBUG(1890): #02 pc 00032c9f /data/data/com.XXXXX.map/lib/libmapengine.so (prepareTiles(int, int, int, double)+158) 05-02 10:14:37.130: I/DEBUG(1890): #03 pc 000332cf /data/data/com.XXXXX.map/lib/libmapengine.so (nativePrepareRender+566) 05-02 10:14:37.130: I/DEBUG(1890): #04 pc 0002fb79 /data/data/com.XXXXX.map/lib/libmapengine.so (Java_com_XXXXX_map_gl_JNI_nativePrepareRender+192) 05-02 10:14:37.130: I/DEBUG(1890): #05 pc 0001de70 /system/lib/libdvm.so (dvmPlatformInvoke+112) 05-02 10:14:37.130: I/DEBUG(1890): #06 pc 0004d0c3 /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+394) 05-02 10:14:37.130: I/DEBUG(1890): #07 pc 000009e0 /dev/ashmem/dalvik-jit-code-cache (deleted)
2. 找到APP中对应的SO包,获取so的汇编源码
注意编译so包时需要注释mk文件中两句:
cmd-strip = $(TOOLCHAIN_PREFIX)strip --strip-all -x $1 -fvisibility=hidden
cmd-strip 是对编译符号进行过滤的脚本,-fvisibility=hidden 是隐藏jni库内部符号表
D:\android-ndk-r7c\toolchains\arm-linux-androideabi-4.4.3\prebuilt\windows\bin
下面的objdump工具,生成so包的汇编。
生成so包汇编代码的命令: arm-linux-androideabi-objdump.exe -dx libmapengine.so > temp.txt
3. 定位问题位置
如果幸运的话可以,logcat输出可以直接定位在函数,接下来要做的就是定位在错误的代码行数,注意指的是C/C++代码行 而不是汇编。
结合so的汇编和logcat输出,函数代码较短的话可以直接阅读arm汇编,函数长的话直接看汇编会很痛苦。
4. arm assemble的一些基本指令
ldr 从指定地址加载寄存器运算数,
str 将寄存器运算数存到指定地址,
add两个寄存器相加,
adds寄存器和数值相加,
mov寄存器之间赋值,
movs将数值赋给寄存器,
cmp为比较两个寄存器
比较条件判断:
b 表示无条件分支:http://sourceware.org/cgen/gen-doc/arm-thumb-insn.html#insn-b
bx lr 表示一个函数执行结束,参见【3】
5. 示例
C/C++ 源码如下:
void TextureCache::_touchListNode(TextureCacheItem* node) { if (node==NULL) { return; } // 将*item移至队尾 if(tail != node){ // 将node结点单独取出 if(head == node){ head = head->next; head->pre = NULL; } else{ // node != head && node != tail node->pre->next = node->next; node->next->pre = node->pre; // ###node->next为空,寻址pre导致CRASH### } tail->next = node; node->pre = tail; tail = node; tail->next = NULL; } }汇编代码一共28行:
00033fc0 <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem>: 33fc0: 2900 cmp r1, #0 33fc2: d012 beq.n 33fea <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x2a> 33fc4: 68c3 ldr r3, [r0, #12] 33fc6: 428b cmp r3, r1 33fc8: d00f beq.n 33fea <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x2a> 33fca: 6883 ldr r3, [r0, #8] 33fcc: 428b cmp r3, r1 33fce: d00d beq.n 33fec <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x2c> 33fd0: 694b ldr r3, [r1, #20] 33fd2: 698a ldr r2, [r1, #24] 33fd4: 619a str r2, [r3, #24] 33fd6: 698b ldr r3, [r1, #24] 33fd8: 694a ldr r2, [r1, #20] 33fda: 615a str r2, [r3, #20] 33fdc: 68c3 ldr r3, [r0, #12] 33fde: 6199 str r1, [r3, #24] 33fe0: 68c3 ldr r3, [r0, #12] 33fe2: 614b str r3, [r1, #20] 33fe4: 2300 movs r3, #0 33fe6: 60c1 str r1, [r0, #12] 33fe8: 618b str r3, [r1, #24] 33fea: 4770 bx lr 33fec: 698b ldr r3, [r1, #24] 33fee: 2200 movs r2, #0 33ff0: 6083 str r3, [r0, #8] 33ff2: 615a str r2, [r3, #20] 33ff4: e7f2 b.n 33fdc <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x1c> 33ff6: 46c0 nop (mov r8, r8)
分析:
touchListNode函数将双向链表中的node结点移至队列尾部。 r0寄存器存放整个当前对象地址,r0 + 8 为head,r0 + 12为tail r1存到函数参数node指针 指针即地址,即寄存器中的值 00033fc0 <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem>: 33fc0: 2900 cmp r1, #0 33fc2: d012 beq.n 33fea <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x2a> 第一次比较node是否为NULL,相等则直接跳至33fea行退出函数 33fc4: 68c3 ldr r3, [r0, #12] // 通过r0寄存器取tail指针 33fc6: 428b cmp r3, r1 // 比较tail和node指针 33fc8: d00f beq.n 33fea <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x2a> 第二次比较tail是否等于node,相等则直接跳至33fea行退出函数 33fca: 6883 ldr r3, [r0, #8] // 通过r0寄存器取head指针 33fcc: 428b cmp r3, r1 // 比较head和node指针 33fce: d00d beq.n 33fec <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x2c> // head!=node直接跳到33fec行 第三次比较head是否等于node,相等则直接跳至33fea行退出函数 33fd0: 694b ldr r3, [r1, #20] // r3 = r1::_pre 取node的pre指针赋给r3 33fd2: 698a ldr r2, [r1, #24] // r2 = r1::_next 取node的next指针赋给r2 33fd4: 619a str r2, [r3, #24] // r3::_next = r2 node->pre->next = node->next 33fd6: 698b ldr r3, [r1, #24] // r3 = r1::_next; 33fd8: 694a ldr r2, [r1, #20] // r2 = r1::_pre; 33fda: 615a str r2, [r3, #20] // r3::_pre = r2; node->next->pre = node->pre 33fdc: 68c3 ldr r3, [r0, #12] // r3 = tail; 取链表的tail赋给r3 33fde: 6199 str r1, [r3, #24] // r3::_next = r1 tail->next = node 33fe0: 68c3 ldr r3, [r0, #12] // r3 = tail; 取链表的tail赋给r3 33fe2: 614b str r3, [r1, #20] // r1::_pre = r3; node->pre = tail 33fe4: 2300 movs r3, #0 // reset r3 register 清零r3寄存器 33fe6: 60c1 str r1, [r0, #12] // tail = node; 将r1(node)赋给r0+12即tail 33fe8: 618b str r3, [r1, #24] // r1::_next = r3; 将r3赋给r1的next指针,此时r3等于0 33fea: 4770 bx lr // 子函数 执行结束! 33fec: 698b ldr r3, [r1, #24] // r3 = r1::_next; 33fee: 2200 movs r2, #0 // reset r2 register 33ff0: 6083 str r3, [r0, #8] // r0::_head = r3; 33ff2: 615a str r2, [r3, #20] // r3::_pre = r2; ## r2==0 ## 33ff4: e7f2 b.n 33fdc <_ZN12TextureCache14_touchListNodeEP16TextureCacheItem+0x1c> 无条件跳转到33fdc行执行 33ff6: 46c0 nop (mov r8, r8)
结合第一部分crash时堆栈顶部信息:#00 pc 00033fda ,对应汇编代码中的33fda行,通过阅读汇编代码可以知道33fda行对应C/C++源码:
node->next->pre = node->pre;
Crash原因是因为[r3, #20]寻址错误 即node->next为空并且执行node->next->pre。
1. http://sourceware.org/cgen/gen-doc/arm-thumb-insn.html
2. http://www.peter-cockerell.net/aalp/html/ch-3.html
3. http://hi.baidu.com/wuqi19881003/item/f293c7a7e228e613a8cfb756