0

我无法理解以汇编语言实现 Tail 调用所需的堆栈操作。

当我们对函数进行 Tail 调用时,我们基本上想用被调用函数的 Activation Frame 覆盖当前的 Activation Frame。

我无法理解完成激活帧切换所需的过程。

RSP 如何更改以便位于正确的地址以及如何保持原始调用函数的 RBP 不发生更改?

4

1 回答 1

2

您只需恢复 RSP 指向返回地址的点(例如pop,您之前保存的任何寄存器,例如 RBP),直到堆栈内存的状态与进入此函数时完全相同。

因此,jmp对另一个函数的 a 将像您的调用者调用该函数一样工作。

(除非您可以将不同的值放在传递参数的寄存器中,如果有的话,也可以放在堆栈上的参数传递空间中。)

在最简单的情况下,整个函数体只是jmp foo,例如https://godbolt.org/z/v57MG6fqW

int foo(int);

int bar(int x){
   return foo(x);
}

使用该 Godbolt 链接,您可以添加一些内容,例如volatile int a = 1;查看 C 编译器如何在最终尾调用的函数中使用堆栈空间。-mno-red-zone使用和/或编译-fno-omit-frame-pointer-O3查看更多功能结尾。

通常,您运行一个正常的函数结尾,就像您要返回一样,然后将call/ret替换为jmp. 诀窍是能够延迟 acall到这一点,在你清理了函数的堆栈帧之后。任何 args 都应该已经被计算并在 arg 传递寄存器或堆栈位置中。

当然,此时 a call/ret在 x86-64 上并不真正合法,因为堆栈对齐是错误的:RSP % 16 == 8在函数入口处,而不是0在 a 之前call。对于 Windows x64 阴影空间也是如此。

于 2022-01-12T19:28:31.950 回答