返回地址寄存器如何在不将返回地址存储在堆栈上的处理器架构中工作?

ElS*_*man 4 assembly cpu-architecture

我试图弄清楚将调用的返回地址存储在寄存器 (RR) 中的架构如何工作(而不是在堆栈上推送和弹出返回地址)。

每次进行嵌套调用时,是否都不会覆盖返回地址寄存器(因此在一次返回后无法返回)?阅读我的作业问题,我应该修改一个汇编程序以使用一个 RR 寄存器来存储调用的返回地址,而不是将它推入和弹出堆栈。我已经搜索了它是如何工作的,但要么那里什么都没有,信息隐藏得很好,要么我的谷歌搜索技能不是那么好。

我不是要求解决问题,但我想知道如何将返回地址存储在一个寄存器中是可行的,在程序中多次调用而不随后将寄存器值存储在堆栈上(这会破坏练习)。

谢谢你的帮助。

Pet*_*des 5

是的,在使用“链接寄存器”传递返回地址的 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保持堆栈对齐,并为您提供一个调用保留寄存器供您使用。


rcg*_*ldr 5

假设不需要递归,您可以发明一个约定,将链接(返回寄存器)存储在不同的寄存器中,具体取决于嵌套级别。

请注意,经典模式下的 IBM 大型机没有堆栈。相反,调用者提供了一个保存区,由 R13 指向,然后在进行调用时,R14 包含返回地址,R15 是被调用函数的基地址。对于递归,每个调用者在调用之前从堆中分配一个新的保存区域。约定是被调用者将 R13 存储在保存区中的适当位置,创建保存区的链接链,称为“链接堆栈”。返回时,被调用者需要在返回之前释放其分配的保存区。