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

发布于 2024-12-11 04:55:30 字数 899 浏览 0 评论 0原文

尝试使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

勿忘初心 2024-12-18 04:55:30

这是我从 comp.unix.programmer 上的帖子中读到的最好的解释

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

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.

逆夏时光 2024-12-18 04:55:30

补充一下吧。

问题中提供的网址中,它提到您可以将 -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 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: 加载共享库时出错:./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:

#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.

生生不灭 2024-12-18 04:55:30

问题是,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 版本相同的代码。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文