Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I compile a Rust program so it doesn't use __cxa_thread_atexit_impl?

Tags:

rust

glibc

I have compiled a Rust program for armv7-unknown-linux-gnueabihf, and I want it to run on a system that has glibc 2.16 installed. Unfortunately when running it I get this error:

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

Running objdump -T foo reveals that the only symbol needed from glibc 2.18 is:

00000000  w   DF *UND*  00000000  GLIBC_2.18  __cxa_thread_atexit_impl

Rust makes __cxa_thread_atexit_impl a weak symbol (as seen by the little w flag from objdump), however GCC is apparently stupid and even though all the symbols from GLIBC_2.18 are weak it still makes GLIBC_2.18 itself a strong requirement. You can see that with 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

Notice that GLIBC_2.18 says Flags: none. It should say Flags: WEAK. Fortunately I found an amazing page where someone shows how to fix this. Unfortunately it involves hex editing the binary!

Take the offset of that .gnu.version_r table (0x001e4c), add the entry offset for GLIBC_2.18 (0x00a0), then add an offset for the flags field of the struct at that address (0x04). That gives 0x001EF0. At that address there should be two zero bytes: 0x0000. Change them to 0x0200.

Verify with 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

Success! Except it still doesn't work:

./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

How is the weak version still required?! I can't wait for glibc to die.

Is there any way to get Rust to build the program without using this symbol?

like image 728
Timmmm Avatar asked Sep 28 '16 10:09

Timmmm


1 Answers

You need a Rust toolchain which was compiled for glibc 2.16 or earlier. glibc 2.17 likely works as well because it lacks __cxa_thread_atexit_impl, so that it will not carry a GLIBC_2.18 symbol version in the binary.

The use of the weak symbol in the Rust code is not particularly useful because GNU's particular version of ELF symbol versioning does not have weak symbol versions. We might change that eventually, but right now, the best way to deal with this is to compile with a sufficiently old toolchain.

Another option is to backport the symbol into the glibc you use. This should be a fairly isolated backport, probably consisting of these commits:

  • C++11 thread_local destructors support
  • Avoid unconditional __call_tls_dtors calls in static linking.
  • Also use l_tls_dtor_count to decide on object unload (BZ #18657)
  • Harden tls_dtor_list with pointer mangling [BZ #19018]

(I have not attempted the backport to glibc 2.16, but as far as such things go, it does not look particularly difficult.)

like image 150
Florian Weimer Avatar answered May 06 '23 10:05

Florian Weimer