Пытаемся понять параметр gcc -fomit-frame-pointer


80

Я попросил Google дать мне значение gccпараметра -fomit-frame-pointer, который перенаправляет меня к приведенному ниже утверждению.

-fomit-frame-pointer

Не храните указатель фрейма в регистре для функций, которым он не нужен. Это позволяет избежать инструкций по сохранению, настройке и восстановлению указателей кадров; он также делает дополнительный регистр доступным для многих функций. Это также делает невозможной отладку на некоторых машинах.

Насколько мне известно о каждой функции, запись активации будет создана в стеке памяти процесса для хранения всех локальных переменных и некоторой дополнительной информации. Я надеюсь, что этот указатель кадра означает адрес записи активации функции.

В этом случае, каковы типы функций, для которых не нужно хранить указатель кадра в регистре? Если я получу эту информацию, я попытаюсь разработать новую функцию на ее основе (если возможно), потому что, если указатель кадра не хранится в регистрах, некоторые инструкции будут опущены в двоичном формате. Это действительно заметно улучшит производительность в приложении, в котором много функций.


5
Достаточно отладить только один аварийный дамп кода, который был скомпилирован с этой опцией, чтобы заставить вас исключить эту опцию из ваших make-файлов. Между прочим, он не удаляет никаких инструкций, он просто дает оптимизатору еще один регистр для работы с хранилищем.
— Hans Passant

1
@HansPassant На самом деле, он очень полезен для релизных сборок. Имея две цели в Makefile - Releaseи Debugна самом деле очень полезно, принять этот вариант в качестве примера.
— Котаускас

3
@VladislavToncharov Я думаю, вам никогда не нужно было отлаживать аварийный дамп от клиента, который запускает вашу Release-build?
— Андреас Магнуссон

Ответы:


60

Большинству небольших функций не нужен указатель кадра - он МОЖЕТ понадобиться более крупным функциям.

На самом деле речь идет о том, насколько хорошо компилятор отслеживает, как используется стек и где что-то находится в стеке (локальные переменные, аргументы, переданные текущей функции, и аргументы, подготовленные для функции, которая будет вызвана). Я не думаю, что легко охарактеризовать функции, которым нужен или не нужен указатель кадра (технически НИКАКАЯ функция НЕ ДОЛЖНА иметь указатель кадра - это скорее случай «если компилятор сочтет необходимым уменьшить сложность другой код »).

Я не думаю, что вам следует «пытаться сделать функции без указателя кадра» как часть вашей стратегии кодирования - как я уже сказал, простые функции в них не нуждаются, поэтому используйте -fomit-frame-pointer, и вы получите еще один доступный регистр для распределителя регистров и сохраните 1-3 инструкции по входу / выходу в functions. Если вашей функции нужен указатель кадра, это потому, что компилятор решает, что это лучший вариант, чем не использовать указатель кадра. Это не цель - иметь функции без указателя фрейма, это цель - иметь код, который работает правильно и быстро.

Обратите внимание, что «отсутствие указателя кадра» должно дать лучшую производительность, но это не какая-то волшебная палочка, которая дает огромные улучшения - особенно на x86-64, у которого для начала уже есть 16 регистров. В 32-битной системе x86, поскольку она имеет только 8 регистров, один из которых является указателем стека, а другой занимает место в качестве указателя кадра, это означает, что занято 25% пространства регистров. Изменить это значение на 12,5% - это большое улучшение. Конечно, компиляция для 64-битной версии тоже очень поможет.


24
Обычно компилятор может отслеживать глубину стека самостоятельно и не нуждается в указателе кадра. Исключение составляют случаи, когда функция использует, allocaкоторый перемещает указатель стека на переменную величину. Отсутствие указателя кадра значительно усложняет отладку. Локальные переменные труднее найти, а трассировки стека гораздо сложнее восстановить без помощи указателя кадра. Кроме того, доступ к параметрам может стать более дорогостоящим, поскольку они находятся далеко от вершины стека и могут потребовать более дорогих режимов адресации.
— Raymond Chen

3
Да, так, если мы не используем alloca[кто использует? - Я на 99% уверен, что никогда не писал код, который использует alloca] или variable size local arrays[который является современной формой alloca], тогда компилятор МОЖЕТ все же решить, что использование указателя кадра - лучший вариант - потому что компиляторы написаны так, чтобы не слепо следовать предоставлены варианты, но предоставят вам лучший выбор.
— Матс Петерссон

6
@MatsPetersson VLA отличается от alloca: они выбрасываются, как только вы покидаете область, в которой они объявлены, тогда как allocaпространство освобождается только тогда, когда вы выходите из функции. allocaЯ думаю, это делает VLA намного проще, чем .
— Йенс Густедт

35
Возможно, стоит упомянуть, что gcc -fomit-frame-pointerпо умолчанию включен для x86-64.
— zwol 02

5
@JensGustedt, проблема не в том, что они выбрасываются, проблема в том, что их размер (например alloca, пространство ed) неизвестен во время компиляции . Обычно компилятор будет использовать указатель фрейма для получения адреса локальных переменных, если размер фрейма стека не меняется, он может найти их по фиксированному смещению от указателя стека.
— vonbrand 02

15

Это все о реестре BP / EBP / RBP на платформах Intel. По умолчанию в этом регистре используется сегмент стека (не требуется специальный префикс для доступа к сегменту стека).

EBP - лучший выбор регистра для доступа к структурам данных, переменным и динамически выделяемому рабочему пространству в стеке. EBP часто используется для доступа к элементам в стеке относительно фиксированной точки в стеке, а не относительно текущего TOS. Обычно он определяет базовый адрес текущего кадра стека, установленного для текущей процедуры. Когда EBP используется в качестве базового регистра при вычислении смещения, смещение вычисляется автоматически в текущем сегменте стека (т. Е. Сегменте, выбранном в данный момент SS). Поскольку SS не нужно указывать явно, кодирование команд в таких случаях более эффективно. EBP также может использоваться для индексации сегментов, адресуемых через другие регистры сегментов.

(источник - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )

Поскольку на большинстве 32-битных платформ сегмент данных и сегмент стека одинаковы, эта ассоциация EBP / RBP со стеком больше не является проблемой. То же самое и на 64-битных платформах: архитектура x86-64, представленная AMD в 2003 году, в значительной степени отказалась от поддержки сегментации в 64-битном режиме: четыре сегментных регистра: CS, SS, DS и ES принудительно установлены на 0. Эти обстоятельства 32-битных и 64-битных платформ x86 по существу означают, что регистр EBP / RBP может использоваться без какого-либо префикса в инструкциях процессора, которые обращаются к памяти.

Таким образом, параметр компилятора, о котором вы писали, позволяет использовать BP / EBP / RBP для других целей, например, для хранения локальной переменной.

Под «Это позволяет избежать инструкций по сохранению, настройке и восстановлению указателей кадров» подразумевается отказ от следующего кода при вводе каждой функции:

или enterинструкция, которая очень пригодилась на процессорах Intel 80286 и 80386.

Также перед возвратом функции используется следующий код:

или leaveинструкция.

Инструменты отладки могут сканировать данные стека и использовать эти переданные данные регистров EBP при поиске call sites, то есть отображать имена функции и аргументы в том порядке, в котором они были вызваны иерархически.

У программистов могут возникнуть вопросы о кадрах стека не в широком смысле (что это единый объект в стеке, который обслуживает только один вызов функции и сохраняет адрес возврата, аргументы и локальные переменные), а в узком смысле - когда термин stack framesупоминается в контекст параметров компилятора. С точки зрения компилятора, кадр стека - это просто код входа и выхода для подпрограммы , который подталкивает якорь к стеку, который также можно использовать для отладки и для обработки исключений. Инструменты отладки могут сканировать данные стека и использовать эти якоря для обратной трассировки, находясь call sitesв стеке, то есть отображать имена функций в том порядке, в котором они были вызваны иерархически.

Вот почему для программиста очень важно понимать, что такое фрейм стека с точки зрения параметров компилятора - потому что компилятор может контролировать, генерировать этот код или нет.

В некоторых случаях кадр стека (код входа и выхода для процедуры) может быть опущен компилятором, а доступ к переменным будет осуществляться напрямую через указатель стека (SP / ESP / RSP), а не через удобный базовый указатель (BP / ESP / RSP). Условия, при которых компилятор пропускает кадры стека для некоторых функций, могут быть разными, например: (1) функция является листовой функцией (т. Е. Конечной сущностью, которая не вызывает другие функции); (2) не используются исключения; (3) никакие процедуры не вызываются с исходящими параметрами в стеке; (4) функция не имеет параметров.

Пропуск кадров стека (код входа и выхода для процедуры) может сделать код меньше и быстрее, но также может отрицательно повлиять на способность отладчиков отслеживать данные в стеке и отображать их программисту. Это параметры компилятора, которые определяют, при каких условиях функция должна удовлетворять, чтобы компилятор присудил ей код входа и выхода из кадра стека. Например, у компилятора могут быть опции для добавления такого кода входа и выхода к функциям в следующих случаях: (а) всегда, (б) никогда, (в) при необходимости (с указанием условий).

Возвращаясь от общих деталей к частностям: если вы будете использовать параметр -fomit-frame-pointerкомпилятора GCC, вы можете выиграть как по коду входа, так и по коду выхода для подпрограммы, а также по наличию дополнительного регистра (если он уже не включен по умолчанию либо сам, либо неявно другими опции, в этом случае вы уже получаете выгоду от использования регистра EBP / RBP, и никакого дополнительного выигрыша не будет получено путем явного указания этой опции, если она уже неявно включена). Однако обратите внимание, что в 16-битном и 32-битном режимах регистр BP не имеет возможности доступа к 8-битным его частям, как у AX (AL и AH).

Поскольку этот параметр, помимо разрешения компилятору использовать 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 на машинах, где это не мешает отладке.

Таким образом, из документации как таковой неясно, -fomit-frame-pointerбудет ли он включен, если вы просто компилируете с одним -Oпараметром на платформе x86. Это может быть проверено эмпирически, но в этом случае разработчики GCC не принимают на себя обязательств не изменять поведение этой опции в будущем без предварительного уведомления.

Однако Питер Кордес указал в комментариях, что есть разница в настройках по -fomit-frame-pointerумолчанию для платформ x86-16 и платформ x86-32 / 64.

Этот параметр - -fomit-frame-pointer- также имеет отношение к Intel C ++ Compiler 15.0 , а не только к GCC:

Для компилятора Intel у этой опции есть псевдоним /Oy.

Вот что об этом написала Intel:

Эти параметры определяют, будет ли 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.

Параметр / Oy устанавливается при указании параметра / O1, / O2 или / O3. Параметр / Oy- устанавливается при указании параметра / Od.

Использование параметра -fno-omit-frame-pointer или / Oy- уменьшает количество доступных регистров общего назначения на 1 и может привести к несколько менее эффективному коду.

ПРИМЕЧАНИЕ Для систем Linux *: в настоящее время существует проблема с обработкой исключений GCC 3.2. Поэтому компилятор Intel игнорирует эту опцию, если GCC 3.2 установлен для C ++ и включена обработка исключений (по умолчанию).

Имейте в виду, что приведенная выше цитата актуальна только для компилятора Intel C ++ 15, а не для GCC.


1
16-битный код и значение BP по умолчанию для SS вместо DS не совсем актуально для gcc. gcc -m16существует, но это странный частный случай, который в основном создает 32-битный код, который работает в 16-битном режиме, используя повсюду префиксы. Также обратите внимание, что -fomit-frame-pointerон был включен по умолчанию в течение многих лет на x86 -m32и дольше, чем на x86-64 ( -m64).
— Питер Кордес

@PeterCordes - спасибо, я обновил правки в соответствии с проблемами, которые вы подняли.
— Максим Масютин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.