试图了解gcc选项-fomit-frame-pointer


79

我要求Google给我gccoption的含义,该选项-fomit-frame-pointer将我重定向到以下语句。

-fomit-frame-pointer

不要将帧指针保存在不需要一个的寄存器中。这避免了保存,设置和恢复帧指针的指令。它还使许多功能中都有一个额外的寄存器。这也使得在某些机器上无法进行调试。

据我对每个功能的了解,将在过程存储器的堆栈中创建一个激活记录,以保留所有局部变量和更多信息。我希望该帧指针表示功能激活记录的地址。

在这种情况下,什么类型的函数不需要将帧指针保留在寄存器中?如果获得此信息,我将尝试基于此设计新功能(如果可能),因为如果帧指针未保存在寄存器中,则某些指令将以二进制形式省略。在具有许多功能的应用程序中,这确实可以显着提高性能。


4
只需调试使用此选项编译的代码的一个崩溃转储,就足以使您从makefile中删除此选项。顺便说一句,它不会删除任何指令,它只是为优化器提供了一个用于存储的寄存器。
汉斯·帕桑

1
@HansPassant实际上,它对于发行版非常有用。有两个目标在Makefile中-ReleaseDebug实际上是非常有用的,拿这个选项作为一个例子。
Kotauskas,

2
@VladislavToncharov我想您从未需要从运行Release-build的客户调试崩溃转储吗?
Andreas Magnusson

Answers:


58

大多数较小的功能不需要框架指针-较大的功能可能需要一个框架指针。

真正的问题在于编译器如何很好地跟踪堆栈的使用方式以及堆栈中的内容(局部变量,传递给当前函数的参数以及为即将被调用的函数准备的参数)。我认为很难描述需要或不需要帧指针的函数(从技术上讲,没有函数必须具有帧指针-而是“如果编译器认为有必要降低代码的复杂性,其他代码”)。

我认为您不应在编码策略中“尝试使函数没有框架指针”-就像我说的那样,简单的函数不需要它们,因此使用-fomit-frame-pointer,您将获得一个可用的寄存器。用于寄存器分配器,并在函数进入/退出时保存1-3条指令。如果您的函数需要帧指针,那是因为编译器认为这是比不使用帧指针更好的选择。没有框架指针的功能不是目标,而拥有能够正确且快速运行的代码不是目标。

请注意,“没有帧指针”应该可以提供更好的性能,但这并不是可以带来巨大改进的魔术子弹,尤其是x86-64上已经没有16个寄存器了。在32位x86上,由于它只有8个寄存器,其中一个是堆栈指针,而占用另一个作为帧指针意味着占用了25%的寄存器空间。将其更改为12.5%是一个很大的进步。当然,为64位编译也将有很大帮助。


24
通常,编译器可以自己跟踪堆栈深度,不需要框架指针。例外情况是函数使用alloca哪个将堆栈指针移动可变的数量。帧指针的遗漏确实使调试变得非常困难。如果没有帧指针来帮助,局部变量将更难定位,堆栈跟踪也将难以重构。而且,由于参数离堆栈顶部很远,因此访问参数可能会变得更昂贵,并且可能需要更昂贵的寻址模式。
Raymond Chen

3
是的,因此,假设我们不使用alloca[谁?-我99%的肯定是我从未编写过使用alloca]或variable size local arrays[这是现代形式的alloca]的代码,然后编译器可能仍认为使用帧指针是更好的选择-因为编写编译器时不会盲目地遵循给出了选择,但给了您最佳选择。
Mats Petersson

6
@MatsPetersson VLA的不同之处在于alloca:一旦您离开声明它们的作用域,它们就会被丢弃,而alloca仅当您离开该函数时才释放空间。alloca我认为,这使得VLA易于遵循。
詹斯·古斯特

34
值得一提的是-fomit-frame-pointer,默认情况下,gcc在x86-64上已启用。
zwol

5
@JensGustedt,问题不在于它们被扔掉时,而是它们的大小(如alloca'ed空间)在编译时未知。通常,编译器将使用帧指针获取局部变量的地址,如果堆栈帧的大小不变,则可以将它们定位在距堆栈指针固定的偏移量处。
vonbrand

15

这与英特尔平台上的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-pointerGCC编译器选项,则可能会赢得例程的进入和退出代码,并获得额外的寄存器(除非默认情况下它本身已被打开,或者已被其他隐式打开)选项,在这种情况下,您已经从使用EBP / RBP寄存器的收益中受益,并且通过显式指定此选项(如果已经隐式启用)将不会获得额外的收益)。但是请注意,在16位和32位模式下,BP寄存器无法像AX的(AL和AH)那样访问其8位部分。

由于此选项除了允许编译器在优化中将EBP用作通用寄存器外,还防止了生成堆栈帧的退出和进入代码,这使调试变得复杂-这就是GCC文档明确声明的原因(通常以粗体强调样式),则启用此选项导致无法在某些计算机上进行调试

另请注意,与调试或优化相关的其他编译器选项可能会隐式打开-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。

因此,从文档本身尚不清楚-fomit-frame-pointer如果仅-O在x86平台上使用单个选项进行编译,是否会打开。可以凭经验进行测试,但是在这种情况下,GCC开发人员没有承诺将来不更改此选项的行为,恕不另行通知。

但是,Peter Cordes在评论中指出-fomit-frame-pointerx86-16平台和x86-32 / 64平台之间的默认设置有所不同。

此选项---fomit-frame-pointer与Intel C ++编译器15.0有关,不仅与GCC有关:

对于Intel编译器,此选项具有别名/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选项。当您指定选项-O1,-O2或-O3时,将设置-fomit-frame-pointer选项。

指定/ O1,/ O2或/ O3选项时,将设置/ Oy选项。当指定/ Od选项时,将设置选项/ Oy-。

使用-fno-omit-frame-pointer或/ Oy-选项会将可用的通用寄存器的数量减少1,并且可能导致代码效率略低。

注意对于Linux *系统:GCC 3.2异常处理当前存在问题。因此,当为C ++安装了GCC 3.2且打开了异常处理功能时(默认),英特尔编译器将忽略此选项。

请注意,以上引用仅与Intel C ++ 15编译器相关,与GCC不相关。


1
16位代码,并且BP默认为SS而不是DS,与gcc无关。 gcc -m16存在,但这是一个奇怪的特殊情况,它基本上使32位代码在各处使用前缀以16位模式运行。另请注意,-fomit-frame-pointer默认情况下已在x86上启用了多年-m32,并且比x86-64(-m64)上启用的时间更长。
彼得·科德斯

@PeterCordes-谢谢,我根据您提出的问题更新了编辑内容。
Maxim Masiutin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.