操作系统-函数堆栈过程
概述
文章讲的是汇编语句中执行函数时堆栈的过程,其中比较重要的是 ESP
和 EBP
这俩个寄存器.
调用过程
假如让我们来设计这个函数调用的堆栈过程,我觉得可以这样思考
现看这个函数调用 ,调用 add
方法需要传过去参数 ,所以必须有一个地方可以让add
函数执行形成的堆栈可以取得到 main
传过来 ,再一个 add
执行完之后还得返回main
中继续执行 ,所以返回地址也是必须保存的.
上面左图这是说明了,在一个进程中,堆是向下增长的, 右图则是从调用者
和 被调用者
两个角度在描述这个调用的过程..
我们常提到的IA32
寄存器就几个 , 当我要去调用下一个函数的时候, 为了腾出寄存器给即将调用的函数使用,所以调用之前我们必须先将寄存器中的数据入栈 ,这样即将调用的函数就可以使用了 (我们后面会看到保存返回地址也是这个套路 ,先将返回地址保存在栈里然后执行完函数之后的执行回到返回地址就好了)
其中需要注意的两个寄存器 :
- ESP (stack point) : 指向当前栈帧的顶部
- EBP (base point) : 指向栈帧的底部
底部是不会动的,而顶部会一直增长
接下来看一个例子 :
上面的例子 ,可以看到 ESP
和 EBP
始终是指向当前栈帧的顶部和底部的, 我们可以看到大致的过程可以分为 :
- 准备阶段
- 过程体
- 结束阶段
这里注意几个细节
每个过程开始的两条指令
pushl %ebp
movl %esp, %ebp
意思就把调用人的 EBP
先入栈了 (方便后续执行找回来),然后将ESP
的值给到 EBP
(上一句我们已经把 EBP
的值存起来了,所以此时 EBP
就可以用来新的函数形成的堆栈的栈底了,秒啊!) ,那我如果要让 EBP
回到我之前入栈的那个值呢?
popl %ebp
即可.
call 语句
call
语句做的事情有两个 :
- 保存返回地址
- 跳转执行函数
在上面例子中, 返回地址就是 :
movl %eax, -4(%ebp)
这一语句的地址
跳转执行函数开始的语句是什么
还是这两句
pushl %ebp
movl %esp, %ebp
执行函数
上面的例子我们看到执行函数的过程中 ,为了获取传进来的参数 ,可以通过 EBP
往上偏移 8或是12 ,具体的语句 :
movl 8(%ebp),%edx
movl (%edx),%ecx
返回
ret
实际会把前面入栈的返回地址
放在 EIP
寄存器 ,这样就可以回到调用函数的下一句啦.(IA32中用EIP存放将要执行的地址)
简图
参考
- https://www.bilibili.com/video/BV1kE411X7S5?p=57
- https://www.icourse163.org/home.htm?userId=1148594341#/home/course