第三篇 函数的调用过程 栈帧的创建和销毁

来源:互联网 发布:hadoop java获取文件 编辑:程序博客网 时间:2024/06/05 03:22
首先我们要了解一些概念。
1.栈(stack)
由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回函数、返回地址等。操作方式类似于数据结构中的栈。
栈向下生成(先定义的地址高,后定义的地址低)
2.堆(heap)
由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
堆向上生成(先定义的地址低,后定义的地址高)
3.文字常量区(只读)
常量字符串存放处。程序结束后由系统释放。运行时检测。
4.程序代码区(只读)
存放函数体(类成员函数和全局函数)的二进制代码
5.全局区(static)
分为已初始化全局数据区(init data)和未初始化全局数据区(uninit data)
存放全局变量、静态数据、常量。程序结束后由系统释放
具体情况如下图:

接下来我们来具体了解栈帧,来看一段代码:
#include<stdio.h>
#include<windows.h>

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

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

int c = myfunction(a, b);

printf("you should run here!,ret:%d\n", c);
getchar();
return 0;
}

由上图我们可以看出,main函数并不是函数启动开始第一个被调用的函数,但它是应用逻辑中的入口函数

函数的调用过程要为函数开辟栈空间,用于函数的调用中临时变量的保存、现场保护。我们把这块空间成为函数栈帧

我们需要了解一些寄存器:
EBP寄存器:基址寄存器,存放了函数栈帧栈底的地址
ESP寄存器:栈顶寄存器,存放了函数栈帧栈顶的地址 S:static
EIP(PC、IP):程序计数器,程序计数器里保存的内容永远是当前正在执行指令的下一条指令的地址(永远指向下一条指令)
EAX寄存器:利用EAX进行函数的返回

对main()函数进行反汇编,反汇编代码如下:

int main()
{
009613D0 push ebp //将ebp压栈
009613D1 mov ebp,esp //使esp的值赋给ebp
009613D3 sub esp,0E4h //给esp减去一个16进制数字0E4h
009613D9 push ebx
009613DA push esi
009613DB push edi
009613DC lea edi,[ebp-0E4h]
009613E2 mov ecx,39h
009613E7 mov eax,0CCCCCCCCh //初始化栈帧开辟的空间为0xCCCCCCCC
009613EC rep stos dword ptr es:[edi] //从edi开始向下重复赋值ecx次,赋值内容为eax的内容
int a = 0xaaaaaaaa;
009613EE mov dword ptr [a],0AAAAAAAAh //处理局部变量a的创建
int b = 0xbbbbbbbb;
009613F5 mov dword ptr [b],0BBBBBBBBh //处理局部变量a的创建

int c = myfunction(a, b);
009613FC mov eax,dword ptr [b]
009613FF push eax //参数压栈,先把b压进去
00961400 mov ecx,dword ptr [a]
00961403 push ecx
00961404 call _myfunction (09610E6h)
00961409 add esp,8 //此地址即将被压入栈中
0096140C mov dword ptr [c],eax

我们用画图可以更清楚的看到main函数栈帧的创建以及参数的传递过程:

执行call指令时按F11,来到下图的地方:
再按F11就来到function函数的执行代码处其反汇编代码如下:

画图表示更清晰:

剩下的就是函数的返回部分:
return z;
00221497 mov eax,dword ptr [ebp-8]
}
0022149A pop edi //出栈
0022149B pop esi //出栈
0022149C pop ebx //出栈,使esp向下移动
0022149D mov esp,ebp //将ebp赋值给esp
0022149F pop ebp //出栈,将出栈的内容保存到ebp回到main函数的栈帧
002214A0 ret //ret指令会使得出栈一次,并将出栈的内容当做地址,将程序执行跳转至此处

继续运行会跳转至main函数中:

总结
在最后,总结一下上述过程中出现过的汇编指令及其作用:
1.call:
通过修改IP实现函数的跳转()
将当前正在执行指令的下一条指令的地址保存起来。
从右往左压栈。
当前正在执行指令的下一条指令的地址是要被压入栈中的。

2. pop:出栈
eg:
pop ebp 出栈至ebp

3.ret:调栈,栈顶回退,从myfunction回退到main。弹出栈顶返回值,将弹出的栈顶返回值存到EIP

假如存入a,b,c,d,e
则在a的上面是b,c,d,e
a的下面是返回值的地址

原创粉丝点击