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

2024-04-29

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

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

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

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

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

我将在内部使用 LR 寄存器的方式function_1就像我之前说过的,我会将其值存储在堆栈中,但如果你仔细观察,function_1 不会调用任何其他子例程,因此这是不必要的。

当使用 ARM 编译器时,该编译器是否有可能决定not将LR存入堆栈?

我在这个网站上读到了有关调用标准的信息信息中心 https://developer.arm.com/docs/ihi0042/g/procedure-call-standard-for-the-arm-architecture-abi-2018q4-documentation


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

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

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

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

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

来源和GCC8.2 -O2 -mapcs-frameGodbolt 的输出 https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScale:14,j:1,lang:___c,selection:(endColumn:2,endLineNumber:15,positionColumn:2,positionLineNumber:15,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:'void+function_1(void)%7B%0A+++//some+code+here%0A%7D%0A%0Avoid+unknown_func(void)%3B%0Avoid+function_2(void)%7B%0A+++function_1()%3B+++//+inlined,+or+IPA+optimized+as+pure+and+not+needing+to+be+called.%0A+++unknown_func()%3B+//+tailcall%0A+++unknown_func()%3B%0A%7D%0A%0Aint+func3(void)%7B%0A++++unknown_func()%3B%0A++++return+1%3B%0A%7D'),l:'5',n:'0',o:'C+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:carmg820,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'1',libraryCode:'1',trim:'1'),fontScale:14,j:1,lang:___c,libs:!(),options:'-O2',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'ARM+gcc+8.2+(Editor+%231,+Compiler+%231)+C',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4,强制它生成 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}

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ARM 调用约定是否允许函数不将 LR 存储到堆栈中? 的相关文章

随机推荐