Matt Corallo via llvm-dev
2021-Jun-24 15:53 UTC
[llvm-dev] 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) Thanks, Matt [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