栈帧与函数的调用

测试环境: vc2008

1、什么是栈

在计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但栈这个容器必须遵守一条规则:先入栈的数据后出栈,多多少少像叠起来的一叠书,先叠上去的书在最下面,因此最后才能取出。
* 程序栈的简图

这里写图片描述
这里的ebp表示栈低寄器,esp表示栈顶寄存器,在栈上压入数据会导致esp减少,弹出数据会使得esp增大。相反,直接减小esp的值相当于开辟空间,增大esp的值相当于回收空间。

2、一个很常见的活动记录
  • 活动记录
    这里写图片描述
3、我们来看一个看例子

接下来,我们用一个简单的程序来说明函数的调用以及栈的布局

 #include <stdio.h>

int fun(int a, int b)
{
    int z = a + b;

    return z;
}


int main()
{
    int a = 0xaaaaaaaa;
    int b = 0xbbbbbbbb;
    int c = fun(a, b);

    printf("c = %d", c);

    return 0;
}

这里我们直接进行调试,通过汇编代码能看的更去清楚。

我们先从main函数开始
这里写图片描述
这里需要强调的是call命令,它有两个作用:
* 把当前指令的下一条指令的地址压入栈中*
* 跳转到函数体执行
到这里可能有人会想,cpu怎么知道我这会执行到那条代码,它是怎样计算的呢,在这里我们还需要知道一个寄存器,EIP寄存器(也叫程序计数器),它总是指向当前指令的下一条指令的地址。
下来我们进入fun函数体

这里写图片描述

这样我就进入到了fun函数内部,当fun函数执行完时,执行

011013DD  mov         esp,ebp 
011013DF  pop         ebp  
011013E0  ret              

使esp=sbp; 然后让 old ebp出栈,至此ebp就回到了main函数的ebp,ret从栈中去出返回地址,并跳到此处。

01101429  add         esp,8 

紧接着又执行了这句,使esp指向main函数的栈顶。

fun函数的这段汇编代码基本分为下面几步:

1.保存edp,让edp指向目前的栈顶

00E913B0  push        ebp  
00E913B1  mov         ebp,esp 

2.在栈上开辟一块空间

00E913B3  sub         esp,0CCh 

3.保存ebx,esi,edi寄存器

00E913B9  push        ebx  
00E913BA  push        esi  
00E913BB  push        edi  

4.加入调试信息

00E913BC  lea         edi,[ebp-0CCh] 
00E913C2  mov         ecx,33h 
00E913C7  mov         eax,0CCCCCCCCh 
00E913CC  rep stos    dword ptr es:[edi] 

5.返回z,在这里是返回值通过eax寄存器传回

00E913D7  mov         eax,dword ptr [z] 

6.从栈上恢复edi,esi,ebx寄存器

00E913DA  pop         edi  
00E913DB  pop         esi  
00E913DC  pop         ebx  

7.恢复进入函数前的esp和edp

00E913DD  mov         esp,ebp 
00E913DF  pop         ebp  

8.使用ret指令返回

00E913E0  ret 

特别注意:main函数也是一个被调函数,因此调用它的过程和调用fun函数基本类似

4、通过栈帧修改ret返回的地址

这里我们调用fun函数后,不让ret返回到main函数,而是修改它的地址,让他来到bug函数,将栈中存的返回地址保存到全局变量ret中,执行完bug函数后,让函数在返回main函数
测试环境为vc6.0

#include <stdio.h>
#include <windows.h>

void *ret = NULL;

void bug()
{
    int first = 0;
    int *p = &first;
    p+=2;
    *p= (int)ret;
    printf("I am a bug...\n");
}
int myfun(int x,int y)
{
    int *p = &x;
    p--;
    ret = *p;
    *p = bug;

    printf("run myfun...!\n");

    return 0;

}
int main()
{

    int a = 0xaaaaaaaa;
    int b = 0xbbbbbbbb;
    int c;
    printf("main run...\n");
    c = myfun(a,b);
    printf("you should run here!\n");
    printf("res : %d\n",c);

    system("pause");

    return 0;
}

执行结果如图:
这里写图片描述

但是一按回车就会出错,原因是call命令会把当前命令的下一条指令的地址压入栈中,执行完后会跳转到函数体。但是我们调用BUG函数时没有通过call命令调用,因此,我们少push一次,按回车后的esp应该指向的是main-esp-4;因此我们需要在main函数里调用完fun函数后将esp+4,保持栈帧平衡,

如下:

#include <stdio.h>
#include <windows.h>

void *ret = NULL;

void bug()
{
    int first = 0;
    int *p = &first;
    p+=2;
    *p= (int)ret;
    printf("I am a bug...\n");
}
int myfun(int x,int y)
{
    int *p = &x;
    p--;
    ret = *p;
    *p = bug;

    printf("run myfun...!\n");

    return 0;

}
int main()
{

    int a = 0xaaaaaaaa;
    int b = 0xbbbbbbbb;
    int c;
    printf("main run...\n");
    c = myfun(a,b);

    __asm
    {
        sub esp, 4;
    }

    printf("you should run here!\n");
    printf("res : %d\n",c);

    system("pause");

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值