要想深度的理解函数,我们就得深入的研究一下函数的调用过程。这个过程要为函数开辟栈空间,用于本次函数的调用中临时变量的保存、现场保护,这块栈空间我们称之为函数栈帧。
首先,我们需要了解一下内存的划分:
其次我们需要了解一下栈(stack):
栈遵循的是”先进后出“或”后进先出“;
入栈:push
出栈:pop
下来就是CPU了,我们需要知道一些相关的寄存器:
通用寄存器:EAX、EBX、ECX、EDX
EIP(PC):程序计数器(当前正在执行指令的下一条指令的地址)
ESP:栈顶寄存器(存放栈顶指针)
EBP:栈底寄存器(存放栈底指针)
为了验证这个过程,我们在VC6.0环境下对一个程序进行研究,下面为源代码:
#include<stdio.h>
int add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = add(a, b);
printf("ret=%d\n", ret);
return 0;
}
下面我们转到汇编来看,首先是main函数栈帧的创建
接下来我们再看add函数的调用,
这里就涉及到了汇编中call的使用,先来介绍一下call:
①保存当前指令的下一条指令的地址(将返回值地址保存到栈里,保存的目的是为了恢复);
②跳转至目标函数的入口地址处(修改EIP实现)(jmp);
执行call指令的时候按F11,就到了这里:
再按F11就进入add函数的执行代码处了:
剩下的就是函数返回部分了,
我们需要先了解汇编中的ret:
①将当前保存的函数返回值地址出栈;
②用弹出来的数据修改EIP;
此时EIP寄存器为:
返回到call的下一条指令地址处,
这里我们不对printf研究,整个函数的调用过程以及栈帧的创建与销毁就是这些了,
注:栈帧的销毁不是把全部数据清零,而是将这块区域失效,可覆盖。