自从我上次使用编码臂装配器以来已经有一段时间了,我对细节有点生疏。如果我从arm调用C函数,我只需要担心保存r0-r3和lr,对吗?
如果C函数使用任何其他寄存器,它是否负责保存堆栈中的那些并恢复它们?换句话说,编译器会生成代码来为C函数执行此操作。
例如,如果我在汇编程序函数中使用r10,我不必将它的值压入堆栈或内存,并在C调用后弹出/恢复它,是吗?
这是针对arm-eabi-gcc 4.3.0。
答案 0 :(得分:66)
这取决于您要编译的平台的ABI。在Linux上,有两个ARM ABI;旧的和新的。 AFAIK,新的(EABI)实际上是ARM的AAPCS。目前完整的EABI定义为here on ARM's infocenter。
被叫方必须保存被叫方保存寄存器(与主叫方保存寄存器的来电保存寄存器相对);所以,如果这是你正在使用的ABI,你不必在调用另一个函数之前保存r10(另一个函数负责保存它)。
编辑:您使用的编译器没有任何区别;特别是gcc可以配置为几个不同的ABI,甚至可以在命令行上进行更改。查看它生成的序言/结尾代码并不是很有用,因为它是为每个函数和量身定制的,编译器可以使用其他方法来保存寄存器(例如,将它保存在一个寄存器中间)功能)。
答案 1 :(得分:22)
在NEON寄存器上添加缺少的信息:
来自the AAPCS,§5.1.1核心登记册:
来自AAPCS,§5.1.2.1VFP寄存器使用惯例:
答案 2 :(得分:16)
对于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位;调用者有责任保留更大的值。
答案 3 :(得分:6)
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开关)可能会影响结果。
答案 4 :(得分:0)
至少在Cortex M3架构中,功能调用和中断也存在差异。
如果发生中断,它将自动将R0-R3,R12,LR,PC压入堆栈,并在返回时从IRQ自动POP返回。如果在IRQ例程中使用其他寄存器,则必须手动将它们推送到堆栈。
我不认为这个自动PUSH和POP是用于函数调用(跳转指令)。如果约定说R0-R3只能用作参数,结果或临时寄存器,那么就不需要在函数调用之前存储它们,因为函数返回后不应该使用任何值。但与中断相同,如果在函数中使用它们,则必须存储所有其他CPU寄存器。