Пытаемся понять параметр 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 - спасибо, я обновил правки в соответствии с проблемами, которые вы подняли.
Максим Масютин