Почему я не могу скомпилировать с -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("%s\n", __func__);
}
void sub_func(void){
subsub_func();//[1]
printf("%s\n", __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
Прямой звонок. В сборе свалок (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
,