调用约定仅定义哪些寄存器是调用保留的,哪些寄存器是调用破坏的,以及在哪里可以找到堆栈参数。
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 的默认值。