Почему я не могу скомпилировать с -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,

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