问题
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?
回答1:
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.
回答2:
Just say something additional.
In url provided in the question, it mentions you can pass -mcmodel=large to gcc to tell the compiler to generate 64-bits immediate address operand for your code.
So, gcc -mcmodel=large -shared a.c will generate a non-PIC shared object.
-
Demos:
a.c:
#include <stdio.h>
void foo(void)
{
printf("%p\n", main);
}
32-bit immediate address operand blocks you from generating non-PIC object.
xiami@gentoo ~ $ cc -shared -o a.so a.c
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/cck3FWeL.o: relocation R_X86_64_32 against `main' can not be used when making a shared object; recompile with -fPIC
/tmp/cck3FWeL.o: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
Use -mcmodel=large to solve it. (The warnings only appear on my system because modification on .text is forbidden by my PaX kernel.)
xiami@gentoo ~ $ cc -mcmodel=large -shared -o a.so a.c
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccZ3b9Xk.o: warning: relocation in readonly section `.text'.
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: warning: creating a DT_TEXTREL in object.
Now you can see the relocation entry's type is R_X86_64_64 instead of R_X86_64_32, R_X86_64_PLT32, R_X86_64_PLTOFF64.
xiami@gentoo ~ $ objdump -R a.so
a.so: file format elf64-x86-64
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
...
0000000000000758 R_X86_64_64 printf
...
And on my system, link this shared object to a normal code and run the program will emit errors like:
./a.out: error while loading shared libraries: ./a.so: cannot make segment writable for relocation: Permission denied
This proves dynamic loader is tring to do relocations on .text which PIC library won't.
回答3:
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.
来源:https://stackoverflow.com/questions/7865059/why-does-gcc-force-pic-for-x64-shared-libs