GCC -FPIC опция

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

6 ответов

Решение

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

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

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

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, он подходит для включения в библиотеку - библиотека должна быть в состоянии перемещаться из своего предпочтительного расположения в памяти на другой адрес, может быть другая уже загруженная библиотека по адресу, который предпочитает ваша библиотека.

Я постараюсь объяснить то, что уже было сказано, более простым способом.

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

В вышеприведенном примере "111" в коде, отличном от PIC, записывается загрузчиком при первой загрузке.

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

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

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

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


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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам