Open heiher opened 7 months ago
为了能兼容上游的Rust 1.73.0编译器,Rust模块目前使用了PIC
模式。与no-PIC
+no-direct-access-external-data
相比,直接用PIC
模式都可能有哪些缺点? @xry111
为了能兼容上游的Rust 1.73.0编译器,Rust模块目前使用了
PIC
模式。与no-PIC
+no-direct-access-external-data
相比,直接用PIC
模式都可能有哪些缺点? @xry111
首先我们要区分 PIC 的两种概念,在教科书中 PIC 是指 position-independent code,就是这段代码加载到哪里都能执行。通过 PC-relative 寻址或者 GOT 都能实现 PIC。目前无论是 GCC 还是 Clang,在 LoongArch 上都是无条件生成此种定义下的 PIC。即使用了 -fno-PIC 生成出来也是 PIC,因为 -fno-PIC 只是说可以不生成 PIC,不是说必须不生成 PIC (总不能强行加个“如果加载地址变化就执行 break 指令”罢)。
编译器生成代码时,如果要访问其他 TU 的符号,无法知道那个 TU 是否跨越了共享库边界,所以会保守地假设它可能跨越共享库边界。这种寻址要么用运行时 text relocation,这是上个世纪 32 位 x86 的做法,目前由于有安全隐患已经在用户态禁用;要么用 copy relocation,目前已经被 Maskray 批倒,估计以后也要禁用;要么就用 GOT。这和是不是 PIC 无关,别的架构上如果把 copy relocation 关掉,生成出来的非位置无关代码也会用 GOT 访问外部 TU 中的符号。
而在 GCC 和 Clang 的命令行选项中,-fPIC 是指为共享库生成代码。这时首先要满足一般 PIC 的条件,因为共享库可能被加载到不同的地址 (古早的 x86 共享库有强行做运行时 text relocation 的替代方法,但我们之前说了这个东西已被禁用)。但是此时对于同一 TU 中具有默认 visibility 的数据符号也会使用 GOT 而非 PC-relative 寻址。例如
lib.c:
int x = 42;
int t (void) { return x; }
exe.c:
int x = 114514;
extern int printf (const char *, ...);
extern int t (void);
int main (void) { printf("%d\n", t ()); }
编译并运行:
cc lib.c -fPIC -shared -o lib.so
cc exec. lib.so -Wl,-rpath .
./a.out
会输出 114514 而不是 42。 因为 SysV ABI 规定此时无论是 lib.c 中还是 exe.c 中访问 x 的时候都必须从 GOT 取 x 的地址。
这个规则是共享库起源时某种试图让共享库更“像”静态库的尝试,现在回头看的结论是这属于邯郸学步,既学得不像又使得编译器在 -fPIC 时不得不禁止很多优化。然而一个规则一旦订立,大家就会开始用它干各种事情,然后规则就很难改了。
那么为什么 -fPIC 不仅表示生成 PIC,还表示要生成用于共享库的代码呢?因为 PIC 最早就是用于共享库的。后来大家发现 PIC 还能干别的 (至少可以把主程序也编译成 PIC 然后做一个 ASLR),于是表示生成 PIC 但不需要用于共享库的开关变成了 -fPIE。如果重做一套体系的话,-fPIE 应该改叫 -fPIC,而 -fPIC 应该改叫 -fso 或者 -fdylib 或者什么东西。
在 LLVM 中我不是很确定 relocation-model={pic,static} 的定义。按定义我们可以让 relocation-model=static 隐含 direct-extern-access 吗?如果不可以那可能就得让 Rustc 把 direct-extern-access 开关给我们暴露出一个 -C 选项了。
@xry111 感谢~
除了宏观规则上的约束,这个问题确实也需要结合后端ISA及具体实现来看,LoongArch(相比于MIPS)指令系统增强了PC-Rel的能力,这一定程度上已经缩小了no-PIC
和PIC
代码生成的差异。所以我的想法就是我们先在现有的可配置参数上评估下能不能找出近似的组合,可能不能完全等价,但只要开销不是很明显或许也是一种方法。这可以减少架构特定的修改和增大编译器版本的适用范围。
Working tree: https://github.com/heiher/linux/commits/loongarch-rust
Built-in
Module