这是关于英特尔平台上的 BP/EBP/RBP 寄存器的全部内容。该寄存器默认为堆栈段(不需要特殊前缀即可访问堆栈段)。
EBP 是访问堆栈内的数据结构、变量和动态分配的工作空间的最佳寄存器选择。EBP 通常用于相对于堆栈上的固定点而不是相对于当前 TOS 来访问堆栈上的元素。它通常标识为当前过程建立的当前堆栈帧的基地址。在偏移量计算中使用EBP作为基址寄存器时,偏移量在当前堆栈段(即SS当前选择的段)中自动计算。因为 SS 不必明确指定,所以在这种情况下指令编码更有效。EBP 还可用于索引可通过其他段寄存器寻址的段。
(来源-http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm)
由于在大多数 32 位平台上,数据段和堆栈段是相同的,因此 EBP/RBP 与堆栈的这种关联不再是问题。在 64 位平台上也是如此:AMD 于 2003 年推出的 x86-64 架构在很大程度上放弃了对 64 位模式分段的支持:其中四个分段寄存器:CS、SS、DS 和 ES 被强制为 0 . x86 32 位和 64 位平台的这些情况本质上意味着在访问内存的处理器指令中可以使用 EBP/RBP 寄存器,无需任何前缀。
因此,您编写的编译器选项允许将 BP/EBP/RBP 用于其他方式,例如保存局部变量。
“这避免了保存、设置和恢复帧指针的指令”意味着避免在每个函数的入口处使用以下代码:
push ebp
mov ebp, esp
或enter
指令,它在 Intel 80286 和 80386 处理器上非常有用。
此外,在函数返回之前,使用了以下代码:
mov esp, ebp
pop ebp
或leave
指令。
调试工具可以扫描堆栈数据并在定位时使用这些推送的EBP寄存器数据call sites
,即,按照它们被分层调用的顺序显示函数的名称和参数。
程序员可能对堆栈帧有疑问,不是广义的(它是堆栈中的一个实体,只服务一个函数调用并保留返回地址、参数和局部变量),而是狭义的——当这个术语stack frames
在编译器选项的上下文。从编译器的角度来看,堆栈帧只是例程的进入和退出代码,它将锚推入堆栈——也可用于调试和异常处理。调试工具可以扫描堆栈数据并使用这些锚点进行回溯,同时定位call sites
在堆栈中,即以与分层调用函数相同的顺序显示函数的名称。
这就是为什么对于程序员来说,从编译器选项的角度理解什么是堆栈帧是至关重要的——因为编译器可以控制是否生成此代码。
在某些情况下,编译器可以省略堆栈帧(例程的入口和出口代码),变量将直接通过堆栈指针(SP/ESP/RSP)而不是方便的基指针(BP/ ESP/RSP)。编译器省略某些函数的堆栈帧的条件可能不同,例如: (1) 该函数是叶函数(即不调用其他函数的终端实体);(2) 不使用任何例外;(3) 在堆栈上没有调用带有传出参数的例程;(4) 函数没有参数。
省略堆栈帧(例程的进入和退出代码)可以使代码更小更快。尽管如此,它们也可能对调试器回溯堆栈数据并将其显示给程序员的能力产生负面影响。这些是编译器选项,用于确定函数应满足哪些条件,以便编译器授予它堆栈帧进入和退出代码。例如,在以下情况下,编译器可以选择将此类进入和退出代码添加到函数中:(a)总是,(b)从不,(c)在需要时(指定条件)。
从一般性到特殊性:如果您使用-fomit-frame-pointer
GCC 编译器选项,您可能会在例程的进入和退出代码以及额外的寄存器上获胜(除非它默认情况下已经打开,或者本身或其他选项隐式打开,在这种情况下,您已经从使用 EBP/RBP 寄存器的增益中受益,并且如果该选项已经隐式打开,则不会通过显式指定此选项获得额外增益)。但是请注意,在 16 位和 32 位模式下,BP 寄存器不能像 AX(AL 和 AH)那样提供对 8 位部分的访问。
由于此选项除了允许编译器在优化中使用 EBP 作为通用寄存器外,还可以防止为堆栈帧生成退出和进入代码,这会使调试复杂化——这就是GCC 文档明确指出的原因(通常用粗体强调style) 启用此选项会使某些机器上的调试变得不可能。
另请注意,与调试或优化相关的其他编译器选项可能会隐式打开-fomit-frame-pointer
或关闭该选项。
我在 gcc.gnu.org 上没有找到任何关于其他选项如何影响-fomit-frame-pointer
x86 平台的官方信息,https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html仅声明以下内容:
-O 还会在不干扰调试的机器上打开 -fomit-frame-pointer。
因此,如果您仅在 x86 平台上使用单个 `-O' 选项进行编译,则文档本身并不清楚是否-fomit-frame-pointer
会打开。它可能会通过经验进行测试,但在这种情况下,GCC 开发人员没有承诺在未来不更改此选项的行为,恕不另行通知。
但是,Peter Cordes在评论中指出,-fomit-frame-pointer
x86-16 平台和 x86-32/64 平台之间的默认设置存在差异。
此选项 – -fomit-frame-pointer
– 也与 Intel C++ Compiler 15.0 相关,不仅与 GCC 相关:
对于英特尔编译器,此选项有一个别名/Oy
。
这是英特尔写的:
这些选项确定 EBP 是否用作优化中的通用寄存器。选项 -fomit-frame-pointer 和 /Oy 允许这样使用。选项 -fno-omit-frame-pointer 和 /Oy- 不允许它。
一些调试器期望 EBP 被用作堆栈帧指针,除非这样,否则无法生成堆栈回溯。-fno-omit-frame-pointer 和 /Oy- 选项指示编译器生成维护和使用 EBP 作为所有函数的堆栈帧指针的代码,以便调试器仍然可以在不执行以下操作的情况下生成堆栈回溯:
对于 -fno-omit-frame-pointer:使用 -O0 关闭优化 对于 /Oy-:关闭 /O1、/O2 或 /O3 优化 -fno-omit-frame-pointer 选项在您指定选项时设置 - O0 或 -g 选项。-fomit-frame-pointer 选项在您指定选项 -O1、-O2 或 -O3 时设置。
当您指定 /O1、/O2 或 /O3 选项时,将设置 /Oy 选项。选项 /Oy- 在您指定 /Od 选项时设置。
使用 -fno-omit-frame-pointer 或 /Oy- 选项可将可用通用寄存器的数量减少 1,并可能导致代码效率稍低。
注意 对于 Linux* 系统:GCC 3.2 异常处理当前存在问题。因此,当为 C++ 安装 GCC 3.2 并打开异常处理(默认设置)时,英特尔编译器会忽略此选项。
请注意,上述引用仅与 Intel C++ 15 编译器相关,与 GCC 无关。