为什么gcc强制PIC为x64共享库?

试图用gcc将非PIC代码编译到x64上的共享库中会导致错误,如下所示:

 /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 

这个问题是关于为什么这样。 我知道x64具有RIP相对寻址function,旨在提高PIC代码的效率。 但是,这并不意味着加载时重定位不能(理论上)适用于这样的代码。

一些在线资源(包括在这个问题上被广泛引用)声称, 由于 RIP相对寻址,共享库中的非PIC代码有一些固有的限制。 我不明白为什么这是真的。

考虑“旧x86” – 一个call指令也有一个IP相对操作数。 然而, call它的x86代码在没有PIC的情况下编译成共享库,但使用加载时重定位 R_386_PC32 。 对于x64中的数据RIP相对寻址,不能这样做吗?

请注意,我完全理解PIC代码的好处,并且性能降低了RIP相对寻址有助于缓解。 不过,我很好奇不允许使用非PIC代码的原因。 是否有一个真正的技术背后的原因,还是只是鼓励编写PIC代码?

Solutions Collecting From Web of "为什么gcc强制PIC为x64共享库?"

下面是我从comp.unix.programmer上的一篇文章中读到的最好的解释:

共享库在x86-64上需要PIC,或者更准确地说,可重定位代码必须是PIC。 这是因为代码中使用的32位立即地址操作数在重定位后可能需要超过32位。 如果发生这种情况,就无法写出新的价值。

事情是,PIC和非PIC代码仍然不同。

C源码:

 extern int x; void func(void) { x += 1; } 

大会,而不是PIC:

 addl $1, x(%rip) 

大会,与PIC:

 movq x@GOTPCREL(%rip), %rax addl $1, (%rax) 

所以看起来PIC代码必须通过重定位表访问全局变量。 它实际上必须为函数做同样的事情,但它可以通过在链接时创建的存根执行功能。 这在汇编级是透明的,而访问全局变量则不是。 (如果你需要一个函数的地址 ,那么PIC和非PIC是不一样的,就像全局变量一样。)注意,如果你改变代码如下:

 __attribute__((visibility("hidden"))) extern int x; 

在这种情况下,由于GCC知道该符号必须与代码位于同一个对象中,因此它会发出与非PIC版本相同的代码。

只要再说点什么。

在问题提供的url中 ,它提到你可以传递-mcmodel=large到gcc来告诉编译器为你的代码生成64位的立即地址操作数。

所以, gcc -mcmodel=large -shared ac会产生一个非PIC的共享对象。

演示:

AC:

 #include <stdio.h> void foo(void) { printf("%p\n", main); } 

32位立即地址操作数会阻止您生成非PIC对象。

 xiami@gentoo ~ $ cc -shared -o a.so ac /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 

使用-mcmodel=large来解决它。 (警告只出现在我的系统上,因为我的PaX内核禁止修改.text。)

 xiami@gentoo ~ $ cc -mcmodel=large -shared -o a.so ac /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. 

现在您可以看到重定位条目的类型为R_X86_64_64,而不是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 ... 

在我的系统上,将这个共享对象链接到一个正常的代码,然后运行程序会发出如下错误: ./a.out: error while loading shared libraries: ./a.so: cannot make segment writable for relocation: Permission denied

这证明动态加载器正在对PIC库不会的.text进行重定位。