Trying to compile non-PIC code into a shared library on x64 with gcc
results in an error, something like:
/usr/bin/ld: /tmp/ccQ2ttcT.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
This question is about why this is so. I know that x64 has RIP-relative addressing which was designed to make PIC code more efficient. However, this doesn't mean load-time relocation can't be (in theory) applied to such code.
Some online sources, including this one (which is widely quoted on this issue) claim that there's some inherent limitation prohibiting non-PIC code in shared libs, because of RIP-relative addressing. I don't understand why this is true.
Consider "old x86" - a call
instruction also has an IP-relative operand. And yet, x86 code with call
in it compiles just fine into a shared lib without PIC, but using the load-time relocation R_386_PC32
. Can't the same be done for the data RIP-relative addressing in x64?
Note that I fully understand the benefits of PIC code, and the performance penalty RIP-relative addressing helps alleviate. Still, I'm curious about the reason for not allowing using non-PIC code. Is there a real technical reasoning behind it, or is it just to encourage writing PIC code?
FPIC is key in giving indigenous peoples and local communities collective rights over what happens in their territories, even in cases where there is no guarantee that they are protected under national laws.
PIC is commonly used for shared libraries, so that the same library code can be loaded in a location in each program address space where it does not overlap with other memory in use (for example, other shared libraries).
gcc -shared -fPIC -o liblibrary.so library.c The flag-shared instructs the compiler that we are building a shared library. The flag -fPIC is to generate position-independent code (ignore for now). The command generates a shared library liblibrary.so in the current working directory.
Here is the best explanation I've read from a post on comp.unix.programmer:
Shared libs need PIC on x86-64, or more accurately, relocatable code has to be PIC. This is because a 32-bit immediate address operand used in the code might need more than 32 bits after relocation. If this happens, there is nowhere to write the new value.
The thing is, PIC and non-PIC code is still different.
C source:
extern int x;
void func(void) { x += 1; }
Assembly, not PIC:
addl $1, x(%rip)
Assembly, with PIC:
movq x@GOTPCREL(%rip), %rax
addl $1, (%rax)
So it looks like PIC code has to go through a relocation table to access global variables. It actually has to do the same thing for functions, but it can do functions through stubs created at link-time. This is transparent at the assembly level, while accessing globals is not. (If you need the address of a function, however, then PIC and non-PIC are different, just like globals.) Note that if you change the code as follows:
__attribute__((visibility("hidden"))) extern int x;
In this case, since GCC knows that the symbol must reside in the same object as the code, it emits the same code as the non-PIC version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With