61

自从我上次编写 arm assembler 以来已经有一段时间了,我对细节有点生疏了。如果我从 arm 调用 C 函数,我只需要担心保存 r0-r3 和 lr,对吗?

如果 C 函数使用任何其他寄存器,它是否负责将这些寄存器保存在堆栈上并恢复它们?换句话说,编译器会为 C 函数生成代码来执行此操作。

例如,如果我在汇编函数中使用 r10,我不必将其值压入堆栈或内存,并在 C 调用后弹出/恢复它,对吗?

这适用于 arm-eabi-gcc 4.3.0。

4

5 回答 5

81

这取决于您正在编译的平台的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 可以为几个不同的 ABI 配置,甚至可以在命令行上进行更改。查看它生成的序言/尾声代码并不是那么有用,因为它是为每个函数量身定制的,并且编译器可以使用其他方式来保存寄存器(例如,将其保存在函数中间)。


术语:“callee-save”是“non-volatile”或“call-preserved”的同义词:什么是被调用者和调用者保存的寄存器?
在进行函数调用时,您可以假设 r4-r11(可能除了 r9)中的值在(调用保留)之后仍然存在,但对于 r0-r3(调用破坏/易失性)则不然。

于 2008-11-04T10:46:02.167 回答
27

32 位 ARM 调用约定由AAPCS指定

AAPCS §5.1.1 核心寄存器:

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

从 AAPCS,§5.1.2.1 VFP 寄存器使用约定:

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

原帖:
arm-to-c-calling-convention-neon-registers-to-save


64 位 ARM 调用约定由AAPCS64指定

通用寄存器部分指定需要保留哪些寄存器。

  • r0 - r7是参数/结果寄存器
  • r9 - r15是临时寄存器
  • r19 - r28是被调用者保存的寄存器。
  • 所有其他(r8r16 - r18r29r30SP)具有特殊含义,有些可能被视为临时寄存器。

SIMD 和浮点寄存器指定了 Neon 和浮点寄存器。

于 2011-03-29T04:44:50.660 回答
20

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

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

  • SP堆栈指针
  • r30 LR 链接寄存器
  • r29 FP 帧指针
  • r19…r28被调用者保存的寄存器
  • r18平台注册(如果需要);否则为临时寄存器。
  • r17 IP1 第二个intra-procedure-call临时寄存器(可以被调用单板和PLT代码使用);在其他时候可以用作临时寄存器。
  • r16 IP0 第一个intra-procedure-call临时寄存器(可以被调用胶合和PLT代码使用);在其他时候可以用作临时寄存器。
  • r9…r15临时寄存器
  • r8间接结果位置寄存器
  • r0…r7参数/结果寄存器

前八个寄存器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 位;调用者有责任保留更大的值。

于 2015-04-13T10:23:23.507 回答
7

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)没有恢复。

总结一下:

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

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

于 2013-08-13T17:45:12.897 回答
0

至少在 Cortex M3 架构上,函数调用和中断也存在差异。

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

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

于 2017-07-13T14:46:20.630 回答