Почему программа, скомпилированная с -fpic и -pie, имеет таблицу перемещения?

Если тривиальная программа компилируется с помощью следующей команды:

arm-none-eabi-gcc -shared -fpic -pie --specs=nosys.specs simple.c -o simple.exe

и записи о перемещении печатаются с помощью команды:

arm-none-eabi-readelf simple.exe -r

Есть куча разделов о переездах (см. Ниже).

Поскольку флаги -fpic / -pie приводят к тому, что компилятор генерирует независимый от позиции исполняемый файл, мое наивное (и явно неверное) предположение состоит в том, что нет необходимости в таблице перемещений, поскольку загрузчик может разместить исполняемый образ в любом месте без проблем. Так почему же там вообще есть таблица перемещения, и означает ли это, что код на самом деле не зависит от позиции?

Relocation section '.rel.dyn' at offset 0x82d4 contains 37 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
000084a8  00000017 R_ARM_RELATIVE   
000084d0  00000017 R_ARM_RELATIVE   
00008508  00000017 R_ARM_RELATIVE   
00008510  00000017 R_ARM_RELATIVE   
0000855c  00000017 R_ARM_RELATIVE   
00008560  00000017 R_ARM_RELATIVE   
00008564  00000017 R_ARM_RELATIVE   
00008678  00000017 R_ARM_RELATIVE   
0000867c  00000017 R_ARM_RELATIVE   
0000870c  00000017 R_ARM_RELATIVE   
00008710  00000017 R_ARM_RELATIVE   
00008714  00000017 R_ARM_RELATIVE   
00008718  00000017 R_ARM_RELATIVE   
00008978  00000017 R_ARM_RELATIVE   
000089dc  00000017 R_ARM_RELATIVE   
000089e0  00000017 R_ARM_RELATIVE   
00008abc  00000017 R_ARM_RELATIVE   
00008ae4  00000017 R_ARM_RELATIVE   
00018af4  00000017 R_ARM_RELATIVE   
00018af8  00000017 R_ARM_RELATIVE   
00018afc  00000017 R_ARM_RELATIVE   
00018c04  00000017 R_ARM_RELATIVE   
00018c08  00000017 R_ARM_RELATIVE   
00018c0c  00000017 R_ARM_RELATIVE   
00018c34  00000017 R_ARM_RELATIVE   
00019028  00000017 R_ARM_RELATIVE   
000084cc  00000c02 R_ARM_ABS32       00000000   __libc_fini
0000850c  00000602 R_ARM_ABS32       00000000   __deregister_frame_inf
00008558  00001302 R_ARM_ABS32       00000000   __register_frame_info
00008568  00001202 R_ARM_ABS32       00000000   _Jv_RegisterClasses
00008664  00000d02 R_ARM_ABS32       00000000   __stack
00008668  00000a02 R_ARM_ABS32       00000000   hardware_init_hook
0000866c  00000802 R_ARM_ABS32       00000000   software_init_hook
00008670  00000502 R_ARM_ABS32       0001902c   __bss_start__
00008674  00000702 R_ARM_ABS32       00019048   __bss_end__
0000897c  00001402 R_ARM_ABS32       00000000   free
00008ac0  00000402 R_ARM_ABS32       00000000   malloc

Relocation section '.rel.plt' at offset 0x83fc contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00018be8  00000416 R_ARM_JUMP_SLOT   00000000   malloc
00018bec  00000616 R_ARM_JUMP_SLOT   00000000   __deregister_frame_inf
00018bf0  00001316 R_ARM_JUMP_SLOT   00000000   __register_frame_info
00018bf4  00001416 R_ARM_JUMP_SLOT   00000000   free

1 ответ

Исполняемый файл состоит из нескольких разделов. Хотя фактические детали реализации различаются, их можно условно разделить на четыре группы:

  1. Исполняемый код только для чтения, также известный как "Текст"
  2. Постоянные данные только для чтения (глобальные константы)
  3. (Инициализировано) Чтение-запись данных (глобальные переменные с инициализаторами)
  4. Неинициализированные данные чтения-записи (другие глобальные переменные, инициализированные в 0)

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

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

int x;
int *y = &x; // y needs a relocation.

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

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

Теперь реальный ответ: PIC был призван решить вышеупомянутые проблемы, избавившись от перемещения текста, а не от всех перемещений.

Сравнительно мало перемещений в данных только для чтения и инициализированных данных, поэтому ни (1.), ни (2.) обычно не представляют проблемы. Мы даже не заботимся о (2.) для данных чтения-записи, так как нам все равно нужны отдельные копии для каждого процесса. И на самом деле компилятор не может сделать данные независимыми от позиции, потому что, если вы попросили глобальный int* y = &x; тогда у компилятора нет другого выбора, кроме как поместить указатель туда.

Теперь, как код становится независимым от позиции? Это зависит от платформы, но часто включает несколько относительно неэффективных операций, или процессор накладывает произвольные ограничения на максимальные смещения, используемые в более эффективных инструкциях для доступа к данным и коду независимым от позиции способом. Кроме того, динамическое связывание означает, что адрес некоторых функций даже не известен как относительное смещение. Таким образом, компиляторы, как правило, используют таблицы, которые содержат фактические адреса, и код будет искать фактические адреса из таблицы. Таблицы, по-разному известные как GOT, TOC, PLT и, возможно, несколько других имен на разных платформах, скорее всего будут постоянными данными с большим количеством перемещений.

Если нельзя избежать перемещений, идея состоит в том, чтобы собрать их все в одном месте, чтобы минимизировать проблемы (1.) и (2.).

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