框架指针省略(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的,在栈中尽可能靠下(大地址端)的位置。

框架指针的省略(FPO)相关推荐

  1. 汇编语言基础之七- 框架指针的省略(FPO)

     框架指针省略(Frame Pointer Omission)(FPO) FPO是一种优化,它压缩或者省略了在栈上为该函数创建框架指针的过程.这个选项加速了函数调用,因为不需要建立和移除框架指针( ...

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

    框架指针省略(Frame Pointer Omission)(FPO) FPO是一种优化,它压缩或者省略了在栈上为该函数创建框架指针的过程.这个选项加速了函数调用,因为不需要建立和移除框架指针(ESP ...

  3. [转]VC获取各类指针

    1.获取应用程序指针 CMyApp* pApp=(CMyApp*)AfxGetApp(); 2.获取主框架指针 CWinApp 中的公有成员变量 m_pMainWnd 就是主框架的指针 CMainFr ...

  4. 框架、文档、视图类之间的调用关系

    在多文档MFC应用程序执行过程中,创建了多于一个的文档类.视图类.子框架类对象和一个主框架类.应用类对象.这些对象之间是通过一定的方式联系在一起的,在应用程序设计中,时常需要通过这些对象之间的关系来实 ...

  5. VC++ 从View类获取各种指针编程实例

    新建一个多文档工程:名为GetPtrDemo: 在视类OnDraw函数,获取其他类指针:然后进行一些操作: 首先获取应用程序类指针:可以获取到:然后利用它输出程序名: CGetPtrDemoApp* ...

  6. MFC框架类、文档类、视图类相互访问(及窗口句柄获取)的方法

    1.获取应用程序指针  CMyApp* pApp=(CMyApp*)AfxGetApp(); 2.获取主框架指针  CWinApp 中的公有成员变量 m_pMainWnd 就是主框架的指针  CMai ...

  7. MFC通过对话框窗口句柄获得对话框对象指针

    mfc如何获得控件句柄 CWnd *pWnd = GetDlgItem(ID_***); // 取得控件的指针 HWND hwnd = pWnd->GetSafeHwnd(); // 取得控件的 ...

  8. 在MFC类中各种类的指针的获取和应用

    获得CWinApp 获得CMainFrame 获得CChildFrame 获得CDocument 获得CView 在CWinApp中 AfxGetMainWnd() m_pMainWnd AfxGet ...

  9. MFC框架类、文档类、视图类相互访问的方法

    1.获取应用程序指针 CMyApp* pApp=(CMyApp*)AfxGetApp(); 2.获取主框架指针  CWinApp 中的公有成员变量 m_pMainWnd 就是主框架的指针  CMain ...

最新文章

  1. Codeforces 997 C - Sky Full of Stars
  2. 在JFinal的Controller中接收json数据
  3. IP虚拟分片重组配置命令
  4. Exchange 2013恢复已禁用用户邮箱
  5. NUMPY数据集练习 ----------SKLEARN类
  6. tomcat java环境配置
  7. r.java是什么_R.java文件介绍
  8. Jetty 服务器架构分析(中)
  9. 3d模型多怎么优化_3D打印人像模型是怎么制作出来的呢?
  10. 刘强东卸任京东集团CEO!接任人是他...
  11. php代理请求失败,http请求失败有哪些原因
  12. Conditional Expectation Entropy
  13. 归并排序(Java)
  14. vue——回到顶部监听滚动事件
  15. 计算机无法访问inter,电脑网络提示无Internet访问权限解决办法
  16. REINFORCE和Reparameterization Trick
  17. 几个著名的心理学实验
  18. 学习|全屏时钟|计时器APP横评
  19. 获取各大电商平台商品详情api(api接口)
  20. GLES2.0中文API-glGetActiveUniform

热门文章

  1. Android的显示色彩位数
  2. Linux之用户添加命令 useradd 详解
  3. EROFS 和 方舟 辩证的看 —— 方舟
  4. 网络安全下用c语言写蠕虫病毒,神经网络在计算机网络安全管理中的应用
  5. 基于51单片机的智能煤气天然气CO检测阈值报警器排气风扇方案原理图设计
  6. aec java ios_Java並發編程之原子操作類
  7. linux前一个的输出作为后一个参数,将Linux命令的结果作为下一个命令的参数
  8. AI项目被谷歌撂挑子 美国防部愤而狂挖硅谷AI人才
  9. noip2015提高组初赛(答案+选择题题目+个人分析)
  10. vue 八大生命周期