Почему я не могу компилировать с помощью-fPIE, но могу с помощью-fPIC?
У меня есть одна интересная проблема компиляции. Сначала, пожалуйста, смотрите код для компиляции.
$ ls
Makefile main.c sub.c sub.h
$ gcc -v
...
gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
## Makefile
%.o: CFLAGS+=-fPIE #[2]
main.so: main.o sub.o
$(CC) -shared -fPIC -o $@ $^
//main.c
#include "sub.h"
int main_func(void){
sub_func();
subsub_func();
return 0;
}
//sub.h
#pragma once
void subsub_func(void);
void sub_func(void);
//sub.c
#include "sub.h"
#include <stdio.h>
void subsub_func(void){
printf("%sn", __func__);
}
void sub_func(void){
subsub_func();//[1]
printf("%sn", __func__);
}
И я компилирую это и получил ошибку, как показано ниже
$ LANG=en make
cc -fPIE -c -o main.o main.c
cc -fPIE -c -o sub.o sub.c
cc -shared -fPIC -o main.so main.o sub.o
/usr/bin/ld: sub.o: relocation R_X86_64_PC32 against symbol `subsub_func' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
make: *** [main.so] Error 1
И после этого я изменил код (удалив строку [1] / используя-fPIC вместо-PIE[2]), а затем успешно скомпилировал их.
$ make #[1]
cc -fPIE -c -o main.o main.c
cc -fPIE -c -o sub.o sub.c
cc -shared -fPIC -o main.so main.o sub.o
$ make #[2]
cc -fPIC -c -o main.o main.c
cc -fPIC -c -o sub.o sub.c
cc -shared -fPIC -o main.so main.o sub.o
Почему произошло это явление?
Я слышал, что вызов функции внутри объекта выполняется через PLT, когда он компилируется с помощью-fPIC но сделано это путем прямого перехода к функции, когда она скомпилирована с помощью-fPIE. Я догадался, что механизм вызова функции с помощью-fPIE воздерживается от перемещения. Но я хотел бы знать точное и точное объяснение этого.
Не могли бы вы помочь мне?
Спасибо вам всем.
1 ответ:
Единственнаяразница в генерации кода между
-fPIC
и-fPIE
для указанного кода заключается в вызове изsub_func
вsubsub_func
. С-fPIC
, этот вызов проходит через PLT; с-fPIE
, это прямой вызов. В сборке dumps (cc -S
) это выглядит следующим образом:--- sub.s.pic 2017-12-07 08:10:00.308149431 -0500 +++ sub.s.pie 2017-12-07 08:10:08.408068650 -0500 @@ -34,7 +34,7 @@ sub_func: .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 - call subsub_func@PLT + call subsub_func leaq __func__.2258(%rip), %rsi leaq .LC0(%rip), %rdi movl $0, %eax
В несвязанных объектных файлах это изменение типа перемещения:
--- sub.o.dump.pic 2017-12-07 08:13:54.197775840 -0500 +++ sub.o.dump.pie 2017-12-07 08:13:54.197775840 -0500 @@ -22,7 +22,7 @@ 1f: 55 push %rbp 20: 48 89 e5 mov %rsp,%rbp 23: e8 00 00 00 00 callq 28 <sub_func+0x9> - 24: R_X86_64_PLT32 subsub_func-0x4 + 24: R_X86_64_PC32 subsub_func-0x4 28: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 2f <sub_func+0x10> 2b: R_X86_64_PC32 .rodata+0x14 2f: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 36 <sub_func+0x17>
И в этой архитектуре, когда вы связываете общую библиотеку с помощью
cc -shared
, компоновщик не позволяет входным объектным файлам содержатьR_X86_64_PC32
перемещения, нацеленные на глобальные символы, таким образом, ошибка, которую вы наблюдали, когда вы использовали-fPIE
вместо-fPIC
.Теперь вы, вероятно, задаетесь вопросом , почему прямые вызовы в общей библиотеке не разрешены. Фактически, ониразрешены, но только когда вызываемый объект не является глобальным. Например, если вы объявили
subsub_func
сstatic
, то цель вызова будет разрешена ассемблером и не будет никакого перемещения вообще в объектном файле, и если вы объявили его с помощью__attribute__((visibility("hidden")))
вы получите перемещениеR_X86_64_PC32
, но компоновщик разрешит это, поскольку вызываемый объект больше не экспортируется из библиотеки. Но в обоих случаяхsubsub_func
больше не будет вызываться из-за пределов библиотеки.теперь вам, вероятно, интересно, что это за глобальные символы, которые означают, что вы должны вызывать их через PLT из общей библиотеки. Это связано с одним аспектом правил разрешения символов ELF, который может вас удивить: любой глобальный символ в общая библиотека может бытьпереопределена либо исполняемым файлом, либо более ранней библиотекой в порядке ссылок. Конкретно, если мы оставим ваши
sub.h
иsub.c
в покое, но заставимmain.c
читать так://main.c #include "sub.h" #include <stdio.h> void subsub_func(void) { printf("%s (main)\n", __func__); } int main(void){ sub_func(); subsub_func(); return 0; }
Так что теперь в нем есть официальная исполняемая точка входа, но также и второе определение
subsub_func
, и мы компилируемsub.c
в общую библиотеку иmain.c
в исполняемый файл, который вызывает его, и запускаем все это, как это$ cc -fPIC -c sub.c -o sub.o $ cc -c main.c -o main.o $ cc -shared -Wl,-soname,libsub.so.1 sub.o -o libsub.so.1 $ ln -s libsub.so.1 libsub.so $ cc main.o -o main -L. -lsub $ LD_LIBRARY_PATH=. ./main
Выход будет таким:
subsub_func (main) sub_func subsub_func (main)
То есть, и вызов из
main
вsubsub_func
, и вызов изsub_func
, в пределах библиотеки, вsubsub_func
, были разрешены до определения в исполняемом файле. Чтобы это было возможно, вызов отsub_func
должен пройти через PLT.Это поведение можно изменить с помощью дополнительного переключателя компоновщика,
-Bsymbolic
.$ cc -shared -Wl,-soname,libsub.so.1 -Wl,-Bsymbolic sub.o -o libsub.so.1 $ LD_LIBRARY_PATH=. ./main subsub_func sub_func subsub_func (main)
Теперь вызов из
sub_func
разрешен для определения в библиотеке. В этом случае использование-Bsymbolic
позволяетsub.c
быть компилируется с-fPIE
вместо-fPIC
, но я не рекомендую вам это делать. Есть и другие эффекты использования-fPIE
вместо-fPIC
, такие как изменение способа доступа к потоковому локальному хранилищу, и их нельзя обойти с помощью-Bsymbolic
.