自从我上次编写 arm assembler 以来已经有一段时间了,我对细节有点生疏了。如果我从 arm 调用 C 函数,我只需要担心保存 r0-r3 和 lr,对吗?
如果 C 函数使用任何其他寄存器,它是否负责将这些寄存器保存在堆栈上并恢复它们?换句话说,编译器会为 C 函数生成代码来执行此操作。
例如,如果我在汇编函数中使用 r10,我不必将其值压入堆栈或内存,并在 C 调用后弹出/恢复它,对吗?
这适用于 arm-eabi-gcc 4.3.0。
自从我上次编写 arm assembler 以来已经有一段时间了,我对细节有点生疏了。如果我从 arm 调用 C 函数,我只需要担心保存 r0-r3 和 lr,对吗?
如果 C 函数使用任何其他寄存器,它是否负责将这些寄存器保存在堆栈上并恢复它们?换句话说,编译器会为 C 函数生成代码来执行此操作。
例如,如果我在汇编函数中使用 r10,我不必将其值压入堆栈或内存,并在 C 调用后弹出/恢复它,对吗?
这适用于 arm-eabi-gcc 4.3.0。
这取决于您正在编译的平台的ABI。在 Linux 上,有两个 ARM ABI;旧的和新的。AFAIK,新的(EABI)实际上是ARM的AAPCS。完整的 EABI 定义目前位于 ARM 的信息中心。
来自AAPCS,§5.1.1:
被调用者必须保存被调用者保存寄存器(与调用者保存寄存器相反,调用者保存寄存器);因此,如果这是您正在使用的 ABI,您不必在调用另一个函数之前保存 r10(另一个函数负责保存它)。
编辑:您使用的编译器没有区别;特别是 gcc 可以为几个不同的 ABI 配置,甚至可以在命令行上进行更改。查看它生成的序言/尾声代码并不是那么有用,因为它是为每个函数量身定制的,并且编译器可以使用其他方式来保存寄存器(例如,将其保存在函数中间)。
术语:“callee-save”是“non-volatile”或“call-preserved”的同义词:什么是被调用者和调用者保存的寄存器?
在进行函数调用时,您可以假设 r4-r11(可能除了 r9)中的值在(调用保留)之后仍然存在,但对于 r0-r3(调用破坏/易失性)则不然。
从 AAPCS,§5.1.2.1 VFP 寄存器使用约定:
原帖:
arm-to-c-calling-convention-neon-registers-to-save
SIMD 和浮点寄存器指定了 Neon 和浮点寄存器。
对于 64 位 ARM,A64(来自 ARM 64 位架构的过程调用标准)
A64 指令集有 31 个 64 位通用(整数)寄存器;这些标记为r0-r30。在 64 位上下文中,这些寄存器通常使用名称x0-x30来引用;在 32 位上下文中,寄存器是使用w0-w30指定的。此外,堆栈指针寄存器SP可以与有限数量的指令一起使用。
前八个寄存器r0-r7用于将参数值传递给子例程并从函数返回结果值。它们也可用于在例程中保存中间值(但通常仅在子例程调用之间)。
寄存器r16 (IP0)和r17 (IP1)可以被链接器用作例程和它调用的任何子例程之间的临时寄存器。它们也可以在例程中用于保存子例程调用之间的中间值。
寄存器r18的作用是特定于平台的。如果平台 ABI 需要一个专用的通用寄存器来承载过程间状态(例如,线程上下文),那么它应该为此目的使用该寄存器。如果平台 ABI 没有这样的要求,那么它应该使用 r18 作为额外的临时寄存器。平台 ABI 规范必须记录此寄存器的使用。
SIMD
ARM 64 位架构还有另外 32 个寄存器v0-v31,可供 SIMD 和浮点运算使用。寄存器的准确名称会随着访问的大小而改变。
注意:与 AArch32 不同,在 AArch64 中,SIMD 和浮点寄存器的 128 位和 64 位视图不会在更窄的视图中与多个寄存器重叠,因此 q1、d1 和 s1 都指寄存器中的相同条目银行。
前八个寄存器v0-v7用于将参数值传递给子例程并从函数返回结果值。它们也可用于在例程中保存中间值(但通常仅在子例程调用之间)。
寄存器v8-v15必须由被调用者跨子例程调用保留;剩余的寄存器(v0-v7, v16-v31)不需要保留(或应该由调用者保留)。此外,只需要保留存储在v8-v15中的每个值的低 64 位;调用者有责任保留更大的值。
CesarB 和 Pavel 的回答提供了 AAPCS 的引述,但未解决的问题仍然存在。被调用者是否保存 r9?r12呢?r14呢?此外,答案非常笼统,并没有按照要求专门针对 arm-eabi 工具链。这是一种实用的方法来找出哪些寄存器是被调用者保存的,哪些不是。
下面的 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(别名 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(别名 lr)没有恢复。
总结一下:
这至少适用于 arm-eabi-gcc 的默认值。有一些命令行开关(特别是 -mabi 开关)可能会影响结果。
至少在 Cortex M3 架构上,函数调用和中断也存在差异。
如果发生中断,它将自动将 R0-R3、R12、LR、PC 压入堆栈,并在从 IRQ 返回时自动 POP。如果您在 IRQ 例程中使用其他寄存器,则必须手动将它们推送/弹出到堆栈中。
我不认为这种自动 PUSH 和 POP 是为函数调用(跳转指令)而制作的。如果约定说 R0-R3 只能用作参数、结果或暂存寄存器,则无需在函数调用之前存储它们,因为在函数返回之后不应再使用任何值。但是与在中断中一样,如果您在函数中使用它们,则必须存储所有其他 CPU 寄存器。