在这篇文章之前,我想先一下声明我只是是一个初学者,如有错误,请指出,望评论区保持和谐。
注:本文章介绍栈帧使用的是 VS2013 编译器
不用VS2019是因为有些东西观察不到;
正文开始:
本文使用的源码:
int sum(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = sum(a, b);
printf("%d", c);
}
首先就是怎么调用堆栈
通过调用堆栈可以
当我们走到箭头指向的位置的时候再按下F10,就会看到其实main函数也是由 __tmainCRTStartup 函数调用的.
其实 __tmainCRTStartup函数也是由其他函数调用的.
所以调用关系就是,如图所示
在这之前先介绍以下这些字母代表什么意思
push是压栈
pop是出栈
ebp是栈底指针
esp是栈顶指针
esp和pbp是用来维护函数栈帧的,他们之间的空间就是本次的函数栈帧
既然main函数是由别的函数调用的,所以在调用 main 函数之前 __tmainCRTStartup函数栈帧已经创建好了,如图
现在我们把目光转移到反汇编上
1.main 函数栈帧开辟
push
当进入mian函数就会压栈,把ebp的值压进栈中,随着压栈esp也会指向上面一位,如图:
接着,我们一步步看
mov
mov代表的是把后面的值赋给前面的值,在这里就是表示ebp此时指向了esp指向的位置
如图
在监视窗口,我们也可以看到他们两的值相同了
再接着下面的汇编代码
sub
sub就是前面的值减去后面的值,即esp的值减去0E4h所以esp会变小,即esp会向上移动,如图
可以看到此时ebp和esp之间又有一块区域,这块空间就是为main函数开辟的空间
再之后就是三次push,这个东西与跟本文没什么太大关系,所以先略过…
push三次之后
lea,mov,mov,rep stos
load effective adress
004213CC lea edi,[ebp-0E4h]
004213D2 mov ecx,39h
004213D7 mov eax,0CCCCCCCCh
004213DC rep stos dword ptr es:[edi]
(那个dword就是代表 double word 。而1word代表2字节,所以dword就代表4字节)
即,
这段的作用就是把ebp - 0E4h的地址赋给edi,39h(十六进制)赋给ecx,把0CCCCCCCCh给eax
并且从edi开始的以下39h个4字节都变成eax也就是CCCCCCCC
所以此时内存中如图所示
赋值
int a = 10;
004213DE mov dword ptr [ebp-8],0Ah
int b = 20;
004213E5 mov dword ptr [ebp-14h],14h
int c = 0;
004213EC mov dword ptr [ebp-20h],0
这里就是把 0Ah就是10的十六进制赋给ebp-8下面同理
如图
2.sum 函数栈帧的开辟
c = sum(a, b);
004213F3 mov eax,dword ptr [ebp-14h]
004213F6 push eax
004213F7 mov ecx,dword ptr [ebp-8]
004213FA push ecx
004213FB call 004210FA
00421400 sum esp,8
00421403 mov dword ptr [ebp-20h],eax
传参
004213F3 mov eax,dword ptr [ebp-14h]
004213F6 push eax
004213F7 mov ecx,dword ptr [ebp-8]
004213FA push ecx
这里的把**[ebp-14]的地址的值赋给eax**,并且push(压栈)eax,以下都同理
如图所示
调用sum函数
004213FB call 004210FA
这里的调用取的是sum函数地址的下一个地址,为的是可以让函数用完可以继续向下执行
sum函数内部
int sum(int x, int y)
{
00421460 55 push ebp
00421461 8B EC mov ebp,esp
00421463 81 EC CC 00 00 00 sub esp,0CCh
00421469 53 push ebx
0042146A 56 push esi
0042146B 57 push edi
0042146C 8D BD 34 FF FF FF lea edi,[ebp+FFFFFF34h]
00421472 B9 33 00 00 00 mov ecx,33h
00421477 B8 CC CC CC CC mov eax,0CCCCCCCCh
0042147C F3 AB rep stos dword ptr es:[edi]
int z = 0;
0042147E C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0
z = x + y;
00421485 8B 45 08 mov eax,dword ptr [ebp+8]
00421488 03 45 0C add eax,dword ptr [ebp+0Ch]
0042148B 89 45 F8 mov dword ptr [ebp-8],eax
return z;
0042148E 8B 45 F8 mov eax,dword ptr [ebp-8]
}
这里有很多都是与上面main函数创建栈帧重复的过程,不知道朋友们看到这里是不是又对函数有更加深刻的理解了呢?
int z = 0;
0042147E C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0
这里可以看到把 0 放到了 [ebp-8] 里
如图所示
有人可能要问:为什么这里没看见x,y?
z = x + y;
00421485 8B 45 08 mov eax,dword ptr [ebp+8]
00421488 03 45 0C add eax,dword ptr [ebp+0Ch]
0042148B 89 45 F8 mov dword ptr [ebp-8],eax
其实x,y就在这里
这段代码就是表示把 [ebp+8] 的值赋给eax,把 [ebp+0Ch] (就是+12)的值加到 eax 上,最后再把eax的值放到**[ebp-8]**里
如图所示
return z;
0042148E 8B 45 F8 mov eax,dword ptr [ebp-8]
把ebp +0Ch 的地址所对应 的值(也就是20)加到 eax 中返回去;
由于eax是寄存器,所以不会因为函数销毁而销毁.
3.sum 函数栈帧的销毁
00421491 5F pop edi
00421492 5E pop esi
00421493 5B pop ebx
00421494 8B E5 mov esp,ebp
00421496 5D pop ebp
00421497 C3 ret
这里的 pop 其实和 push 就是相反的步骤把
没进行一次 esp 就要++一次,也就是向下移动一次
如图
00421494 8B E5 mov esp,ebp
这里就是把 ebp 的值赋给 esp
如图
接着继续
00421496 5D pop ebp
由于此时栈顶放的元素是 main 函数的栈底
所以 pop 之后直接 ebp 指向 mian 函数栈底
而 esp 向下走一格
如图
00421497 C3 ret
这里的 ret 这条指令直接返回到 call 的下一条指令.
这是 ret 指令执行之后跳转的位置.
由这张图可以看到,现在是把 8 加到 esp 上去
如图
此时形参销毁.
——————————————————————————————————————
这就是函数栈帧的图文讲解。喜欢这篇文章的话点点赞哦.