这里,我们来研究如下代码的栈帧过程。为了初学者理解汇编指令,所以编译环境是在vc++6.0下
#include
#include
int sum(int _a,int _b)
{
int c=0;
c=_a+_b;
return c;
}
int main()
{
int a=10;
int b=20;
int ret=0;
ret=sum(a,b);
printf("%d\n",ret);
system("pause");
return 0;
}
//如下是从vc++6.0中截取的汇编指令
--- f:\vc\a\a.cpp ----------------------------------------
10:
11: int main()
12: {
00401060 push ebp
00401061 mov ebp,esp
00401063 sub esp,4Ch
00401066 push ebx
00401067 push esi
00401068 push edi
00401069 lea edi,[ebp-4Ch]
0040106C mov ecx,13h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi]
13: int a=10;
00401078 mov dword ptr [ebp-4],0Ah
14: int b=20;
0040107F mov dword ptr [ebp-8],14h
15: int ret=0;
00401086 mov dword ptr [ebp-0Ch],0
16:
17: ret=sum(a,b);
0040108D mov eax,dword ptr [ebp-8]
00401090 push eax
00401091 mov ecx,dword ptr [ebp-4]
00401094 push ecx
00401095 call @ILT+0(sum) (00401005)
0040109A add esp,8
0040109D mov dword ptr [ebp-0Ch],eax
18: printf("%d\n",ret);
004010A0 mov edx,dword ptr [ebp-0Ch]
004010A3 push edx
004010A4 push offset string "%d\n" (00424024)
004010A9 call printf (00401200)
004010AE add esp,8
19:
20: system("pause");
004010B1 push offset string "pause" (0042401c)
004010B6 call system (004010f0)
004010BB add esp,4
21: }
004010BE pop edi
004010BF pop esi
004010C0 pop ebx
004010C1 add esp,4Ch
004010C4 cmp ebp,esp
004010C6 call __chkesp (00401280)
004010CB mov esp,ebp
004010CD pop ebp
004010CE ret
--- f:\vc\a\a.cpp ----------------------------------------
1: #include
2: #include
3:
4: int sum(int _a,int _b)
5: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
6: int c=0;
00401038 mov dword ptr [ebp-4],0
7: c=_a+_b;
0040103F mov eax,dword ptr [ebp+8]
00401042 add eax,dword ptr [ebp+0Ch]
00401045 mov dword ptr [ebp-4],eax
8: return c;
00401048 mov eax,dword ptr [ebp-4]
9: }
0040104B pop edi
0040104C pop esi
0040104D pop ebx
0040104E mov esp,ebp
00401050 pop ebp
00401051 ret
注:以下栈帧图是连续的,即可以拼接成一副完整的图,并且,按照图片出现的次序,依次向栈的低地址方向走。
1.
我们知道main函数也是被其它函数调用的,这个函数就是mainCRTStartup(),所以,该函数也有一个栈帧,即运行时堆栈,用esp、ebp维护。
2.
00401060 push ebp //把ebp压入栈顶
00401061 mov ebp,esp //把esp的值给ebp,此时,ebp指向esp的位置
3.
00401063 sub esp,4Ch //esp的值减去4Ch,esp向低地址方向增长
00401066 push ebx //ebx压入栈顶
00401067 push esi //esi压入栈顶
00401068 push edi //edi压入栈顶
4.
00401069 lea edi,[ebp-4Ch] //从[ebp-4Ch] 的地方开始拷贝
0040106C mov ecx,13h //拷贝13h次
00401071 mov eax,0CCCCCCCCh //拷贝的内容为0CCCCCCCCh
00401076 rep stos dword ptr [edi] //每次拷贝双字
在内存中查看:
5.
13: int a=10;
00401078 mov dword ptr [ebp-4],0Ah //0Ah压入[ebp-4]表示的空间中
14: int b=20;
0040107F mov dword ptr [ebp-8],14h //14h压入[ebp-8]表示的空间中
15: int ret=0;
00401086 mov dword ptr [ebp-0Ch],0 //0压入[ebp-0Ch]表示的空间中
在内存中查看:
6.
16:
17: ret=sum(a,b);
0040108D mov eax,dword ptr [ebp-8] //[epb-8]指向的内容b=20放入eax中
00401090 push eax //eax中的内容入栈
00401091 mov ecx,dword ptr [ebp-4] //[ebp-4]指向的内容a=10放入ecx中
00401094 push ecx //ecx中的内容入栈
00401095 call @ILT+0(sum) (00401005) //压入call指令的下一条指令的地址,这样函数才能找到返回地址
7.下面,跳转到了sum函数的内部
4: int sum(int _a,int _b)
5: {
00401020 push ebp //ebp中的内容(即mian函数的基地址)入栈
00401021 mov ebp,esp //把esp的值给ebp,此时ebp指向esp指向的地方
8.下面的指令同main中的指令
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
ret=0
9.
6: int c=0; //sum函数中的局部变量c
00401038 mov dword ptr [ebp-4],0 //把c=0放入[ebp-4]指向的空间中
7: c=_a+_b;
0040103F mov eax,dword ptr [ebp+8] //把[ebp+8]指向的内容放入eax中
00401042 add eax,dword ptr [ebp+0Ch] //把[ebp+0Ch]指向的内容与eax中的内容相加放入eax中
00401045 mov dword ptr [ebp-4],eax //把eax中的内容(即30)放入[ebp-4]指向的空间中(即给c变量赋值)
8: return c;
00401048 mov eax,dword ptr [ebp-4] //把[ebp-4]指向的内容(即30)放入eax中
9: }
在内存中查看c=0
在内存中查看取_a和_b的值
在内存中查看c=30
10.
0040104B pop edi //出栈,内容放入edi中
0040104C pop esi //出栈,内容放入esi中
0040104D pop ebx //出栈,内容放入ebx中
11.
0040104E mov esp,ebp //把ebp的值给esp,此时,esp指向ebp的地方(即保存了main函数的栈基地址处的空间)
sum函数的栈帧全部销毁,注意黄色区域
12.
还原main函数的栈帧
00401050 pop ebp
//出栈,内容放入ebp中。注意,此时栈中的内容就是之前保留的main
//函数的栈基地址,经过这条指针,ebp从新回到main函数的栈基地址处
//main函数的栈帧被还原;并且,esp从步骤11处的地方移到保存了“call指令的下一条指令的地址”处
00401051 ret
为了方便看图,我将之前的标记变成了灰色,注意紫色区域
13.回到call指令下一条指令
0040109A add esp,8
//返回到call值令的下一条指令后,esp移动到指向形参_a的地方,经过这条,esp指向_b形参的后面(请参照步骤12中的图),也就是说形参这块空间被销毁
0040109D mov dword ptr [ebp-0Ch],eax
14.结束
18: printf(“%d\n”,ret);
004010A0 mov edx,dword ptr [ebp-0Ch]
004010A3 push edx
004010A4 push offset string “%d\n” (00424024)
004010A9 call printf (00401200)
004010AE add esp,8
19:
20: system(“pause”);
004010B1 push offset string “pause” (0042401c)
004010B6 call system (004010f0)
004010BB add esp,4
21: }
004010BE pop edi
004010BF pop esi
004010C0 pop ebx
004010C1 add esp,4Ch
004010C4 cmp ebp,esp
004010C6 call __chkesp (00401280)
004010CB mov esp,ebp
004010CD pop ebp
004010CE ret