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
-fPIC
and-fPIE
for the code shown is in the call fromsub_func
tosubsub_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_PC32
relocations targeting global symbols, thus the error you observed when you used-fPIE
instead 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_func
withstatic
, 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_PC32
relocation but the linker would allow it because the callee is no longer exported from the library. But in both casessubsub_func
would 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.h
andsub.c
alone but makemain.c
read 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.c
into a shared library andmain.c
into an executable that calls it, and run the whole thing, like thisthe output will be
That is, both the call from
main
tosubsub_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_func
must go through the PLT.You can change this behavior with an additional linker switch,
-Bsymbolic
.Now the call from
sub_func
is resolved to the definition within the library. In this case, using-Bsymbolic
allowssub.c
to be compiled with-fPIE
instead of-fPIC
, but I don't recommend you do that. There are other effects of using-fPIE
instead of-fPIC
, such as changing how access to thread-local storage needs to be done, and those cannot be worked around with-Bsymbolic
.