栈帧——函数的调用过程

结构化程序的一个最基本的单元就是“函数”或者叫“过程”。在汇编这一层自然也相应的有支持这些概念的指令操作,如栈操作和栈帧的概念。

我们知道,一个由C/C++编译的程序占用的内存分为以下几个部分:

1、栈区(stack)— 由编译器自动分配释放 ,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等  。                                                                                

2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表 。                                                                           

3、全局区(静态区)(static)—存放全局变量、静态数据、常量。程序结束后由系统释放。

4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。

5、程序代码区—存放函数体(类成员函数和全局函数)的二进制代码。

其中比较特殊的是堆区和栈区,二者中间有一块很大的内存,堆区申请内存是往高地址处向上生长,而栈地址则是往下增长的。

所以我们今天要研究的就是栈这块的空间。

我们知道,每一次函数的调用的过程,电脑都要为函数开辟栈空间,用于本次函数中临时变量的保存和现场保护。这块栈的空间就被我们称之为函数栈帧。

            上面说过 ,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。

而栈帧的维护,我们就必须了解以下几个寄存器:

                              ebp(基址寄存器)———指向当前的栈帧的底部(高地址)

                              esp(栈顶寄存器)————指向当前的栈帧的顶部(低地址)

                              eip(指令指针寄存器)——存储着cpu要读取指令的地址,没有它,cpu就无法执行

                              eax,ebx,ecx,edx(数据寄存器)————主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间

                              esi,edi(变址寄存器)———存放存储单元在段内的偏移量


结合此图可以更好地理解栈帧结构

下面,举个栗子:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
   
   
    
    
#include 
    
    
     
     
int mul(int x, int y)
{
	int z = 0;
	z = x*y;
	return z;
}
int main()
{
	int a = 4;
	int b = 5;
	int ret = mul(a, b);
	printf("ret=%d", ret);
	system("pause");
	return 0;
}
    
    
   
   
反汇编如下:
--- c:\users\lcl\documents\visual studio 2013\projects\栈帧\栈帧\mul.c -------------
int main()
{
00FB13D0  push        ebp                        //压栈
00FB13D1  mov         ebp,esp                    //把栈底指针挪到新的位置
00FB13D3  sub         esp,0E4                    //扩展新的栈帧,是栈底和栈顶分离,即给esp减去一个16进制数0E4h,产生新的esp
00FB13D9  push                                   //依次入栈
00FB13DA  push        esi  
00FB13DB  push        edi  
00FB13DC  lea         edi,[ebp-0E4h]             //lea指令:将有效地址传到指定的寄存器     这里即把栈的最低地址load到edi寄存器   
00FB13E2  mov         ecx,39h                    //4*39H=0E4H
00FB13E7  mov         eax,0CCCCCCCCh             //把栈帧预开辟的空间全部初始化为0*CCCCCCCC,未初始化则为常见的“烫烫烫烫”
00FB13EC  rep stos    dword ptr es:[edi]  
 int a = 4;
00FB13EE  mov         dword ptr [a],4            // 局部变量a的创建,dword 通过指针赋值,ptr就是指针,相当于把4赋值给a
 int b = 5;
00FB13F5  mov         dword ptr [b],5            //同上
 int ret = mul(a, b);
00FB13FC  mov         eax,dword ptr [b]          //参数b压栈  注意:先把b的值mov到eax寄存器中
00FB13FF  push        eax                               //然后b先行入栈
00FB1400  mov         ecx,dword ptr [a]          //同上:a接着入栈
00FB1403  push        ecx  
00FB1404  call        _mul (0FB1127h)            //CALL:a1.将下一条指令所在的地址00FB1409入栈;a2.并将子程序的起始地址送入PC  (即CPU的下一                                                            条指令就转去执行子程序)
                                                   即跳转到_mul: 00FB1127  jmp         mul (0FB1480h) //  jmp指令是预示着即将跳转到mul                                                   函数的起始位置00FB1480h
00FB1409  add         esp,8                     //栈顶向上移两个变量,8个字节
00FB140C  mov         dword ptr [ret],eax       //eax寄存器在mul函数中保存了z的值,现在赋给ret
 printf("ret=%d", ret);                                      
00FB140F  mov         esi,esp                   //esp指向esi
00FB1411  mov         eax,dword ptr [ret]       //ptr[ret]=20赋值给eax
00FB1414  push        eax                       //eax入栈
00FB1415  push        0FB5858h                  //不知道为啥
00FB141A  call          dword ptr ds:[0FB9118h] //同上   然后直接跳转到PRINT函数开头      59E7CE70  push        ebp 
 printf("ret=%d", ret);
00FB1420  add         esp,8                     //同上   栈顶向上移
00FB1423  cmp         esi,esp                   //cmp指令 :比较两个操作数的大小,第一个操作数减去第二个操作数 ,不保存结果,只影响标志寄存器(SF,ZF,CF,OF)  
                                                           EXP:mov   ax  ,8      换行   mov    bx, 3    换行   cmp  ax ,bx
                                                           执行后 ax=8,ZF=0 ,PF=1,SF=0,CF=0,OF=0
                                                           其逻辑含义:ZF=1则AX=BX;    CF=1则AX<BX;   CF=0则AX>=BX
00FB1425  call        __RTC_CheckEsp (0FB1140h)  
 system("pause");                               //system函数
00FB142A  mov         esi,esp  
00FB142C  push        0FB5860h  
00FB1431  call        dword ptr ds:[0FB9110h]  
00FB1437  add         esp,4  
00FB143A  cmp         esi,esp  
00FB143C  call        __RTC_CheckEsp (0FB1140h)  
 return 0;                                      //return函数
00FB1441  xor         eax,eax  
}
00FB1443  pop         edi  
00FB1444  pop         esi  
00FB1445  pop         ebx  
00FB1446  add         esp,0E4h  
00FB144C  cmp         ebp,esp  
00FB144E  call        __RTC_CheckEsp (0FB1140h)  
00FB1453  mov         esp,ebp  
00FB1455  pop         ebp  
00FB1456  ret                                   //ending 
--- c:\users\lcl\documents\visual studio 2013\projects\栈帧\栈帧\mul.c -------------
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h >
#include <Windows.h>
int mul(int x, int y)                           //今天的主题——函数调用栈帧
{
00FB1480  push        ebp                       //main函数的栈底压入栈中,注意:它的上一条指令是00FB1409
00FB1481  mov         ebp,esp                   //把调用mul()函数前的栈顶esp作为mul()栈帧的栈底ebp
00FB1483  sub         esp,0CCh                  //esp往下移开辟空间,新的栈帧已经形成    
00FB1489  push        ebx                       //三者依次入栈
00FB148A  push        esi  
00FB148B  push        edi  
00FB148C  lea         edi,[ebp-0CCh]            //将有效地址传给edi寄存器,即取偏移地址
00FB1492  mov         ecx,33h                   //跟前面一样4*33h=它的偏移地址0CCh
00FB1497  mov         eax,0CCCCCCCCh            //初始化
00FB149C  rep stos    dword ptr es:[edi]        //rep指令:重复上面的指令,ecx的值是重复次数;stos指令:将eax的值拷贝esp中(即edi指向的地址)
 int z = 0;
00FB149E  mov         dword ptr [z],0           //创建变量z
 z = x*y;
00FB14A5  mov         eax,dword ptr [x]         //把指针ptr[x]的值赋给eax
00FB14A8  imul        eax,dword ptr [y]         //imul函数:将乘数与被乘数均作为有符号数;即eax*=ptr[y]
                                                   mul函数:将乘数与被乘数均作为无符号数
00FB14AC  mov         dword ptr [z],eax         //把eax的值赋给ptr[z]
 return z;
00FB14AF  mov         eax,dword ptr [z]         //将结果保存到eax寄存器,通过寄存器带回函数的返回值
}
00FB14B2  pop         edi                       //三者依次出栈
00FB14B3  pop         esi  
00FB14B4  pop         ebx  
00FB14B5  mov         esp,ebp                   //把ebp的内容给esp,即esp箭头指向ebp,ebp,esp同时在ebp处
00FB14B7  pop         ebp                       //看第一条指令 ebp指向的是main函数的栈底,然后ebp出栈  (局部变量已经释放)
00FB14B8  ret                                   //将函数的返回值地址返回到eip当中,此时将跳转到上面CALL指令的下一条指令
--- 无源文件 -----------------------------------------------------------------------

*******************************************************************************/
int __cdecl printf (
        const char *format,
        ...
        )
/*
 * stdout 'PRINT', 'F'ormatted                   //输出函数print
 */
{
59E7CE70  push        ebp                        //知识盲区。
59E7CE71  mov         ebp,esp  
59E7CE73  push        0FFFFFFFEh  
59E7CE75  push        59F95998h  
59E7CE7A  push        59F3FEF0h  
59E7CE7F  mov         eax,dword ptr fs:[00000000h]  
59E7CE85  push        eax  
59E7CE86  add         esp,0FFFFFFE4h  
59E7CE89  push        ebx  
59E7CE8A  push        esi  
59E7CE8B  push        edi  
59E7CE8C  mov         eax,dword ptr ds:[59FA8100h]  
59E7CE91  xor         dword ptr [ebp-8],eax  
59E7CE94  xor         eax,ebp  
59E7CE96  push        eax  
59E7CE97  lea         eax,[ebp-10h]  
59E7CE9A  mov         dword ptr fs:[00000000h],eax  
    va_list arglist;
    int buffing;
    int retval = 0;
59E7CEA0  mov         dword ptr [retval],0  


以上大概就是函数调用时栈帧的全部过程了,如有不对,望大佬们批评指正。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值