Почему программа, скомпилированная с -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 ответ
Исполняемый файл состоит из нескольких разделов. Хотя фактические детали реализации различаются, их можно условно разделить на четыре группы:
- Исполняемый код только для чтения, также известный как "Текст"
- Постоянные данные только для чтения (глобальные константы)
- (Инициализировано) Чтение-запись данных (глобальные переменные с инициализаторами)
- Неинициализированные данные чтения-записи (другие глобальные переменные, инициализированные в 0)
Непозиционно-независимый код содержит множество ссылок на адреса функций, глобальных переменных и глобальных констант.
Данные только для чтения и инициализированные данные чтения-записи иногда содержат ссылки на адреса функций, глобальных переменных и глобальных констант:
int x;
int *y = &x; // y needs a relocation.
Загрузчик может перемещать код на основе перемещений, есть только две проблемы:
- Перемещение занимает время при запуске программы / загрузке библиотеки
- Если мы переместимся, у нас теперь будет измененная копия текстового сегмента в оперативной памяти, которая отличается для каждого процесса, который загружает нашу библиотеку, поэтому мы будем тратить впустую оперативную память.
Теперь реальный ответ: PIC был призван решить вышеупомянутые проблемы, избавившись от перемещения текста, а не от всех перемещений.
Сравнительно мало перемещений в данных только для чтения и инициализированных данных, поэтому ни (1.), ни (2.) обычно не представляют проблемы. Мы даже не заботимся о (2.) для данных чтения-записи, так как нам все равно нужны отдельные копии для каждого процесса. И на самом деле компилятор не может сделать данные независимыми от позиции, потому что, если вы попросили глобальный int* y = &x;
тогда у компилятора нет другого выбора, кроме как поместить указатель туда.
Теперь, как код становится независимым от позиции? Это зависит от платформы, но часто включает несколько относительно неэффективных операций, или процессор накладывает произвольные ограничения на максимальные смещения, используемые в более эффективных инструкциях для доступа к данным и коду независимым от позиции способом. Кроме того, динамическое связывание означает, что адрес некоторых функций даже не известен как относительное смещение. Таким образом, компиляторы, как правило, используют таблицы, которые содержат фактические адреса, и код будет искать фактические адреса из таблицы. Таблицы, по-разному известные как GOT, TOC, PLT и, возможно, несколько других имен на разных платформах, скорее всего будут постоянными данными с большим количеством перемещений.
Если нельзя избежать перемещений, идея состоит в том, чтобы собрать их все в одном месте, чтобы минимизировать проблемы (1.) и (2.).