函数的调用过程,栈桢的创建和销毁
我们首先要知道关于函数的调用,我们知道main函数也是要被调用的,在_tmainCRTStartup函数中调用,而_tmainCRTStartup函数是在mainCRTStartup中被调用的。
函数调用过程要为函数开辟栈空间,用于本次函数调用中临时变量的保存,称为函数栈桢。
下面我用一个简单的程序说明函数的调用过程:
#include<stdio.h>
#include<string>
int Add(int x, int y)
{
int sum = 0;
sum = x + y;
return sum;
}
int main()
{
int a = 1;
int b = 2;
int ret = 0;
ret = Add(a, b);
printf("%d", ret);
system("pause");
return 0;
}
在这个程序里,主函数定义了3个局部变量,然后调用了Add函数。三个局部变量都在栈空间上存放。
可是当程序运行起来后,main函数是如何实现对Add函数的调用过程呢?我们转到反汇编一步步分析一下。
下面是main函数的汇编代码(在VS2013平台下):
int main()
{
009253D0 push ebp
009253D1 mov ebp,esp
009253D3 sub esp,0E4h
009253D9 push ebx
009253DA push esi
009253DB push edi
009253DC lea edi,[ebp-0E4h]
009253E2 mov ecx,39h
009253E7 mov eax,0CCCCCCCCh
009253EC rep stos dword ptr es:[edi]
int a = 1;
009253EE mov dword ptr [a],1
int b = 2;
009253F5 mov dword ptr [b],2
int ret = 0;
009253FC mov dword ptr [ret],0
ret = Add(a, b);
00925403 mov eax,dword ptr [b]
00925406 push eax
00925407 mov ecx,dword ptr [a]
0092540A push ecx
0092540B call Add (0921113h)
00925410 add esp,8
00925413 mov dword ptr [ret],eax
printf("%d", ret);
00925416 mov esi,esp
00925418 mov eax,dword ptr [ret]
0092541B push eax
0092541C push 92CC7Ch
00925421 call dword ptr ds:[93019Ch]
00925427 add esp,8
0092542A cmp esi,esp
0092542C call __RTC_CheckEsp (09212E4h)
system("pause");
00925431 mov esi,esp
00925433 push 92CD34h
00925438 call dword ptr ds:[930184h]
0092543E add esp,4
00925441 cmp esi,esp
00925443 call __RTC_CheckEsp (09212E4h)
return 0;
00925448 xor eax,eax
}
0092544A pop edi
}
0092544B pop esi
0092544C pop ebx
0092544D add esp,0E4h
00925453 cmp ebp,esp
00925455 call __RTC_CheckEsp (09212E4h)
0092545A mov esp,ebp
0092545C pop ebp
0092545D ret
1、从main函数的调用开始,需要为main函数创建函数栈桢。
int main()
{
009253D0 push ebp //将ebp压入栈底
009253D1 mov ebp,esp //将esp的值赋给ebp,得到新的ebp
009253D3 sub esp,0E4h //将esp的值减去0E4h,得到新的esp
009253D9 push ebx //从低地址到高地址三次压栈压入三个寄存器ebx、
009253DA push esi //esi、 009253DB push edi //edi
009253DC lea edi,[ebp-0E4h] //加载有效地址
009253E2 mov ecx,39h //将加载到edi的地址重复拷贝ecx次,
009253E7 mov eax,0CCCCCCCCh //即把main函数预开辟的空间全部初始化为0CCCCCCCCh
009253EC rep stos dword ptr es:[edi] //重复拷贝
int a = 1;
009253EE mov dword ptr [a],1 //处理局部变量a,把1赋给a
int b = 2;
009253F5 mov dword ptr [b],2 // 处理局部变量b,把2赋给b
int ret = 0;
009253FC mov dword ptr [ret],0
ret = Add(a, b);
00925403 mov eax,dword ptr [b]
00925406 push eax
00925407 mov ecx
初始化如下,调试可知:2、Add函数的调用,参数传递过程如下:
int ret = 0;
011C540C mov dword ptr [ret],0 //ret的初始化
ret = Add(a, b);
011C5413 mov eax,dword ptr [b] //将b的值赋给eax,压栈eax,形参b的实例化
011C5416 push eax
011C5417 mov ecx,dword ptr [a] //将a的值赋给ecx,压栈ecx,形参a的实例化
011C541A push ecx
011C541B call Add (011C1113h) //调用,压栈call指令的下一条指令的地址,再跳转到Add函数的地方
011C5420 add esp,8
011C5423 mov dword ptr [ret],eax
printf("%d", ret);
继续调试,进入Add函数:
#include<stdio.h>
#include<string>
int Add(int x, int y)
{
011C3860 push ebp //压入ebp
011C3861 mov ebp,esp //将esp的值赋给ebp
011C3863 sub esp,0CCh //将esp的值减去0CCh
011C3869 push ebx
011C386A push esi
011C386B push edi
011C386C lea edi,[ebp-0CCh] //加载有效地址
011C3872 mov ecx,33h
011C3877 mov eax,0CCCCCCCCh //初始化开辟的空间
011C387C rep stos dword ptr es:[edi] //重复拷贝
int sum = 0;
011C387E mov dword ptr [sum],0 //创建sum,并初始化
sum = x + y;
011C3885 mov eax,dword ptr [x] //将形参x的值赋给eax
011C3888 add eax,dword ptr [y] //将形参y的值赋给eax
011C388B mov dword ptr [sum],eax //将相加后的值存储到sum中
return sum;
011C388E mov eax,dword ptr [sum] //将sum的值赋给eax,即将结果存储到寄存器中
}
011C3891 pop edi //出栈
011C3892 pop esi //出栈
011C3893 pop ebx //出栈 011C3894 mov esp,ebp //将ebp的值赋给esp,使esp向下移动
011C3896 pop ebp //出栈,将出栈的内容保存在ebp中,回到main的栈桢
011C3897 ret //出栈一次,并将出栈的内容当做地址,将程序执行跳转到该地址处
跳转到Add函数处,
以上是简单的过程介绍,下面是栈桢创建的模拟过程图: