ARM芯片詳解翻譯
RISC處理器被廣泛應(yīng)用在小型設(shè)備上,例如PDA,移動電話,智能熱水器等。有很多關(guān)于RISC處理器的匯編程序,但最常見的還是ARM。
下面我要談的是ARM7,因為我研究的是這個。
讓我們先了解一下ARM的架構(gòu)。ARM處理器包含37個寄存器:31個通用的32位寄存器,以及6個狀態(tài)寄存器。寄存器的設(shè)置取決于處理器狀態(tài)。ARM狀態(tài)執(zhí)行32位指令,Thumb狀態(tài)執(zhí)行16位指令集。
在ARM狀態(tài),有18個寄存器可用:可供直接存儲的R0―R15,CPSR(當(dāng)前程序狀態(tài)寄存器),SPSR(被存儲程序狀態(tài))。其中3個可直接存儲器被稱為服務(wù)寄存器。
(R13)SP??堆棧指針
(R14)LR??連接寄存器,用來存儲調(diào)用過程的函數(shù)地址(譯注:可簡單理解為過程返回地址)。并且,LR并非存儲在堆棧中-它存在于寄存器中。
(R15)PC??當(dāng)前指令指針。用一般的mov指令就可以改變它的值,從而執(zhí)行它所指向的命令。
在Thumb狀態(tài),有13個寄存器可用:R0-R8,R13-R15,CPSR,SPSR
狀態(tài)的改變,不會影響寄存器內(nèi)容的變化。
如果想進入Thumb狀態(tài),可以先將操作寄存器的狀態(tài)位設(shè)為1(bit1),然后執(zhí)行BX指令。如果想進入ARM(譯注:原文誤為APM)狀態(tài),可以先將操作寄存器的狀態(tài)位設(shè)為0(bit0),然后執(zhí)行BX指令。
2種狀態(tài)的指令集是不同的,但是很多指令都是類似的。Thumb指令集長度為2bytes,ARM-4bytes。關(guān)于2種狀態(tài)指令的具體資料可以參考:http://www.atmel.com/dyn/resources/p...ts/doc0673.pdf
有趣的是很多指令可以同時操作多個寄存器。例如:
ADDR3,SP,#4相當(dāng)于: R3:=SP+4
或者,用來存儲寄存器入棧的指令:
PUSH{R2-R4,R7,LR} 這和x86匯編里面的pushad指令不同,在ARM匯編里面,這種將寄存器存入堆棧的方式是可行的。
內(nèi)存中,數(shù)據(jù)存儲方式可以是低位存儲(例如Intel寄存器)或者高位存儲(例如Motorola寄存器)。所以,寫代碼時候,有必要指明數(shù)據(jù)存放方式。
下面是一些ARM編譯器的資料:
http://heanet.dl.sourceforge.net/sou...de-arm-win.exe-GNUcompilerwithallconsequences-allthroughcommandline+debuggingthroughgdb.
http://www.goldroad.co.uk/grARM.html-unpretentiousARMassembler.
http://www.arm.com/support/downloads/index.html-officialtoolsforARM’sdevelpment.Hereyoucanonlybuythem.
http://www.iar.com/-alternativetoIDAforARM.30-daystrialversionisoffered.
下面講解一下由C++的ARM編譯器生成的ARM匯編程序。
一般地,分析不同程序的時候,經(jīng)常碰到的并不是純粹的匯編語言,而是由C++編譯器生成的代碼。當(dāng)然,x86匯編程序員一般不會如此。
函數(shù)調(diào)用:
這里不存在函數(shù)參數(shù)調(diào)用約定(例如cdecl,stdcall等)!所有的函數(shù)調(diào)用約定類似于Borland的fastcall。參數(shù)由寄存器傳入,如果數(shù)目不夠,由堆棧傳入。
例如:
ROM:0001F4E2MOVR0,SP
ROM:0001F4E4MOVR2,*6
ROM:0001F4E6ADDR1,R4,*0
ROM:0001F4E8BLmemcmp
參數(shù)的傳遞順序?qū)?yīng)于寄存器編號,R0為第一個,R1為第二個,R2為第三個(譯注:比較有意思)。相當(dāng)于:
intmemcmp(
constvoid*buf1,
constvoid*buf2,
size_tcount
);
buf1=R0
buf2=R1
count=R2
函數(shù)返回值被存放在R0中:
ROM:0001F4E2MOVR0,SP
ROM:0001F4E4MOVR2,*6
ROM:0001F4E6ADDR1,R4,*0
ROM:0001F4E8BLmemcmp
ROM:0001F4ECCMPR0,*0
ROM:0001F4EEBNEloc_1F4F4
下面是一個利用堆棧傳遞參數(shù)的例子:
ROM:000BCDECMOVR2,*0
ROM:000BCDEESTRR2,[SP]
ROM:000BCDF0MOVR2,*128
ROM:000BCDF2MOVR3,*128
ROM:000BCDF4MOVR1,*14
ROM:000BCDF6MOVR0,*0
ROM:000BCDF8BLFillBoxColor
上面,R0-R3存儲坐標(biāo),第5個參數(shù)(色彩)被存放在堆棧中。
只有通過分析才可以確定操作數(shù)的數(shù)目。我們可以分析函數(shù)和它的調(diào)用部分。有時候,參數(shù)信息可以通過對寄存器和堆棧的操作觀察出來。例如,在Thumb狀態(tài)下,程序?qū)Γ遥埃遥泛头?wù)寄存器的操作。所以,如果看到類似于下面的代碼:
ROM:00059ADAgetTextBounds
ROM:00059ADAPUSH{R4-R7,LR},
可以認(rèn)為它的參數(shù)被存放在R0,R1,R2,R3和SP。如果見到:
ROM:0005924EADDR0,SP,*0x14
ROM:00059250ADDR1,SP,*0x6C
ROM:00059252ADDR2,SP,*0x68
ROM:00059254ADDR3,SP,*0x64
ROM:00059256BLgetTextBounds
我們看到只有R0-R3被使用,就是說只有4個參數(shù)被傳遞過來。
轉(zhuǎn)移(Transitions)
一般,轉(zhuǎn)移分為條件轉(zhuǎn)移和無條件轉(zhuǎn)移。轉(zhuǎn)移目標(biāo)可以存放在寄存器或者其他處。寄存器轉(zhuǎn)移一般用于Thumb/ARM狀態(tài)轉(zhuǎn)換。無條件短轉(zhuǎn)移指令為B(branch)命令。長跳轉(zhuǎn)指令-BX(交換轉(zhuǎn)移)。函數(shù)調(diào)用采用BL(連接轉(zhuǎn)移),且調(diào)用時將返回地址存入LR寄存器。當(dāng)然,改變PC寄存器內(nèi)容也可以改變轉(zhuǎn)移地址:
ADDPC,*0x64
但是C編譯器通常不這樣處理,它們在轉(zhuǎn)移的時候,只是以寫入命令改變PC寄存器。
分支(Branches)
也稱為轉(zhuǎn)換,一般用法如下:
ROM:0027806ECMPR2,*0x4D;M
ROM:00278070BCSloc_27807A
ROM:00278072ADRR3,word_27807C
ROM:00278074ADDR3,R3,R2
ROM:00278076LDRHR3,[R3,R2]
ROM:00278078ADDPC,R3
ROM:0027807A
ROM:0027807Aloc_27807A
ROM:0027807ABloc_278766
ROM:0027807Cword_27807CDCW0xAA,0xBE,0xC6,0x180,0x186;0
ROM:0027807CDCW0x190,0x1A0,0x1A8,0x1DE,0x1E4;5
ROM:0027807CDCW0x1B0,0x212,0x276,0x1FE,0x294;10
首先,檢查跳轉(zhuǎn)標(biāo)記,該標(biāo)記必須小于0x40,如果大于,則跳到默認(rèn)處理位置,即:loc_27807A。
然后執(zhí)行位于word_27807C的轉(zhuǎn)移控制表。這個表里面存放的是偏移,并非地址。隨后,根據(jù)跳轉(zhuǎn)標(biāo)記,取表中的偏移,擴展之,加操作放入PC寄存器。比如,如果跳轉(zhuǎn)標(biāo)記為0,將會跳轉(zhuǎn)到地址:
0x278078(currentvaluePC)+0xAA(offsetfromthetable)+0x4(!!!)=0x278126
之所以加4,是因為ARM處理器的特征:操作PC寄存器時,其值應(yīng)該比預(yù)先確定的數(shù)值大4(在文檔“toensureitiswordaligned”中有說明)。
內(nèi)存存取
在Thumb狀態(tài),處理器可以存取+/-256字節(jié)的空間。因此,無法直接存取內(nèi)存,而需要利用寄存器來引導(dǎo)。也就是無法直接定位到0x974170,而需要采用寄存器。例如:
ROM:00277FF6LDRR0,=unk_974170
ROM:00277FF8LDRR0,[R0]
我們獲得了0x974170處的數(shù)據(jù),但是事情還沒有結(jié)束!該有效地址(0x974170)處于有效的正負256字節(jié)中:
ROM:00278044off_278044DCDunk_974170
這樣,就是說,LDR指令的機器碼中存儲了該命令當(dāng)前的地址。(譯注:就是說0x974170雖然看起來比較大,實際上還是那+-256字節(jié)內(nèi),只不過通過LDR指令來定位)
這里存在一個很藝術(shù)的優(yōu)化方法:如果一個地址和該函數(shù)中另外一個被用到的地址有關(guān)聯(lián),那么這個地址可以通過算術(shù)運算指令或者間接存取來獲取。舉例來說,如果一個函數(shù)需要用到0x100000處的變量,并且需要用到0x100150處的另外一個變量,那么,編譯器可以將這2個變量建立關(guān)聯(lián),或者采用以下代碼:
LDRR0,=0x100000
ADDR0,*0xFF
ADDR0,*0x51
LDRR0,[R0]
在x86里面,這種方法應(yīng)用于結(jié)構(gòu)中獲取子結(jié)構(gòu)接口。但是此處,卻是一個常用的優(yōu)化,這有什么好處呢?可以減小內(nèi)存存儲,并且算術(shù)運算比數(shù)據(jù)加載快得多??梢哉J(rèn)為整個ARM匯編程序充滿了不同的寄存器間算術(shù)運算。事實上,有多達16個寄存器用來進行此操作-減少內(nèi)存和堆棧定位頻度。因此,只有在非常大的函數(shù)中才需要用堆棧存儲變量。對堆棧的操作和x86處理堆棧的方式一樣。
IDA中的代碼分析
既然ARM文件沒有統(tǒng)一格式,那么在加載ARM二進制映像的時候,有必要先加載該文件。在加載的時候,需要確定處理器類型。如果處理器規(guī)定代碼必須按照處理器模塊處理順序,那么你可以加載映像文件并且指定需要的處理方式,ARM處理方式(低位處理)或者ARMB(高位處理)。并且,有必要建立ROM或者RAM段??傊疀]有固定的處理方式,具體的處理有賴于映像和每個ARM處理器的架構(gòu)。例如,在ARM7中,內(nèi)存一般有如下格式:
0x0-0x8000ofRAMprocessor
0x8000-0x1000000ROM
0x1000000-0x.....-SRAM(這里看出自身數(shù)目)
現(xiàn)在就可以分析代碼了,在很多設(shè)備中(一般都是移動電話),代碼的入口設(shè)定為0x8000。ARM模式下的代碼從0x8000開始執(zhí)行,所以,開始執(zhí)行的指令和該處的一樣。處理器的IDA模塊可以簡單地分析此類switching語句,然后Thumb代碼在ARM中執(zhí)行。如果手工修改跳轉(zhuǎn),可以按ALT-G,然后修改文件中的標(biāo)記,如果為ARM文件,設(shè)為0,Thumb文件,設(shè)為1。
評論