函数调用过程——栈帧结构

首先我们了解一下内存结构


由上图可以知道,栈是由高地址到低地址,而函数的调用就是在栈上开辟一段空间供函数使用;其次我们要了解几个寄存器,

esp:寄存器存放当前线程的栈顶指针
ebp:寄存器存放当前线程的栈底指针

eip(pc):寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从eip寄存器中读取下一条指令的内存地址,然后继续执行。

接下来我们来了解一下函数调用过程中的栈帧结构。


#include <stdio.h>
int main()
{
 int a=0xAAAAAAAA;
 int b=0xBBBBBBBB;
 int ret=myadd(a,b);
 printf("you should run here!: %d\n",ret);
 return 0;

}
 int myadd(int x,int y)
 {
  int z=x+y;
  return z;
 }

首先,我们发现,main()函数并不是第一个被调用的函数,实际上要先被mainCRTStartup()函数调用。


接下来将程序翻译成汇编

3:    {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,4Ch
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-4Ch]
0040102C   mov         ecx,13h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
4:     int a=0xAAAAAAAA;
00401038   mov         dword ptr [ebp-4],0AAAAAAAAh
5:     int b=0xBBBBBBBB;
0040103F   mov         dword ptr [ebp-8],0BBBBBBBBh
6:     int ret=myadd(a,b);
00401046   mov         eax,dword ptr [ebp-8]
00401049   push        eax
0040104A   mov         ecx,dword ptr [ebp-4]
0040104D   push        ecx
0040104E   call        @ILT+0(_myadd) (00401005)
00401053   add         esp,8
00401056   mov         dword ptr [ebp-0Ch],eax
7:     printf("you should run here!: %d\n",ret);
00401059   mov         edx,dword ptr [ebp-0Ch]
0040105C   push        edx
0040105D   push        offset string "you should run here!: %d\n" (0042201c)
00401062   call        printf (004010e0)
00401067   add         esp,8
8:     return 0;
0040106A   xor         eax,eax
9:
10:   }
0040106C   pop         edi
0040106D   pop         esi
0040106E   pop         ebx
0040106F   add         esp,4Ch
00401072   cmp         ebp,esp
00401074   call        __chkesp (00401160)
00401079   mov         esp,ebp
0040107B   pop         ebp
0040107C   ret

然后具体分析。

1、在栈中由高地址到低地址开辟空间,创建局部变量a,b



2、将形参实例化,然后压入栈中,先压b。



3、跳转至myadd函数内。


4、调用结束后弹出。


5、过程调用结束。



至此,通过图例演示了函数调用的过程,栈帧结构的形成和释放(即临时变量的形成和释放)。


小练习:了解了栈帧结构,我们是否可以通过a的地址去更改b的值和返回值的地址?

1、通过a的地址修改b的值

#include <stdio.h>
#include <windows.h>
int main()
{
 int a=10;
 int b=15;
 int ret=0;
 ret=myadd(a,b);
 printf("you should run here!: %d\n",ret);
 system("pause");
 return 0;

}
 int myadd(int x,int y)
 {
  int z=0;
  int *p=&x;
  p++;
  *p=20;
  z=x+y;
  return z;
 }

2、修改返回值的地址

#include <stdio.h>
#include <windows.h>
 void bug();
int main()
{
 int a=10;
 int b=15;
 int ret=0;
 printf("main run here....\n");
 ret=myadd(a,b);
 printf("you should run here!: %d\n",ret);
 system("pause");
 return 0;

}
 int myadd(int x,int y)
 {
  int z=0;
  int *p=&x;
  p--;
  *p=(int)bug;
  z=x+y;
  printf("myadd run here....\n");
  return z;
 }

 void bug()
 {
  printf("i am here!!!");
  system("pasue");
 }

注:这里程序运行结束后会崩掉,为什么呢?

因为调用bug函数时,时指针跳转过去的,过程中,没有push,但返回的时候有pop,所以多了一次pop,栈结构不平衡了。


猜你喜欢

转载自blog.csdn.net/weixin_40246808/article/details/80271924
今日推荐