arm64 架構之入棧/出棧操做

本文首發在個人我的博客: blog.shenyuanluo.com,喜歡的朋友歡迎訂閱。html

arm64 彙編準備

寄存器

通用寄存器

31R0 ~ R30,每一個寄存器能夠存取一個 64 位大小的數。 當使用 x0 - x30訪問時,是一個 64位的數;當使用 w0 - w30訪問時,是一個 32 位的數,訪問的是寄存器的 32,如圖:objective-c

向量寄存器

(也能夠說是 浮點型寄存器)每一個寄存器的大小是 128 位的。 分別能夠用Bn、Hn、Sn、Dn、Qn的方式來訪問不一樣的位數;如圖:shell

**注:**word 是 32 位,也就是 4 Byte大小。xcode

  • Bn: 一個 Byte的大小,即 8
  • Hn: half word,即 16
  • Sn: single word,即 32
  • Dn: double word,即 64
  • Qn: quad word,即128

特殊寄存器

  • sp: (Stack Pointer),棧頂寄存器,用於保存棧頂地址;
  • fp(x29): (Frame Pointer)爲棧基址寄存,用於保存棧底地址;
  • lr(x30): (Link Register) ,保存調用跳轉指令 bl 指令的下一條指令的內存地址;
  • zr(x31): (Zero Register),xzr/wzr分別表明 64/32 位,其做用就是 0,寫進去表明丟棄結果,讀出來是 0
  • pc: 保存將要執行的指令的地址(有操做系統決定其值,不能改寫)。

狀態寄存器 CPSR

CPSR (Current Program Status Register)和其餘寄存器不同,其餘寄存器是用來存放數據的,都是整個寄存器具備一個含義;而 CPSR 寄存器是按位起做用的,即,每一位都有專門的含義,記錄特定的信息;以下圖數據結構

注: CPSR 寄存器是 32 位的。架構

  1. CPSR低8位(包括 IFTM[4:0])稱爲控制位,程序沒法修改,除非 CPU 運行於 特權模式 下,程序才能修改控制位。app

  2. NZCV 均爲條件碼標誌位;其內容可被算術或邏輯運算的結果所改變,而且能夠決定某條指令是否被執行。jsp

    • N(Negative)標誌: CPSR 的第 31 位是 N,符號標誌位;記錄相關指令執行後其結果是否爲負數,若是爲負數,則 N = 1;若是是非負數,則 N = 0函數

    • Z(Zero)標誌: CPSR 的第 30 位是 Z,0標誌位;記錄相關指令執行後,其結果是否爲0,若是結果爲0,則 Z = 1;若是結果不爲0,則 Z = 0post

    • C(Carry)標誌: CPSR 的第 29 位是C,進位標誌位

      • 加法運算:當運算結果產生了 進位 時(無符號數溢出),C = 1,不然 C = 0
      • 減法運算(包括 CMP): 當運算時產生了 借位 時(無符號數溢出),C = 0,不然 C = 1
    • V(Overflow)標誌: CPSR 的第 28 位是 V,溢出標誌位;在進行有符號數運算的時候,若是超過了機器所能標識的範圍,稱爲溢出。

條件碼列表

操做碼 條件碼助記符 標誌 含義
0000 EQ Z=1 相等
0001 NE(Not Equal) Z=0 不相等
0010 CS/HS(Carry Set/High or Same) C=1 無符號數大於或等於
0011 CC/LO(Carry Clear/LOwer) C=0 無符號數小於
0100 MI(MInus) N=1 負數
0101 PL(PLus) N=0 正數或零
0110 VS(oVerflow set) V=1 溢出
0111 VC(oVerflow clear) V=0 沒有溢出
1000 HI(High) C=1,Z=0 無符號數大於
1001 LS(Lower or Same) C=0,Z=1 無符號數小於或等於
1010 GE(Greater or Equal) N=V 有符號數大於或等於
1011 LT(Less Than) N!=V 有符號數小於
1100 GT(Greater Than) Z=0,N=V 有符號數大於
1101 LE(Less or Equal) Z=1,N!=V 有符號數小於或等於
1110 AL 任何 無條件執行(默認)
1111 NV 任何 從不執行

指令讀取

arm64 架構中,每一個指令讀取都是 64 位,即 8字節 空間

arm64 約定(通常來講)

  • x0 ~ x7 分別會存放方法的前 8 個參數;若是參數個數超過了8個,多餘的參數會存在棧上,新方法會經過棧來讀取。
  • 方法的返回值通常都在 x0 上;若是方法返回值是一個較大的數據結構時,結果會存在 x8 執行的地址上。

常見彙編指令

  • mov: 將某一寄存器的值複製到另外一寄存器(只能用於寄存器與寄存器或者寄存器與常量之間傳值,不能用於內存地址),如:

    mov x1, x0        ; 將寄存器 x0 的值複製到寄存器 x1 中 
    複製代碼
  • add: 將某一寄存器的值和另外一寄存器的值 相加 並將結果保存在另外一寄存器中,如:

    add x0, x0, #1    ; 將寄存器 x0 的值和常量 1 相加後保存在寄存器 x0 中 
    add x0, x1, x2    ; 將寄存器 x1 和 x2 的值相加後保存到寄存器 x0 中 
    add x0, x1, [x2]  ; 將寄存器 x1 的值加上寄存器 x2 的值做爲地址,再取該內存地址的內容放入寄存器 x0 中 
    複製代碼
  • sub: 將某一寄存器的值和另外一寄存器的值 相減 並將結果保存在另外一寄存器中,如:

    sub x0, x1, x2        ; 將寄存器 x1 和 x2 的值相減後保存到寄存器 x0 中 
    複製代碼
  • mul: 將某一寄存器的值和另外一個寄存器的值 相乘 並將結果保存在另外一寄存器中,如:

    mul x0, x1, x2        ; 將寄存器 x1 和 x2 的值相乘後結果保存到寄存器 x0 中
    複製代碼
  • sdiv:(有符號數,對應 udiv: 無符號數)將某一寄存器的值和另外一個寄存器的值 相除 並將結果保存在另外一寄存器中,如:

    sdiv x0, x1, x2       ; 將寄存器 x1 和 x2 的值相除後結果保存到寄存器 x0 中
    複製代碼
  • and: 將某一寄存器的值和另外一寄存器的值 按位與 並將結果保存到另外一寄存器中,如:

    and x0, x0, #0xf      ; 將寄存器 x0 的值和常量 0xf 按位與後保存到寄存器 x0 中 
    複製代碼
  • orr: 將某一寄存器的值和另外一寄存器的值 按位或 並將結果保存到另外一寄存器中,如:

    orr x0, x0, #9        ; 將寄存器 x0 的值和常量 9 按位或後保存到寄存器 x0 中
    複製代碼
  • eor: 將某一寄存器的值和另外一寄存器的值 按位異或 並將結果保存到另外一寄存器中,如:

    eor x0, x0, #0xf      ; 將寄存器 x0 的值和常量 0xf 按位異或後保存到寄存器 x0 中 
    複製代碼
  • str: (store register) 將寄存器中的值寫入到內存中,如:

    str w9, [sp, #0x8]    ; 將寄存器 w9 中的值保存到棧內存 [sp + 0x8] 處 
    複製代碼
  • strb: (store register byte) 將寄存器中的值寫入到內存中(只存儲一個字節),如:

    strb w8, [sp, #7]     ; 將寄存器 w8 中的低 1 字節的值保存到棧內存 [sp + 7] 處 
    複製代碼
  • ldr: (load register) 將內存中的值讀取到寄存器中,如:

    ldr x0, [x1]          ; 將寄存器 x1 的值做爲地址,取該內存地址的值放入寄存器 x0 中 
    ldr w8, [sp, #0x8]    ; 將棧內存 [sp + 0x8] 處的值讀取到 w8 寄存器中 
    ldr x0, [x1, #4]!     ; 將寄存器 x1 的值加上 4 做爲內存地址, 取該內存地址的值放入寄存器 x0 中, 而後將寄存器 x1 的值加上 4 放入寄存器 x1 中 
    ldr x0, [x1], #4      ; 將寄存器 x1 的值做爲內存地址,取內該存地址的值放入寄存器 x0 中, 再將寄存器 x1 的值加上 4 放入寄存器 x1 中 
    ldr x0, [x1, x2]      ; 將寄存器 x1 和寄存器 x2 的值相加做爲地址,取該內存地址的值放入寄存器 x0 中 
    複製代碼
  • ldrsb: (load register byte) 將內存中的值(只讀取一個字節)讀取到寄存器中,如:

    ldrsb	w8, [sp, #7]    ; 將棧內存 [sp + 7] 出的 低 1 字節的值讀取到寄存器 w8 中
    複製代碼
  • stur:str 將寄存器中的值寫入到內存中(通常用於 地址運算中),如:

    stur w10, [x29, #-0x4]    ; 將寄存器 w10 中的值保存到棧內存 [x29 - 0x04] 處 	
    複製代碼
  • ldur:ldr 將內存中的值讀取到寄存器中(通常用於 地址運算中),如:

    ldur w8, [x29, #-0x4]     ; 將棧內存 [x29 - 0x04] 處的值讀取到 w8 寄存器中
    複製代碼
  • stp: 入棧指令(str 的變種指令,能夠同時操做兩個寄存器),如:

    stp x29, x30, [sp, #0x10] 	; 將 x29, x30 的值存入 sp 偏移 16 個字節的位置 
    複製代碼
  • ldp: 出棧指令(ldr 的變種指令,能夠同時操做兩個寄存器),如:

    ldp x29, x30, [sp, #0x10] 	; 將 sp 偏移 16 個字節的值取出來,存入寄存器 x29 和寄存器 x30 
    複製代碼
  • scvtf: (Signed Convert To Float)帶符號 定點數 轉換爲 浮點數,如:

    scvtf	d1, w0      ; 將寄存器 w0 的值(頂點數,轉化成 浮點數) 保存到 向量寄存器/浮點寄存器 d1 中
    複製代碼
  • fcvtzs: (Float Convert To Zero Signed)浮點數 轉化爲 定點數 (舍入爲0),如:

    fcvtzs w0, s0	    ; 將向量寄存器 s0 的值(浮點數,轉換成 定點數)保存到寄存器 w0 中
    複製代碼
  • cbz: 和 0 比較(Compare),若是結果爲零(Zero)就轉移(只能跳到後面的指令);

  • cbnz: 和非 0 比較(Compare),若是結果非零(Non Zero)就轉移(只能跳到後面的指令);

  • cmp: 比較指令,至關於 subs,影響程序狀態寄存器 CPSR ;

  • cset: 比較指令,知足條件,則並置 1,不然置 0 ,如:

    cmp w8, #2        ; 將寄存器 w8 的值和常量 2 進行比較
    cset w8, gt       ; 若是是大於(grater than),則將寄存器 w8 的值設置爲 1,不然設置爲 0
    複製代碼
  • brk: 能夠理解爲跳轉指令特殊的一種

  • LSL: 邏輯左移

  • LSR: 邏輯右移

  • ASR: 算術右移

  • ROR: 循環右移

  • adrp: 用來定位數據段中的數據用, 由於 aslr 會致使代碼及數據的地址隨機化, 用 adrp 來根據 pc 作輔助定位

  • b: (branch)跳轉到某地址(無返回), 不會改變 lr (x30) 寄存器的值;通常是本方法內的跳轉,如 while 循環,if else 等 ,如:

    b LBB0_1      ; 直接跳轉到標籤 ‘LLB0_1’ 處開始執行
    複製代碼
  • bl: 跳轉到某地址(有返回),先將下一指令地址(即函數返回地址)保存到寄存器 lr (x30)中,再進行跳轉 ;通常用於不一樣方法直接的調用 ,如:

    bl 0x100cfa754	; 先將下一指令地址(‘0x100cfa754’ 函數調用後的返回地址)保存到寄存器 ‘lr’ 中,而後再調用 ‘0x100cfa754’ 函數
    複製代碼
  • blr: 跳轉到 某寄存器 (的值)指向的地址(有返回),先將下一指令地址(即函數返回地址)保存到寄存器 lr (x30)中,再進行跳轉 ;如:

    blr x20       ; 先將下一指令地址(‘x20’指向的函數調用後的返回地址)保存到寄存器 ‘lr’ 中,而後再調用 ‘x20’ 指向的函數
    複製代碼
  • br: 跳轉到某寄存器(的值)指向的地址(無返回), 不會改變 lr (x30) 寄存器的值。

  • brk: 能夠理解爲跳轉指令特殊的一種。

  • ret: 子程序(函數調用)返回指令,返回地址已默認保存在寄存器 lr (x30) 中

函數調用

每一個函數調用,都會有 入棧出棧 操做。

例子: PushAndPop.c

源代碼

#include <stdio.h>

void TestPushAndPop()
{
    printf("Push an Pop !");
}	
複製代碼

彙編代碼

  • 經過 Xcode "Product——>Perform Action——>Assemble PushAndPop.c" 查看其對應的彙編代碼:

  • 也能夠經過 clang 編譯成彙編代碼:

    // 注意,如下代碼將默認生成pc版的彙編指令
    clang -S PushAndPop.c
        
    // arm64彙編須要以下命令,指定架構和系統頭文件所在的目錄,請務必將isysroot的sdk版本修改成對應的 xcode 中存在的版本!
    clang -S -arch arm64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.1.sdk PushAndPop.c
    複製代碼

去除一大堆 不相干東西 獲得對應彙編代碼,以下:

sub	sp, sp, #32             ; 更新棧頂寄存器的值,(能夠看出:申請 32 字節佔空間做爲新用)
stp	x29, x30, [sp, #16]     ; 保存調用該函數前的棧頂寄存器的值和該函數結束返回後下一將執行指令地址值
add	x29, sp, #16            ; 更新棧底寄存器的值,(能夠看出:還剩餘 16 字節空間給該函數用)
adrp     x0, l_.str@PAGE        ; 獲取 ‘l_.str’ 標籤所在的頁的地址 
add x0, x0, l_.str@PAGEOFF	; 獲取 ‘l_.str’ 標籤對應頁地址的偏移
bl	_printf	                ; 調用 ‘printf’ 函數進行打印
stur	w0, [x29, #-4]          ; 將 w0 寄存器的值('bl' 函數調用的返回值)保存到 [x29 - 4] 的內存地址中
ldp	x29, x30, [sp, #16]     ; 恢復調用該函數以前棧底寄存器的值
add	sp, sp, #32             ; 恢復調用該函數以前棧頂寄存器的值
ret													; 返回
複製代碼

對與上面的彙編代碼,分配了 32 本身空間,其中 16 字節是用做 入棧操做,剩下的 16 字節是用於存儲臨時變量的。

疑問: 例子函數命名是沒有臨時變量,爲何還會須要申請佔空間?

解釋: 雖然該函數沒有臨時變量,可是調用 printf 函數後,編譯器自動會加上 該函數返回值 的處理,因爲 arm64 規定了整數型返回值放在 x0 寄存器裏,所以會隱藏有一個局部變量 int return_value; 的聲明在,該臨時變量佔用 4字節空間;又由於 arm64 下對於使用 sp 做爲地址基址尋址的時候,必需要 16byte-alignment(對齊),因此申請了 16字節空間做爲臨時變量使用。具體參見 這裏

  • 入棧操做 彙編代碼流程解析以下:

  • 出棧操做 彙編代碼流程解析以下:

    注意: 對棧的 分配/釋放 操做只會對棧指針作加減法, 而不會對棧內存中的內容作任何修改(也不會把釋放的棧空間設置爲 0)。

參考

相關文章
相關標籤/搜索