ElS*_*man 4 assembly cpu-architecture
我试图弄清楚将调用的返回地址存储在寄存器 (RR) 中的架构如何工作(而不是在堆栈上推送和弹出返回地址)。
每次进行嵌套调用时,是否都不会覆盖返回地址寄存器(因此在一次返回后无法返回)?阅读我的作业问题,我应该修改一个汇编程序以使用一个 RR 寄存器来存储调用的返回地址,而不是将它推入和弹出堆栈。我已经搜索了它是如何工作的,但要么那里什么都没有,信息隐藏得很好,要么我的谷歌搜索技能不是那么好。
我不是要求解决问题,但我想知道如何将返回地址存储在一个寄存器中是可行的,在程序中多次调用而不随后将寄存器值存储在堆栈上(这会破坏练习)。
谢谢你的帮助。
是的,在使用“链接寄存器”传递返回地址的 ISA 上,非叶函数必须保存/恢复它们的返回地址,非常类似于他们保存想要在函数内部使用的调用保留寄存器的方式. 即通常在调用堆栈上。
许多 RISC ISA 没有 push/pop 指令,但可以用多条指令完成相同的操作。例如,从堆栈指针中减去以腾出空间,然后在函数入口处保存一些寄存器,包括 LR。然后在返回之前,重新加载寄存器并添加到堆栈指针以恢复调用者的 SP 和任何其他寄存器的值。
叶函数(不进行任何函数调用)可以只保留该寄存器,以便返回地址在它们ret(或调用任何返回指令,例如 MIPS jr $ra- 跳转寄存器到返回地址寄存器)时仍然存在。
例如,查看编译器输出:
void external();
void foo(int *p) {
external();
*p = 0; // defeat tail-call optimization
}
Run Code Online (Sandbox Code Playgroud)
在 Godbolt 编译器浏览器上由 GCC 5.4 为 MIPS-O2 -fno-delayed-branch 编译
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
Run Code Online (Sandbox Code Playgroud)
这不是通常所必需的功能,同一个寄存器之前,在与返回地址返回,这取决于什么样的返回指令的ISA用途。不过,这是典型的,并且可能有助于对某些微体系结构进行分支预测。
32 位 ARM 很有趣,并且具有采用寄存器位域来推送和弹出的微编码push/pop指令。所以这是典型的以push {r4, lr}在函数入口和pop {r4, pc} 为返回指令。(ARM 将程序计数器作为 16 个架构通用寄存器之一。写入它是一个跳转。)r4与链接寄存器一起压入lr保持堆栈对齐,并为您提供一个调用保留寄存器供您使用。
假设不需要递归,您可以发明一个约定,将链接(返回寄存器)存储在不同的寄存器中,具体取决于嵌套级别。
请注意,经典模式下的 IBM 大型机没有堆栈。相反,调用者提供了一个保存区,由 R13 指向,然后在进行调用时,R14 包含返回地址,R15 是被调用函数的基地址。对于递归,每个调用者在调用之前从堆中分配一个新的保存区域。约定是被调用者将 R13 存储在保存区中的适当位置,创建保存区的链接链,称为“链接堆栈”。返回时,被调用者需要在返回之前释放其分配的保存区。