1. 程式人生 > >Android NDK中結合彙編分析Crash行為

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。


6. 參考:

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