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)
## 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__);
}
And I compile this and got a error as below
$ 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
And after this, I modified the code(removing a line [1]/using -fPIC instead of -PIE[2]) and then successfully compiled these.
$ 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
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.
The only code generation difference between
-fPICand-fPIEfor the code shown is in the call fromsub_functosubsub_func. With-fPIC, that call goes through the PLT; with-fPIE, it's a direct call. In assembly dumps (cc -S), that looks like this:In unlinked object files, it's a change of relocation type:
And, on this architecture, when you link a shared library using
cc -shared, the linker does not allow the input object files to containR_X86_64_PC32relocations targeting global symbols, thus the error you observed when you used-fPIEinstead of-fPIC.Now, you are probably wondering why direct calls within a shared library are not allowed. In fact, they are allowed, but only when the callee is not a global. For instance, if you declared
subsub_funcwithstatic, then the call target would be resolved by the assembler and there would be no relocation at all in the object file, and if you declared it with__attribute__((visibility("hidden")))then you would get anR_X86_64_PC32relocation but the linker would allow it because the callee is no longer exported from the library. But in both casessubsub_funcwould not be callable from outside the library anymore.Now you're probably wondering what it is about global symbols that means you have to call them through the PLT from a shared library. This has to do with an aspect of the ELF symbol resolution rules that you may find surprising: any global symbol in a shared library can be overridden by either the executable, or an earlier library in the link order. Concretely, if we leave your
sub.handsub.calone but makemain.cread like this:so it's now got the an official executable entry point in it but also a second definition of
subsub_func, and we compilesub.cinto a shared library andmain.cinto an executable that calls it, and run the whole thing, like thisthe output will be
That is, both the call from
maintosubsub_func, and the call fromsub_func, within the library, tosubsub_func, were resolved to the definition in the executable. For that to be possible, the call fromsub_funcmust go through the PLT.You can change this behavior with an additional linker switch,
-Bsymbolic.Now the call from
sub_funcis resolved to the definition within the library. In this case, using-Bsymbolicallowssub.cto be compiled with-fPIEinstead of-fPIC, but I don't recommend you do that. There are other effects of using-fPIEinstead of-fPIC, such as changing how access to thread-local storage needs to be done, and those cannot be worked around with-Bsymbolic.