Спроба зрозуміти параметр gcc -fomit-frame-pointer


80

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

-fomit-frame-pointer

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

Відповідно до моїх знань про кожну функцію, у стеку пам’яті процесу буде створено запис активації, щоб зберегти всі локальні змінні та деяку більше інформації. Сподіваюсь, цей покажчик кадру означає адресу запису активації функції.

У цьому випадку, який тип функцій, для яких не потрібно зберігати покажчик кадру в реєстрі? Якщо я отримаю цю інформацію, я спробую розробити нову функцію, виходячи з цього (якщо це можливо), тому що, якщо покажчик кадру не зберігається в регістрах, деякі вказівки будуть опущені у двійковому файлі. Це дійсно значно покращить продуктивність програми, де є багато функцій.


5
Доведеться налагоджувати лише один збій дампа коду, скомпільований за допомогою цієї опції, достатньо для того, щоб змусити вилучити цю опцію зі своїх файлів make. Це не видаляє жодних вказівок, до речі, воно просто надає оптимізатору ще один регістр для роботи з ним для зберігання.
Ганс Пасант

1
@HansPassant Насправді, це досить корисно для збірок випусків. Маючи дві цілі в Makefile - Releaseі Debugнасправді дуже корисно, візьмемо цей варіант як приклад.
Kotauskas

3
@VladislavToncharov Я думаю, вам ніколи не потрібно було налагоджувати дамп аварійного завершення роботи від клієнта, який працює на вашій- Releasebuild?
Андреас Магнуссон

Відповіді:


60

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

Це насправді про те, наскільки добре компілятору вдається відстежувати, як використовується стек, і де знаходяться речі в стеку (локальні змінні, аргументи, передані поточній функції, та аргументи, що готуються для функції, яку потрібно викликати). Я не думаю, що легко охарактеризувати функції, які потребують або не потребують вказівника на кадр (технічно, НІЯКА функція НЕ МОЖЕ мати вказівник на кадр - це скоріше випадок "якщо компілятор вважає за необхідне зменшити складність інший код ").

Я не думаю, що вам слід "намагатися зробити так, щоб функції не мали вказівника на кадр" як частину вашої стратегії кодування - як я вже сказав, простим функціям вони не потрібні, тому використовуйте -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 02

6
@MatsPetersson VLA відрізняються від alloca: вони викидаються, як тільки ви залишаєте область, в якій вони оголошені, тоді як allocaпростір звільняється лише тоді, коли ви залишаєте функцію. Це робить VLA набагато простішим, ніж alloca, я думаю.
Jens Gustedt

35
Можливо, варто згадати, що gcc -fomit-frame-pointerза замовчуванням увімкнено для x86-64.
zwol

5
@JensGustedt, проблема полягає не в тому, коли їх викидають, проблема полягає в тому, що їх розмір (як-от alloca"простір") невідомий під час компіляції . Зазвичай компілятор використовує покажчик кадру для отримання адреси локальних змінних, якщо розмір кадру стека не змінюється, він може знаходити їх із фіксованим зміщенням від покажчика стека.
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) функція не має параметрів.

Опускання кадрів стека (код входу та виходу для процедури) може зробити код меншим і швидшим, але також може негативно вплинути на здатність налагоджувачів робити зворотне відстеження даних у стеці та відображати їх програмісту. Це параметри компілятора, які визначають, за яких умов повинна задовольняти функція, щоб компілятор призначив їй код входу та виходу з фрейму стека. Наприклад, компілятор може мати варіанти додавання такого коду входу та виходу до функцій у таких випадках: (a) завжди, (b) ніколи, (c) за потреби (вказуючи умови).

Повернення від загального до особливостей: якщо ви будете використовувати -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 ++ 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 For / Oy-: вимкнення / O1, / O2 або / O3-оптимізація Параметр -fno-omit-frame-pointer встановлюється, коли ви вказуєте параметр - O0 або опція -g. Параметр -fomit-frame-pointer встановлюється, коли ви вказуєте параметр -O1, -O2 або -O3.

Параметр / Oy встановлюється, коли ви вказуєте параметр / O1, / O2 або / O3. Option / 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.