尝试使用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相对寻址,旨在提高PIC代码的效率。但是,这并不意味着加载时重定位不能(理论上)应用于此类代码。
一些在线资源,包括this one(在此问题上被广泛引用)声称,在共享库中存在一些禁止非PIC代码的固有限制,因为的RIP相对寻址。我不明白为什么会这样。
考虑“旧的x86” - call
指令也有一个IP相对操作数。然而,其中带有call
的x86代码可以很好地编译成没有PIC的共享库,但使用load-time relocation R_386_PC32
。对于x64中的数据RIP相对寻址,不能做同样的事情吗?
请注意,我完全理解PIC代码的好处,并且性能损失RIP相对寻址有助于缓解。不过,我很好奇不允许使用非PIC代码的原因。它背后是否有真正的技术推理,还是只是鼓励编写PIC代码?
答案 0 :(得分:16)
以下是我从a post on comp.unix.programmer读到的最佳解释:
共享库需要x86-64上的PIC,或更准确地说,可重定位代码 必须是PIC。这是因为一个32位的立即数地址操作数 重定位后,代码中使用的代码可能需要超过32位。如果 发生这种情况,无处可写新值。
答案 1 :(得分:5)
只是说些别的话。
在url provided in the question中,它提到您可以将-mcmodel=large
传递给gcc,告诉编译器为您的代码生成64位立即数地址操作数。
因此,gcc -mcmodel=large -shared a.c
将生成非PIC共享对象。
-
演示:
交流转换器:
#include <stdio.h>
void foo(void)
{
printf("%p\n", main);
}
32位立即数地址操作数会阻止您生成非PIC对象。
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
使用-mcmodel=large
来解决它。 (警告仅出现在我的系统上,因为我的PaX内核禁止对.text进行修改。)
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.
现在您可以看到重定位条目的类型为 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
这证明了动态加载器正在对 .text 进行重定位,PIC库不会。
答案 2 :(得分:2)
问题是,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版本相同的代码。