关于栈帧最重要的事——

关于栈帧,我们需要了解一下C/C++程序内存的分配,如图所示:
这里写图片描述

栈区:由编译器自动分配释放,存放为运行函数而分配的局部变量、函数、参数、返回值数据、返回地址等。栈区地址依次逐减,与堆区相对而生。
栈帧:函数的调用过程中为函数开辟栈空间,用于本次函数的调用中临时变量的保存、现场保护。这块栈空间就是函数栈帧。
一个数据结构——栈,栈顶与栈底。
两个操作——push(入栈)与pop(出栈)。
寄存器——EAX、EBX、ECX、EDX (通用寄存器)
EBP 栈底寄存器(基址寄存器)
ESP 栈顶寄存器
EIP 指令寄存器(程序寄存器)
部分汇编——call(每人将当前正在执行指令的下一条指令的地址压入栈 中,并且跳转至目标函数的地址,开始过程调用。)
ret (弹出栈顶地址,将数据放入EIP)
VC++6.0对栈帧进行研究(不同平台下操作,原理相同偏移量不同)

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

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

int main(){
int a=10;
int b=20;
int ret=Add(a,b);
printf("%d\n",ret);
system("pause");
return 0;
} 

将以上代码进行调试,转成汇编语言,打开寄存器与内存。
mian函数及临时变量部分
这里写图片描述
此时寄存器与内存如下这里写图片描述

栈区的情况如下
这里写图片描述

10  int a=10;
00401078   mov         dword ptr [ebp-4],0Ah //a_EAX
11:   int b=20;
0040107F   mov         dword ptr [ebp-8],14h//b_ECX
12:   int ret=Add(a,b);
00401086   mov         eax,dword ptr [ebp-8]
00401089   push        eax          //ESP下移,b_EAX
0040108A   mov         ecx,dword ptr [ebp-4]
0040108D   push        ecx          //ESP下移,a_ECX
0040108E   call        @ILT+0(Add) (00401005)
                                    //ESP下移,存入00401093

Add函数栈帧部分

4:    int Add(int x,int y){
00401020   push        ebp //ESP下移,EBP压栈
00401021   mov         ebp,esp//ESP地址_EBP
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
5:    int z=x+y;
00401038   mov         eax,dword ptr [ebp+8]//a_EAX
0040103B   add         eax,dword ptr [ebp+0Ch]//a+b_EAX
0040103E   mov         dword ptr [ebp-4],eax
6:    return z;
00401041   mov         eax,dword ptr [ebp-4]//  返回EAX

寄存器与内存
这里写图片描述

返回

00401044   pop         edi
00401045   pop         esi
00401046   pop         ebx
00401047   mov         esp,ebp
00401049   pop         ebp
0040104A   ret          //弹出栈顶地址数据,并放入EIP


00401093   add         esp,8//ESP 上移8
00401096   mov         dword ptr [ebp-0Ch],eax
13:   printf("%d\n",ret);
00401099   mov         edx,dword ptr [ebp-0Ch]
0040109C   push        edx 
0040109D   push        offset string "%d\n" (00424024)
004010A2   call        printf (00401200)

这里写图片描述

整个栈帧的调用过程完成。

小扩展:
1.证明形参实例化时由右向左的

int Add(int x,int y){
int*p=&x;
p++;   
*p=20;
int z=x+y;
return z;
}// 21
int Add(int x,int y){
int*p=&x;
p++;
//*p=20;
int z=x+y;
return z;
}// 3

2.函数内部调用函数,返回方式的研究

#include<stdio.h>
#include<windows.h>
void bug(){
printf("hello,i am a bug.\n");
system("pause");
}

int Add(int x,int y){
int*p=&x;
p--;
p=(int)bug;//导致系统奔溃
int z=x+y;
return z;
}

int main(){
int a=1;
int b=2;
int ret=Add(a,b);
printf("%d\n",ret);
system("pause");
return 0;
}

在Add函数中调用bug函数,返回值返回到bug函数中,但是编译器会崩掉,所以调用完bug函数后最终还是要返回到main函数中

这里写图片描述
bug要返回,首先得找自己的返回值
那就先来研究一下函数内部的第一个变量和第一个参数的关系:

void fun(int x)
{
    int y;
    printf("&x: %p\n");
    printf("&y: %p\n");
}

得出x和y 的地址差12
对应于上图中局部变量到00401093的地址差8,所以可以将bug函数的代码改为:

void bug()
{
    int x=0;
    int *p=&x;
    p+=2;
}

但是运行的时候编译器依然会崩掉,原因:
调用bug函数时,是指针跳转过去,没用call指令,也就是没有进行push操作
但是返回的时候执行了ret指令,也就是多pop了一次,即esp变大了一个地址,
所以还需将esp再减去4;
在c语言中插入汇编语言:–asm

   __asm
    {
        sub esp,4;

    }

这样就成功的从bug函数中返回到了main函数中.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值