我们先从一个简单的小程序看起
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("%d", ret);
system("pause");
return 0;
}
在函数的执行期间,我们可以打开调用堆栈
我们可以看到其实main函数并不是第一个被调用的函数,程序在运行时第一个被调用的函数是mainCRTStartup,然后mainCRTStartup 函数在调用 ___tmainCRTStartup 函数,之后__tmainCRTStartup函数在调用main函数,由此可见我们知道函数其实是一级一级调用的,这个过程就叫做函数的调用,在函数调用的过程中会开辟栈空间,用来保护函数参数,这就叫做函数的栈桢。
在正式开始分析函数调用之前,我们先来了解几个寄存器
ebp:保存指向栈底指针的寄存器
esp:保存指向栈顶指针的寄存器
eip/pc/ip:程序计数器(永远指向下一条指令(即下一条指令的地址))
eap:临时寄存器
我们用汇编语言来分析,先来了解几个汇编指令的作用
call:通过修改ip来实现函数跳转(当前正在执行指令的下一条指令的地址保存起来)
jmp:将当前正在执行指令的下一条指令的地址压入栈中
pop:单独为出栈后加寄存器为将值放入寄存器中
ret:弹出栈顶返回值到eap
sub:
_ asm _:写汇编(用c控制汇编)
1.先来看main函数的调用(main函数栈桢的调用)
main函数被-tmainCRTStartup调用,栈是自下往上生长,下面是高地址,上面是低地址,所以main函数的栈桢应在-tmainCRTStartup函数栈桢的上面。
main函数栈桢形成时,ebp指向栈底,之后ebx,esi,ebi相继入栈,此时ebi保存三个寄存器入栈之前的指向地址,然后给main函数预开辟空间,然后实参相继入栈,先a后b
2.Add函数的调用
之后形参压栈,从右往左先b后a
之后call指令将函数跳转到Add函数处,执行Add函数
3.Add函数的创建和返回过程
Add函数先将main函数给Add函数预开辟的空间初始化为0xcccccccch
ebx,esi,ebi相继如何入栈
之后首先入栈并初始化为0之后,获取形参x和y相加将结果保存到z中放在eax中,通过eax带回函数的返回值
开始出栈:esp下移,ebi,esi,ebx相继出栈
将ebp的值赋给esp,即都指向Add函数的栈底,之后出栈将出栈的内容保存在ebp中之后回到main函数的栈桢
至此,函数的调用已经全部完成