函数调用与栈帧

3.  利用下述程序理解函数调用时创建栈帧的过程

#include<stdio.h>

#include<windows.h>

 

void *main_ret =NULL;

 

int bug()

{

int first = 0;

int *p =&first;

p+=2;

 

*p =(int)main_ret;

 

Sleep(1000);

printf("haha,I catch you!, I am a bug!\n");

Sleep(1000);

return 0;

}

 

int myadd(int x,int y)

{

int *p = &x;

int z=0;

p--;

 

printf("beginmyadd...\n");

main_ret=(void*) *p;

 

*p =(int) bug;

 

z = x + y;

printf("endmyadd...\n");

return z;

}

 

void mytest(intx, int y)

{

int z = 0;

int *p = &x;

int *q = &z;

 

p--;

q+=2;

 

printf("%#x,%#x\n", p, q);

}

 

 

 

int main()

{

 

int a =0xAAAAAAAA;

int b =0xBBBBBBBB;

int c = 0;

 

printf("beginmain...\n");

//mytest(a, b);

 

c = myadd(a, b);

_asm{

sub esp,4

}

 

printf("youshould run here!\n");

printf("result:%d\n", c);

printf("endmain...\n");

system("pause");

return 0;

}

函数调用与栈帧

以下是该程序的部分汇编代码,通过该汇编代码,理解函数调用时创建栈帧的过程

因为main函数也是被调用的,所以也会创建栈帧,如上图所示

初始时,栈底指针和栈底指针指向main函数的栈帧起止处。

int a = 0xAAAAAAAA;

(1)00401208  mov         dword ptr[ebp-4],0AAAAAAAAh 

 int b= 0xBBBBBBBB;

(2)0040120F  mov         dword ptr[ebp-8],0BBBBBBBBh

          int c = 0;

(3)00401216  mov         dword ptr [ebp-0Ch],0

上述三条语句,表示a,b,c放置的位置如上图所示

 

c = myadd(a, b);

(4)0040122A  mov         eax,dword ptr [ebp-8]   //将b放入寄存器eax中

(5)0040122D  push        eax    //将eax压入栈中

push的作用:栈顶指针下移,将操作数压入栈顶,此句表示将b压入栈顶,见图中(5)

(6)0040122E  mov         ecx,dword ptr [ebp-4]

(7)00401231  push        ecx  //同(4),(5),将a压入栈中,见图中(7)

(8)00401232  call        @ILT+15(myadd)(00401014)

执行到call语句时,按f11进入call函数。

call的作用有两个:第一,将正在执行指令的下一条指令的地址(00401237)压入栈中

                  第二,跳到调用函数处,见下述(9

00401237   add        esp,8

0040123A   mov        dword ptr [ebp-0Ch],eax

 

(9)00401014  jmp         myadd (004010d0)

 //004010d0表示调用函数的入口地址,程序要执行myadd函数,寄存器eip的内容要存有下一条要执行指令的地址,所以,执行语句(9)时,寄存器eip的内容变为004010d0

myadd(a, b)

{

(10)004010D0  push        ebp  //该语句表示将main函数的栈底指针内容压入栈中,见图中(10)

(11)004010D1  mov         ebp,esp

 //该语句是将栈底指针的地址传给栈顶指针,所以栈顶,栈底指针指向同一处,见图中(10),(11)

(12)004010D3  sub         esp,48h  //表示栈顶指针减去48h,见图中(12)

此时表示myadd函数栈帧已经创建好

int *p = &x;

(13)004010E8  lea         eax,[ebp+8]

(14)004010EB  mov         dword ptr [ebp-4],eax

//13)(14)两句表示创建好指针变量p,并将上述x的地址赋给p

int z=0;

004010EE   mov        dword ptr [ebp-8],0//创建好变量z

p--; //此时p指向x的下一个地址,即main:ret处

004010F5   mov        ecx,dword ptr [ebp-4]

004010F8   sub        ecx,4

004010FB   mov        dword ptr [ebp-4],ecx

main_ret =(void*) *p;  //将p指向的内容保存在去全局变量main_ret中

0040110B   mov        edx,dword ptr [ebp-4]

0040110E   mov        eax,dword ptr [edx]

00401110   mov        [main_ret (00429c64)],eax

*p =(int) bug;  //将p指向的内容改为bug函数的入口地址,即图中main:ret变为bug函数的入口地址

00401115   mov        ecx,dword ptr [ebp-4]

00401118   mov        dword ptr [ecx],offset @ILT+5(bug) (0040100a)

z = x + y; //将x + y即a+b得结果赋给变量z,将图中(15)处

0040111E   mov        edx,dword ptr [ebp+8]

00401121   add        edx,dword ptr [ebp+0Ch]

00401124   mov        dword ptr [ebp-8],edx

return z;

00401134   mov        eax,dword ptr [ebp-8] //返回z值时,是先将该值保存到寄存器eax中

}  //此时myadd函数的调用完成

接下来就是销毁myadd函数栈帧的过程:

00401144   mov        esp,ebp 

//先将ebp的值赋给esp,即栈顶,栈底指针此时都指向myadd函数栈帧的起始处

00401146   pop        ebp

//pop:将栈顶指针中的内容弹出到ebp中,栈顶指针上移,即栈底指针重新指向main函数的起始处,而//栈顶指针指向图中的mainret处,即后来内容被修改为bug函数入口地址处的位置

00401147   ret

//ret:将栈顶指针指向的内容弹出到寄存器eip中,栈顶指针esp上移,见图中的(16

//因为寄存器eip中的内容时程序要执行的下一条指令,所以执行完ret后,程序开始执行调用bug函数

0040100A   jmp        bug (00401030) //bug函数的入口地址为00401030,所以eip的内容变为00401030

 

 

int bug()

{

00401030   push       ebp

//myadd函数的调用类似,将main函数的ebo压入栈顶,栈顶指针下移,见图中(17

00401031   mov        ebp,esp

//栈底指针也指向栈顶处,将图中(18

00401033   sub        esp,48h//栈顶指针减48h,栈顶指针下移,见图中(18)

此处,创建好bug函数的栈帧

 

int first = 0; //bug函数的栈帧中创建变量first

00401048   mov        dword ptr [ebp-4],0

      int *p= &first;  //bug函数的栈帧中创建指针变量p,并指向first

0040104F   lea        eax,[ebp-4]

00401052   mov        dword ptr [ebp-8],eax

  p+=2; //p上移两个单元

00401055   mov        ecx,dword ptr [ebp-8]

00401058   add        ecx,8

0040105B   mov        dword ptr [ebp-8],ecx

*p = (int)main_ret;

//p指向的内容改为)main_ret,即main函数在调用myadd函数时的下一条指令地址

0040105E   mov        edx,dword ptr [ebp-8]

00401061   mov        eax,[main_ret (00429c64)]

00401066   mov        dword ptr [edx],eax

}//此时bug函数调用结束

004010AC   mov        esp,ebp  //esp上移到ebp处,见图中(20

004010AE   pop        ebp  //弹出栈顶内容到ebp中,栈底指针指向main函数栈帧的起始处

004010AF   ret  //bug函数的栈帧销毁

开始返回到main函数继续执行调用myadd函数之后的指令:

00401237   add        esp,8

0040123A   mov        dword ptr [ebp-0Ch],eax

……..

执行结束!

 

运行结果:

函数调用与栈帧


总结:

1. 形参实例化的变量在两栈帧之间

2. 形参实例化的变量从右向左开始创建

3. 每调用一次函数形成一个栈帧,函数自身形成自己的栈帧,函数定义的变量在自己的栈帧中,这些变量的生命周期随栈帧结束而释放,所以称之为临时变量

4. 常规情况下,函数的返回值以寄存器的形式返回。