gpt4 book ai didi

c - 为什么我不能用 -fPIE 编译但可以用 -fPIC 编译?

转载 作者:IT王子 更新时间:2023-10-29 00:59:52 26 4
gpt4 key购买 nike

我有一个有趣的编译问题。首先,请看要编译的代码。

$ ls
Makefile main.c sub.c sub.h
$ gcc -v
...
gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
## Makefile
%.o: CFLAGS+=-fPIE #[2]

main.so: main.o sub.o
$(CC) -shared -fPIC -o $@ $^
//main.c
#include "sub.h"

int main_func(void){
sub_func();
subsub_func();

return 0;
}
//sub.h
#pragma once
void subsub_func(void);
void sub_func(void);
//sub.c
#include "sub.h"
#include <stdio.h>
void subsub_func(void){
printf("%s\n", __func__);
}
void sub_func(void){
subsub_func();//[1]
printf("%s\n", __func__);
}

然后我编译它并得到如下错误

$ LANG=en make
cc -fPIE -c -o main.o main.c
cc -fPIE -c -o sub.o sub.c
cc -shared -fPIC -o main.so main.o sub.o
/usr/bin/ld: sub.o: relocation R_X86_64_PC32 against symbol `subsub_func' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
make: *** [main.so] Error 1

在此之后,我修改了代码(删除了一行 [1]/使用 -fPIC 而不是 -PIE[2]),然后成功编译了这些代码。

$ make #[1]
cc -fPIE -c -o main.o main.c
cc -fPIE -c -o sub.o sub.c
cc -shared -fPIC -o main.so main.o sub.o
$ make #[2]
cc -fPIC -c -o main.o main.c
cc -fPIC -c -o sub.o sub.c
cc -shared -fPIC -o main.so main.o sub.o

为什么会出现这种现象?

我听说在使用-fPIC 编译时通过PLT 调用对象内的函数,但在使用-fPIE 编译时通过直接跳转到该函数来完成。我猜是带有-fPIE的函数调用机制避免了重定位。但我想知道对此的准确解释。

你愿意帮我吗?

谢谢大家

最佳答案

-fPIC 之间的唯一 代码生成差异和 -fPIE因为显示的代码在来自 sub_func 的调用中至 subsub_func .与 -fPIC , 该调用通过 PLT;与 -fPIE , 是直接调用。在程序集转储 (cc -S) 中,它看起来像这样:

--- sub.s.pic   2017-12-07 08:10:00.308149431 -0500
+++ sub.s.pie 2017-12-07 08:10:08.408068650 -0500
@@ -34,7 +34,7 @@ sub_func:
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
- call subsub_func@PLT
+ call subsub_func
leaq __func__.2258(%rip), %rsi
leaq .LC0(%rip), %rdi
movl $0, %eax

在未链接的目标文件中,这是重定位类型的改变:

--- sub.o.dump.pic  2017-12-07 08:13:54.197775840 -0500
+++ sub.o.dump.pie 2017-12-07 08:13:54.197775840 -0500
@@ -22,7 +22,7 @@
1f: 55 push %rbp
20: 48 89 e5 mov %rsp,%rbp
23: e8 00 00 00 00 callq 28 <sub_func+0x9>
- 24: R_X86_64_PLT32 subsub_func-0x4
+ 24: R_X86_64_PC32 subsub_func-0x4
28: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 2f <sub_func+0x10>
2b: R_X86_64_PC32 .rodata+0x14
2f: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 36 <sub_func+0x17>

并且,在此架构上,当您使用 cc -shared 链接共享库时, 链接器不允许输入目标文件包含 R_X86_64_PC32以全局符号为目标的重定位,因此您在使用 -fPIE 时观察到的错误而不是 -fPIC .

现在,您可能想知道为什么不允许在共享库中直接调用。事实上,它们允许的,但前提是被调用者不是全局的。例如,如果您声明 subsub_funcstatic , 然后调用目标将由汇编器解析并且目标文件中根本没有重定位,如果你用 __attribute__((visibility("hidden"))) 声明它那么你会得到一个 R_X86_64_PC32重定位,但链接器会允许它,因为被调用者不再从库中导出。但在这两种情况下subsub_func将无法再从库外调用。

现在您可能想知道全局符号是什么意思,这意味着您必须通过共享库中的 PLT 调用它们。这与您可能会感到惊讶的 ELF 符号解析规则的一个方面有关:共享库中的任何全局符号都可以被可执行文件或链接顺序中较早的库覆盖。具体来说,如果我们离开你的 sub.hsub.c孤独却使main.c像这样阅读:

//main.c
#include "sub.h"
#include <stdio.h>

void subsub_func(void) {
printf("%s (main)\n", __func__);
}

int main(void){
sub_func();
subsub_func();

return 0;
}

所以它现在有一个官方的可执行入口点,还有一个 subsub_func 的第二个定义。 ,我们编译sub.c进入共享库和main.c进入一个调用它的可执行文件,然后运行整个过程,就像这样

$ cc -fPIC -c sub.c -o sub.o
$ cc -c main.c -o main.o
$ cc -shared -Wl,-soname,libsub.so.1 sub.o -o libsub.so.1
$ ln -s libsub.so.1 libsub.so
$ cc main.o -o main -L. -lsub
$ LD_LIBRARY_PATH=. ./main

输出将是

subsub_func (main)
sub_func
subsub_func (main)

也就是说,两个来自main的电话至 subsub_func ,以及来自 sub_func 的电话, 在图书馆内,到 subsub_func , 被解析为可执行文件中的定义。为了实现这一点,来自 sub_func 的电话必须通过PLT。

您可以使用额外的链接器开关来更改此行为, -Bsymbolic .

$ cc -shared -Wl,-soname,libsub.so.1 -Wl,-Bsymbolic sub.o -o libsub.so.1
$ LD_LIBRARY_PATH=. ./main
subsub_func
sub_func
subsub_func (main)

现在来自 sub_func 的电话解析为库中的定义。在这种情况下,使用 -Bsymbolic允许 sub.c-fPIE 编译而不是 -fPIC ,但我不建议你这样做。使用 -fPIE 还有其他效果而不是 -fPIC ,例如更改访问 thread-local storage 的方式需要完成,这些不能用 -Bsymbolic 解决.

关于c - 为什么我不能用 -fPIE 编译但可以用 -fPIC 编译?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47695390/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com