第16部分- Linux ARM汇编 ARM64调用标准
本章主要是是一些描述性的内容,我们先来看下4个术语。
- AArch32 – the legacy 32-bit instruction set architecture (ISA) defined by ARM, including Thumb mode execution.
- AArch64 – the new 64-bit instruction set architecture (ISA) defined by ARM.
- ARMv7 – the specification of the "7th generation" ARM hardware, which only includes support for AArch32. This version of the ARM hardware is the first version Windows for ARM supported.
- ARMv8 – the specification of the "8th generation" ARM hardware, which includes support for both AArch32 and AArch64.
ARM64中参数1~参数8 分别保存到 X0~X7 寄存器中 ,剩下的参数从右往左一次入栈,被调用者实现栈平衡,返回值存放在 X0 中。调用者保存的
寄存器x8–x18是每个函数的临时寄存器。 因此,无法在返回函数时对其值进行任何假设。 实际上,这些寄存器也是调用者保存的。
寄存器x19–x28是寄存器,如果由函数使用,则必须保留其值,然后在返回函数时将其恢复。 此寄存器称为被调用者保存的。
非可变参数函数
AArch64体系结构的过程调用标准:
阶段A–初始化
在开始处理参数之前,此阶段仅执行一次。
下一个通用寄存器号(NGRN)设置为零。
下一个SIMD和浮点寄存器号(NSRN)设置为零。
下一个堆栈参数地址(NSAA)设置为当前堆栈指针值(SP)。
B阶段–预填充和参数扩展
对于列表中的每个参数,将应用以下列表中的第一个匹配规则。如果没有规则匹配,则不修改参数。
如果参数类型是一个复合类型,其大小不能同时由调用方和被调用方静态确定,则将参数复制到内存中,并将该参数替换为指向副本的指针。 (在C / C ++中没有这样的类型,但是它们以其他语言或语言扩展存在)。
如果参数类型是HFA或HVA,则该参数将未经修改地使用。
如果参数类型是大于16个字节的Composite Type,则将参数复制到调用方分配的内存中,并将该参数替换为指向副本的指针。
如果参数类型为“复合类型”,则参数的大小将四舍五入为最接近的8字节倍数。
C阶段–将参数分配给寄存器和堆栈
对于列表中的每个参数,依次应用以下规则,直到分配了参数为止。将自变量分配给寄存器时,寄存器中任何未使用的位都具有未指定的值。如果将参数分配给堆栈插槽,则任何未使用的填充字节均具有未指定的值。
如果自变量是半精度,单精度,双精度或四精度浮点或短向量类型,并且NSRN小于8,则将自变量分配给寄存器v [NSRN]的最低有效位。 NSRN增加1。现在已分配了参数。
如果自变量是HFA或HVA,并且有足够的未分配SIMD和浮点寄存器(NSRN +成员数≤8),则将自变量分配给SIMD和浮点寄存器,每个成员一个寄存器HFA或HVA。 NSRN增加所用寄存器的数量。现在已分配了参数。
如果参数是HFA或HVA,则将NSRN设置为8,并且将参数的大小四舍五入到8个字节的最接近倍数。
如果自变量是HFA,HVA,四精度浮点或短向量类型,则将NSAA向上舍入为8或自变量类型的自然对齐中的较大者。
如果参数是半精度或单精度浮点类型,则参数的大小设置为8个字节。效果就好像该参数已被复制到64位寄存器的最低有效位,而其余位则填充了未指定的值。
如果参数是HFA,HVA,半精度,单精度,双精度或四精度浮点或短向量类型,则将参数复制到调整后的NSAA处的内存中。 NSAA增加自变量的大小。现在已分配了参数。
如果参数是整数或指针类型,则参数的大小小于或等于8个字节,并且NGRN小于8,则将参数复制到x [NGRN]中的最低有效位。 NGRN加1。现在已分配了参数。
如果参数的对齐方式为16,则NGRN会四舍五入到下一个偶数。
如果参数是整数类型,则参数的大小等于16,并且NGRN小于7,则将参数复制到x [NGRN]和x [NGRN + 1]。 x [NGRN]应包含自变量的内存表示形式的低位寻址双字。 NGRN增加2。现在已分配了参数。
如果参数是Composite Type,并且参数的双字大小不超过8减去NGRN,则将参数复制到连续的通用寄存器中,从x [NGRN]开始。传递自变量,就好像它已从双字对齐的地址加载到寄存器中一样,并带有适当顺序的LDR指令,这些指令会从内存中加载连续的寄存器。该标准未规定寄存器中任何未使用部分的内容。 NGRN增加所用寄存器的数量。现在已分配了参数。
NGRN设置为8。
NSAA向上舍入为8或参数类型的自然对齐中的较大者。
如果参数是复合类型,则将参数复制到调整后的NSAA的内存中。 NSAA增加自变量的大小。现在已分配了参数。
如果参数的大小小于8个字节,则参数的大小将设置为8个字节。效果就好像该参数已复制到64位寄存器的最低有效位,而其余位都填充有未指定的值。
该参数将复制到调整后的NSAA的内存中。 NSAA增加自变量的大小。 现在已分配了参数。
可变参数函数
带有可变数量参数的函数的处理方式与上面所述不同,如下所示:
所有复合材料均一视同仁; 没有对HFA或HVA的特殊处理。
不使用SIMD和浮点寄存器。
实际上,将参数分配给虚拟堆栈与遵循规则C.12–C.15相同,其中,堆栈的前64个字节被加载到x0-x7中,而其余的堆栈参数则被正常放置。
返回值
整数值以x0返回。
浮点值将根据需要以s0,d0或v0返回。
HFA和HVA值视情况以s0-s3,d0-d3或v0-v3返回。
根据值返回的类型是否具有某些属性,将对其进行不同的处理。具有所有这些属性的类型,
- 它们是根据C ++ 14标准定义聚合的,也就是说,它们没有用户提供的构造函数,没有私有或受保护的非静态数据成员,没有基类,也没有虚函数,并且
- 他们有一个简单的副本分配运算符,并且
- 他们有一个琐碎的破坏者,
使用以下返回样式:
- 小于或等于8个字节的类型以x0返回。
- 小于或等于16个字节的类型在x0和x1中返回,其中x0包含低8位字节。
- 对于大于16个字节的类型,调用方应保留足够大的内存块并对齐以保存结果。存储块的地址应作为附加参数传递给x8中的函数。被调用方可以在执行子例程的任何时候修改结果存储块。被调用方不需要保留存储在x8中的值。
所有其他类型使用此约定:
调用者应保留足够大小和对齐的内存块以保存结果。存储块的地址应作为附加参数传递给函数x0,如果$this传递x0,则应传递x1。被调用方可以在执行子例程的任何时候修改结果存储块。被调用方以x0返回存储块的地址。
堆
按照ARM提出的ABI,堆栈必须始终保持 16字节对齐。 AArch64包含一项硬件功能,只要SP不按16字节对齐并且完成了相对于SP的加载或存储,它就会生成堆栈对齐错误。
分配4k或更多堆栈值的函数必须确保依次触摸最后一页之前的每一页。此操作可确保没有代码可以“越过” Windows用于扩展堆栈的保护页。通常,触摸是由__chkstk帮助程序完成的,该帮助程序具有自定义的调用约定,该约定将x15中的总堆栈分配除以16。
红色区域
当前堆栈指针正下方的16字节区域保留供分析和动态修补方案使用。该区域允许插入精心生成的代码,该代码在[sp,#-16]中存储两个寄存器,并将它们临时用于任意目的。
函数调用命令
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) 中。或者br x30等同ret指令。