关于函数栈帧的思考与分析

1. ebp和esp是什么?
ebp是指向当前stack frame底部的指针;esp是指向当前stack frame顶部的指针。

2. stack是什么?
stack是存储器中一种很昂贵的资源,默认情况下linux给每个线程分配的空间为8MB,
可以使用ulmit -s或者ulmit -a进行查看:
sh-# ulimit -s
8192
sh-# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 2303
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 2303
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
因为stack是昂贵的资源,所以我们时时刻刻都要省着点用,通常不建议:
(1)在函数内部开辟大数组,比如int a_info[10000] = {0};
会瞬间让你的stack爆掉从而使系统crash掉,通常这种大数组建议使用malloc函数进行dynamically allocation。
(2)太深的函数递归调用,也有可能会造成线程的stack很快耗尽甚至爆掉。
由于32位linux系统下process的user space是从0~3GB,这部分是process独占的。
所以理论上一个process最多可以创建8MB*N<3*1024MB,即N<384个thread。

3. linux内存布局初分析?
对32位linux系统,从高地址到底地址:
(1)3GB~4GB(>=0xc0000000),这部分是内核态使用,用户态代码不可见,所有进程共享内核空间;
(2)地址空间小于3GB(<0xc0000000)的部分都是用户空间。首先是stack空间,stack从高地址向地址值增长;
(3)文件映射区(>0x40000000),这部分没有研究过,暂不讨论;
(4)堆空间,堆是从底地址向高地址增长;
(5)代码段、初始化数据区以及未初始化数据区;
(6)保留区域(<0x08048000?)。
关于存储器布局部分,未来有机会会再做一次比较详细的分析。

4. 函数调用的真实过程?
假设有如下的代码:
void function_b(int a, int b)
{
    int m = 0;
    int n = 0;
    do_something();
}
void function_a(void)
{
    function_b(a, b);
    do_something();
}
此时function_a调用function_b的栈变化过程是:
(1)将do_something();这一条语句的地址push到stack中,目的是当从function_b中return时执行下一条语句;
(2)将ebp push到stack中,因为此时ebp仍然是function_a的栈帧底部的地址,存ebp的目的也是为了在
function_b返回时恢复function_a的栈帧用;
注意(1)(2)两步仍然是在function_a的栈帧中完成的;
(3)move ebp, esp
注意哦,到了这一步,才是建立function_b的栈帧的开始,可以看到起始时ebp与esp都指向栈底;
(4)为function_b的变量m在栈上面分配空间,同时ebp = ebp -4移动栈顶指针;
(5)为function_b的变量n在栈上面分配空间,同时ebp = ebp -4移动栈顶指针;
所以我们经常看到的因为数组越界、字符串copy越界等造成的系统crash的现象也就不会奇怪了,
因为函数的返回地址可能会被轻易的被改掉。
(6)将function_b的参数a,b分别push到stack中。

5. 函数返回的过程?
当function_b执行完return时,
(1)move esp, ebp
这一步的目的就是用来恢复function_a的栈帧的栈顶;
(2)pop ebp
将之前压栈的function_a的栈帧的栈底地址恢复出来,有了ebp和esp,那function_a的栈帧就恢复了。
(3)pop next_statement's address
将下一条待执行命令的address从栈中pop出来,function_a可以继续做事了。

6. 为什么栈上分配的内存可以自动释放?
如问题5中分析,当函数function_b返回时会执行move esp, ebp,
这一步即是释放了function_b栈帧中分配的局部变量了。
那为什么从堆上动态分配的内存必须要用户自己手动释放呢?
要想清晰了解这部分,可能需要看一下malloc函数的实现了。
目前个人粗浅的猜测可能是如果用malloc分配的话,操作系统会标记已经分配的内存;
如果用户不主动释放,那这部分已分配的就会一直标记为已用的状态,
所以内存就会越来越少了,这就是内存泄漏。
关于堆这一块内容,后面会在适当的时候再来分析。

7. 如何确认stack是从高地址到底地址增长呢?
uint8 ui1_test_1 = 0x11;
uint8 ui1_test_2 = 0x22;
printf("\npui1_test_1=0x%x,pui1_test_2=0x%x\n", &ui1_test_1, &ui1_test_2);
编译运行程序,会看到如下的log:
pui1_test_1=0x7007BA87,pui1_test_2=0x7007BA86
可以看到先申请的变量ui1_test_1的地址比后申请的ui1_test_2的地址的值要高,
而且正好相差的是一个byte。

实验结果很好的验证了stack是从高地址到底地址增长的说法。

待解决问题:
从分析结果来看,通过试验测试,想象中的系统挂掉还未出现,还需要努力查找其中原因。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值