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