我该如何编译 Rust 程序以避免使用 __cxa_thread_atexit_impl?

13

我已经为armv7-unknown-linux-gnueabihf编译了一个Rust程序,并希望在安装有glibc 2.16的系统上运行。不幸的是,在运行它时,我遇到了以下错误:

./foo: /lib/libc.so.6: version `GLIBC_2.18' not found (required by ./foo)

运行 objdump -T foo 命令会显示只需要从 glibc 2.18 中获取一个符号:

00000000  w   DF *UND*  00000000  GLIBC_2.18  __cxa_thread_atexit_impl

Rust将__cxa_thread_atexit_impl设置为弱符号(通过objdump中的小w标志可见),然而GCC显然很愚蠢,尽管所有来自GLIBC_2.18的符号都是弱的,但它仍然使GLIBC_2.18本身成为强要求。您可以使用readelf查看:

$ readelf -V foo
...
Version needs section '.gnu.version_r' contains 5 entries:
 Addr: 0x0000000000001e4c  Offset: 0x001e4c  Link: 6 (.dynstr)
  000000: Version: 1  File: ld-linux-armhf.so.3  Cnt: 1
  0x0010:   Name: GLIBC_2.4  Flags: none  Version: 9
  0x0020: Version: 1  File: librt.so.1  Cnt: 1
  0x0030:   Name: GLIBC_2.4  Flags: none  Version: 5
  0x0040: Version: 1  File: libgcc_s.so.1  Cnt: 4
  0x0050:   Name: GCC_4.3.0  Flags: none  Version: 10
  0x0060:   Name: GCC_3.0  Flags: none  Version: 7
  0x0070:   Name: GCC_3.5  Flags: none  Version: 6
  0x0080:   Name: GCC_3.3.1  Flags: none  Version: 4
  0x0090: Version: 1  File: libc.so.6  Cnt: 2
  0x00a0:   Name: GLIBC_2.18  Flags: none  Version: 8
  0x00b0:   Name: GLIBC_2.4  Flags: none  Version: 3
  0x00c0: Version: 1  File: libpthread.so.0  Cnt: 1
  0x00d0:   Name: GLIBC_2.4  Flags: none  Version: 2

请注意,GLIBC_2.18显示的是Flags: none。它应该显示Flags: WEAK。幸运的是,我找到了一篇神奇的网页,在这个网页上有人展示了如何修复此问题。不幸的是,它涉及对二进制文件进行十六进制编辑
取出那个.gnu.version_r表的偏移量(0x001e4c),加上GLIBC_2.18的项偏移量(0x00a0),然后再加上该地址处结构体的标志字段的偏移量(0x04)。这将得到0x001EF0。在该地址处应该有两个零字节:0x0000。将它们改为0x0200
使用readelf验证:
Version needs section '.gnu.version_r' contains 5 entries:
 Addr: 0x0000000000001e4c  Offset: 0x001e4c  Link: 6 (.dynstr)
  000000: Version: 1  File: ld-linux-armhf.so.3  Cnt: 1
  0x0010:   Name: GLIBC_2.4  Flags: none  Version: 9
  0x0020: Version: 1  File: librt.so.1  Cnt: 1
  0x0030:   Name: GLIBC_2.4  Flags: none  Version: 5
  0x0040: Version: 1  File: libgcc_s.so.1  Cnt: 4
  0x0050:   Name: GCC_4.3.0  Flags: none  Version: 10
  0x0060:   Name: GCC_3.0  Flags: none  Version: 7
  0x0070:   Name: GCC_3.5  Flags: none  Version: 6
  0x0080:   Name: GCC_3.3.1  Flags: none  Version: 4
  0x0090: Version: 1  File: libc.so.6  Cnt: 2
  0x00a0:   Name: GLIBC_2.18  Flags: WEAK   Version: 8
  0x00b0:   Name: GLIBC_2.4  Flags: none  Version: 3
  0x00c0: Version: 1  File: libpthread.so.0  Cnt: 1
  0x00d0:   Name: GLIBC_2.4  Flags: none  Version: 2

成功!除了它仍然不起作用:
./foo: /lib/libc.so.6: weak version `GLIBC_2.18' not found (required by ./foo)
./foo: relocation error: ./foo: symbol __cxa_thread_atexit_impl, version GLIBC_2.18 not defined in file libc.so.6 with link time reference

弱版本还需要吗?!我迫不及待地希望glibc消失。

有没有办法在不使用这个符号的情况下让Rust构建程序?


3
接受事情不好的现实是改善它们的唯一途径。如果我冒犯了任何GNU开发者,抱歉。 - Timmmm
2
你故意冒犯那些能够回答你问题的人。我赞同@tumdum的建议,建议你删除那些反生产力的部分。作为部分答案,GLIBC之所以采用目前的工作方式是有充分的理由的。如果你认为Musl是答案,那么到底是什么阻止了你使用它呢? - Employed Russian
很遗憾,armv7-unknown-linux-muscl 目前还不可用作目标。如果我能让它工作,那么要求 glibc 2.4 是合理的,因为它已经足够老(2006年),并且与 muscl 相比可以节省空间。如果没有办法避免需要 2.18(2013年),那么 muscl 明显是更好的选择。 - Timmmm
说得太早了 - Rust 1.12今天发布了,支持 armv7-unknown-linux-musclabihf - Timmmm
实际上,目前尚无针对muscl目标的二进制版本发布,但有望很快推出。 - Timmmm
1个回答

4
您需要一个Rust工具链,该工具链是为glibc 2.16或更早版本编译的。 glibc 2.17也可能有效,因为它缺少__cxa_thread_atexit_impl,因此在二进制文件中不会携带GLIBC_2.18符号版本。
在Rust代码中使用弱符号并不特别有用,因为GNU特定版本的ELF符号版本控制没有弱符号版本。我们可能最终会改变这种情况,但现在处理这个问题的最佳方法是使用足够旧的工具链进行编译。
另一种选择是将该符号回溯到您使用的glibc中。这应该是一个相当孤立的回溯,可能包括以下提交:
C++11 thread_local destructors support
避免在静态链接中无条件调用__call_tls_dtors。
还使用l_tls_dtor_count来决定对象卸载(BZ#18657)
使用指针混淆加固tls_dtor_list [BZ#19018]
(我尚未尝试将其回溯到glibc 2.16,但就这些事情而言,看起来并不特别困难。)

1
你能否就工具链使用glib的想法提供一些思路,以放松__cxa_thread_atexit_impl的使用?该函数只能通过工具链的显式调用来使用吗?还是有一条路径可以间接地使用它? - Digikata

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接