Android NDK中結合彙編分析Crash行為
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