函数栈帧详解(2)

序言

今天说的承接函数栈帧详解(1),具体谈谈MyAdd()函数内部的事情。

样例代码

int MyAdd(int a, int b)
{
	int c = 0
	c = a + b;
	return c;
}

int main()
{
	int x = 0xA;
	int y = 0xB;
	int z = 0;

	z = MyAdd(x, y);
	printf("z = %d\n", z);
	return 0;
}

今天的汇编语言

int MyAdd(int a, int b)
{
001E2EC0  push        ebp  
001E2EC1  mov         ebp,esp  
001E2EC3  sub         esp,0CCh  
001E2EC9  push        ebx  
001E2ECA  push        esi  
001E2ECB  push        edi  
001E2ECC  lea         edi,[ebp-0Ch]  
001E2ECF  mov         ecx,3  
001E2ED4  mov         eax,0CCCCCCCCh  
001E2ED9  rep stos    dword ptr es:[edi]  
001E2EDB  mov         ecx,1EC003h  
001E2EE0  call        001E130C  
	int c = 0;
001E2EE5  mov         dword ptr [ebp-8],0  
	c =a + b;
001E2EEC  mov         eax,dword ptr [ebp+8]  
001E2EEF  add         eax,dword ptr [ebp+0Ch]  
001E2EF2  mov         dword ptr [ebp-8],eax  
	return c;
001E2EF5  mov         eax,dword ptr [ebp-8]  
}
001E2EF8  pop         edi  
001E2EF9  pop         esi  
001E2EFA  pop         ebx  
001E2EFB  add         esp,0CCh  
001E2F01  cmp         ebp,esp  
001E2F03  call        001E1235  
001E2F08  mov         esp,ebp  
001E2F0A  pop         ebp  
001E2F0B  ret 

我们的栈帧图
函数栈帧详解(2)_第1张图片

MyAdd函数栈帧的形成

第一步

00821740  push        ebp  

这条命令是将ebp(也就是栈底)的内容压入栈中,同时栈顶也发生变化
函数栈帧详解(2)_第2张图片
函数栈帧详解(2)_第3张图片

第二步

mov:数据转移指令

00821741  mov         ebp,esp 

该命令的意思是将esp的内容覆盖到ebp中

  • esp的内容直接将ebp的内容给覆盖
  • 该过程没有通过内存,直接通过CPU
  • 函数栈帧详解(2)_第4张图片

函数栈帧详解(2)_第5张图片

那么我们可能会发出疑惑,那栈底怎么办,是不是找不回来了?实际上不是的,上一步我们不是把栈底的内容给保存了吗!!!

第三步

sub:减法命令

00821743  sub         esp,0CCh
                      //0CCh 的大小和你定义的函数的规模有关

该命令的意思是esp减去一定的值,结果放在esp中
函数栈帧详解(2)_第6张图片
到这里我们已近形成了MyAdd()的栈帧了

函数栈帧详解(2)_第7张图片

第四步

int c = 0;
001E2EE5  mov         dword ptr [ebp-8],0  
                      //在ebp-8处在开辟一个空间,将c的值放进去

这和main的变量开辟一样
函数栈帧详解(2)_第8张图片

第五步

c =a + b;
001E2EEC  mov         eax,dword ptr [ebp+8]  
001E2EEF  add         eax,dword ptr [ebp+0Ch]  
001E2EF2  mov         dword ptr [ebp-8],eax 

一条一条分析

001E2EEC  mov         eax,dword ptr [ebp+8]  

把ebp+8放在eax中
那么ebp+8是多少呢?答案就是我们的x值的拷贝
函数栈帧详解(2)_第9张图片
函数栈帧详解(2)_第10张图片

同理

001E2EEF  add         eax,dword ptr [ebp+0Ch] 

这个命令是将ebp+0Ch的内容和eax加起来放到 eax中
ebp+0Ch就是y值的拷贝
函数栈帧详解(2)_第11张图片

函数栈帧详解(2)_第12张图片

001E2EF2  mov         dword ptr [ebp-8],eax 

这条命令是将eax写入到ebp-8,也就是c中
函数栈帧详解(2)_第13张图片
函数栈帧详解(2)_第14张图片

准备返回

第一步

001E2EF5  mov         eax,dword ptr [ebp-8]  

保存返回值
函数栈帧详解(2)_第15张图片

第二步

001E2F08  mov         esp,ebp  

把ebp 覆盖到 esp
这一步也可以称为“释放栈帧”
函数栈帧详解(2)_第16张图片
函数栈帧详解(2)_第17张图片

第三步

pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

001E2F0A  pop         ebp

“弹栈”
将main函数的栈底放在ebp中,esp内容改变
函数栈帧详解(2)_第18张图片

第四步

ret:恢复返回地址,压入eip,类似pop eip命令

001E2F0B  ret 

将之前call的下一个地址放在eip,esp内容改变
函数栈帧详解(2)_第19张图片

第五步

释放临时拷贝的变量

001E1E87  add         esp,8

意思是esp+8放在esp中
函数栈帧详解(2)_第20张图片
现在我们已经返回到MyAdd执行之前了

第六步

001E1E8A  mov         dword ptr [ebp-20h],eax 

接收返回值
将eax的值放到ebp-20(也就是z)

返回的本质

  • 返回到main的栈帧
  • 返回到对应的代码处

总结

  • 函数的的栈帧是由编译器决定的
  • push进去的变量的空间是连续的
  • 0CCh 的大小和你定义的函数的规模有关

为什么函数的的栈帧是由编译器决定的?
我们C语言中很多数据类型,这就是的编译器有能力知道所有类型变量的大小。

有关栈帧的知识大概都说的差不多了,后面我会看看是否还要补充一些知识。

你可能感兴趣的:(c语言)