1. 程式人生 > >2.解析C語言的內部執行機制

2.解析C語言的內部執行機制

目錄

1.解析C語言的內部機制

2.瞭解ARM-THUMB 子程式呼叫規則 ATPCS

3.分析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] 地址的內容。

再返回去看看,我們在程式開始執行之前這些地址儲存的東西是什麼?