第16部分- Linux ARM汇编 ARM64调用标准

243 阅读9分钟

第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指令。