JZ2440裸板開發練習#2 - 點亮LED程序反彙編分析

平臺:JZ2440開發板——CPU:S3C2440(ARM920)


前言

上一個練習中,使用C語言編寫了一個點亮LED的程序,本次練習將對該程序的反彙編文件做一次分析,挖掘其中的知識點。

預備知識

ARM寄存器

 

ATPCS(arm-thumb 程序調用標準)

目的

爲了讓C語言程序和彙編程序之間能夠互相調用而指定的調用規則,只要所用語言遵守該標準,它們就能相互調用且正常運行。

內容

1.寄存器使用規則

一個ARM模式下能夠使用的寄存器共有16個,爲R0-R15。

R0-R3傳遞參數用,子函數使用完後可以不用恢復原值。

R4-R11保存局部變量,如果使用到需要先保存原值,並在返回值恢復原值。

R12用作子程序的scratch寄存器,別名ip,常用來保存其他寄存器的值做入棧保存等操作,如下文中mov ip,sp的操作

R13作爲棧指針,SP,這個是所有架構包括ARM,IA-32都存在的寄存器,需要準確保存和恢復,否則會影響正常運行,也稱保持棧平衡。

R14保存返回地址,LR,bl指令會將返回值保存到該地址,常見的用法是:lr保存地址入棧保存,然後將函數退出時lr賦值到pc寄存器,達到運行到返回地址的目的。

R15,PC,程序計數器,該寄存器指向的地址爲程序即將執行的地址,根據ARM的流水線機制,三級流水線情況下,PC將指向當前正在運行指令地址+8的地址。該寄存器中還有其他位用來保存程序狀態,如溢出,進位,0等。PC不能被用在除上述描述外的功能上,與IA-32架構有差異的是,該寄存器可以被直接賦值,而IA-32只能通過jmp等指令間接修改。

2.數據棧使用規則

規定好棧的生長方向,這個一般都爲降序棧。以降序棧爲例,入棧時,sp需要db(decrease before)還是da(decrease after),出棧時sp需要ia(increase after)還是ib(increase before),這些都是需要統一化才能讓棧正常使用。

3.參數傳遞規則

函數調用中,傳遞參數使用R0-R3,參數個數超出4個時,通過棧傳遞剩下的參數,同時返回值也是使用R0-R3返回。

4.現場保護和恢復規則

函數調用時會破壞一些寄存器或者其他存儲的值,需要進行現場保護才能在函數返回後上一級函數正常運行。相應的函數退出時也要根據約定的規則恢復現場。目前採用的是子函數中需要用到的寄存器按照寄存器順序(高序寄存器先入棧,如R15)入棧,出棧時則依次退棧即可。

分析


led.elf:     file format elf32-littlearm

Disassembly of section .text:

00008074 <_start>:
    8074:	e3a00000 	mov	r0, #0	; 0x0
    8078:	e5901000 	ldr	r1, [r0]
    807c:	e5800000 	str	r0, [r0]
    8080:	e5902000 	ldr	r2, [r0]
    8084:	e1520000 	cmp	r2, r0
    8088:	e59fd00c 	ldr	sp, [pc, #12]	; 809c <.text+0x28>
    808c:	03a0da01 	moveq	sp, #4096	; 0x1000
    8090:	05801000 	streq	r1, [r0]
    8094:	eb000001 	bl	80a0 <main>    //bl跳轉main,返回地址lr=8098

00008098 <halt>:
    8098:	eafffffe 	b	8098 <halt>
    809c:	40001000 	andmi	r1, r0, r0

000080a0 <main>:
    80a0:	e1a0c00d 	mov	ip, sp
    80a4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc} 
    80a8:	e24cb004 	sub	fp, ip, #4	; 0x4
    80ac:	e24dd008 	sub	sp, sp, #8	; 0x8
    80b0:	e3a03456 	mov	r3, #1442840576	; 0x56000000
    80b4:	e2833050 	add	r3, r3, #80	; 0x50
    80b8:	e50b3010 	str	r3, [fp, #-16]
    80bc:	e3a03456 	mov	r3, #1442840576	; 0x56000000
    80c0:	e2833054 	add	r3, r3, #84	; 0x54
    80c4:	e50b3014 	str	r3, [fp, #-20]
    80c8:	e51b2010 	ldr	r2, [fp, #-16]
    80cc:	e51b3010 	ldr	r3, [fp, #-16]
    80d0:	e5933000 	ldr	r3, [r3]
    80d4:	e3c33c3f 	bic	r3, r3, #16128	; 0x3f00
    80d8:	e5823000 	str	r3, [r2]
    80dc:	e51b2010 	ldr	r2, [fp, #-16]
    80e0:	e51b3010 	ldr	r3, [fp, #-16]
    80e4:	e5933000 	ldr	r3, [r3]
    80e8:	e1e03003 	mvn	r3, r3
    80ec:	e2033c15 	and	r3, r3, #5376	; 0x1500
    80f0:	e1e03003 	mvn	r3, r3
    80f4:	e5823000 	str	r3, [r2]
    80f8:	e51b2014 	ldr	r2, [fp, #-20]
    80fc:	e51b3014 	ldr	r3, [fp, #-20]
    8100:	e5933000 	ldr	r3, [r3]
    8104:	e3c33070 	bic	r3, r3, #112	; 0x70
    8108:	e5823000 	str	r3, [r2]
    810c:	e3a03000 	mov	r3, #0	; 0x0
    8110:	e1a00003 	mov	r0, r3
    8114:	e24bd00c 	sub	sp, fp, #12	; 0xc
    8118:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:

00000000 <.comment>:
   0:	43434700 	cmpmi	r3, #0	; 0x0
   4:	4728203a 	undefined
   8:	2029554e 	eorcs	r5, r9, lr, asr #10
   c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}
  10:	Address 0x10 is out of bounds.

反彙編代碼如上,假設本次以nandflash方式啓動,則sp被設置爲4096。啓動文件bl main跳轉到C程序中的main函數,此時lr寄存器保存了返回的地址8098。

進入到main後,首先保存需要保存現場,將需要用到的寄存器入棧保存

 80a0:	e1a0c00d 	mov	ip, sp                      //fp=? ip=4096 sp=4096 lr=8098 pc=80A8
 80a4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp <-sp

然後將fp調整到保存現場的首地址,根據後面fp的使用情況,fp寄存器我認爲是和IA-32架構的EBP類似作用的寄存器,作爲棧幀的基準,在此基礎上添加偏移值來尋址棧幀中的任意變量。而後sp - 8是彙編中常見的爲局部變量開闢內存,這點在IA-32架構也是一樣的,對應到C程序是我們指向兩個寄存器的指針。

80a8:	e24cb004 	sub	fp, ip, #4	; 0x4           //fp = ip - 4 =4096-4 =4092
80ac:	e24dd008 	sub	sp, sp, #8	; 0x8           //sp =sp -8 = 4072  //兩個int 局部變量
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp  
4076        
4072       <-sp

開闢完內存後,以fp爲基準尋址到剛開闢內存的地址處,賦值寄存器值

    80b0:	e3a03456 	mov	r3, #1442840576	; 0x56000000//r3=0x56000000
    80b4:	e2833050 	add	r3, r3, #80	; 0x50          //r3=0x56000050
    80b8:	e50b3010 	str	r3, [fp, #-16]              //fp - 16 = 4092 - 16 = 4076
    80bc:	e3a03456 	mov	r3, #1442840576	; 0x56000000
    80c0:	e2833054 	add	r3, r3, #84	; 0x54
    80c4:	e50b3014 	str	r3, [fp, #-20]
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp  
4076   0x56000050    
4072   0x56000054   <-sp

接着是一段對局部變量處理的程序,對應着C程序可以很清晰地看懂,沒有什麼知識點,因此不做說明。

    
    //*pGPFCON &= ~((3<<8) | (3<<10) | (3<<12));
    80c8:	e51b2010 	ldr	r2, [fp, #-16]      
    80cc:	e51b3010 	ldr	r3, [fp, #-16]      
    80d0:	e5933000 	ldr	r3, [r3]            
    80d4:	e3c33c3f 	bic	r3, r3, #16128	; 0x3f00       
    80d8:	e5823000 	str	r3, [r2]            

    //*pGPFCON |= ~((1<<8) | (1<<10) | (1<<12));
    80dc:	e51b2010 	ldr	r2, [fp, #-16]      
    80e0:	e51b3010 	ldr	r3, [fp, #-16]      
    80e4:	e5933000 	ldr	r3, [r3]            
    80e8:	e1e03003 	mvn	r3, r3              
    80ec:	e2033c15 	and	r3, r3, #5376	; 0x1500
    80f0:	e1e03003 	mvn	r3, r3
    80f4:	e5823000 	str	r3, [r2]            

    //*pGPFDAT &=~((1<<4) | (1<<5) | (1<<6));
    80f8:	e51b2014 	ldr	r2, [fp, #-20]
    80fc:	e51b3014 	ldr	r3, [fp, #-20]
    8100:	e5933000 	ldr	r3, [r3]
    8104:	e3c33070 	bic	r3, r3, #112	; 0x70
    8108:	e5823000 	str	r3, [r2]

最後是返回值的賦值,根據ATPCS可知,此處使用R0寄存器作爲返回值存儲的寄存器,返回值爲0,此處反彙編代碼經過R3作爲中介沒有什麼特殊的意義,如果編譯器智能點應該會把此處優化掉。

    810c:	e3a03000 	mov	r3, #0	; 0x0
    8110:	e1a00003 	mov	r0, r3

至此,main函數執行結束,準備返回到lr指向的地址處,此時需要把棧保存的值恢復到寄存器中,同時恢復sp。首先將分配給局部變量的棧空間回收,此處回收僅僅是將sp指向分配局部變量地址的上端,而非堆內存那樣做相應的回收操作,因爲下次需要使用到該內存時,該地址必然會被覆蓋,因此不需要做其他處理。

    8114:	e24bd00c 	sub	sp, fp, #12	; 0xc   //局部變量退棧
stack address   value comment  
4092   0x80A8 pc  
4088   0x8098 lr  
4084   4096 ip=old sp  
4080   unknowvalue fp <-sp
4076   0x56000050    
4072   0x56000054    

最後ldmia對應開頭的stmdb,將棧中存放的數據恢復到{}中的寄存器中。對比上個棧表可以看到,棧中保存的fp會恢復到寄存器fp,ip保存着舊sp的值,由此sp恢復到main調用前的sp,最後存放的lr保存的是返回地址,剛好恢復到pc寄存器,因此下一條執行的指令pc指向的是返回值地址,main函數正常退出。(ldmia涉及對sp的賦值,但是sp仍舊會ia繼續正常運行,這是因爲指令中ia操作是對sp的tmp做操作的,具體可以查看 https://blog.csdn.net/G_METHOD/article/details/104126283 中對於ldm的描述)

    8118:	e89da800 	ldmia	sp, {fp, sp, pc}
stack address   value comment  
4092   0x80A8 pc <-sp
4088   0x8098 lr   ======》pc  
4084   4096 ip=old sp===》sp  
4080   unknowvalue fp=======》fp  
4076   0x56000050    
4072   0x56000054    
發佈了13 篇原創文章 · 獲贊 7 · 訪問量 6057
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章