• 汇编-程序运行时栈模型介绍


      本文我们单考虑内存中的栈区域,考虑函数的调用、参数的传递和局部变量的使用——这一切在汇编的抽象级别是如果进行的。

      (Contents from Assembly Language For x86 Processors Sixth Edition By Kip R.Irvine)

      

      首先一上来作者用了这么一段话非常精炼的总结了stack的几乎所有功能,其实要想完全掌握这个概念,脑中只要把调用子程序时生成stack的动态模型给记住即可。为了便于大家对stack模型的理解,我根据ebp相对固定的特点,将stack模型分为两部分,分别是ebp之下的空间和ebp之上的空间。


    1. EBP之下的空间

    1.1 Prologue

    在一进入子程序的时候,我们首先要做的是设置栈顶的位置,数据入栈出栈有esp掌控位置的变化,但我们也需要一个固定不变的位置来在内存中标记当前stack的具体位置,这项工作就由ebp寄存器来完成。

    首先保存ebp的值,然后将ebp指向esp

    1.2 Saving and Restoring Registers

    Ideally,the registers in question should be pushed on the stack just after setting EBP to ESP,and just before reserving space for local variables.

    通过书中的这句话我们可以很清晰的看到将寄存器的值push进栈(保存其值以便返回前恢复)这个步骤具体是在什么时候进行的,而我们之所以要保存一些寄存器的值,是因为在子程序的调用过程中,我们可能会需要借用一些寄存器来处理相应的数据,为了保护寄存器原来的值不被这次调用破坏,我们会选择先将其保存起来。这一行为的专业术语我们称之为保护现场

    1.3 Local Variables

    Mysub proc
        push ebp
        mov ebp,esp
        sub esp,8
        mov DWORD PTR [ebp-4],10
        mov DWORD PTR [ebp-8],20
        mov esp,ebp
        pop ebp
        ret
    Mysub endp

    在我们想创建局部变量之前,首先要为他们在stack中开辟一段空间,而这个任务就由sub esp,8这条指令来完成,其内存模型如下图:

    显而易见,ebp保持位置不变,esp进行相应的移动为局部变量的存储创造空间。那么这些创造出来的空间在子程序调用结束后该怎么处理呢?请见1.4

    1.4 Epilogue

    在runtime stack中为local variable和一些要保存原来值的registers预留了空间,在子程序调用结束后,这些空间必须要被释放出来,如果不释放,那么当pop ebp时,ebp就不能得到stack为他保留的值了。因此epilogue这一环节的意义可以总结为释放stack中ebp和esp之间的空间,从而使得ret 指令在调用结束后能将程序返回到正确的地址。

    2. EBP之上的空间

    2.1 Stack Parameters

    push 6
    push 5
    call AddTwoNum

     上述语句的执行会产生下面的stack:

    在调用子程序(subroutine)的时候,调用者会将实参push进栈,以便稍后的调用过程中使用。注意我们传递参数这一步是在调用(call)之前进行的,比如如下代码,要在调用计算两数和的子程序之前就将我们需要的参数push进栈。调用子程序的call指令就相当于将子程序的返回地址压栈。

    2.2 参数空间的释放

    我们看这样一个例子,在main函数中调用Example1函数,再在Example1中调用AddTwo函数,然后ret,通过这个例子来说明子程序返回时释放参数空间的重要性

    我们先看一下程序的栈模型:

    在AddTwo子程序调用结束后,ret指令会返回到当前ESP所指向的地址,也就是之前传递的参数5和6的位置,这就导致了错误的产生——我们没有跳转到return address上去啊!解决这种错误有两种方法,一是在caller中释放该调用的空间,另一个是在子程序中自行完成,二者的实现分别如下:

    在caller中释放参数空间:

    在子程序中释放空间:

    这种方法也被称为STDCALL Calling Convention)

    通常情况下也就是子程序的连续或循环调用可能会出现此种错误,需要注意

  • 相关阅读:
    时间单位的档案
    Linux中表示“时间”的结构体和相关函数
    linux 下如何给用户添加权限
    C++中虚函数的作用浅析
    C语言中strstr函数
    测试环境下将centos6.8升级到centos7的操作记录(转)
    CURL常用命令(转)
    Centos7管理selinux及使用firewalld管理防火墙
    UML类图几种关系的总结
    linux下面覆盖文件,如何实现直接覆盖,不提示
  • 原文地址:https://www.cnblogs.com/immortal-worm/p/5487417.html
Copyright © 2020-2023  润新知