函数调用栈帧,函数调用栈帧包含哪些内容
Yyds干货库存
前言模块接近C语言的边界,需要一段时间学习。但是,当我们知道了这些知识,我们在C语言的函数中所能看到的不仅仅是外观,还有函数是如何调用的。然而,我的能力有限。以下知识如有不当,请指正。
知识点储备了对函数的初步了解(这里说的函数默认是用户自定义函数)。了解C程序地址空间的基本寄存器。了解汇编语言函数的一些概念功能。大家应该都不陌生,这里就不细说了。让我们看一看。
ret_type fun_name(para1,*)
{
声明;//语句项
}
Ret_type返回类型
Fun_name函数名
Para1函数参数C程序地址空间(关键内存)我们一直说:“全局变量的生命周期就是它们所在的整个程序”,“静态修改变量的生命周期越来越长”,“最重要的临时变量都要被销毁了”。但我们需要知道这是怎么回事。在C语言中,我们创建的每个变量都会有自己的存储类别,就像汽车一般不会停在高楼里,所有的东西都会有自己的集合。
看一看代码来验证它。
#包含stdio.h
#包含stdlib.h
int g _ val1=10
int g _ val2=10
int g _ val3
int g _ val4
int main()
{
const char * str= abcdef
printf(code: %p\n ,main);
printf(只读:%p\n ,str);
printf(init g_val1 : %p\n ,g _ val 1);
printf(init g_val2 : %p\n ,g _ val 2);
printf(uninit g_val2 : %p\n ,g _ val 3);
printf(uninit g_val2 : %p\n ,g _ val 4);
char * P1=(char *)malloc(sizeof(char *)* 10);
char * p2=(char *)malloc(sizeof(char *)* 10);
printf(堆地址:%p\n ,P1);
printf(堆地址:%p\n ,p2);
printf(stack addr : %p\n ,str);
printf(stack addr : %p\n ,P1);
printf(堆栈地址:%p\n ,p2);
返回0;
}
可以看出,局部变量存储在堆栈上,堆栈空间向低位地址方向打开。
相关寄存器函数的调用与CPU中的寄存器有很大关系。下面是一些基础知识。
Eax:通用寄存器,保存临时数据。常用于返回值ebx:通用寄存器,保存临时数据ebp:底部寄存器esp:顶部寄存器eip:指令寄存器,保存当前指令下一条指令的地址。相关汇编语言mov:数据传输指令push:数据放入堆栈,esp堆栈的顶层寄存器也需要改变。pop:数据弹出到指定位置。同时,esp栈顶的寄存器也要改变。sub:减法命令add:加法命令调用:函数调用,1。输入寄信人地址,请按2。跳转到目标函数:ret:恢复返回地址,推入eip,类似于pop eip命令。看了这么多知识,一定觉得很枯燥,认为这和函数栈框架无关。别担心,给你
这里的栈帧,为了方便理解,我们这样看栈空间,所以会多画一些图。
我们知道主函数也是函数,也可以被调用,所以主函数也会形成堆栈框架。
代码示例
int MyAdd(int a,int b)
{
int c=a b;
返回c;
}
int main()
{
int x=0xA
int y=0xB
int z=0;
z=MyAdd(a,b);
printf(z=%d\n ,z);
返回0;
}转到反汇编,打开寄存器。
我复制了汇编代码,我们一步步分析这些东西。
int main()
{
int main()
{
int main()
{
00821E40推送ebp
00821E41 mov ebp,esp
00821E43子esp,0E4h
00821E49推送ebx
00821E4A推送esi
00821E4B推送edi
00821E4C lea edi,[ebp-24h]
00821E4F mov ecx,9
00821E54 mov eax,0CCCCCCCCh
00821 e59 rep stos dword ptr es:[EDI]
00821E5B mov ecx,82C003h
00821E60电话0082130C
int x=0xA
00821E65 mov dword ptr [ebp-8],0Ah
int y=0xB
00821E6C mov dword ptr [ebp-14h],0Bh
int z=0;
00821E73 mov双字指针[ebp-20h],0
z=MyAdd(x,y);
00821E7A mov eax,dword ptr [ebp-14h]
00821E7D推送eax
00821E7E mov ecx,dword ptr [ebp-8]
00821E81推送ecx
00821E82致电008211E5
00821E87添加esp,8
00821E8A mov dword ptr [ebp-20h],eax
printf(z=%d\n ,z);
00821E8D mov eax,dword ptr [ebp-20h]
00821E90推送eax
00821E91推送827BCCh
00821E96致电008213A2
00821E9B添加esp,8
返回0;
00821E9E xor eax,eax
}
00821EA0 pop edi
00821EA1 pop esi
00821EA2 pop ebx
00821EA3添加esp,0E4h
00821EA9凸轮轴位置ebp,esp
00821EAB电话00821235
00821EB0 mov esp,ebp
00821EB2 pop ebp
0821eb3ret EBP指向堆栈底部,esp指向堆栈顶部,eip指向下一个要执行的地址。第一步int x=0xA还没有执行;
01011E65 mov dword ptr [ebp-8],0Ah
//在ebp-8打开一个空间,把x的值放进去
int y=0xB
01011E6C mov dword ptr [ebp-14h],0Bh
//在ebp-14打开一个空间,把Y的值放进去
int z=0;
00821E73 mov双字指针[ebp-20h],0
//在ebp-20处开辟一个空间,把Z的值放进去
可以看出,X,Y,Z的空间是不连续的,这是一种VS保护机制,防止一些程序员猜测对应的地址。
步骤2 00821E7A MOV eax,DWORD PTR [ebp-14H]将EBP-14(即Y)分配给eax。
Eax是一个临时寄存器,它保存临时数据,通常用于返回值。
00821E7D push eaxpush命令将eax的值放入栈中,同时栈顶位置变化4个字节,因为Y是int类型。
推送后堆栈顶部
0821e7emov ecx,Dword PTR [ebp-8]将ebp-8(也就是X)分配给ecx。
0821E81Push ecx同上。ECX的值被压入堆栈,堆栈顶部的位置发生变化。
结论临时变量(自变量的临时副本)的形成是在函数调整之前完成的。参数实例化的顺序是从右到左,参数的空间与调用函数相邻。先说通话命令的功能。
按回邮地址(最重要的)进入目标函数按回邮地址,谁?为什么要压进去?向谁施压?按下一个命令的地址。
为什么要压进去?根本原因是函数被调用后,可能需要返回00821E82调用008211E5。
Jump命令改变eip,转移到目标函数,并调用它。
Jmpqian
Jmp侯
现在我们终于进入了MyAdd()函数,画出我们的堆栈框架图。
由于篇幅有限,先说到这里,下一个再接着讲MyAdd()函数内部的东西。
示例代码int MyAdd(int a,int b)
{
int c=0
c=a b;
返回c;
}
int main()
{
int x=0xA
int y=0xB
int z=0;
z=MyAdd(x,y);
printf(z=%d\n ,z);
返回0;
}今天的汇编语言
int MyAdd(int a,int b)
{
001E2EC0推送ebp
001E2EC1 mov ebp,esp
001E2EC3子esp,0CCh
001E2EC9推送ebx
001E2ECA推送esi
001E2ECB推送edi
001E2ECC lea edi,[ebp-0Ch]
001E2ECF mov ecx,3
001E2ED4 mov eax,0CCCCCCCCh
001 e 2 ed 9 rep stos dword ptr es:[EDI]
001E2EDB mov ecx,1EC003h
001E2EE0呼叫001E130C
int c=0;
001E2EE5 mov双字指针[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
返回c;
001E2EF5 mov eax,dword ptr [ebp-8]
}
001E2EF8 pop edi
001E2EF9 pop esi
001E2EFA pop ebx
001E2EFB添加esp,0CCh
001E2F01凸轮轴位置ebp,esp
001E2F03呼叫001E1235
001E2F08 mov esp,ebp
001E2F0A pop ebp
01e2f0bret我们的堆栈框架图
MyAdd函数栈框架形成的第一步是00821740 push ebp。这个命令是把ebp(也就是栈底)的内容压入栈中,栈顶也是变化的。
步骤2 mov:数据传输指令
0821741 MOVebp,esp该命令的意思是将ESP的内容覆盖到ebp。
esp的内容直接覆盖ebp的内容。这个过程不通过内存,而是直接通过CPU。
那么我们可能会想,栈底呢?我们不能把它拿回来吗?其实不是的。上一步我们不是保存了栈底的内容吗!
步骤3 sub:减法命令
00821743子esp,0CCh
//0CCh的大小和你定义的函数大小有关。这个命令意味着esp减去某个值,结果放入esp。
至此,我们差不多形成了MyAdd()的堆栈框架。
第四步int c=0;
001E2EE5 mov双字指针[ebp-8],0
//在ebp-8处开辟一个空格,把C的值放进去,和开辟main的变量是一样的。
第五步c=a b;
001E2EEC mov eax,dword ptr [ebp 8]
001E2EEF add eax,dword ptr [ebp 0Ch]
01e2f2mov dword ptr [EBP-8],EAX一一分析
01e2eeec mov eax,dword ptr [ebp 8]把ebp 8放在eax,那么ebp 8是什么?答案是我们x值的一个副本。
以同样的方式;以类似的方式
01e2eeef add eax,dword ptr [ebp 0Ch]这个命令是将ebp 0Ch和eax的内容加入eax,ebp 0Ch是y值的副本。
01e2eef2mov dword ptr [ebp-8],eax这个命令是把eax写入ebp-8,也就是c
准备返回第一步001e2ef5moveax,dword ptr [EBP-8]保存返回值。
步骤2 001E2F08 mov esp,ebp涵盖ebp到esp。这一步也可以称为“释放堆栈帧”
第三步:pop:数据弹出到指定位置,esp栈顶寄存器也发生变化。
001E2F0A pop ebp“反弹栈”将ebp中主函数的栈底,esp内容发生变化。
第4步ret:恢复返回地址,按eip,类似于pop eip命令。
01e2f0bret把上一次调用的下一个地址放在eip中,esp的内容发生了变化。
步骤5释放临时复制的变量。
01e1e87add添加esp,8表示esp 8放在esp里。
现在我们又回到了MyAdd被执行死刑之前。
步骤6: 001e1e8amov dword ptr [ebp-20h],eax接收返回值,将EAX的值放入EBP-20(即Z)
回归…的本质
返回给main的堆栈帧被返回给相应的代码。汇总函数的堆栈框架由编译器决定。推入的变量空间是连续的。0CCh的大小与您定义的函数的大小有关。为什么函数的堆栈帧是由编译器决定的?我们的C语言有很多数据类型,也就是我们的编译器有能力知道所有类型变量的大小。
我可能已经说了所有关于堆栈帧的内容。我看看以后还需不需要补充一些知识。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。