ARM到C调用约定,注册保存

从我上次编码的arm汇编程序起,我已经有一段时间了,细节上我有点生疏。 如果我从arm调用C函数,我只需要担心保存r0-r3和lr,对吧? 如果C函数使用其他寄存器,是否负责将这些寄存器保存在堆栈中并恢复它们? 换句话说,编译器会为C函数生成代码。 例如,如果我在汇编函数中使用r10,我不必将其值推入堆栈或内存,并在C调用后popup/恢复它,是吗?

这是为arm-eabi-gcc 4.3.0。

我意识到我可以阅读整个EABI,但是那么缩写RTFM就是为了什么,对吧? 🙂

这取决于您正在编译的平台的ABI 。 在Linux上,有两个ARM ABI; 旧的和新的。 AFAIK,新的(EABI)实际上是ARM的AAPCS。 目前完整的EABI定义在ARM的信息中心 。

从AAPCS,§5.1.1 :

  • r0-r3是参数和暂存寄存器; r0-r1也是结果寄存器
  • r4-r8是被保存的寄存器
  • r9可能是一个被调用者保存寄存器或不(在AAPCS的某些变种是一个特殊的寄存器)
  • r10-r11是被调用的保存寄存器
  • r12-r15是特殊寄存器

被叫保存寄存器必须由被叫保存(与主叫保存寄存器相反,主叫保存寄存器)。 所以, 如果这是您正在使用的ABI,则在调用另一个函数(另一个函数负责保存)之前,不必保存r10。

编辑:你使用的编译器没有区别; gcc尤其可以为几个不同的ABIconfiguration,甚至可以在命令行上进行更改。 看看它产生的序言/结尾代码是没有用的,因为它是为每个函数定制的, 而且编译器可以使用其他方式来保存寄存器(例如,把它保存在一个函数的中间)。

在NEON寄存器上添加丢失的信息:

从AAPCS ,§5.1.1核心寄存器:

  • r0-r3是参数和暂存寄存器; r0-r1也是结果寄存器
  • r4-r8是被保存的寄存器
  • r9可能是一个被调用者保存寄存器或不(在AAPCS的某些变种是一个特殊的寄存器)
  • r10-r11是被调用的保存寄存器
  • r12-r15是特殊寄存器

从AAPCS,§5.1.2.1VFP注册使用约定:

  • 必须保留s16-s31(d8-d15,q4-q7)
  • s0-s15(d0-d7,q0-q3)d16-d31(q8-q15)不需要保存

原文:
arm到C-调用约定氖寄存器对保存

对于64位ARM,A64 (来自ARM 64位体系结构的过程调用标准)

A64指令集有三十一个64位的通用(整数)寄存器可见; 这些被标记为r0-r30 。 在64位上下文中,这些寄存器通常使用名称x0-x30 ; 在32位的上下文中,寄存器使用w0-w30指定。 此外,堆栈指针寄存器SP可以与有限数量的指令一起使用。

  • SP堆栈指针
  • r30 LR链接寄存器
  • r29 FP帧指针
  • r19 … r28被保存的寄存器
  • r18平台登记册,如果需要的话; 否则是临时登记。
  • r17 IP1第二个程序内调用临时寄存器(可被单板和PLT代码使用); 在其他时候可以用作临时登记册。
  • r16 IP0第一个程序内呼叫暂存寄存器(可被单板和PLT代码使用); 在其他时候可以用作临时登记册。
  • r9 … r15临时寄存器
  • r8间接结果位置寄存器
  • r0 … r7参数/结果寄存器

前八个寄存器r0-r7用于将参数值传递给子例程,并返回函数的结果值。 它们也可以用来保存例程中的中间值(但通常只在子例程调用之间)。

寄存器r16(IP0)r17(IP1)可以被链接器用作例程和它调用的任何子程序之间的暂存寄存器。 它们也可以在例程中用来保存子程序调用之间的中间值。

注册r18的作用是平台特定的。 如果平台ABI需要专用的通用寄存器来执行程序间的状态(例如线程上下文),那么它应该使用这个寄存器来达到这个目的。 如果平台ABI没有这样的要求,那么它应该使用r18作为一个额外的临时注册。 平台ABI规范必须logging这个寄存器的用法。

SIMD

ARM 64位架构还有另外32个寄存器, v0-v31 ,可供SIMD和浮点运算使用。 寄存器的确切名称将改变,表示访问的大小。

注意:与AArch32不同,在AArch64中,SIMD和浮点寄存器的128位和64位视图不会在较窄的视图中重叠多个寄存器, 因此q1,d1和s1都指向寄存器中的相同条目银行。

前8个寄存器v0-v7用于将参数值传递给子例程,并返回函数的结果值。 它们也可以用来保存例程中的中间值(但通常只在子例程调用之间)。

寄存器v8-v15必须由子程序调用中的被调用者保存; 其余寄存器( v0-v7,v16-v31 )不需要保存(或者应该由调用者保存)。 另外,只需要保存v8-v15中存储的每个值的底部64位; 调用者有责任保留更大的值。

CesarB和Pavel的答案提供了来自AAPCS的报价,但仍然存在未解决的问题。 被叫方是否保存r9? 那么r12呢? 那么r14呢? 此外,答案是非常笼统的,而不是按照要求特定于武装工具链。 这里有一个实际的方法来找出哪些寄存器被保存,哪些不是。

以下C代码包含一个内联汇编块,声称修改寄存器r0-r12和r14。 编译器将生成代码以保存ABI所需的寄存器。

 void foo() { asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14"); } 

使用命令行arm-eabi-gcc-4.7 -O2 -S -o - foo.c并为您的平台添加交换机(例如-mcpu=arm7tdmi )。 该命令将在STDOUT上打印生成的汇编代码。 它可能看起来像这样:

 foo: stmfd sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr} nop ldmfd sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr} bx lr 

请注意,编译器生成的代码会保存并恢复r4-r11。 编译器不保存r0-r3,r12。 它恢复r14(alias lr)纯粹是偶然的,正如我从经验中知道的那样,退出代码也可以将保存的lr加载到r0中,然后执行“bx r0”而不是“bx lr”。 通过添加-mcpu=arm7tdmi -mno-thumb-interwork或使用-mcpu=cortex-m4 -mthumb我们可以获得稍微不同的汇编代码,如下所示:

 foo: stmfd sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr} nop ldmfd sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc} 

再次,r4-r11被保存和恢复。 但是r14(alias lr)没有被恢复。

总结:

  • r0-r3 没有被保存
  • r4-r11被保存
  • r12(alias ip) 不是被保存的
  • r13(别名sp)被保存
  • r14(别名lr) 被保存
  • r15(别名pc)是程序计数器,在函数调用之前设置为lr的值

这至less适用于arm-eabi-gcc的默认设置。 有命令行开关(特别是-mabi开关)可能会影响结果。

至less在Cortex M3架构上,函数调用和中断也是有区别的。

如果发生中断,则会自动将R0-R3,R12,LR,PC推入堆栈,并在返回时自动将POPpopup。 如果在IRQ例程中使用其他寄存器,则必须手动将它们推入/popup到堆栈。

我不认为这个自动PUSH和POP是用于函数调用(跳转指令)。 如果约定表示R0-R3只能用作参数,结果或暂存寄存器,所以在函数调用之前不需要存储它们,因为函数返回后不应该有任何值使用。 但是和中断一样,如果在函数中使用它们,则必须存储所有其他CPU寄存器。