arm64程序调用规则

2019-06-27  本文已影响0人  maniackk

前言

我的博客

这篇主要介绍arm64程序调用规则,详细分析了程序调用过程中,参数是如何传递的。Android、iOS、Linux等基本遵循这些规则,但是各个操作系统平台也有小部分自己特定的规则。下一篇,我将介绍iOS平台的特定规则。

术语介绍

术语 意义
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。详细见下文参数传递

数据类型和对齐

基本数据类型

程序调用规则

寄存器

arm64有两种寄存器:

  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不需要保存或者调用者保存。

进程、内存、栈

一个进程的内存可分为5类:

  1. 代码区。只能被进程读,不可些。
  2. 可写静态数据。
  3. 只读静态数据。
  4. 堆。
  5. 栈。

可写静态数据可以细分为初始化,零初始化和未初始化数据。 除了栈之外,其它4类内存不需要占用连续的内存。 进程必须具有一些代码和栈,其它3类不是必须有。
堆是由进程管理的内存区域, 通常用于创建动态数据对象。

内存地址

地址空间包括一个或多个不相交的区域。 区域不能跨越零地址,但是可以从零开始。
标记寻址(tagged addressing)的使用是特定平台解释的。 当禁用标记寻址时,指针的所有64位都被传递到地址转换系统。 启用标记寻址时,为了进行地址转换,将忽略指针的前八位。注意:此tagged addressing,非iOS里的Tagged Pointer。

栈是连续的内存空间,可用于存储局部变量和参数传递(用于传递参数的寄存器不够用时候)。栈地址是从高到低,栈的地址保存在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的值是从寄存器中读取。

参数传递

参数可通过r0-r7、v0-v7,栈来传递;如果参数个数不多,且参数可放进寄存器,那仅用寄存器传递参数。

可变参数

可变参数可分为命名参数(已声明的)和匿名参数(可选的参数)。
当可变参数的函数,调用时候,没有可选参数时候(只有已声明的参数),调用过程和固定参数的函数一样的。

参数传递规则

参数传递从概念上可以分为2阶段:

  1. 从源语言参数类型到机器类型的映射(不同源语言,映射规则不同)
  2. 整理机器类型,生成最终参数列表

参数传递过程分为3个阶段:

从上面规则,可以得到经验:

  1. 处理完参数列表中所有的参数后,调用者一定知道传递参数用了多少栈空间。(NSAA - SP)
  2. 浮点数和short vector types通过v寄存器和栈传递,不会通过r寄存器传递。(除非是小复合类型的成员)
  3. 寄存器和栈中,参数未填充满的部分的值,不可确定。

函数返回结果

函数返回方式取决于返回结果的类型。

  1. 如果返回是类型T,如下
void func(T arg)

arg值通过寄存器(组)传递,返回的结果也是通过相同的寄存器(组)返回。

  1. 调用者申请内存(内存大小足够放入返回结果且是内存对齐的),将内存地址放入x8中传递给子函数,子函数运行时候,可以更新x8指向内存的内容,从而将结果返回。

结语

假如文章有不对地方,欢迎大家留言指出;或者给我发邮件(wu_k_k@foxmail.com)。

引用

  1. http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
  2. https://blog.csdn.net/adaptiver/article/details/80492292
  3. https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html

--EOF-- 转载请保留链接,谢谢

上一篇下一篇

猜你喜欢

热点阅读