zoukankan      html  css  js  c++  java
  • 汇编语言基础之七 框架指针的省略(FPO)

    框架指针省略(Frame Pointer Omission)(FPO)

    FPO是一种优化,它压缩或者省略了在栈上为该函数创建框架指针的过程。这个选项加速了函数调用,因为不需要建立和移除框架指针(ESP,EBP)了。同时,它还解放出了一个寄存器,用来存储使用频率较高的变量。只在IntelCPU的架构上才有这种优化。

    目前已经讨论过的任何一种调用约定都保存了前一函数中栈的信息(压栈ebp,然后让ebp = esp,再移动esp来保存局部变量)。一个FPO的函数可能会保存前一函数的栈指针(ESP,EBP),但是并不为当前的函数调用设立EBP。相反,他使用EBP来存储一些其他的变量。debugger 会计算栈指针,但是debugger必须得到一个使用FPO的提醒,该提醒是基于FPO类型的信息的来完成的。

    这项特性可以在MS Visual C++专业版和企业版中开启。使用的是编译器的/Oy选项。

    FPO的数据结构可以在Microsoft的SDK中的winnt.h中找到,其中包含了描述栈框架内容的信息。这些信息被使用在debugger上,或者其他的需要在栈中寻找FPO函数的程序中。KV命令可以显示出包括FPO信息在内的额外的运行时信息。

    0:000> kv

    ChildEBP RetAddr

    0012ff74 00401009 addemup!Addemup (FPO: [2,0,0])

    0012ff80 00401115 addemup!main+0x9 (FPO: [0,0,0])

    0012ffc0 77e87903 addemup!mainCRTStartup+0xb4

    0012fff0 00000000 KERNEL32!BaseProcessStart+0x3d (FPO: [Non-Fpo]

    上面的例子中,括号括起来的FPO数据结构的意义分别是:

    FPO数据表示形式 (FPO: [ebp addr][x,y,z])
    x代表 作为参数压栈了的DWORDS个数
    y代表 作为局部变量压栈了的DWORDS个数
    z代表 在开场代码中(prologue)压栈了的寄存器个数
    ebp addr代表 仅在EBP在开场代码中保存了的时候显示

    上面的例子中,由于debugger有正确的symbol,所以debugger会计算出栈底(Frame Pointer)的位置,而不是在EBP之中保存它。比如说,第一个参数的位置是栈底+0x8,返回值的位置是栈底+0x4. 开启了FPO之后,这些值就不能通过ebp + 0x8这样拿到了,跟ebp等值的栈底(Frame Pointer)需要计算才能拿到。

    仅仅靠上面的信息来理解FPO还是感觉有点云里雾里的。

    关于FPO,有篇文章做了非常好的介绍,来龙去脉都讲的很清楚。链接列在下面:

    http://blogs.msdn.com/larryosterman/archive/2007/03/12/fpo.aspx

    我总结了一些要点在下面,方便大家更好的理解FPO的一些概念。

    下表列出了同样功能,但是FPO选项不同的汇编代码。

    未开启FPO的函数的汇编代码 开启了FPO的函数的汇编代码

    MyFunction:
        PUSH    EBP
        MOV     EBP, ESP
        SUB      ESP, <LocalVariableStorage>
        MOV     EAX, [EBP+8]
          :
          :
        MOV     ESP, EBP
        POP      EBP
        RETD

    MyFunction:
        SUB      SP, <LocalVariableStorage>
        MOV     EAX, [ESP+4+<LocalVariableStorage>]
          :
          :
        ADD     SP, <LocalVariableStorage>
        RETD

    注意,这里访问第一个参数的代码是

    MOV     EAX, [EBP+8]

    注意,这里访问第一个参数的代码是

    MOV     EAX, [ESP+4+<LocalVariableStorage>]

    下两表分别列出了同样功能,但是FPO选项栈内容分配,以及参数的访问方式。

    未开启FPO的指针指向 栈中的内容
    [ebp-04]
    [ebp-01]
    [ebp+00] 
    [ebp+04] 
    [ebp+08]
    第一个局部变量的首地址
    第一个局部变量的最后一个字节
    上一个EBP的值
    返回值,即调用该函数之前的EIP寄存器的值
    第一个参数的首地址
      说明:
         因为参数都是从右至左压栈的,所以ebp+8是最后一个压栈的参数,所以是第一个参数。
         因为被调用函数中,先将ESP向上移动出所有局部变量的尺寸,然后根据EBP的位置从下到上,局部变量从第一个往最后一个赋值的,所以ebp-1是第一个局部变量的最后一个字节。
    开启了FPO的指针指向 栈中的内容
    [esp]
    [esp+08]
    [esp+11]
    [esp+12]
    [esp+16]
    [esp+20]
        最后一个局部变量的第一个字节
        … 
        第一个局部变量的首地址
        第一个局部变量的最后一个字节
        返回值
        第一个参数的首地址 
        …
        前一个函数的局部变量
      说明:
        假设当前FPO的数据为(FPO: [1,2,0])
        即参数有1个dwords(4字节)局部变量2个dwords(8字节)压栈的寄存器为0个(0字节)

    我还参考了另两篇文章,有点难懂,不过出于对我理解这个概念的贡献,还是将文章链接列在下面。

    Frame pointer omission (FPO) and consequences when debugging, part 1.

    Frame pointer omission (FPO) and consequences when debugging, part 2.

    观察FPO函数

    只要当前函数和前一个函数的symbol被正确加载的话,debugger就可以计算出栈底指针的位置。

    因为EBP被保留下来用作通用寄存器了,并没有用来建立栈框架,所以没有必要将这个寄存器压入栈中。这就是为什么它不再指向前一个EBP的原因。

    如果FPO函数拥有局部变量,debugger计算出来的栈底位置指向第一个局部变量。

    如果FPO函数没有任何的局部变量,debugger计算出来的栈底位置指向第一个被保留下来的寄存器。

    如果FPO函数没有任何局部变量,也没有保留的寄存器,debugger计算出来的栈底位置指向没被使用的栈空间。

    让人迷惑的地方是,当一个FPO函数,使用FASTCALL调用约定的时候。函数没有栈底指针,参数也没有被压在栈上。尽管如此,这些函数通常都比较小。反汇编前面的函数,就可以看到寄存器是如何被加载的了。

    学友认为,上面的话可以总结如下:FPO函数没有保存EBP,所以访问参数等的时候就无法使用EBP了。所以FPO函数依靠symbol中的信息来计算一个类似于EBP指向的位置的指针。不同的是,ebp指向的是栈中保存的前一个栈底的位置。而现在是指向一个尽可能接近原始EBP的,在栈中尽可能靠下(大地址端)的位置。

  • 相关阅读:
    <--------------------------常用的API方法------------------------------>
    <--------------------------构造方法------------------------------>
    <--------------------------Java多态如何使用------------------------------>
    <--------------------------Java接口如何使用------------------------------>
    <--------------------------Java继承及抽象类------------------------------>
    Eclipse工具的设置
    Java数组逆序排列
    Java打印九九乘法表及倒打九九乘法表
    Java打印水仙花数
    Java实现选择排序以及冒泡排序
  • 原文地址:https://www.cnblogs.com/awpatp/p/1595988.html
Copyright © 2011-2022 走看看