2.解析C語言的內部執行機制
目錄
1.解析C語言的內部機制
1.把上一節編譯第10節的C語言控制程式碼在Linux系統反彙編檔案,led.dis檔案傳windows系統檢視,然後分析這個程式時如何執行的,具體看上一節內容:點我檢視
led.dis的檔案內容如下:
led.elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e3a0da01 mov sp, #4096 ; 0x1000 4: eb000000 bl c <main> 00000008 <halt>: 8: eafffffe b 8 <halt> 0000000c <main>: c: e1a0c00d mov ip, sp 10: e92dd800 stmdb sp!, {fp, ip, lr, pc} 14: e24cb004 sub fp, ip, #4 ; 0x4 18: e24dd008 sub sp, sp, #8 ; 0x8 1c: e3a03456 mov r3, #1442840576 ; 0x56000000 20: e2833050 add r3, r3, #80 ; 0x50 24: e50b3010 str r3, [fp, #-16] 28: e3a03456 mov r3, #1442840576 ; 0x56000000 2c: e2833054 add r3, r3, #84 ; 0x54 30: e50b3014 str r3, [fp, #-20] 34: e51b2010 ldr r2, [fp, #-16] 38: e3a03c01 mov r3, #256 ; 0x100 3c: e5823000 str r3, [r2] 40: e51b2014 ldr r2, [fp, #-20] 44: e3a03000 mov r3, #0 ; 0x0 48: e5823000 str r3, [r2] 4c: e3a03000 mov r3, #0 ; 0x0 50: e1a00003 mov r0, r3 54: e24bd00c sub sp, fp, #12 ; 0xc 58: 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.
看起來,比直接使用匯編語言複雜很多,其實c語言最終也是要轉換成組合語言,最終轉換成機器碼(二進位制),燒寫到soc上執行的。
先捋一下思路:
1.在彙編檔案中,設定棧記憶體的地址
2.使用bl命令呼叫main,且設定返回地址存入lr暫存器
3.在c程式中,有一個main函式,寫暫存器的值
問題:
1.為何要設定棧?
答:因為c語言要設定區域性變數,有變數就需要記憶體
2.如何使用棧?
答:區域性變數是儲存在棧中的,儲存lr等暫存器
問題:
1.呼叫者如何向被呼叫者傳遞引數?
2.被呼叫者如何向傳返回著給呼叫者?
3.如何從棧中恢復暫存器?
想要了解這些問題需要對ARM-THUMB 子程式呼叫規則有所瞭解。
2.瞭解ARM-THUMB 子程式呼叫規則 ATPCS
為了使 C 語言程式和彙編程式之間能夠互相呼叫,必須為子程式間的呼叫制定規則,在ARM 處理器中,這個規則被稱為 ATPCS:ARM 程式和 Thumb 程式中子程式呼叫的規則。基本的ATPCS 規則包括暫存器使用規則、資料棧使用規則、引數傳遞規則。
2.1. 暫存器使用規則
ARM 處理器中有 r0~r15 共 16 個暫存器,它們的用途有一些約定的習慣,並依具這些用途定義了別名,如下表所示:
- PCS 中各暫存器的使用規則及其名稱
暫存器 別名 使用規則
r15 pc
r14 lr 連線暫存器
r13 sp 資料棧指標
r12 ip 子程式內部呼叫的 scratch 暫存器
r11 v8 ARM 狀態區域性變數暫存器 8
r10 v7、s1 ARM 狀態區域性變數暫存器 7、在支援資料棧檢查的 ATPCS 中為資料棧限制指標
r9 v6、sb ARM 狀態區域性變數暫存器 6、在支援 RWPI 的 ATPCS 中為靜態基址暫存器
r8 v5 ARM 狀態區域性變數暫存器 5
r7 v4、wr ARM 狀態區域性變數暫存器 4、Thumb 狀態工作暫存器
r6 v3 ARM 狀態區域性變數暫存器 3
r5 v2 ARM 狀態區域性變數暫存器 2
r4 v1 ARM 狀態區域性變數暫存器 1
r3 a4 引數/結果/scratch 暫存器 4
r2 a3 引數/結果/scratch 暫存器 3
r1 a2 引數/結果/scratch 暫存器 2
r0 a1 引數/結果/scratch 暫存器 1
暫存器的使用規則總結如下:
- 子程式間通過暫存器 r0~r3 來傳遞引數,這時可以使用它們的別名 a0~a3。被呼叫的子程式返回前無需恢復 r0~r3 的內容。
- 在子程式中,使用 r4~r11 來儲存區域性變數,這時可以使用它們的別名 v1~v8。如果在子程式中使用了它們的某些暫存器,子程式進入時要儲存這些暫存器的值,在返回前恢復它們;對於子程式中沒有使用到的暫存器則不必進行這些操作。在 Thumb 程式中,通常只能使用暫存器 r4~r7 來儲存區域性變數。
- 暫存器 r12 用作子程式間 scratch 暫存器,別名為 ip。
- 暫存器r13用作資料棧指標,別名為sp。在子程式中暫存器r13不能用作其他用途。它的值在進入、退出子程式時必須相等。
- 暫存器 r14 被稱為連線暫存器,別名為 lr。它用於儲存子程式的返回地址。如果在子程式中儲存了返回地址(比如將 lr 值儲存到資料棧中),r14 可以用作其他用途。
- 暫存器 r15 是程式計數器,別名為 pc。它不能用作其他用途。
2.2.資料棧使用規則
資料棧有兩個增長方向:向記憶體地址減小的方向增長時,稱為 DESCENDING 棧;向內地址增加的方向增長時,稱為 ASCENDING 棧。
所謂資料棧的增長就是移動棧指標。當棧指標指向棧頂元素(最後一個入棧的資料)時,稱為 FULL 棧;當棧指標指向棧頂元素(最後一個入棧的資料)相鄰的一個空的資料單元時,稱為 EMPTY 棧。
綜合這兩個特點,資料棧可以分為以下 4 種:
① FD Full Descending,滿遞減
② ED Empty Descending,空遞減
③ FA Full Ascending,滿遞增
④ EA Empty Ascending,空遞增
注意:ATPCS 規定資料棧為 FD 型別,並且對資料棧的操作是 8 位元組對齊的。使用 stmdb/ldmia批量記憶體訪問指令來操作 FD 資料棧。使用 stmdb 命令往資料棧中儲存內容時,“先遞減 sp 指標,再儲存資料”,使用 ldmia命令從資料棧中恢復資料時, “先獲得資料,再遞增 sp 指標”──sp 指標總是指向棧頂元素,這剛好是 FD 棧的定義。
2.3. 引數傳遞規則
一般來說,當引數個數不超過 4 個時,使用 r0~r3 這 4 個暫存器來傳遞引數;如果引數個數超過 4 個,剩餘的引數通過資料棧來傳遞。對於一般的返回結果,通常使用 a0~a3 來傳遞。
簡圖分析如下:
3.分析C語言的反彙編程式碼
3.1.知識基礎
假設程式從Nand啟動,對於Nand啟動的程式,硬體上會把Nand Flash 前4K的內容完全複製到片內的4K記憶體上,那麼上面的哪些機器碼在程式執行就會儲存在片內記憶體4k記憶體的前面。
機器碼在片內RAM的儲存結構簡圖如下所示:
3.2.從第一句開始分析程式碼:
開發版一上電,從0地址開始執行
接著開始執行main函式的內容了
再執行下面的語句:
得到的結果使用簡圖表示為:
接著分析下一條語句:
執行完如下圖所示:
繼續
因為r0-r3用來儲存呼叫和被呼叫者的引數,因此,C語言中的ruturn 應該儲存在這幾個變數中的一個。
退出主函式時恢復棧
從棧中恢復暫存器採用的是 ldmia 指令
指令:ldm
含義:讀記憶體讀取資料,然後把讀取的資料寫入多個暫存器
命令解析:
例子:
ldmia sp, {fp, sp, pc}
假設:sp=4080
例子:ia的含義是過後增加(Increment After),就是先讀取後增加,而且的順序的依據是:高編號的暫存器儲存在高地址
fp,sp, pc 這三個的暫存器編號分別如下所示(ARM程式設計手冊檢視)
pc->R15,sp->R13,fp->R11.所以存取的順序是:fp-sp-pc(與指令順序無關)
附錄:
其他形式簡單的描述指令的行為,意思分別是過後增加(Increment After)、預先增加(Increment Before)、過後減少(Decrement After)、預先減少(Decrement Before)。
因此執行完這條指令之後:fp = [4080-4083] 地址的內容。sp =[4084-4087]地址的內容,pc=[4088-4092] 地址的內容。
再返回去看看,我們在程式開始執行之前這些地址儲存的東西是什麼?