Why I cannot compile with -fPIE but can with -fPIC?

nut*_*man 4 c linux gcc fpic position-independent-code

I have one interesting compilation problem. At first, please see code to be compiled.

$ ls
Makefile main.c sub.c sub.h
$ gcc -v
...
gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
Run Code Online (Sandbox Code Playgroud)
## Makefile
%.o: CFLAGS+=-fPIE #[2]

main.so: main.o sub.o
    $(CC) -shared -fPIC -o $@ $^
Run Code Online (Sandbox Code Playgroud)
$ ls
Makefile main.c sub.c sub.h
$ gcc -v
...
gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
Run Code Online (Sandbox Code Playgroud)
## Makefile
%.o: CFLAGS+=-fPIE #[2]

main.so: main.o sub.o
    $(CC) -shared -fPIC -o $@ $^
Run Code Online (Sandbox Code Playgroud)
//main.c
#include "sub.h"

int main_func(void){
    sub_func();
    subsub_func();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

And I compile this and got a error as below

//sub.h
#pragma once
void subsub_func(void);
void sub_func(void);
Run Code Online (Sandbox Code Playgroud)

And after this, I modified the code(removing a line [1]/using -fPIC instead of -PIE[2]) and then successfully compiled these.

//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__);
}
Run Code Online (Sandbox Code Playgroud)

Why did this phenomenon happened?

I have heard that calling a function within an object is done through PLT when it is compiled with -fPIC but done by jumping to the function directly when it si compiled with -fPIE. I guessed that the function call mechanism with -fPIE refrains from relocation. But I would like to know exact and accurate explanation of that.

Would you help me?

Thank you, all.

zwo*_*wol 10

和之间的唯一代码生成差异是在从发给的调用中。使用,该呼叫通过PLT进行;使用,这是直接调用。在程序集转储()中,如下所示:-fPIC-fPIEsub_funcsubsub_func-fPIC-fPIEcc -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
Run Code Online (Sandbox Code Playgroud)

在未链接的目标文件中,这是重定位类型的更改:

--- 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>
Run Code Online (Sandbox Code Playgroud)

并且,在这种体系结构上,当您使用链接共享库时cc -shared,链接器不允许输入目标文件包含R_X86_64_PC32针对全局符号的重定位,因此使用-fPIE而不是时会观察到错误-fPIC

现在,您可能想知道为什么不允许在共享库中直接调用。实际上,它们允许的,但仅当被调用者不是全局对象时才允许。举例来说,如果你宣布subsub_funcstatic,则调用目标将被汇编解决,就不会有根本的目标文件中没有搬迁,而且如果你宣布它__attribute__((visibility("hidden"))),那么你会得到一个R_X86_64_PC32重新定位,但链接器将允许它,因为被调用者不再从库中导出。但是在两种情况下,subsub_func都无法从库外部调用它了。

现在您可能想知道全局符号的含义,这意味着您必须通过PLT从共享库中调用它们。这与您可能会感到惊讶的ELF符号解析规则的一个方面有关:共享库中的任何全局符号都可以可执行文件或链接顺序中的更早的库覆盖。具体来说,如果我们不理会您sub.hsub.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;
}
Run Code Online (Sandbox Code Playgroud)

因此,它现在有了正式的可执行文件入口点,并且还有的第二个定义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
Run Code Online (Sandbox Code Playgroud)

输出将是

subsub_func (main)
sub_func
subsub_func (main)
Run Code Online (Sandbox Code Playgroud)

也就是说,这两个从呼吁mainsubsub_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)
Run Code Online (Sandbox Code Playgroud)

现在,来自的调用sub_func被解析为库中的定义。在这种情况下,using -Bsymbolic可以sub.c使用-fPIE而不是进行编译-fPIC,但我不建议您这样做。使用-fPIE代替代替还有其他效果-fPIC,例如更改需要完成对线程本地存储的访问的方式,而不能解决这些问题-Bsymbolic