程序运行之栈空间

mac2022-06-30  73

一般来讲,应用程序使用的内存空间里有如下的默认区域:

1 栈:用于维护函数调用的上下文。栈通常在用户空间的最高地址出分配,通常有数兆字节的大小

2 堆:堆是用来容纳应用程序动态分配的内存区域。比如使用malloc和new分配内存就从堆里分配。

3 可执行文件镜像:这里存储着可执行文件在内存里的映射

首先来介绍栈:

在操作系统中,栈总是向下增长的,栈顶由称为esp的寄存器进行定位,压栈的操作使栈顶的地址减小,弹出的操作使栈顶的地址增大。栈保存了一个函数调用所需要维护的信息,这通常称为堆栈帧或活动记录。堆栈帧包括如下几个方面的内容:

1 函数返回地址和参数

2 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量

3 保存的上下文:包括在函数调用前后需要保持不变的寄存器。

 

在i386中,一个函数的活动记录用ebp和esp这两个寄存器划定范围。esp寄存器始终指向栈的顶部,ebp寄存器指向了函数活动记录的一个固定位置,ebp寄存器又称为帧指针。常见的活动记录如下图所示:

在ebp之前首先是这个函数的返回地址,它的地址是ebp-4, 再往前是压入栈中的参数,它们的地址分别是ebp-8,ebp-12等等。ebp所直接指向的的数据是调用该函数前ebp的值,这样函数在返回的时候,ebp可以读取这个值恢复到调用前的值。所以一个i386的程序调用顺序如下:

1 把所有或者一部分参数压入栈中,如果有其他参数没有入栈,那么使用某些特定的寄存器传递

2 把当前指令的下一条指令的地址压入栈中

3 跳转到函数体执行

其中2,3由执行call一起执行。跳转到函数体之后就开始执行函数。I386函数体的标准开头过程如下:

1 push ebp: 把ebp压入栈中,也就是old ebp

2 move ebp,esp: ebp=esp(ebp指向栈顶,此时栈顶就是old ebp)

3 sub esp,xxx  在栈上分配xxx字节的临时空间

4 push xxx 保存名为xxx的寄存器

把ebp压入栈中,是为了在函数返回的时候便于恢复以前的ebp值,函数返回的时候过程正好相反。

1 pop xxx

2 mov esp,ebp 恢复esp同时收回局部变量空间

3 pop ebp:从栈中恢复保存的ebp的值

4 ret: 从栈中取得返回地址,并跳转到该位置。

我们用一个简单的函数调用然后查看汇编代码来看下这个过程

#include <stdio.h>

int foo()

{      

        return 123;

}      

int main()

{      

        int ret;

        ret=foo();

        return 1;

}  

objdump –d stack_test.o 可以看到如下结果

00000000000005fa <foo>:

 5fa:        55                           push  

转载请注明原文地址: https://mac.8miu.com/read-21784.html
最新回复(0)