在实践中是否有可能将数百万个小函数编译成静态二进制文件?

Sei*_*ios 5 c linux linker static x86-64

我创建了一个包含大约 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

要么不使用标准库,要么对其进行修补。至于2.34版本,Glibc不支持大代码模型。(另请参阅Glibc 邮件列表Redhat Bugzilla

\n

解释

\n

让我们检查一下 Glibc 源代码,以了解为什么重新编译却-mcmodel=large一事无成。它取代了源自 C 文件的重定位。但 Glibc 在原始汇编文件中包含硬编码的 32 位重定位,例如start.S( sysdeps/x86_64/start.S) 中。

\n
call *__libc_start_main@GOTPCREL(%rip)\n
Run Code Online (Sandbox Code Playgroud)\n

start.SR_X86_64_GOTPCREL为发出__libc_start_main,它使用相对寻址。x86_64CALL指令不支持超过 32 位位移的相对跳转,请参阅AMD64 手册 3。因此,ld无法抵消重定位R_X86_64_GOTPCREL,因为代码大小超过了 2GB。

\n

由于相同的 ISA 限制,添加-fPIC没有帮助。对于位置无关的代码,编译器仍然生成相对跳转。

\n

打补丁

\n

简而言之,您必须替换汇编代码中的 32 位重定位。有关实现 64 位重定位的更多信息,请参阅System V 应用程序二进制接口 AMD64 架构进程补充。另请参阅此内容以获取代码模型的更深入解释。

\n

为什么 32 位重定位不足以满足大型代码模型?因为我们不能依赖 2GB 范围内的其他符号。所有的召唤都必须变得绝对。与小型 PIC 代码模型相比,编译器会尽可能生成相对跳转。

\n

让我们仔细看看R_X86_64_GOTPCREL搬迁情况。它包含 RIP 和符号的 GOT 入口地址之间的 32 位差异。它有一个 64 位替代品 \xe2\x80\x94 R_X86_64_GOTPCREL64,但我找不到在 Assembly 中使用它的方法。

\n

因此,为了替换GOTPCREL,我们必须计算符号条目 GOT 基址偏移量GOT地址本身。我们可以在函数序言中计算一次 GOT 位置,因为它不会改变。

\n

首先,让我们获取 GOT 基础(从 ABI 补充中批量提取的代码)。重定位指定对于当前位置的GLOBAL_OFFSET_TABLE偏移量:

\n
leaq 1f(%rip), %r11\n1: movabs $_GLOBAL_OFFSET_TABLE_, %r15\nleaq (%r11, %r15), %r15\n
Run Code Online (Sandbox Code Playgroud)\n

由于 GOT 基址位于寄存器中%r15,现在我们必须找到符号的 GOT 条目偏移量。此次搬迁R_X86_64_GOT64正是明确了这一点。这样,我们可以将调用重写为__libc_start_main

\n
movabs $__libc_start_main@GOT, %r11\ncall *(%r11, %r15)\n
Run Code Online (Sandbox Code Playgroud)\n

我们替换R_X86_64_GOTPCRELGLOBAL_OFFSET_TABLER_X86_64_GOT64。以同样的方式替换其他人。

\n

注意:替换R_X86_64_GOT64R_X86_64_PLTOFF64动态链接可执行文件中的函数。

\n

测试

\n

使用以下需要大型代码模型的测试来验证补丁的正确性。它不包含一百万个小函数,而是具有一个大函数和一个小函数。

\n

您的编译器必须支持大型代码模型。如果您使用 GCC,则需要使用标志从源代码构建它-mcmodel=large。启动文件不应包含 32 位重定位。

\n

foo函数占用超过 2GB 空间,导致 32 位重定位无法使用。因此,如果在没有-mcmodel=large. 另外,添加旗帜-O0 -fPIC -static,与黄金链接。

\n
call *__libc_start_main@GOTPCREL(%rip)\n
Run Code Online (Sandbox Code Playgroud)\n

注意,我使用了修补过的 Glibc 启动文件,而没有标准库本身,所以我必须同时定义_libc_start_mainmain

\n