为什么 gcc 对 x64 共享库强制使用 PIC?
尝试使用 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 代码的效率。但是,这并不意味着加载时重定位不能(理论上)应用于此类代码。
一些在线资源,包括这个(在这个问题上被广泛引用) )声称存在一些固有的限制,禁止共享库中的非 PIC 代码,因为是 RIP 相对寻址。我不明白为什么这是真的。
考虑“旧的 x86”——call
指令也有一个与 IP 相关的操作数。然而,带有 call
的 x86 代码可以很好地编译成没有 PIC 的共享库,但使用 加载时重定位 R_386_PC32
。不能对 x64 中的数据 RIP 相对寻址执行同样的操作吗?
请注意,我完全理解 PIC 代码的好处,并且 RIP 相对寻址的性能损失有助于减轻。不过,我很好奇不允许使用非 PIC 代码的原因。它背后是否有真正的技术原因,或者只是为了鼓励编写 PIC 代码?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是我从 comp.unix.programmer 上的帖子中读到的最好的解释:
Here is the best explanation I've read from a post on comp.unix.programmer:
补充一下吧。
在问题中提供的网址中,它提到您可以将
-mcmodel=large
传递给 gcc,告诉编译器为您的代码生成 64 位立即地址操作数。因此,
gcc -mcmodel=large -shared ac
将生成一个非 PIC 共享对象。-
演示:
ac:
32 位立即地址操作数阻止您生成非 PIC 对象。
使用
-mcmodel=large
来解决。 (这些警告仅出现在我的系统上,因为我的 PaX 内核禁止对 .text 进行修改。)现在您可以看到重定位条目的类型是 R_X86_64_64,而不是 R_X86_64_32、R_X86_64_PLT32、R_X86_64_PLTOFF64。
在我的系统上,将此共享对象链接到普通代码并运行该程序将发出如下错误:
./a.out: 加载共享库时出错:./a.so: 无法使段可写以进行重定位:权限被拒绝
这证明动态加载器正在尝试对 .text< 进行重定位/strong> 哪个 PIC 库不会。
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:
32-bit immediate address operand blocks you from generating non-PIC object.
Use
-mcmodel=large
to solve it. (The warnings only appear on my system because modification on .text is forbidden by my PaX kernel.)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.
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.
问题是,PIC 和非 PIC 代码仍然不同。
C 源代码:
汇编,而不是 PIC:
汇编,带有 PIC:
所以看起来 PIC 代码必须通过重定位表才能访问全局变量。它实际上必须对函数执行相同的操作,但它可以通过在链接时创建的存根来执行函数。这在程序集级别是透明的,而访问全局变量则不然。 (但是,如果您需要函数的地址,那么 PIC 和非 PIC 是不同的,就像全局变量一样。)请注意,如果您按如下方式更改代码:
在这种情况下,因为 GCC 知道由于符号必须与代码驻留在同一对象中,因此它会发出与非 PIC 版本相同的代码。
The thing is, PIC and non-PIC code is still different.
C source:
Assembly, not PIC:
Assembly, with PIC:
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:
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.