arm64程序調用規則

前言

這篇主要介紹arm64程序調用規則,詳細分析了程序調用過程當中,參數是如何傳遞的。Android、iOS、Linux等基本遵循這些規則,可是各個操做系統平臺也有小部分本身特定的規則。下一篇,我將介紹iOS平臺的特定規則。html

術語介紹

術語 意義
A32 在ARMv7架構中,使用32位固定長度指令的ARM指令集。
A64 AArch64可用時的指令集。
AAPCS64 AArch64程序調用標準。(PCS:Procedure Call Standard)
AArch32 ARMv8中的32位通用寄存器,兼容ARMv7-A。
AArch64 ARMv8中的64位通用寄存器
ABI(Application Binary Interface) 彙編接口規範,跟執行環境相關,好比Linux ABI,說的是Linux環境下的彙編接口規範;
ARM-based 基於ARM
Floating point 根據上下文有這三種意思:(1)遵循IEEE 754 2008的浮點運算; (2)ARMv8浮點指令集; (3)一個被ARMv8浮點指令集和ARMv8 SIMD指令集共享的寄存器組。
Q-o-I Quality of Implementation
SIMD Single Instruction Multiple Data 一條指令操做多個數據
T32 T32使用可變16bit和32bit
Routine, subroutine Routine:調用者;subroutine:被調用者
Procedure 沒有返回值的函數
Function 有返回值的函數
PIC, PID Position-independent code, position-independent data.
Program state 指程序內存和寄存器的值
Caller- saved register 調用者在調用函數以前,保存寄存器(通常入棧),函數返回後恢復寄存器(通常出棧)
Callee-saved register 被調用者(函數內部),在起始地方保存寄存器,在結束時,恢復寄存器
NGRN(The Next General-purpose Register Number ) 能夠理解爲,記錄r0-r7(見下文寄存器)使用個數,參數傳遞前設爲0,每放一個參數進入寄存器(整型寄存器),值加1。當等於8時候,說明r0-r7寄存器使用完了,再有參數,只能放入內存了。
NSRN (The Next SIMD and Floating-point Register Number) 同上,記錄v0-v7使用個數
NSAA (The next stacked argument address) 記錄參數放入內存,參數傳遞前設爲SP,因此內存中參數範圍應該是 sp~NSAA。詳細見下文參數傳遞

數據類型和對齊

基本數據類型

Type Class Machine Type Byte
size
Natural
Alignment
(bytes)
Integral Unsigned byte 1 1
Signed byte 1 1
Unsigned half-
word
2 2
Signed half-
word
2 2
Unsigned word 4 4
Signed word 4 4
Unsigned
double-word
8 8
Signed double-
word
8 8
Unsigned quad-
word
16 16
Signed quad-
word
16 16
Floating Point Half precision 2 2
Single precision 4 4
Double
precision
8 8
Quad precision 16 16
Short vector 64-bit vector 8 8
128-bit vector 16 16
Pointer Data pointer 8 8
Code pointer 8 8

程序調用規則

寄存器

arm64有兩種寄存器:bash

  1. 處理整型和指針的寄存器
    1. 通用寄存器和AAPCS64用法
寄存器 別名 意義
SP Stack Pointer:棧指針
r30 LR Link Register:在調用函數時候,保存下一條要執行指令的地址。
r29 FP Frame Pointer:保存函數棧的基地址。
r19...r28 Callee-saved registers(含義見上面術語解釋)
r18 平臺寄存器,有特定平臺解釋其用法。若是平臺未把其作特殊用途,可當作臨時寄存器使用。(iOS平臺保留的寄存器,應用不可以使用)
r17 IP1 The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
r16 IP0 The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
r9...r15 臨時寄存器
r8 在一些狀況下,返回值是經過r8返回的
r0...r7 r0-r7在函數調用過程當中傳遞參數和返回值
NZCV 狀態寄存器:N(Negative)負數 Z(Zero) 零 C(Carry) 進位 V(Overflow) 溢出

arm64有31個通用整型寄存器,r0-r30。當使用64bits時候,命名x0-x30;使用32bits時,命名w0-w30。當寄存器在此程序調用標準中具備固定角色時,使用大寫。架構

  1. SIMD 和 Floating-Point寄存器

ARM64有32個寄存器v0-v31,用於處理SIMD和浮點運算。長度不一樣稱謂也不一樣,b,h,s,d,q,分別表明byte(8位),half(16位),single(32位),double(64位),quad(128位)。v0-v7在函數調用過程當中傳遞參數和返回值;v8-v15 是Callee-saved registers(見術語解釋),且是保存前64bits(更大的位數,調用者負責保存),v0-v7, v16-v31不須要保存或者調用者保存。app

進程、內存、棧

一個進程的內存可分爲5類:函數

  1. 代碼區。只能被進程讀,不可些。
  2. 可寫靜態數據。
  3. 只讀靜態數據。
  4. 堆。
  5. 棧。

可寫靜態數據能夠細分爲初始化,零初始化和未初始化數據。 除了棧以外,其它4類內存不須要佔用連續的內存。 進程必須具備一些代碼和棧,其它3類不是必須有。 堆是由進程管理的內存區域, 一般用於建立動態數據對象。post

內存地址

地址空間包括一個或多個不相交的區域。 區域不能跨越零地址,可是能夠從零開始。 標記尋址(tagged addressing)的使用是特定平臺解釋的。 當禁用標記尋址時,指針的全部64位都被傳遞到地址轉換系統。 啓用標記尋址時,爲了進行地址轉換,將忽略指針的前八位。注意:此tagged addressing,非iOS裏的Tagged Pointer。spa

棧是連續的內存空間,可用於存儲局部變量和參數傳遞(用於傳遞參數的寄存器不夠用時候)。棧地址是從高到低,棧的地址保存在SP中。 棧使用限制:操作系統

  1. Stack-limit < SP <= stack-base
  2. 進程只能訪問這個範圍內的棧空間:[SP, stack-base – 1]
  3. SP mod 16 = 0

函數調用

A64指令集包含函數調用指令BL和BLR。 執行BL:PC(program counter)順序的下一個值,也就是返回地址(函數調用完成返回要執行指令的地址),存放到LR中,將跳轉地址傳給PC。BLR跟BL相似,只不過PC的值是從寄存器中讀取。.net

參數傳遞

參數可經過r0-r七、v0-v7,棧來傳遞;若是參數個數很少,且參數可放進寄存器,那僅用寄存器傳遞參數。指針

可變參數

可變參數可分爲命名參數(已聲明的)和匿名參數(可選的參數)。 當可變參數的函數,調用時候,沒有可選參數時候(只有已聲明的參數),調用過程和固定參數的函數同樣的。

參數傳遞規則

參數傳遞從概念上能夠分爲2階段:

  1. 從源語言參數類型到機器類型的映射(不一樣源語言,映射規則不一樣)
  2. 整理機器類型,生成最終參數列表

參數傳遞過程分爲3個階段:

  • 階段A – 初始化 (在開始處理參數以前,該階段僅執行一次)

    1. NGRN = 0 (NGRN意義,見術語)
    2. NSRN = 0 (NSRN意義,見術語)
    3. NSAA = SP(NSAA意義,見術語)
  • 階段B - 預填充和擴展參數 (把參數列表中的每個參數,去匹配下面規則,第一個被匹配到的規則,應用到該參數上。)

    1. 若是參數類型是複合類型,調用者和被調用者都不能肯定其大小,則將參數複製到內存中,並將參數替換爲指向該內存的指針。 (C / C ++語言中沒有這樣的類型,其它語言存在。)
    2. 若是參數是HFA或HVA類型,則參數不修改。
    3. 若是參數是大於16個字節的複合類型,調用者申請一個內存,將參數複製到內存裏去,並將參數替換爲指向該內存的指針。
    4. 若是參數是複合類型,則參數的大小向上舍入爲最接近8個字節的倍數。(例如參數大小爲9字節,修改成16字節)
  • 階段C- 把參數放到寄存器或棧裏 (參數列表中的每一個參數,將依次應用如下規則,直到參數放到寄存器或棧裏,此參數處理完成,而後再從參數列表中取參數。注: 將參數分配給寄存器時,寄存器中未使用的位的值不肯定。 將參數分配給棧時,未填充字節的值不肯定。)

    1. (1) 若是參數是half(16bit),single(16bit),double(32bit)或quad(64bit)浮點數或Short Vector Type,而且NSRN小於8,則將參數放入寄存器v[NSRN]的最低有效位。 NSRN增長1。 此參數處理完成。
    2. (2) 若是參數是HFA(homogeneous floating-point aggregate)或HVA(homogeneous short vector aggregate)類型,且NSRN + (HFA或HVA成員個數) ≤ 8,則每一個成員依次放入SIMD and Floating-point 寄存器,NSRN=NSRN+ HFA或HVA成員個數。此參數處理完成。
    3. (3) 若是參數是HFA(homogeneous floating-point aggregate)或HVA(homogeneous short vector aggregate)類型,可是NSRN已經等於8(說明v0-v7被使用完畢)。則參數的大小向上舍入爲最接近8個字節的倍數。(例如參數大小爲9字節,修改成16字節)
    4. (4) 若是參數是HFA(homogeneous floating-point aggregate)、HVA(homogeneous short vector aggregate)、quad(64bit)浮點數或Short Vector Type,NSAA = NSAA+max(8, 參數天然對齊大小)。
    5. (5) 若是參數是half(16bit),single(16bit)浮點數,參數擴展到8字節(放入最低有效位,其他bits值不肯定)
    6. (6) 若是參數是HFA(homogeneous floating-point aggregate)、HVA(homogeneous short vector aggregate)、half(16bit),single(16bit),double(32bit)或quad(64bit)浮點數或Short Vector Type,參數copy到內存,NSAA=NSAA+size(參數)。此參數處理完成。
    7. (7) 若是參數是整型或指針類型、size(參數)<=8字節,且NGRN小於8,則參數複製到x[NGRN]中的最低有效位。 NGRN增長1。 此參數處理完成。
    8. (8) 若是參數對齊後16字節,NGRN向上取偶數。(例如:NGRN爲2,那值保持不變;假如NGRN爲3,則取4。 注:iOS ABI沒有這個規則)
    9. (9) 若是參數是整型,對齊後16字節,且NGRN小於7,則把參數複製到x[NGRN] 和 x[NGRN+1],x[NGRN]是低位。NGRN = NGRN + 2。 此參數處理完成。
    10. (10) 若是參數是複合類型,且參數能夠徹底放進x寄存器(8-NGRN>= 參數字節大小/8)。從x[NGRN]依次放入參數(低位開始)。未填充的bits的值不肯定。NGRN = NGRN + 此參數用掉的寄存器個數。此參數處理完成。
    11. (11) NGRN設爲8。
    12. (12) NSAA = NSAA+max(8, 參數天然對齊大小)。
    13. (13) 若是參數是複合類型,參數copy到內存,NSAA=NSAA+size(參數)。此參數處理完成。
    14. (14) 若是參數小於8字節,參數設置爲8字節大小,高位bits值不肯定。
    15. (15) 參數copy到內存,NSAA=NSAA+size(參數)。此參數處理完成。

從上面規則,能夠獲得經驗:

  1. 處理完參數列表中全部的參數後,調用者必定知道傳遞參數用了多少棧空間。(NSAA - SP)
  2. 浮點數和short vector types經過v寄存器和棧傳遞,不會經過r寄存器傳遞。(除非是小複合類型的成員)
  3. 寄存器和棧中,參數未填充滿的部分的值,不可肯定。

函數返回結果

函數返回方式取決於返回結果的類型。

  1. 若是返回是類型T,以下
void func(T arg)
複製代碼

arg值經過寄存器(組)傳遞,返回的結果也是經過相同的寄存器(組)返回。 2. 調用者申請內存(內存大小足夠放入返回結果且是內存對齊的),將內存地址放入x8中傳遞給子函數,子函數運行時候,能夠更新x8指向內存的內容,從而將結果返回。

結語

假如文章有不對地方,歡迎你們留言指出;或者給我發郵件(wu_k_k@foxmail.com)。

引用

  1. infocenter.arm.com/help/topic/…
  2. blog.csdn.net/adaptiver/a…
  3. developer.apple.com/library/arc…

--EOF-- 轉載請保留連接,謝謝

相關文章
相關標籤/搜索