函数调用栈理解

来源:互联网 发布:游戏视频制作软件 编辑:程序博客网 时间:2024/06/05 14:47

一、相关寄存器

(1) esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2) ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。(ebp在当前栈帧内位置固定,故函数中对大部分数据的访问都基于ebp进行)
(3) eip:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。 可以说如果控制了EIP寄存器的内容,就控制了进程——我们让eip指向哪里,CPU就会去执行哪里的指令。eip可被jmp、call和ret等指令隐含地改变(事实上它一直都在改变)(ret指令就是把当前栈顶保存的返回值地址 弹到eip中)
(4)eax:累加(Accumulator)寄存器,常用于函数返回值。

二、当函数调用的时候发生了什么?

例如:

int main(void){    foo(1,2,3) ;    return 0 ;}

当方法main需要调用foo时,它的标准行为:

  • 1、在main方法的调用栈中,将 foo的参数从右向左依次push到栈中。
  • 2、把main方法当前指令的下一条指令地址 (即return address)push到栈中。(隐藏在call指令中)
  • 3、使用call指令调用目标函数体foo。
    请注意,以上3步都处于main的调用栈,其中ebp保存其栈底,而esp保存其栈顶。

接下来,在foo函数中:

  • 1、push ebp: 将ebp的当前值push到栈中,即保存ebp。
  • 2、mov ebp,esp: 将esp的值赋给ebp,则意味着进入了foo方法的调用栈。
  • 3、[可选]sub esp, XXX: 在栈上分配XXX字节的临时空间。(抬高栈顶)(编译器根据函数中的局部变量的总大小确定临时空间的大小)
  • 4、[可选]push XXX: 保存(push)一些寄存器的值。

而在foo方法调用完毕后,便执行前面阶段的逆操作:

  • 1、保存返回值: 通常将函数的返回值保存在寄存器eax中。
  • 2、[可选]恢复(pop)一些寄存器的值。
  • 3、mov esp,ebp: 恢复esp同时回收局部变量空间。(恢复原栈顶)
  • 4、pop ebp: 将栈顶的值赋给ebp,即恢复main调用栈的栈底。(恢复原栈底)
  • 5、ret: 从栈顶获得之前保留的return address,并跳转到此位置继续执行。
    这里写图片描述

main方法先将foo方法所需的参数压入栈中,然后再改变ebp,进入foo方法的调用栈。因此,如果在foo方法中需要访问那些参数,则需要根据当前ebp中的值,再向高地址偏移后进行访问——因为高地址才是main方法的调用栈。也就是说,地址ebp + 8存放了foo方法的第1个参数,地址ebp + 12存放了foo方法的第2个参数,以此类推。那么地址ebp + 4存放了什么呢?它存放的是return address,即foo方法返回后,需要继续执行下去的main方法指令的地址。

0 0
原创粉丝点击