我正在尝试弄清楚将调用的返回地址存储在寄存器(RR)中的体系结构将如何工作(与将返回地址压入并弹出堆栈)相对。
每次进行嵌套调用时,返回地址寄存器是否都将被覆盖(因此,一次返回就不可能返回)?阅读我的作业问题,我应该修改一个汇编程序以使用一个RR寄存器来存储调用的返回地址,而不是将其压入并弹出堆栈。我已经搜索了它的工作方式,但是要么没有东西了,信息被很好地隐藏了,要么我的谷歌搜索技能不是那么好。
我不是要解决的问题,但是我想知道在程序中多次调用如何将返回地址存储在一个寄存器中而不在随后将寄存器值存储在堆栈中是如何可行的(这会破坏点的练习)。
感谢您的帮助。
是的,在使用“链接寄存器”传递返回地址的ISA上,非叶子函数必须保存/恢复其返回地址,这与他们保存要在内部使用的保留呼叫的寄存器的方式非常相似功能。即通常在调用堆栈上。
许多RISC ISA没有推送/弹出指令,但是可以使用多个指令完成相同的操作。例如从堆栈指针中减去以腾出空间,然后在函数入口保存一些寄存器,包括LR。然后在返回之前,重新加载寄存器并添加到堆栈指针以恢复调用者的SP值以及其他任何寄存器。
叶子函数(不进行任何函数调用)可以只保留该寄存器,因此当它们ret
(或任何返回指令被调用,例如MIPS jr $ra
-跳转寄存器)时,返回地址仍然存在到返回地址寄存器)。
例如,查看编译器输出:
void external();
void foo(int *p) {
external();
*p = 0; // defeat tail-call optimization
}
由GCC 5.4-O2 -fno-delayed-branch
on the Godbolt compiler explorer编译为MIPS)>
函数必须返回的地址与之前所在的寄存器相同,这取决于ISA使用哪种类型的返回指令。它是典型的,但可能有助于某些微体系结构的分支预测。foo(int*): addiu $sp,$sp,-32 # reserve 32 bytes of stack space (MIPS calling convention I think guarantees some "shadow space" for callees) sw $31,28($sp) # $31 is MIPS's $ra return address reg sw $16,24($sp) # $16 is a call-preserved register move $16,$4 # save p for later use jal external nop # branch-delay slot lw $31,28($sp) # reload return address sw $0,0($16) # *p = 0 lw $16,24($sp) # restore caller's $16 addiu $sp,$sp,32 # restore stack j $31 # jump to return address nop # branch delay slot
通常,不是
32位ARM很有趣,并且具有微码的push
/ pop
指令,这些指令采用寄存器的位域进行压入和弹出操作。因此,通常在函数输入时输入push {r4, lr}
,在返回指令中输入pop {r4, pc}
as
r4
与链接寄存器lr
一起按下可保持堆栈对齐,并为您保留调用注册玩。假设不需要递归,您可以发明一个约定,即链接(返回寄存器)存储在不同的寄存器中,具体取决于嵌套的级别。