我无法理解以汇编语言实现 Tail 调用所需的堆栈操作。
当我们对函数进行 Tail 调用时,我们基本上想用被调用函数的 Activation Frame 覆盖当前的 Activation Frame。
我无法理解完成激活帧切换所需的过程。
RSP 如何更改以便位于正确的地址以及如何保持原始调用函数的 RBP 不发生更改?
我无法理解以汇编语言实现 Tail 调用所需的堆栈操作。
当我们对函数进行 Tail 调用时,我们基本上想用被调用函数的 Activation Frame 覆盖当前的 Activation Frame。
我无法理解完成激活帧切换所需的过程。
RSP 如何更改以便位于正确的地址以及如何保持原始调用函数的 RBP 不发生更改?
您只需恢复 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 阴影空间也是如此。