热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

为什么RBP而不是另一个寄存器作为帧指针?

我理解在函数的开头和结尾使用pushrbppoprbp来保留rbp调用函数的值,因为rb

我理解在函数的开头和结尾使用push rbp...pop rbp来保留rbp调用函数的值,因为rbp寄存器是被调用者保留的。然后我理解使用rbp作为当前正在执行的过程的堆栈帧的当前顶部的“约定” 。但与此相关,我有两个问题:


  1. 是否rbp只是一个约定?我可以像r11堆栈帧的基础一样轻松地使用(或任何其他寄存器甚至堆栈上的 8 个字节)吗?rbp寄存器有什么特别之处,或者它只是用作基于历史和约定的堆栈框架?

  2. 为什么mov %rbp, %rsp在离开函数之前用作“清理”方法?例如,push/pop指令通常是对称的,所以mov %rbp, %rsp只是一种速记方式,有人可以“跳过”执行对称的弹出/添加等操作?什么mov %rbp, %rsp是有用的实际用途?几乎所有时候我在编译器输出中看到它(启用零优化),它似乎是不必要的或多余的,而且我很难想到它实际上可能有用的场景。


回答

优化的代码根本不使用帧指针,除了像 VLA/ alloca(RSP 的可变大小移动)之类的东西,或者如果您专门使用-fno-omit-frame-pointer(例如,使perf record堆栈采样更有效/可靠)。未优化的代码通常看起来不那么有趣。如何从 GCC/clang 程序集输出中去除“噪音”?


  • x86_64:堆栈帧指针几乎没用吗?

  • 为什么使用 ebp 比使用 esp 寄存器更好地定位堆栈上的参数?(仅适用于代码大小)

  • 帧指针的优点是什么?

因此,关于何时/为什么使用帧指针的部分有很多重复。有趣的部分是是否可以选择 RBP 以外的寄存器。


RBP 唯一的特别之处leave可以紧凑地做 RSP=RBP + pop RBP;并且(%rbp) 寻址模式需要显式disp8disp32(值为 0)。

因此,如果您打算使用帧指针,则应该选择 RBP,因为它在作为帧指针方面至少与任何其他 reg 一样好,但在某些其他用途方面比其他 reg 差。 你永远不需要0(frame_pointer),只需要其他偏移量。(R13 具有相同的 always-needs-a-disp8=0 效果,但是每个堆栈访问都需要一个 REX 前缀,就像add -12(%r13), %eaxRBP 不一样。)

此外,所有其他的“传统”寄存器(你可以不用REX使用,即不R8-R15)具有至少一个隐式使用至少一个指令编译器实际上可能产生,比如cmpxchg16bcpuidshl %cl, %regrep movsb或什么的,所以任何其他reg 作为帧指针会更糟。如果您需要调整一些东西以释放 RBX 用于某些需要它用于不同目的的指令,则不能进行简单的未优化(或玩具编译器)代码生成。(异常堆栈展开也可能依赖于帧指针始终位于特定寄存器中,如果您的.cfi_*指令指定了这一点。)

与以前的 x86 模式保持一致将是使用 RBP 的充分理由,使微不足道的人类更容易记住,但如果您打算使用 RBP,仍然有代码大小和其他原因选择 RBP。(实际上,由于(%rsp)寻址模式总是需要一个 SIB 字节,因此设置帧指针的指令实际上可以在代码大小方面为大型函数付出代价,尽管不是在指令/微指令中。)


仍然不相关的原因:

RBP 基地址暗示 SS 段,如 RSP,它在 16 位模式下是相关的,理论上在 32 位(非平面内存模型是可能的),但在 64 位模式下它只影响你的异常从非规范地址获取。所以这部分原因基本上消失了,几乎没有人关心#GP#SS那里。

enter太慢而无法使用,但leave如果 RSP 尚未指向保存的 RBP,则仍然值得使用,与手动mov %rbp, %rsp/pop %rbp在 Intel CPU 上相比,仅花费 1 额外 uop ,所以这就是 GCC 所做的。您声称看到了无用的mov %rbp, %rsp指令,但这并不是编译器实际所做的。

请注意,mov %rbp, %rsp(3 bytes) 小于add $imm8, %rsp(4 bytes),因此如果您使用的是帧指针,那么如果 RSP 未指向已保存的 RBP,则最好以这种方式恢复 RSP。(除非您需要恢复其他寄存器,如果您将它们保存在 RBP 下方而不是 a 之后sub $imm, %rsp,尽管您可以使用mov加载而不是弹出来进行恢复。)






推荐阅读
  • 【技术分享】一个 ELF 蠕虫分析
    【技术分享】一个 ELF 蠕虫分析 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 原文地址http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/最开始时 ... [详细]
  • 三、查看Linux版本查看系统版本信息的命令:lsb_release-a[root@localhost~]#lsb_release-aLSBVersion::co ... [详细]
  • linux 字符串数组初始化,C++字符数组初始化方法的分析
    发现了一个字符数组初始化的误区,而这个往往能导致比较严重的性能问题,分析介绍如下:往往我们在初始化一个字符数组,大概有如下几 ... [详细]
  • 32位ubuntu编译android studio,32位Ubuntu编译Android 4.0.4问题
    问题一:在32位Ubuntu12.04上编译Android4.0.4源码时,出现了关于emulator的错误,关键是其Makefile里的 ... [详细]
  • 1.      准备工作: 程序:MinGW-3.1.0-1.exe     windows下的gcc,编译c语言的工具下载地址: http:umn.dl.sourceforge. ... [详细]
  • 主函数:CStringGetCPUIDString()Copyright(C)shangweixiao2011 ... [详细]
  • 1引言在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着JavaSE1.6对Synchronized进行 ... [详细]
  • 我有一个vim默认安装在Linux上,在一个系统上有vt52终端仿真器和unicode功能。Vim不会接受换行一切都工作正常,直到今天,当 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 嵌入式处理器的架构与内核发展历程
    本文主要介绍了嵌入式处理器的架构与内核发展历程,包括不同架构的指令集的变化,以及内核的流水线和结构。通过对ARM架构的分析,可以更好地理解嵌入式处理器的架构与内核的关系。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 通过Anaconda安装tensorflow,并安装运行spyder编译器的完整教程
    本文提供了一个完整的教程,介绍了如何通过Anaconda安装tensorflow,并安装运行spyder编译器。文章详细介绍了安装Anaconda、创建tensorflow环境、安装GPU版本tensorflow、安装和运行Spyder编译器以及安装OpenCV等步骤。该教程适用于Windows 8操作系统,并提供了相关的网址供参考。通过本教程,读者可以轻松地安装和配置tensorflow环境,以及运行spyder编译器进行开发。 ... [详细]
author-avatar
左边我们画圈圈
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有