您的位置:首页 > 其它

函数调用过程——栈帧

2017-12-06 20:25 281 查看
函数的调用是一个过程,那么在函数的调用过程中要开辟栈空间,用来对本次函数的调用中需要的临时变量保存。这块空间叫栈帧。这个过程调用包括将数据和控制从代码的一部分传递到另一部分。过程调用的任务:为过程的局部变量分配空间,并在退出时释放这些空间,俗称保存现场/恢复现场。栈的作用:参数传递、局部变量分配、保存调用的返回地址、保存寄存器以供恢复栈帧:为单个过程分配的那部分栈称为栈帧

这是代码在内存的分布:



一般栈形图如下:



下面,我们用一个简单的函数调用过程来看这个过程。我们写了一段简单的代码:

#include<stdio.h>

int Add(int x,int y)

{

 int z = 0;

 z = x + y;

 return z;

}

int main()

{

 int a = 10;

 int b = 20;

 int ret = Add(a, b);

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

 return 0;

}


对应的反汇编为:

int main()

{

008C17D0  push        ebp  //把ebp压入栈中,方便返回

008C17D1  mov         ebp,esp  //把esp赋值给ebp

008C17D3  sub         esp,0E4h  //产生新的esp

(这一部分的功能就是开辟新的栈帧)



008C17D9  push        ebx 

008C17DA  push        esi 

008C17DB  push        edi 

008C17DC  lea         edi,[ebp-0E4h] 

008C17E2  mov         ecx,39h 

008C17E7  mov         eax,0CCCCCCCCh 

008C17EC  rep stos    dword ptr es:[edi]  

(把栈帧中开辟的空间初始化)

 int a = 10;
008C17EE  mov         dword ptr [a],0Ah  

 int b = 20;


008C17F5  mov         dword ptr ,14h  

(局部变量的创建)



[b]

 int ret = Add(a, b);


008C17FC  mov         eax,dword ptr  

008C17FF  push        eax 

008C1800  mov         ecx,dword ptr [a] 

008C1803  push        ecx 
008C1804  call        _Add (08C10FAh)  //call指令有两个作用把call下一条指令存进去,来方便下一次查找,然后:jmp,跳转到_add

(把中间变量存储)



#include<stdio.h>
[b]int Add(int x,int y)

{


008C16C0  push        ebp 

008C16C1  mov         ebp,esp 

008C16C3  sub         esp,0CCh 

008C16C9  push        ebx 

008C16CA  push        esi 

008C16CB  push        edi 

008C16CC  lea         edi,[ebp-0CCh] 

008C16D2  mov         ecx,33h 

008C16D7  mov         eax,0CCCCCCCCh 

008C16DC  rep stos    dword ptr es:[edi]  

(类似于main函数,自己给自己开辟新的栈帧,然后初始化)



 int z = 0;

008C16DE  mov         dword ptr [z],0  //创建z变量
 z = x + y;

008C16E5  mov         eax,dword ptr [x] 

008C16E8  add         eax,dword ptr [y] 

008C16EB  mov         dword ptr [z],eax  
(进行a,b相加并把结果存在eax中,通过eax寄存器待会a+b的值)



 return z;

008C16EE  mov         eax,dword ptr [z] 

}

008C16F1  pop         edi 

008C16F2  pop         esi 

008C16F3  pop         ebx  //pop 使esp下移

008C16F4  mov         esp,ebp 

008C16F6  pop         ebp 
008C16F7  ret //ret要进行两个步骤,pop把pop的内容保存在ebp中,然后jump到call命令的下一跳。



008C1809  add         esp,8 

008C180C  mov         dword ptr [ret],eax 
 printf("result: %d\n", ret);
008C180F  mov         eax,dword ptr [ret] 

008C1812  push        eax 

008C1813  push        offset string "result: %d\n" (08C6B30h) 

008C1818  call        _printf (08C1325h) 

008C181D  add         esp,8 
 return 0;

008C1820  xor         eax,eax 
}

008C1822  pop         edi 

008C1823  pop         esi 

008C1824  pop         ebx 

}

008C1825  add         esp,0E4h 

008C182B  cmp         ebp,esp 

008C182D  call        __RTC_CheckEsp (08C1118h) 

008C1832  mov         esp,ebp 

008C1834  pop         ebp 

008C1835  ret 

从中我们发现

中间变量存在两个栈帧之间,

形参的读取是从右向左的~如图,先b后a

临时变量存在自己的栈帧中用完,随着栈帧一起释放,

自己的栈帧是自己开辟的。

返回值是通过寄存器如eax带回返回值的。

pc指针及elp存的是当前指令的下一条指令。

我们知道函数是通过call实现跳转,ret进行返回

然后我们观察栈帧发现,call的下一跳指令存在自定义变量的上第2个(指针p+=2),所以只要我们自己定义一个变量,顺着就可以找到call的下一跳指令,然后通过指针对地址进行修改。

同样的,我们可以观察main返回存储在栈帧中的位置我们可以发现,我们可以通过找到形参(这里要是最后一个形参,及最左的),(指针P--)就可以找到。

我们可以在vc6.0上试一下这个程序。

_________________________________________________________________________________

#include<stdio.h>

#include<windows.h>

int  main_ret = 0;

int bug()

{

 int i = 0;

 int *p = &i;

 p += 2;

 *p = main_ret;

 Sleep(1000);

 //system("cls");

 printf("joke,you,now you are in bug!\n\n");

 //system("pause");

 return 1;

}

int Add(int x,int y)

{

 

 int z = 0;

 int *p = &x;

 p--;

 main_ret =*p;

 *p = (int)bug;

 printf("now is in Add\n");

 z = x + y;

 printf("jump out Add\n");

 return z;

}

int main()

{

 printf("begin in main\n");

 int a = 10;

 int b = 20;

 int ret = Add(a, b);

 _asm {

  sub esp, 4;

 }

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

 printf("return to main\n");

 system("pause");

 return 0;

}

_____________________________________________________________________

运行结果如下~

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: