原文: https:// manybutfinite.com/post/ journey-to-the-stack/
翻译:RobotCode俱乐部
上篇文章,我们看了函数堆栈是如何工作的,以及在函数“序言”期间栈帧是如何构建的。现在是时候看看逆过程了,栈帧在函数结束时被销毁。继续来看看add函数:
Simple Add Program - add.c
int add(int a, int b)
{
int result = a + b;
return result;
}
int main(int argc)
{
int answer;
answer = add(40, 2);
}
我们在执行第4行,就在a + b赋值到result之后。事情是这样的:
第一个指令是冗余的,有点傻,因为我们知道eax已经等于result,但这是你优化关闭后的结果。然后leave指令运行,以一件事的代价做两件事:它重置esp以指向当前栈帧的起点,然后恢复保存的ebp值。这两个操作在逻辑上是不同的,因此在图中是分开的(图2,3所示),但是如果你使用调试器进行跟踪,它们就会自动发生。
在leave运行之后,将恢复前一个栈帧。add被调用的唯一痕迹,是堆栈顶部的返回地址。它包含add完成后必须运行的main指令的地址。ret指令负责处理它:它将返回地址弹出到eip寄存器中,该寄存器指向要执行的下一条指令。该程序现已返回main,如下:
main将add的返回值复制到本地变量answer中,然后运行自己的“尾声”,这与任何其他栈桢被销毁的过程相同。main的唯一特性是保存的ebp为null,因为它是代码中的第一个栈帧。在最后一步中,执行流程转到C库(libc),返回到操作系统。这里有一个图,展示了函数调用的返回序列。
现在你已经很好地掌握了堆栈的操作方式,下一篇中介绍堆栈缓冲区溢出。
由于本人水平有限,翻译必然有很多不妥的地方,欢迎指正。
同时,欢迎关注下方微信公众号,一起交流学习:)