Weak (glibc) Symbol binding override only works for calls, not null-checks

Apologies if this is the wrong place for discussion, happy to take it elsewhere.

I'm trying to build a shared library with LTO across Rust and C parts [1] which does not require glibc versions newer than 2.17. With just a straight build, the only 2.18+ symbol linked is __cxa_thread_atexit_impl. rust's stdlib links the symbol as extern_weak and checks if it is null before calling it [2]. In the only object which is included in the final build which references __cxa_thread_atexit_impl, this translates into the following LLVM disassembly:

$ cat ldk.ldk.c1v7ysym-cgu.15.rcgu.o.ll | grep __cxa_thread_atexit_impl
@__cxa_thread_atexit_impl = extern_weak global i8
   br i1 icmp eq (i8* @__cxa_thread_atexit_impl, i8* null), label %5, label %58, !dbg !277544
   tail call void @llvm.assume(i1 icmp ne (i32 (void (i8*)*, i8*, i8*)* bitcast (i8* @__cxa_thread_atexit_impl to i32 (void (i8*)*, i8*, i8*)*), i32 (void (i8*)*, i8*, i8*)* null))
   %59 = tail call i32 bitcast (i8* @__cxa_thread_atexit_impl to i32 (void (i8*)*, i8*, i8*)*)(void (i8*)* nonnull @_ZN3std6thread5local4fast13destroy_value17ha7ca9dde7aa309feE, i8* nonnull getelementptr inbounds (<{ [56 x i8] }>, <{ [56 x i8] }>* @_ZN3std10sys_common11thread_info11THREAD_INFO7__getit5__KEY17habd5e1c16da9ed08E, i64 0, i32 0, i64 0), i8* bitcast (i8** @_rust_extern_with_linkage___dso_handle.llvm.12589863591117529814 to i8*)) #44, !dbg !277724
^6904 = gv: (name: "__cxa_thread_atexit_impl") ; guid = 18076065427897830669

When I link in an additional object which simply defines __cxa_thread_atexit_impl as `void *__cxa_thread_atexit_impl = NULL`, the result segfaults.

Specifically, after linking, the `br i1 icmp` instruction is still left referencing __cxa_thread_atexit_impl from libc, while the `tail call` gets replaced with the local one, jumping to the zero page.

The final compilation of several LLVM IR-containing object files (including the above) plus a C file is, roughly,
clang -pthread -shared -fPIC -Wl,--version-script=libcode.version -fuse-ld=lld -flto -O3 file.c llvm_irs.a
with libcode.version simply only containing:

  global: Java_org_*;
  local: *;

I've also tried passing -Wl,-wrap,__cxa_thread_atexit_impl and --defsym=__cxa_thread_atexit_impl=... to try to replace the branch as well, but both seem to have the same effect - replacing the tail call with my own NULL but leaving the branch looking up the glibc symbol (though its not in the final result's symbol table).

$ clang --version --verbose
Debian clang version 11.0.1-2
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ rustc --version --verbose
rustc 1.48.0
binary: rustc
commit-hash: unknown
commit-date: unknown
host: x86_64-unknown-linux-gnu
release: 1.48.0
LLVM version: 11.0

(note, not subscribed, please cc me on replies)


[1] Yay Debian's rustc packages which use system LLVM so its all super seamless!
[2] https://github.com/rust-lang/rust/blob/71965ab4d05b023cd29c914ef1262a72cac02e01/library/std/src/sys/unix/thread_local_dtor.rs#L30