操作系统-函数堆栈过程


概述

文章讲的是汇编语句中执行函数时堆栈的过程,其中比较重要的是 ESPEBP 这俩个寄存器.

调用过程

假如让我们来设计这个函数调用的堆栈过程,我觉得可以这样思考
现看这个函数调用 ,调用 add方法需要传过去参数 ,所以必须有一个地方可以让add函数执行形成的堆栈可以取得到 main 传过来 ,再一个 add 执行完之后还得返回main中继续执行 ,所以返回地址也是必须保存的.

上面左图这是说明了,在一个进程中,堆是向下增长的, 右图则是从调用者被调用者两个角度在描述这个调用的过程..

我们常提到的IA32 寄存器就几个 , 当我要去调用下一个函数的时候, 为了腾出寄存器给即将调用的函数使用,所以调用之前我们必须先将寄存器中的数据入栈 ,这样即将调用的函数就可以使用了 (我们后面会看到保存返回地址也是这个套路 ,先将返回地址保存在栈里然后执行完函数之后的执行回到返回地址就好了)

其中需要注意的两个寄存器 :

  • ESP (stack point) : 指向当前栈帧的顶部
  • EBP (base point) : 指向栈帧的底部

底部是不会动的,而顶部会一直增长

接下来看一个例子 :

上面的例子 ,可以看到 ESPEBP始终是指向当前栈帧的顶部和底部的, 我们可以看到大致的过程可以分为 :

  • 准备阶段
  • 过程体
  • 结束阶段

这里注意几个细节

每个过程开始的两条指令

pushl %ebp
movl %esp, %ebp

意思就把调用人的 EBP 先入栈了 (方便后续执行找回来),然后将ESP 的值给到 EBP(上一句我们已经把 EBP的值存起来了,所以此时 EBP就可以用来新的函数形成的堆栈的栈底了,秒啊!) ,那我如果要让 EBP 回到我之前入栈的那个值呢?

popl %ebp 

即可.

call 语句

call语句做的事情有两个 :

  1. 保存返回地址
  2. 跳转执行函数
    在上面例子中, 返回地址就是 :
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