ARM晶片詳解[翻譯]

看雪資料發表於2015-11-15

標 題:ARM晶片詳解[翻譯]

發信人:nbw 

時 間:2004-12-20,19:45 

詳細資訊: 


ARM晶片詳解
作者:不詳
翻譯:nbw

  譯者注:這篇文章主要介紹了Risc結構的PDA晶片組成和彙編程式,翻譯不周,肯定有錯誤,請多包涵,另外我忘記了出處,這裡向作者表示歉意。
  
  RISC處理器被廣泛應用在小型裝置上,例如PDA,行動電話,智慧熱水器等。有很多關於RISC處理器的彙編程式,但最常見的還是ARM。
  下面我要談的是ARM 7,因為我研究的是這個。
  讓我們先了解一下ARM的架構。ARM處理器包含37個暫存器:31個通用的32位暫存器,以及6個狀態暫存器。暫存器的設定取決於處理器狀態。ARM狀態執行32位指令,Thumb狀態執行16位指令集。
  在ARM狀態,有18個暫存器可用:可供直接儲存的R0―R15,CPSR(當前程式狀態暫存器),SPSR(被儲存程式狀態)。其中3個可直接儲存器被稱為服務暫存器。
  
  (R13)SP DD堆疊指標
  (R14)LRDD連線暫存器,用來儲存呼叫過程的函式地址(譯註:可簡單理解為過程返回地址)。並且,LR並非儲存在堆疊中-它存在於暫存器中。
  (R15)PCDD當前指令指標。用一般的mov指令就可以改變它的值,從而執行它所指向的命令。
  
  在Thumb 狀態,有13個暫存器可用:R0-R8, R13-R15, CPSR, SPSR

  狀態的改變,不會影響暫存器內容的變化。
  如果想進入Thumb狀態,可以先將操作暫存器的狀態位設為1(bit 1),然後執行BX指令。如果想進入ARM (譯註:原文誤為APM)狀態,可以先將操作暫存器的狀態位設為0(bit 0),然後執行BX指令。
  2種狀態的指令集是不同的,但是很多指令都是類似的。Thumb指令集長度為2bytes,ARM-4bytes。關於2種狀態指令的具體資料可以參考: http://www.atmel.com/dyn/resources/prod_documents/doc0673.pdf 
  有趣的是很多指令可以同時操作多個暫存器。例如:
ADD     R3, SP, #4     相當於: R3:=SP+4 
  或者,用來儲存暫存器入棧的指令:
PUSH {R2-R4, R7, LR}  這和x86彙編裡面的pushad指令不同,在ARM彙編裡面,這種將暫存器存入堆疊的方式是可行的。
  記憶體中,資料儲存方式可以是低位儲存(例如Intel暫存器)或者高位儲存(例如Motorola暫存器)。所以,寫程式碼時候,有必要指明資料存放方式。
  下面是一些ARM編譯器的資料:
http://heanet.dl.sourceforge.net/sourceforge/gnude/gnude-arm-win.exe  - GNU compiler with all consequences - all through command line + debugging through gdb. 

http://www.goldroad.co.uk/grARM.html - unpretentious ARM assembler. 

http://www.arm.com/support/downloads/index.html  - official tools for ARM’s develpment. Here you can only buy them. 

http://www.iar.com/  - alternative to IDA for ARM. 30-day's trial version is offered. 
  下面講解一下由C++的ARM編譯器生成的ARM彙編程式。
  一般地,分析不同程式的時候,經常碰到的並不是純粹的組合語言,而是由C++編譯器生成的程式碼。當然,x86彙編程式設計師一般不會如此。
  函式呼叫:
  這裡不存在函式引數呼叫約定(例如cdecl,stdcall 等)!所有的函式呼叫約定類似於Borland的fastcall。引數由暫存器傳入,如果數目不夠,由堆疊傳入。
  例如:
ROM:0001F4E2   MOV R0, SP 

ROM:0001F4E4   MOV R2, *6 

ROM:0001F4E6   ADD R1, R4, *0 

ROM:0001F4E8  BL memcmp 
  引數的傳遞順序對應於暫存器編號,R0為第一個,R1為第二個,R2為第三個(譯註:比較有意思)。相當於:
int memcmp ( 

   const void *buf1, 

   const void *buf2, 

   size_t count 

); 
buf1 = R0 

buf2 = R1 

count = R2 

  函式返回值被存放在R0中:
ROM:0001F4E2   MOV R0, SP 

ROM:0001F4E4   MOV R2, *6 

ROM:0001F4E6   ADD R1, R4, *0 

ROM:0001F4E8  BL memcmp 

ROM:0001F4EC   CMP R0, *0 

ROM:0001F4EE   BNE loc_1F4F4 
  下面是一個利用堆疊傳遞引數的例子:
ROM:000BCDEC   MOV R2, *0 

ROM:000BCDEE   STR R2, [SP] 

ROM:000BCDF0   MOV R2, *128 

ROM:000BCDF2   MOV R3, *128 

ROM:000BCDF4  MOV R1, *14 

ROM:000BCDF6   MOV R0, *0 

ROM:000BCDF8   BL FillBoxColor 
  上面,R0-R3儲存座標,第5個引數(色彩)被存放在堆疊中。

  只有透過分析才可以確定運算元的數目。我們可以分析函式和它的呼叫部分。有時候,引數資訊可以透過對暫存器和堆疊的操作觀察出來。例如,在Thumb狀態下,程式對R0-R7和服務暫存器的操作。所以,如果看到類似於下面的程式碼:
ROM:00059ADA   getTextBounds                          

ROM:00059ADA   PUSH {R4-R7, LR}, 
  可以認為它的引數被存放在R0,R1,R2,R3和SP。如果見到:
ROM:0005924E   ADD R0, SP, *0x14 

ROM:00059250   ADD R1, SP, *0x6C 

ROM:00059252   ADD R2, SP, *0x68 

ROM:00059254   ADD R3, SP, *0x64 

ROM:00059256   BL getTextBounds 
  我們看到只有R0-R3被使用,就是說只有4個引數被傳遞過來。

轉移(Transitions )
  一般,轉移分為條件轉移和無條件轉移。轉移目標可以存放在暫存器或者其他處。暫存器轉移一般用於Thumb/ARM 狀態轉換。無條件短轉移指令為B(branch)命令。長跳轉指令-BX(交換轉移)。函式呼叫採用BL(連線轉移),且呼叫時將返回地址存入LR暫存器。當然,改變PC暫存器內容也可以改變轉移地址:
ADD PC, *0x64
但是C編譯器通常不這樣處理,它們在轉移的時候,只是以寫入命令改變PC暫存器。

分支(Branches)
  也稱為轉換,一般用法如下:
ROM:0027806E   CMP R2, *0x4D; 'M' 

ROM:00278070   BCS loc_27807A 

ROM:00278072   ADR R3, word_27807C 

ROM:00278074   ADD R3, R3, R2 

ROM:00278076  LDRH R3, [R3, R2] 

ROM:00278078   ADD PC, R3 

ROM:0027807  A

ROM:0027807  A loc_27807A                               

ROM:0027807  A B loc_278766 

ROM:0027807  C word_27807C DCW 0xAA, 0xBE, 0xC6, 0x180, 0x186; 0 

ROM:0027807  C DCW 0x190, 0x1A0, 0x1A8, 0x1DE, 0x1E4; 5 

ROM:0027807  C DCW 0x1B0, 0x212, 0x276, 0x1FE, 0x294; 10 
  首先,檢查跳轉標記,該標記必須小於0x40,如果大於,則跳到預設處理位置,即:loc_27807A。
  然後執行位於word_27807C 的轉移控制表。這個表裡面存放的是偏移,並非地址。隨後,根據跳轉標記,取表中的偏移,擴充套件之,加操作放入PC暫存器。比如,如果跳轉標記為0,將會跳轉到地址:
0x278078 (current value PC) +0xAA (offset from the table) + 0x4 (!!!) = 0x278126
  之所以加4,是因為ARM處理器的特徵:操作PC暫存器時,其值應該比預先確定的數值大4(在文件“to ensure it is word aligned ”中有說明)。
  
記憶體存取

  在Thumb狀態,處理器可以存取256 位元組的空間。因此,無法直接存取記憶體,而需要利用暫存器來引導。也就是無法直接定位到0x974170,而需要採用暫存器。例如:
ROM:00277FF6 LDR R0, =unk_974170 

ROM:00277FF8 LDR R0, [R0] 
  我們獲得了0x974170處的資料,但是事情還沒有結束!該有效地址(0x974170)處於有效的正負256 位元組中:
ROM:00278044 off_278044 DCD unk_974170
這樣,就是說,LDR指令的機器碼中儲存了該命令當前的地址。(譯註:就是說0x974170雖然看起來比較大,實際上還是那+-256位元組內,只不過透過LDR指令來定位)
  這裡存在一個很藝術的最佳化方法:如果一個地址和該函式中另外一個被用到的地址有關聯,那麼這個地址可以透過算術運算指令或者間接存取來獲取。舉例來說,如果一個函式需要用到0x100000處的變數,並且需要用到0x100150處的另外一個變數,那麼,編譯器可以將這2個變數建立關聯,或者採用以下程式碼:
LDR R0, =0x100000 

ADD    R0, *0xFF 

ADD    R0, *0x51 

LDR R0, [R0] 
  在x86裡面,這種方法應用於結構中獲取子結構介面。但是此處,卻是一個常用的最佳化,這有什麼好處呢?可以減小記憶體儲存,並且算術運算比資料載入快得多。可以認為整個ARM彙編程式充滿了不同的暫存器間算術運算。事實上,有多達16個暫存器用來進行此操作-減少記憶體和堆疊定位頻度。因此,只有在非常大的函式中才需要用堆疊儲存變數。對堆疊的操作和x86處理堆疊的方式一樣。

IDA中的程式碼分析

  既然ARM檔案沒有統一格式,那麼在載入ARM二進位制映像的時候,有必要先載入該檔案。在載入的時候,需要確定處理器型別。如果處理器規定程式碼必須按照處理器模組處理順序,那麼你可以載入映像檔案並且指定需要的處理方式,ARM處理方式(低位處理)或者ARMB(高位處理)。並且,有必要建立ROM或者RAM段。總之沒有固定的處理方式,具體的處理有賴於映像和每個ARM處理器的架構。例如,在ARM7中,記憶體一般有如下格式:
0x0 - 0x8000  of   RAM processor 

0x8000 -   0x1000000 ROM 

0x1000000 -   0x..... - SRAM (這裡看出自身數目)
  現在就可以分析程式碼了,在很多裝置中(一般都是行動電話),程式碼的入口設定為0x8000。ARM模式下的程式碼從0x8000開始執行,所以,開始執行的指令和該處的一樣。處理器的IDA模組可以簡單地分析此類switching語句,然後Thumb 程式碼在ARM中執行。如果手工修改跳轉,可以按ALT-G,然後修改檔案中的標記,如果為ARM檔案,設為0,Thumb檔案,設為1。

相關文章