assembly - ARM 调用约定是否允许函数不将 LR 存储到堆栈?

标签 assembly arm standards calling-convention

正如标题所说,我在理解 ARM 架构的调用约定时遇到了问题。特别是,我仍然很难知道调用子程序时 LR 寄存器会发生什么。

我认为在进入子程序时处理 LR 寄存器的最明显和更安全的方法是将其存储到堆栈中,但该行为并未出现在文档中,因此我想到了以下示例。

我将用 C 语言编写,因为我认为用 C 语言更容易解释。假设你只有两个函数

void function_1(void){
   //some code here
}

void function_2(void){
   //some code here
   function_1();
   //some code here
}

我在 function_1 中使用 LR 寄存器的方式就像我之前说的那样,我会将它的值存储在堆栈中,但如果你仔细观察,function_1 不会调用任何其他子程序,这样就没有必要了。

是否有可能在使用 ARM 编译器时,该编译器会决定将 LR 存储到堆栈中?

我在 infocenter 的这个网站上读到了调用标准

最佳答案

调用约定仅定义哪些寄存器是调用保留的,哪些是调用破坏的,以及在哪里可以找到堆栈参数。

当函数准备好返回时,如何确保其返回地址在某处可用,这 100% 取决于函数。处理这个问题的最简单和有效的方法是将它一直留在 LR 中,在叶函数中。 (一个不调用其他函数的函数:它是调用图/树中的一片叶子)。

实践中的编译器通常会将它留在叶函数的 LR 中,即使禁用了优化。例如,GCC 设置了一个禁用优化的帧指针,但当它知道不需要那么多暂存寄存器以使用 LR 时,它仍然不存储/重新加载 LR。

否则在非叶函数中,普通编译器通常将其存储到堆栈中,但如果他们愿意,他们可以将 R4 保存到堆栈中,然后 mov r4, lr,然后在他们准备好返回时恢复 LR 并重新加载 R4。

如果需要,非租用/非线程安全函数理论上可以将其返回地址保存在静态存储中。

来源和GCC8.2 -O2 -mapcs-frame output from Godbolt ,强制它生成 APCS(ARM 过程调用标准)堆栈帧,即使在不需要时也是如此。 (看起来它与 -fno-omit-frame-pointer 具有类似的效果,后者在优化时默认打开。)

void function_1(void){
   //some code here
}
function_1:
    bx      lr     @ with or without -mapcs-frame
void unknown_func(void);   // not visible to the compiler; can't inline
void function_2(void){
   function_1();   // inlined, or IPA optimized as pure and not needing to be called.
   unknown_func(); // tailcall
   unknown_func();
}
function_2:              @@ Without -macps-frame
    push    {r4, lr}         @ save LR like you expected
    bl      unknown_func
    pop     {r4, lr}         @ around a call
    b       unknown_func     @ but then tailcall for the 2nd call.

或使用 APCS:

    mov     ip, sp
    push    {fp, ip, lr, pc}
    sub     fp, ip, #4
    bl      unknown_func
    sub     sp, fp, #12
    ldm     sp, {fp, sp, lr}
    b       unknown_func
int func3(void){
    unknown_func();
    return 1;               // prevent tailcall
}
func3:           @@ Without -macps-frame
    push    {r4, lr}
    bl      unknown_func
    mov     r0, #1
    pop     {r4, pc}

或使用 APCS:

func3:
    mov     ip, sp
    push    {fp, ip, lr, pc}
    sub     fp, ip, #4
    bl      unknown_func
    mov     r0, #1
    ldmfd   sp, {fp, sp, pc}

由于不需要 thumb 交互操作(使用默认编译选项),GCC 会将保存的 LR 弹出到 PC 中,而不是仅仅返回到 LR 中用于 bx lr

将 R4 与 LR 一起压入可使堆栈按 8 位对齐,这是 IIRC 的默认设置。

关于assembly - ARM 调用约定是否允许函数不将 LR 存储到堆栈?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60927163/

相关文章:

algorithm - 汇编中的 Fletcher 算法

c - NEON 比较

sql - 为什么给别名SQL表使用“AS”?

javascript - JavaScript 保证输出 "[object X]"吗?

assembly - 在 MASM : esi difficulty 中将字符串转换为整数

c - 在汇编代码中查找结构变量的值

assembly - 使用虚拟机学习汇编

c - 'switch' 比 'if' 快吗?

optimization - 快速饱和并在ARM asm中移动两个半字

javascript - W3C 标准中的 document.FORMNAME 是什么?