lec 02 arm組合語言基礎

木木ちゃん發表於2024-11-12

Lecture 02: ARM 彙編基礎

Contents

  • 為什麼學習ARM/ISA彙編
  • 從C到彙編
  • 理解arm彙編
  • 理解機器執行

1 為什麼學習彙編和指令集架構?

1.令人困惑的應用表現

困惑

2.指令集架構ISA(Instruction Set Architecture)

  • CPU向軟體(應用程式和作業系統)提供的介面。
  • 理解軟體在CPU上的執行(OS設計,程式除錯)。
  • 作業系統包含體系結構相關的彙編程式碼。
  • 作業系統啟動程式碼(棧沒有設定)
  • 部分操作C語言無法表達。(e.g.獲取系統狀態,重新整理TLB)
  • 部分場景下彙編更加高效(e.g. memcpy)

2.2 從C語言到彙編

1.為什麼硬體不能直接執行C

  • 硬體設計
    (1)高階語言表達能力很強
    (2)硬體理解高階語言複雜度過高難以高效設計。
  • 機器指令
    (1)格式相對固定
    (2)功能相對簡單
    (3)二進位制編碼

2.編譯過程
link
hex
二進位制檔案難以理解->彙編較為適合閱讀。

2.3 理解arm彙編

  1. 在完成程式編寫後,程式被儲存在磁碟中。
  2. OS載入程式,將其放入記憶體,CPU中的PC指向當前需要執行的第一個彙編指令。每執行一個指令,PC=PC+4.
  3. 資料剛開始儲存在磁碟。後來會載入到記憶體當中。CPU中具有特殊的儲存單元:暫存器,用於臨時儲存資料。可以用load/store指令來搬運資料。
    general reg
    vector reg

2.4 常用匯編

2.4.1 資料搬運

mov

2.4.2 算術指令

arith

2.4.3 移位指令

mov

2.4.4 邏輯運算指令

logic

2.4.5 Modified Register

  1. z=z*48分解成z=z*3與z=z*16,這樣可以採用右移,減少執行時間。(浮點乘法消耗時間遠大於位移)
    alt
  2. Modified register 優勢
    alt
  3. 對運算元進行移位/位擴充套件。
    alt

2.4.6 訪存指令

alt

2.4.7 記憶體結構

1.CPU視角下的記憶體:

  • 記憶體可以被視為一個很大的位元組陣列。
  • 陣列每個元素可以由唯一的地址來索引。

2.記憶體地址

  • 記憶體陣列的名稱計為M。M[addr]為addr開始的記憶體單元的內容。addr為記憶體陣列的索引。記憶體單元大小由上下文決定。
  • addr的具體格式由定址模式決定。

3.定址模式
(1)基地址模式(索引定址)

  • \([r_b]\)

(2)基地址+偏移量

  • \([r_b,\text{offset}]\)

(3)前索引定址(定址操作前更新基地址)

  • \([r_b,\text{offset}]!\)\(r_b+=\text{offset}\),定址\(M[r_b]\)

(4)後索引定址(定址操作後更新基地址)

  • \([r_b],\text{offset}\) 定址\(M[r_b]\);\(r_b+=\text{offset}\).

(5)offset可以是

  • 立即數 #imm
  • 64位通用暫存器\(r_i\)
  • 修改過的暫存器。例如:移位運算lsl #3,位擴充套件sxtw

(4)example:

alt

2.4.8 條件碼,分支指令

alt

標籤:.L3,.L1
分支指令 .cbz,bne

alt

1.條件碼

  • 一組標識位的統稱。
  • 由PSTATE暫存器維護。
  • N(Negative),Z(zero),C(carry),V(overflow)
  • 條件碼保留之前相關指令的執行狀態,其中有
  • 帶有s字尾的算術/邏輯指令(subs,adds)
  • 比較指令

2.條件碼的設定

  • 第一類:透過s字尾資料處理指令隱式設定。
adds Rd, Rn, Op2

相當於t=a+b

  • C:運算產生進位時設定。
  • Z:當t=0時被設定。
  • N:當t<0時被設定。
  • V:當運算產生有符號溢位時被設定。
(a>0 && b>0 && t<0) || (a<0 && b<0 && t>=0)
  • 第二類:透過比較指令cmp顯式設定。
cmp src1, src2

計算src1-src2,不儲存結果,只改變條件碼。

  • C:運算不產生借位時設定。
  • Z:當運算元相等時被設定。
  • N:當src1<src2時被設定。
  • V:當運算產生有符號溢位時被設定。

3.跳轉條件
alt

4.跳轉指令

  • 直接分支指令
  • 標籤對應地址作為跳轉目標
  • 無條件分支指令:b <label>
  • 有條件分支指令:bcond <label>, bcond=beq,bne,ble,...
  • 間接分支指令
  • 暫存器中地址作為跳轉目標。br reg

alt

2.5 函式呼叫

2.5.1 函式呼叫=無條件跳轉

alt

2.5.2 基本概念

  • 術語:
  1. Caller 呼叫者
  2. Callee 被呼叫者

alt

2.5.3 函式呼叫與返回指令

函式呼叫bl <label>,返回ret

alt

函式呼叫

  • 指令:
  1. bl <label> 直接呼叫函式
  2. blr Rn 間接呼叫,呼叫函式指標。
  • 功能:
  1. 將返回地址儲存在連結暫存器x30
  2. 跳轉到被呼叫者的入口地址。

返回指令

  • 指令
  1. ret 不區分直接呼叫和間接呼叫。
  • 功能
  1. 跳轉到返回地址X30

alt

2.5.4 多級函式呼叫

alt

  • [ ]一級:cube呼叫square
  • cube中的bl指令將返回地址儲存在LR(X30)中
  • square中的ret指令返回到LR(X30)記錄的地址
  • [ ]二級:cube呼叫square,square呼叫foo
  • LR首先儲存了square返回cube的地址
  • 巢狀呼叫時發生覆蓋:LR(X30)儲存foo返回square的地址

2.5.5 函式棧幀

  • 棧楨:函式在執行期間使用的一段記憶體

  • 生命週期:從被呼叫到返回前

  • 作用:存放其區域性狀態,包括:
    (1) 存放返回地址
    (2) 存放上一個棧楨的位置
    (3) 存放區域性變數

  • 多級函式呼叫

  • 例如,A呼叫B、B呼叫C

  • 程式執行中存在多個未返回的函式

  • 函式棧楨按照呼叫順序排列
    (1) 先被呼叫者後返回,後被呼叫者先返回
    (2) 棧:先進後出,後進先出

  • CPU中的另一個特殊暫存器SP
    SP: Stack Pointer
    指向棧頂(低地址)

alt

2.5.6 函式呼叫返回過程中棧的變化

alt

alt

2.5.7 幀指標FP:X29暫存器

  • 棧楨回溯
  • 棧楨大小不一
  • 如何找到上一個棧楨(如除錯)
    (1) 儲存x29(上一個棧楨的SP)
    (2) 將當前SP寫入x29(讓callee能儲存)
    alt

2.5.8 函式的呼叫,返回與棧

alt

2.6 函式引數與返回值

2.6.1 暫存器傳遞資料

  1. x0-x7暫存器傳遞前8個引數
  2. x0作為返回值

alt

2.6.2 傳遞資料

  • 呼叫者壓到棧上的資料
  • 第8個之後的引數
  • 按宣告順序從右到左
    Why? 因為引數的數量無法確定。而編譯器讀取引數時只是讀取sp指標以上的記憶體。因此可以得到每個引數的地址。反之則會導致每個引數的記憶體地址無法確定。
  • 所有資料對齊到8位元組
  • 被呼叫者透過SP+

alt

舉例說明:

void proc(long a1, long *a1p,
          int a2, int *a2p,
          short a3, short *a3p,
          char a4, char *a4p,
          char a5, char *a5p)
{
    *a1p += a1;
    *a2p += a2;
    *a3p += a3;
    *a4p += a4;
    *a5p += a5;
}

void caller(long *n)
{
    proc (1,0x2000,3,0x4000,5,0x6000,7,0x8000,9,0xA000);
}

檢視caller的組合語言:

00000000000000a0 <_caller>:
      a0: ff c3 00 d1  	sub	sp, sp, #48
      a4: fd 7b 02 a9  	stp	x29, x30, [sp, #32]
      a8: fd 83 00 91  	add	x29, sp, #32
      ac: a0 83 1f f8  	stur	x0, [x29, #-8]
      b0: e9 03 00 91  	mov	x9, sp
      b4: 28 01 80 52  	mov	w8, #9
      b8: 28 01 00 39  	strb	w8, [x9]
      bc: 08 00 94 d2  	mov	x8, #40960
      c0: 28 05 00 f9  	str	x8, [x9, #8]
      c4: 20 00 80 d2  	mov	x0, #1
      c8: 01 00 84 d2  	mov	x1, #8192
      cc: 62 00 80 52  	mov	w2, #3
      d0: 03 00 88 d2  	mov	x3, #16384
      d4: a4 00 80 52  	mov	w4, #5
      d8: 05 00 8c d2  	mov	x5, #24576
      dc: e6 00 80 52  	mov	w6, #7
      e0: 07 00 90 d2  	mov	x7, #32768
      e4: 00 00 00 94  	bl	0xe4 <_caller+0x44>
      e8: fd 7b 42 a9  	ldp	x29, x30, [sp, #32]
      ec: ff c3 00 91  	add	sp, sp, #48
      f0: c0 03 5f d6  	ret

2.7 暫存器儲存

2.7.1 通用暫存器儲存

  • 不同函式共享同一批通用暫存器
  • 因此能夠透過暫存器傳遞引數和返回值
  • 然而,不同的函式對通用暫存器的使用會存在衝突 — 覆蓋
  • 避免衝突的思路
  • 函式在使用某個暫存器之前儲存該暫存器的值,返回前恢復
  • 儲存在哪:函式棧楨中
  • 效率問題:有時候可能無需儲存
    如:一個函式內不呼叫其他函式
    編譯器會盡可能減少冗餘儲存的程式碼

2.7.2 暫存器使用約定

alt

  • 呼叫者儲存的暫存器包括 X9~X15

  • 呼叫者在呼叫前按需(僅考慮自己是否需要)進行儲存
    呼叫者在被呼叫者返回後恢復這些暫存器的值

  • 被呼叫者可以隨意使用
    這些暫存器呼叫後的值可能發生改變

  • 被呼叫者儲存的暫存器包括 X19~X28

  • 被呼叫者在使用前進行儲存

  • 被呼叫者在返回前進行恢復

  • 呼叫者視角:這些暫存器的值在函式呼叫前後不會改變

alt

2.7.3 舉例理解

0000000000000000 <square>:
   0:	1b007c00 	mul	w0, w0, w0
   4:	d65f03c0 	ret

0000000000000008 <cube>:
   8:	a9be7bfd 	stp	x29, x30, [sp, #-32]!	// 開闢棧幀,保留呼叫者的棧幀x29,儲存返回地址x30
   c:	910003fd 	mov	x29, sp		// 當前幀的棧頂地址寫入x29
  10:	f9000bf3 	str	x19, [sp, #16]	//被呼叫者使用前儲存呼叫者或之前保留的資料
  14:	2a0003f3 	mov	w19, w0		// 使用資料引數
  18:	94000000 	bl	0 <square>
  1c:	1b137c00 	mul	w0, w0, w19
  20:	f9400bf3 	ldr	x19, [sp, #16]	// 從記憶體中恢復資料
  24:	a8c27bfd 	ldp	x29, x30, [sp], #32	// 返回彈棧
  28:	d65f03c0 	ret

2.8 區域性變數

2.8.1 函式區域性變數存放在函式棧楨中

  • 為什麼不直接把區域性變數儲存在暫存器?
  • 暫存器數量有限
  • 陣列和結構體等複雜資料結構
  • 區域性變數可能需要定址 (如&a)

2.8.2 區域性變數

  • 區域性變數的分配
  • 在分配棧幀時被一起分配
  • 區域性變數的釋放
  • 在返回前釋放棧幀時釋放
  • 區域性變數透過SP相對地址引用
  • (例如ldr x1, [sp, #8])

2.8.3 小結

alt
alt
alt

相關文章