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

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.