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-- 转载请保留连接,谢谢

相关文章
相关标签/搜索