GCC -fPIC варіант


437

Я читав про Параметри GCC для конвенцій генерації коду , але не міг зрозуміти, що робить "Створення коду, незалежного від позиції" (PIC). Наведіть приклад, щоб пояснити мені, що це означає.


25
Кланг також використовує -fPIC.
пішов

Відповіді:


526

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

Скажімо, скачки будуть генеруватися як відносні, а не абсолютні.

Псевдоскладання:

PIC: Це спрацювало б у коді за адресою 100 чи 1000

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

Non-PIC: Це буде працювати лише в тому випадку, якщо код за адресою 100

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

EDIT: У відповідь на коментар.

Якщо ваш код складено з -fPIC, він підходить для включення до бібліотеки - бібліотека повинна бути в змозі переїхати зі свого бажаного місця в пам'яті на іншу адресу, може бути ще одна завантажена бібліотека за адресою, яку ваша бібліотека віддає перевагу.


36
Цей приклад зрозумілий, але як користувач, яка буде різниця, якщо я створять спільний бібліотечний (.so) файл без можливості? Чи є випадки, що без -fPIC моя ліб недійсна?
Нарек

16
Так, створення спільної бібліотеки, яка не є PIC, може бути помилкою.
Джон Цвінк

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

19
@Narek: помилка виникає, якщо один процес хоче завантажити більше однієї спільної бібліотеки на одну і ту ж віртуальну адресу. Оскільки бібліотеки не можуть передбачити, які ще бібліотеки можуть бути завантажені, ця проблема неминуча з традиційною концепцією спільної бібліотеки. Тут не допомагає віртуальний адресний простір.
Філіпп

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

61

Спробую пояснити те, що вже було сказано, більш простим способом.

Щоразу, коли завантажується спільна ліб, завантажувач (код в ОС, який завантажує будь-яку програму, яку ви запускаєте) змінює деякі адреси в коді залежно від того, куди був завантажений об'єкт.

У наведеному вище прикладі "111" в не-PIC-коді записується навантажувачем під час першого завантаження.

Для спільних об'єктів, можливо, ви хочете, щоб це було так, оскільки компілятор може зробити деякі оптимізації цього коду.

Для спільного об'єкта, якщо інший процес захоче "посилання" на цей код, він повинен прочитати його на ті самі віртуальні адреси, або "111" не матиме сенсу. але цей віртуальний простір вже може бути використаний у другому процесі.


Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.Я думаю, що це неправильно, якщо їх компілюють із -fpic та причиною, чому -fpic існує, тобто з міркувань продуктивності або тому, що у вас є завантажувач, який не в змозі переїхати, або тому, що вам потрібно кілька копій в різних місцях або з багатьох інших причин.
robsn

Чому б не завжди використовувати -fpic?
Джей

1
@Jay - тому що для кожного виклику функції знадобиться ще один розрахунок (адреса функції). Отже, якщо не потрібно, то краще не використовувати його.
Roee Gavirel

45

Код, вбудований у спільні бібліотеки, як правило, повинен бути незалежним від позиції кодом, так що спільна бібліотека може легко завантажуватися за (більш-менш) будь-якою адресою в пам'яті. -fPICВаріант гарантує , що GCC виробляє такий код.


Чому спільну бібліотеку не було б завантажено за будь-якою адресою в пам'яті, не маючи -fPICпрапора? це не пов'язано з програмою? коли програма працює, операційна система завантажує її в пам'ять. Я щось пропускаю?
Тоні Танноус

1
Чи використовується -fPICпрапор, щоб забезпечити завантаження цієї вкладки на будь-яку віртуальну адресу в процесі, що пов'язує її? вибачте за подвійні коментарі минуло 5 хвилин не може редагувати попередній.
Тоні Танноус

1
Розрізняють побудову спільної бібліотеки (створення libwotnot.so) та посилання на неї ( -lwotnot). Посилаючись, вам не потрібно метушитися -fPIC. Раніше було так, що при складанні спільної бібліотеки вам потрібно було переконатися, що -fPICвикористовувались усі об'єктивні файли, які потрібно вбудувати у спільну бібліотеку. Правила можуть бути змінені, оскільки компілятори складаються за PIC-кодом за замовчуванням в ці дні. Отже, те, що було критичним 20 років тому, а може бути важливим ще 7 років тому, є менш важливим у ці дні, я вважаю. Адреси поза ядром o / s - це "завжди" віртуальні адреси.
Джонатан Леффлер

Тому раніше вам довелося додати -fPIC. Не передаючи цей прапор, згенерований код під час створення .so потрібно завантажувати на конкретні віртуальні адреси, які можуть бути використані?
Tony Tannous

1
Так, оскільки якщо ви не використовували прапор PIC, код не був надійно переміщений. Такі речі, як ASLR (рандомізація розміщення адресного простору) неможливі, якщо код не є PIC (або, принаймні, настільки важко досягти, що вони фактично неможливі).
Джонатан Леффлер

21

Додавання далі ...

У кожному процесі є однаковий віртуальний адресний простір (Якщо рандомізацію віртуальної адреси зупинено за допомогою прапора в ОС Linux) (Детальніше Вимкніть та повторно увімкніть рандомізацію макета адресного простору лише для себе )

Отже, якщо його один exe без спільного посилання (Гіпотетичний сценарій), то ми завжди можемо надати ту саму віртуальну адресу тій же інструкції Asm без будь-якої шкоди.

Але коли ми хочемо пов’язати спільний об'єкт із exe, тоді ми не впевнені у стартовій адресі, призначеній спільному об'єкту, оскільки це буде залежати від порядку, яким були об'єднані спільні об'єкти. різні віртуальні адреси залежно від процесу, на який він пов'язується.

Таким чином, один процес може дати початкову адресу.

Тому вони завжди повинні використовувати режим відносної адресації і, отже, опцію fpic.


1
Дякуємо за пояснення віртуального додатка.
Hot.PxL

2
Чи може хтось пояснити, як це не проблема зі статичною бібліотекою, чому вам не доведеться використовувати -fPIC у статичній бібліотеці? Я розумію, що зв'язування здійснюється в час компіляції (або безпосередньо після насправді), але якщо у вас є 2 статичні бібліотеки з кодом, залежним від позиції, як вони будуть пов'язані?
Майкл П

3
Об'єктний файл @MichaelP має таблицю міток, залежних від положення, і коли конкретний файл obj пов'язаний, усі мітки оновлюються відповідно. Це неможливо зробити для спільної бібліотеки.
Слава

16

Посилання на функцію в динамічній бібліотеці вирішується під час завантаження бібліотеки або під час виконання. Тому і виконуваний файл, і динамічна бібліотека завантажуються в пам'ять при запуску програми. Адреса пам'яті, на яку завантажується динамічна бібліотека, не може бути визначена заздалегідь, оскільки фіксована адреса може стикатися з іншою динамічною бібліотекою, яка вимагає тієї самої адреси.


Існує два найпоширеніших методи вирішення цієї проблеми:

1.Релокація. Усі вказівники та адреси в коді змінюються, якщо необхідно, відповідно до фактичної адреси завантаження. Переселення здійснюють линкер і навантажувач.

2. Незалежний від положення код. Усі адреси в коді відносно поточного положення. Спільні об'єкти в системах, подібних Unix, за замовчуванням використовують незалежний від позиції код. Це менш ефективно, ніж переїзд, якщо програма триває тривалий час, особливо в 32-бітному режимі.


Назва " незалежний від позиції код " насправді означає:

  • Розділ коду не містить абсолютних адрес, які потребують переїзду, а лише власні відносні адреси. Тому розділ коду можна завантажувати за довільною адресою пам'яті та ділити між декількома процесами.

  • Розділ даних не поділяється між декількома процесами, оскільки він часто містить дані, що записуються. Тому розділ даних може містити покажчики або адреси, які потребують переїзду.

  • Усі публічні функції та загальнодоступні дані можуть бути замінені в Linux. Якщо функція в головному виконуваному файлі має те саме ім'я, що і функція в спільному об'єкті, то версія в магістралі матиме перевагу не тільки при виклику з головного, але і при виклику з спільного об'єкта. Так само, коли глобальна змінна в основному має те саме ім'я, що і глобальна змінна у спільному об'єкті, тоді головний екземпляр буде використаний, навіть коли звертатися до цього спільного об'єкта.


Ця так звана символьна інтерпозиція покликана імітувати поведінку статичних бібліотек.

Об'єкт, що поділяється, має таблицю покажчиків на його функції, звану таблицею зв’язків процедур (PLT) та таблицю покажчиків на його змінні, що називаються глобальною таблицею зміщення (GOT), щоб реалізувати цю функцію "переосмислення". Усі звернення до функцій та загальнодоступних змінних проходять через ці таблиці.

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

Ви можете прочитати більше з цієї статті: http://www.agner.org/optimize/optimizing_cpp.pdf


9

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

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

Операційна система спробує поділитися єдиною копією "спільної бібліотеки об'єктів", завантаженою в пам'ять, з усіма програмами, які пов'язані з тією самою спільною бібліотекою об'єктів.

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

Очевидно, будь-яка спроба рандомізувати адресу завантаження бібліотеки між програмами та / або програмними екземплярами (щоб зменшити можливість створення експлуатованого шаблону) зробить такі випадки поширеними, а не рідкісними, тому, коли система включила цю можливість, слід робити всі спроби компілювати всі бібліотеки спільних об'єктів, щоб вони були незалежними.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.