rustlinkerllvmrust-no-std

How to manually provide core::panicking::panic* to lld?


I am compiling the Rust code of an rlib to LLVM IR, and then using Clang to compile & link it with a C program. This works until my code contains panics, at which point I get linker errors:

ld.lld: error: undefined symbol: core::panicking::panic_bounds_check::hc3a71010bf41c72d
>>> referenced by ld-temp.o
>>>               lto.tmp:(run)
>>> referenced by ld-temp.o
>>>               lto.tmp:(run)
>>> referenced by ld-temp.o
>>>               lto.tmp:(run)
>>> referenced 11 more times

ld.lld: error: undefined symbol: core::panicking::panic::hd695e3b1d0dd4ef4
>>> referenced by ld-temp.o
>>>               lto.tmp:(run)
clang: error: ld.lld command failed with exit code 1 (use -v to see invocation)

I have tried two things to mitigate this:

  1. I have added a panic_handler to my library:

    use core::panic::PanicInfo;
    
    #[panic_handler]
    pub extern fn panic(_: &PanicInfo<'_>) -> ! {
        loop{}
    }
    
  2. I have set the panic mode to abort in my Cargo.toml:

    [profile.release]
    panic = "abort"
    

Neither on its own, nor combined, solves the issue.

Further details

In the comments, @Solomon Ucko requested more details on the whole compilation pipeline. As I wrote in the tags, this is with no_std; also, the compilation target is MOS 6502. This is the full list of commands to compile and (try to) link:

llvm-mos/bin/clang --config llvm-mos-sdk/build/commodore/64.cfg \
    -O2 -c \
    -o _build/main.c.o \
    src/main.c
cargo rustc --release -- \
    -C debuginfo=0 -C opt-level=1 --emit=llvm-ir
llvm-mos/bin/clang --config llvm-mos-sdk/build/commodore/64.cfg \
    -O2 \
    -o _build/charset.prg \
    _build/main.c.o \
    target/release/deps/chip8_c64-e21ff59526dd729a.ll

Solution

  • Based on this answer, I worked around this by passing -Z build-std to Cargo, thereby getting the LLVM IR from Rust's core library, and linking it in.

    But then I realized I can do one better, and avoid the long compilation time imposed by -Z std, by just taking the definitions of core::panicking::panic_bounds_check and core::panicking::panic from these IR files, simplifying their body, and adding them to a hand-written panic.ll file:

    %"panic::location::Location" = type { { [0 x i8]*, i64 }, i32, i32 }
    
    ; core::panicking::panic
    ; Function Attrs: cold noinline noreturn nounwind nonlazybind
    define void @_ZN4core9panicking5panic17hd695e3b1d0dd4ef4E([0 x i8]* noalias nonnull readonly align 1 %expr.0, i64 %expr.1, %"panic::location::Location"* noalias readonly align 8 dereferenceable(24) %0) unnamed_addr #22 {
    start:
      unreachable
    }
    
    ; core::panicking::panic_bounds_check
    ; Function Attrs: cold noinline noreturn nounwind nonlazybind
    define void @_ZN4core9panicking18panic_bounds_check17hc3a71010bf41c72dE(i64 %0, i64 %1, %"panic::location::Location"* noalias readonly align 8 dereferenceable(24) %2) unnamed_addr #22 {
    start:
      unreachable
    }
    

    TBH I'm not sure how robust that is (maybe they should loop by branchin to start before `unreachable?), but at least it got me to a fully linked and runnable program.