从汇编的角度看栈

       大家都知道,栈区是存储函数,局部变量的一块内存区域。

       那么让我们从汇编的角度,来看看函数的执行过程。首先,当我们使用pushl将数据入栈时,栈顶会移动,以容纳新增加的值。实际上,我们能不断将值入栈,栈会在内存中保持向下增长,知道存放代码或数据的地方。那么,我们如何知道栈顶地址呢?栈寄存器%esp总是包含一个指向当前栈顶的指针。

        在执行函数之前,一个程序将函数的所有参数按逆序压入栈中。接着程序发出一条call指令,表明程序希望开始执行哪一个函数。call指令会去做两件事;首先,它将下一条指令的地址即返回地址压入栈中;然后,修改指令指针(%eip)以指向函数起始处。

参数 N
。。。
参数2
参数1
返回地址 

 

        现在函数自己也有一些工作。首先通过pushl %ebp指令保存当前基址指针寄存器%ebp。基址指针是一个特殊的寄存器,用于访问函数的参数和局部变量。其次,它会用movl %esp, %ebp。将指针%esp复制到%ebp,这使你能够把函数参数作为相对于基址指针的固定索引进行访问。

        在函数开始时将栈指针复制到基址指针寄存器可以让你一直清楚参数的位置(局部变量也是如此),即使在其它数据压入弹出栈的情况下。%ebp将一直是栈指针在函数开始是的位置,所以可以说是对栈帧的常量引用。(栈帧包含一个函数中所使用的所有栈变量,包括参数、局部变量和返回地址。)   

参数 N            N×4+4(%ebp)
。。。 
参数2                12(%ebp)
参数1                8(%ebp)
返回地址                 4(%ebp)
旧%ebp      (%ebp)  和 (%esp)
 可以看到,每个参数都可以用%ebp通过基址寻址方式访问。


        接下来,函数为其所需的所有局部变量保留栈空间,只需将栈指针向外移动即可实现。假设要运行函数,我们需要两个字的内存,只需要将栈指针向下移动两个字即可。

        sub  $8, %esp

        这样,我们久能就爱那个栈用于变量的存储,而不需要担心函数调用引起的入栈会破坏存储的变量。因为函数调用是在栈帧上分配的,而变量仅仅在函数运行期间有效,而当函数返回时,栈帧久不复存在,这些变量也就不存在了。

        假如我们有两个字可用于本地存储。那么栈就是如下情况了:

参数 N   N×4+4(%ebp)
。。。 
参数2        12(%ebp)
参数1        8(%ebp)
返回地址        4(%ebp)
旧%ebp         (%ebp)
局部变量 1       -4(%ebp)
局部变量 2  -8(%ebp) 和 (%esp)
所以我们可以通过使用&ebp中的不同偏移量,通过基址寻址访问函数的所有数据。


       当一个函数执行完毕后,会做三件事。

      (1) 将其返回值存储到%eax。

      (2) 将栈恢复到调用函数时的状态(移除当前栈帧,并使调用代码的栈帧重新生效)。

      (3) 将控制权换给调用它的程序。这是通过ret指令实现的,该指令将栈顶的值弹出,并将指令指针寄存器%eip设置为该弹出值。

      因此,要执行如下命令:

      movl %ebp, %esp

      popl %ebp

      ret


      现在控制权已经回到,调用函数的那里,你可以检查%eax中的返回值。弹出其入栈的所有参数,将栈指针复位至其原先的位置(如果不需要参数值,按你可以用addl 将“4 *参数个数”加到%esp即可)。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数调用过程是程序中常见的一种操作,它通常涉及到参数传递、帧的建立与销毁、返回值的传递等多个方面。从汇编角度来看,函数调用过程可以分为以下几个步骤: 1. 将函数的参数压入中。在调用函数时,需要将函数所需的参数传递给它。这些参数通常以一定的顺序压入中,以便在函数内部使用。在 x86 架构中,参数的传递是通过将参数压入顶实现的。 2. 调用函数。函数调用的指令通常是 CALL 指令。在调用函数前,需要将函数的入口地址压入中,以便在函数执行完毕后返回到调用位置。CALL 指令会将当前的程序计数器(PC)压入中,并将函数的入口地址作为新的 PC。 3. 建立帧。在函数被调用时,需要为函数建立一个独立的帧,以便在函数内部使用局部变量和临时变量。帧通常包括以下几个部分:返回地址、旧的基址指针、局部变量和临时变量。在 x86 架构中,帧的建立是通过将 ESP 寄存器减去一个固定的值实现的。 4. 执行函数。在函数被调用后,CPU 会跳转到函数的入口地址并开始执行函数。函数内部可以通过中的参数和局部变量完成相应的计算和操作。 5. 返回值传递。在函数执行完毕后,需要将函数的返回值传递给调用者。在 x86 架构中,函数的返回值通常通过 EAX 寄存器传递。 6. 销毁帧。在函数执行完毕后,需要将帧销毁,以便释放空间。帧的销毁通常是通过将 ESP 寄存器还原到旧的基址指针处实现的。 7. 返回到调用位置。在函数执行完毕后,需要返回到函数被调用的位置。在 x86 架构中,返回指令通常是 RET 指令。RET 指令会将顶的返回地址弹出,并将其作为新的 PC。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值