我已经创建了一个包含大约 200 万个小函数的静态库,但我在 Linux x86_64 下使用 GCC(测试 4.8.5 或 7.3.0(将其链接到我的主函数时遇到了问题。
链接器抱怨重定位截断,非常类似于此问题中的截断。
我已经尝试过使用-mcmodel=large
,但正如同一问题的答案所说,我会 "需要一个可以处理完整 64 位地址的 CRT1.o"。然后我尝试编译一个,按照这个答案,但最近的 glibc 不会在-mcmodel=large
下编译,即使 libgcc 这样做,也没有任何作用。
我也尝试添加标志-fPIC
和/或-fPIE
无济于事。我得到的最好的是这个唯一的错误:
ld:无法转换 GOTPCREL 重定位;重新链接 --no-relax
添加该标志也无济于事。
我已经在互联网上搜索了几个小时,但大多数帖子都非常旧,我找不到一种方法来做到这一点。
我知道这不是一件常见的事情,但我认为应该可以做到这一点。我在 HPC 环境中工作,因此内存或时间限制不是这里的问题。
有没有人成功地用最近的编译器和工具链完成了类似的事情?
要么不要使用标准库,要么修补它。至于 2.34 版本,Glibc 不支持大代码模型。(另见Glibc邮件列表和Redhat Bugzilla(
解释
让我们检查一下 Glibc 源代码,以了解为什么使用-mcmodel=large
重新编译一无所获。它替换了源自 C 文件的重定位。但是 Glibc 在原始程序集文件中包含硬编码的 32 位重定位,例如在start.S
(sysdeps/x86_64/start.S
中(。
call *__libc_start_main@GOTPCREL(%rip)
start.S
发出R_X86_64_GOTPCREL
用于使用相对寻址的__libc_start_main
。 x86_64CALL
指令不支持超过 32 位位移的相对跳转,请参阅 AMD64 手册 3。因此,ld
无法抵消重新定位R_X86_64_GOTPCREL
,因为代码大小超过 2GB。
由于相同的 ISA 约束,添加-fPIC
没有帮助。对于与位置无关的代码,编译器仍会生成相对跳转。
修补
简而言之,您必须替换程序集代码中的 32 位重定位。有关实现 64 位重定位的详细信息,请参阅 System V 应用程序二进制接口 AMD64 体系结构进程补充。另请参阅此处,了解有关代码模型的更深入说明。
为什么 32 位重定位不足以满足大型代码模型?因为我们不能依赖其他符号在 2GB 的范围内。所有调用都必须是绝对的。与小型 PIC 代码模型相反,在小型 PIC 代码模型中,编译器尽可能生成相对跳转。
让我们仔细看看搬迁R_X86_64_GOTPCREL
。它包含 RIP 和符号的 GOT 条目地址之间的 32 位差异。它有一个 64 位替代品 —R_X86_64_GOTPCREL64
,但我找不到在汇编中使用它的方法。
因此,要替换GOTPCREL
,我们必须计算符号条目GOT基本偏移量和GOT地址本身。我们可以在函数序言中计算一次 GOT 位置,因为它不会改变。
首先,让我们获取 GOT 基础(从 ABI 补充中批量提升的代码(。GLOBAL_OFFSET_TABLE
重定位指定相对于当前位置的偏移:
leaq 1f(%rip), %r11
1: movabs $_GLOBAL_OFFSET_TABLE_, %r15
leaq (%r11, %r15), %r15
由于GOT基位于%r15
寄存器上,现在我们必须找到交易品种的GOT条目偏移量。R_X86_64_GOT64
重新定位正是指定了这一点。有了这个,我们可以将__libc_start_main
调用重写为:
movabs $__libc_start_main@GOT, %r11
call *(%r11, %r15)
我们用GLOBAL_OFFSET_TABLE
和R_X86_64_GOT64
替换了R_X86_64_GOTPCREL
.以同样的方式替换其他人。
注意:将动态链接可执行文件中的函数的R_X86_64_GOT64
替换为R_X86_64_PLTOFF64
。
测试
使用以下需要大型代码模型的测试验证修补程序的正确性。它不包含一百万个小函数,而是有一个大函数和一个小函数。
编译器必须支持大型代码模型。如果您使用 GCC,则需要使用标志-mcmodel=large
从源代码构建它。启动文件不应包含 32 位重定位。
foo
函数占用超过 2GB,导致 32 位重定位不可用。因此,如果在没有-mcmodel=large
的情况下编译,测试将失败并出现溢出错误。另外,添加标志-O0 -fPIC -static
,用黄金链接。
extern int foo();
extern int bar();
int foo(){
bar();
// Call sys_exit
asm( "mov $0x3c, %%rax n"
"xor %%rdi, %%rdi n"
"syscall n"
".zero 1 << 32 n"
: : : "rax", "rdx");
return 0;
}
int bar(){
return 0;
}
int __libc_start_main(){
foo();
return 0;
}
int main(){
return 0;
}
注:注:我使用了没有标准库本身的修补 Glibc 启动文件,所以我必须同时定义_libc_start_main
和main
。