GCC -FPIC опция

437

Я читал об опциях GCC для соглашений по генерации кода , но не мог понять, что делает «Генерировать независимый от позиции код (PIC)». Пожалуйста, приведите пример, чтобы объяснить мне, что это значит.

Нарек
источник
25
Clang также использует -fPIC.
прошло
3
Связанный: -fpie: stackoverflow.com/questions/2463150/…
Сиро Сантилли 法轮功 冠状 病 六四 事件 法轮功

Ответы:

526

Независимый от позиции код означает, что сгенерированный машинный код не зависит от того, находится ли он по определенному адресу для работы.

Например, скачки будут генерироваться как относительные, а не абсолютные.

Псевдо-сборки:

PIC: это сработало бы, если бы код был по адресу 100 или 1000

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

Номера для PIC: это будет работать, только если код находится по адресу 100

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

РЕДАКТИРОВАТЬ: В ответ на комментарий.

Если ваш код скомпилирован с -fPIC, он подходит для включения в библиотеку - библиотека должна быть в состоянии перемещаться из ее предпочтительного расположения в памяти на другой адрес, может быть другая уже загруженная библиотека по адресу, который предпочитает ваша библиотека.

Erik
источник
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 - потому что для каждого вызова функции потребуется еще один расчет (адрес функции). Таким образом, с точки зрения производительности, если не нужно, лучше не использовать его.
Роу Гавирел
45

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

Джонатан Леффлер
источник
Почему разделяемая библиотека не будет загружена по любому адресу в памяти без установленного -fPICфлага? разве это не связано с программой? когда программа запущена, операционная система загружает ее в память. Я что-то пропустил?
Тони Тэннус
1
-fPICИспользуется ли флаг, чтобы гарантировать, что эта библиотека может быть загружена на любой виртуальный адрес в процессе, который связывает ее? извините за двойной комментарий 5 мин. истекшее время не может редактировать предыдущее.
Тони Тэннус
1
Различают сборку разделяемой библиотеки (создание libwotnot.so) и связь с ней ( -lwotnot). При связывании вам не нужно суетиться -fPIC. Раньше был случай, когда при создании разделяемой библиотеки вам нужно было убедиться, что -fPICона используется для всех объектных файлов, которые будут встроены в разделяемую библиотеку. Правила, возможно, изменились, потому что компиляторы строят с кодом PIC по умолчанию, в наши дни. Итак, то, что было критическим 20 лет назад и могло быть важным 7 лет назад, в наши дни, я считаю, менее важно. Адреса вне ядра o / s всегда являются «виртуальными адресами».
Джонатан Леффлер
Так что ранее вы должны были добавить -fPIC. Не передавая этот флаг, сгенерированный код при сборке .so должен быть загружен на конкретные виртуальные адреса, которые могут быть использованы?
Тони Тэннус
1
Да, потому что, если вы не использовали флаг PIC, код не был надежно перемещен. Такие вещи, как ASLR (рандомизация размещения адресного пространства), невозможны, если код не PIC (или, по крайней мере, настолько трудно достичь, что фактически невозможно).
Джонатан Леффлер
21

Добавление дальше ...

Каждый процесс имеет одно и то же виртуальное адресное пространство (если рандомизация виртуального адреса остановлена ​​с помощью флага в ОС Linux) (для получения более подробной информации отключите и снова включите рандомизацию макета адресного пространства только для себя )

Так что если это один exe-файл без разделяемой ссылки (гипотетический сценарий), то мы всегда можем дать один и тот же виртуальный адрес одной и той же инструкции asm без какого-либо вреда.

Но когда мы хотим связать общий объект с exe, то мы не уверены в начальном адресе, назначенном для общего объекта, так как он будет зависеть от порядка, в котором были связаны общие объекты. При этом инструкция asm внутри .so всегда будет иметь разные виртуальные адреса в зависимости от процесса, на который он ссылается.

Таким образом, один процесс может дать начальный адрес .so как 0x45678910 в своем собственном виртуальном пространстве, а другой процесс может одновременно дать начальный адрес 0x12131415, и если они не используют относительную адресацию, .so не будет работать вообще.

Поэтому они всегда должны использовать режим относительной адресации и, следовательно, опцию fpic.

Ritesh
источник
1
Спасибо за объяснение виртуального адреса.
Hot.PxL 22.12.14
2
Может кто-нибудь объяснить, как это не проблема со статической библиотекой, почему вам не нужно использовать -fPIC для статической библиотеки? Я понимаю, что связывание выполняется во время компиляции (или сразу после него), но если у вас есть 2 статические библиотеки с позиционно-зависимым кодом, как они будут связаны?
Майкл П
3
В объектном файле @MichaelP имеется таблица меток, зависящих от позиции, и когда конкретный файл obj связан, все метки обновляются соответствующим образом. Это нельзя сделать с общей библиотекой.
Слава
16

Ссылка на функцию в динамической библиотеке разрешается при загрузке библиотеки или во время выполнения. Следовательно, и исполняемый файл, и динамическая библиотека загружаются в память при запуске программы. Адрес памяти, по которому загружается динамическая библиотека, не может быть определен заранее, поскольку фиксированный адрес может конфликтовать с другой динамической библиотекой, требующей того же адреса.


Есть два широко используемых метода для решения этой проблемы:

1.Relocation. Все указатели и адреса в коде модифицируются, если необходимо, для соответствия фактическому адресу загрузки. Перемещение осуществляется компоновщиком и загрузчиком.

2. Независимый от позиции код. Все адреса в коде относятся к текущей позиции. Общие объекты в Unix-подобных системах по умолчанию используют независимый от позиции код. Это менее эффективно, чем перемещение, если программа выполняется долгое время, особенно в 32-битном режиме.


Название « позиционно-независимый код » фактически подразумевает следующее:

  • Раздел кода не содержит абсолютных адресов, которые нужно переместить, а только собственные относительные адреса. Следовательно, секция кода может быть загружена по произвольному адресу памяти и распределена между несколькими процессами.

  • Раздел данных не используется несколькими процессами, поскольку он часто содержит данные для записи. Поэтому раздел данных может содержать указатели или адреса, которые необходимо переместить.

  • Все общедоступные функции и общедоступные данные могут быть переопределены в Linux. Если функция в главном исполняемом файле имеет то же имя, что и функция в общем объекте, то версия в главном будет иметь приоритет не только при вызове из основного, но и при вызове из общего объекта. Аналогично, когда глобальная переменная в main имеет то же имя, что и глобальная переменная в общем объекте, тогда будет использоваться экземпляр в main, даже если доступ к нему осуществляется из общего объекта.


Это так называемое взаимное расположение символов предназначено для имитации поведения статических библиотек.

Общий объект имеет таблицу указателей на свои функции, называемую таблицей связывания процедур (PLT), и таблицу указателей на его переменные, называемую глобальной таблицей смещений (GOT), для реализации этой функции «переопределения». Все обращения к функциям и общедоступным переменным проходят через эти таблицы.

ps Там, где нельзя избежать динамического связывания, существуют различные способы избежать трудоемких функций позиционно-независимого кода.

Вы можете прочитать больше из этой статьи: http://www.agner.org/optimize/optimizing_cpp.pdf

bruziuz
источник
9

Незначительное дополнение к уже опубликованным ответам: объектные файлы, не скомпилированные, чтобы быть независимыми от позиции, можно перемещать; они содержат записи таблицы перемещения.

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

Операционная система попытается поделиться единственной копией «библиотеки общих объектов», загруженной в память, со всеми программами, которые связаны с этой же библиотекой общих объектов.

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

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

Поскольку вызовы в эти библиотеки из основной программы также будут перемещаться, это значительно снижает вероятность копирования общей библиотеки.

user1016759
источник