rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.81k stars 12.5k forks source link

Undefined reference to `_Unwind_Resume` #47493

Open jefftime opened 6 years ago

jefftime commented 6 years ago

I tried to compile a no_std project with Rust, but setting the eh_unwind_resume lang_item does not seem to work properly. Building in release with cargo or -O with rustc works properly, but a debug build fails with an undefined reference to '_Unwind_Resume'.

Here is the code I tried to get working.

rustc --version --verbose:

rustc 1.25.0-nightly (e6072a7b3 2018-01-13)
binary: rustc
commit-hash: e6072a7b3835f1875e81c9fd27799f9b20a0770c
commit-date: 2018-01-13
host: x86_64-unknown-linux-gnu
release: 1.25.0-nightly
LLVM version: 4.0
steveklabnik commented 6 years ago

We had talked on Hacker News, and while this version compiles for me, it doesn't seem to for @jefftime

https://gist.github.com/steveklabnik/5abd59a8fe7e5abda3db58bd8c208fbb

hanna-kruppe commented 6 years ago

Seems similar to #47442. As mentioned there, projects that don't want unwinding need to compile with panic=abort to work reliably.

jefftime commented 6 years ago

Neither setting panic=abort nor specifying the eh_personality, eh_unwind_resume, and panic_fmt lang_items successfully compile with debug settings for me

FenrirWolf commented 6 years ago

Even when you recompile core via xargo?

pietroalbini commented 6 years ago

Might this be related with #47551?

bossmc commented 6 years ago

@pietroalbini Probably not, in that case the link is fine, but the generated binary is broken.

This also fails to link for me (rustc 1.25.0-nightly (79a521bb9 2018-01-15)) with:

Error: linking with `cc` failed: exit code: 1
  |
  = note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/andy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "test.test0.rcgu.o" "test.test1.rcgu.o" "test.test2.rcgu.o" "-o" "test" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-L" "/home/andy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-l" "c" "-Wl,-Bstatic" "/home/andy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9d4b7130b0117f1c.rlib" "-Wl,-Bdynamic"
  = note: test.test0.rcgu.o: In function `_$LT$core..result..Result$LT$T$C$$u20$E$GT$$GT$::unwrap::h4cfe66017ee4cfbf':
          test0-317d481089b8c8fe83113de504472633.rs:(.text._ZN47_$LT$core..result..Result$LT$T$C$$u20$E$GT$$GT$6unwrap17h4cfe66017ee4cfbfE+0x3d): undefined reference to `_Unwind_Resume'
          test.test0.rcgu.o: In function `core::result::unwrap_failed::h8821e290e357350d':
          test0-317d481089b8c8fe83113de504472633.rs:(.text._ZN4core6result13unwrap_failed17h8821e290e357350dE+0x95): undefined reference to `_Unwind_Resume'
          collect2: error: ld returned 1 exit status

If I add -l gcc_s (since the glibc unwinding implementation is in libgcc_s) to the link command it links and runs correctly. Either:

Attempting to build against x86_64-unknown-linux-musl suggests it's the former, that build also fails with a much longer link failure (longer, but still all about _Unwind_XXX functions).

bossmc commented 6 years ago

Ah, sorry, my analysis at the end there is bogus, the #[lang = "eh_unwind_resume"] function is supposed to be used in place of the "default" implementation (which is just extern-ing a rust_eh_unwind_resume). See https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc_trans/context.rs#L395-L422.

glandium commented 6 years ago

Today I've hit the same error with the following:

#![no_std]
#![no_main]
#![feature(lang_items)]
#[link(name="c")]
extern "C" {}

#[lang = "panic_fmt"]
#[no_mangle]
pub fn panic_fmt(_: core::fmt::Arguments, _: &'static str, _: u32, _: u32) -> ! {
    loop {}
}

#[no_mangle]
pub extern "C" fn main(_argc: isize, _arg: *const *const u8) -> isize {
    let a: Result<(), usize> = Err(42);
    a.unwrap();
    0
}

And with the following added to the corresponding Cargo.toml:

[profile.release]
panic="abort"

[profile.dev]
panic="abort"

The above fails to build with cargo +nightly build but builds fine with cargo +nightly build --release. x86_64-unknown-linux-gnu target.

glandium commented 6 years ago

FWIW, when looking at the llvm-ir emitted for unwrap_failed, with opt-level=0, I can see multiple to label %somelabel unwind label %cleanup, but there are none with opt-level=1. They seem to correspond to the assembly calling _Unwind_Resume.

hadronized commented 6 years ago

Hello. I have a similar issue there.

Code here.

jefftime commented 5 years ago

I looked into this a bit more since Rust 1.30 came out. I thought with the ability for stable Rust to allow [no_std] executables that this issue would be fixed. However, it still persists with a simple call to something like:

let x = Some(5);
let y = x.unwrap();

After a bit more reading on the undefined reference to _Unwind_Resume, I found out that this seems to be an issue with C++ applications when two or more objects files have been compiled with different versions of g++ and are trying to be linked. g++ has different stack unwinding methods depending on how it was compiled, and the linker has trouble in situations where object files differ in how they unwind the stack.

Coming back to Rust, I believe the issue lies with the panic = "abort" in Cargo.toml. I don't know enough about C++ or g++ stack unwinding to really say, but my guess is that by changing the panic handling method on Linux, the Rust object code has problems being linked with system libraries because there's a mismatch in stack unwinding. I'm not sure if this bug is applicable to Rust anymore. It might just be a side effect of trying to use [no_std] Rust executables that link with the C standard library.

bossmc commented 5 years ago

I suspect this is (loosely) similar to #55352 where Rustc creates landing pads for unwinding even if those pads are unreachable. A --release build will strip these (since they are unreachable) but a --debug build will not. If those landing pads are explicitly calling _Unwind_Resume rather than eh_unwind_resume (or similar) that would lead to this issue for debug builds.

I doubt this is specifically related to mixing unwind implementations (the .eh_frames section allows separate translation modules to use different unwind strategies, though there might be an issue if multiple of them are expecting to use different implementations with the same name...).

elichai commented 4 years ago

Same problem. this code works with panic=abort and --release but not on debug. https://github.com/rust-bitcoin/rust-secp256k1/pull/173

bossmc commented 4 years ago

Updated test case:

#![no_std]
#![no_main]

#[panic_handler]
pub fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[no_mangle]
pub extern "C" fn _start() -> ! {
    let a: Result<(), usize> = Err(42);
    a.unwrap();
    loop {}
}

Compile with rustc foo.rs -C panic=abort -C link-args=-nostartfiles :-

error: linking with `cc` failed: exit code: 1
  |
  = note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/andy/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "foo.foo.7rcbfp3g-cgu.0.rcgu.o" "foo.foo.7rcbfp3g-cgu.1.rcgu.o" "foo.foo.7rcbfp3g-cgu.2.rcgu.o" "foo.foo.7rcbfp3g-cgu.3.rcgu.o" "-o" "foo" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/andy/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "/home/andy/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-837ca740df32db0a.rlib" "/home/andy/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-db27c965e824589f.rlib" "/home/andy/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-68a4f8466685ed76.rlib" "-nostartfiles" "-Wl,-Bdynamic"
  = note: foo.foo.7rcbfp3g-cgu.3.rcgu.o: In function `core::result::Result<T,E>::unwrap':
          foo.7rcbfp3g-cgu.3:(.text._ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+0x20): undefined reference to `_Unwind_Resume'
          foo.foo.7rcbfp3g-cgu.3.rcgu.o:(.data.DW.ref.rust_eh_personality[DW.ref.rust_eh_personality]+0x0): undefined reference to `rust_eh_personality'
          collect2: error: ld returned 1 exit status

So there's still references to _Unwind_Resume and rust_eh_personality, but where are they? Unleash a debugger!

Dump of assembler code for function _ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E:
   0x0000000000000000 <+0>: 48 83 ec 28 sub    $0x28,%rsp
   0x0000000000000004 <+4>: 48 89 3c 24 mov    %rdi,(%rsp)
   0x0000000000000008 <+8>: 48 89 74 24 08  mov    %rsi,0x8(%rsp)
   0x000000000000000d <+13>:    48 8b 04 24 mov    (%rsp),%rax
   0x0000000000000011 <+17>:    48 85 c0    test   %rax,%rax
   0x0000000000000014 <+20>:    74 12   je     0x28 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+40>
   0x0000000000000016 <+22>:    eb 00   jmp    0x18 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+24>
   0x0000000000000018 <+24>:    eb 20   jmp    0x3a <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+58>
   0x000000000000001a <+26>:    48 8b 7c 24 18  mov    0x18(%rsp),%rdi
   0x000000000000001f <+31>:    e8 00 00 00 00  callq  0x24 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+36>
   0x0000000000000024 <+36>:    0f 0b   ud2    
   0x0000000000000026 <+38>:    0f 0b   ud2    
   0x0000000000000028 <+40>:    48 83 3c 24 00  cmpq   $0x0,(%rsp)
   0x000000000000002d <+45>:    74 3c   je     0x6b <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+107>
   0x000000000000002f <+47>:    eb 3f   jmp    0x70 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+112>
   0x0000000000000031 <+49>:    48 83 3c 24 00  cmpq   $0x0,(%rsp)
   0x0000000000000036 <+54>:    74 31   je     0x69 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+105>
   0x0000000000000038 <+56>:    eb e0   jmp    0x1a <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+26>
   0x000000000000003a <+58>:    48 8b 44 24 08  mov    0x8(%rsp),%rax
   0x000000000000003f <+63>:    48 89 44 24 10  mov    %rax,0x10(%rsp)
   0x0000000000000044 <+68>:    48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 0x4b <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+75>
   0x000000000000004b <+75>:    48 8d 0d 00 00 00 00    lea    0x0(%rip),%rcx        # 0x52 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+82>
   0x0000000000000052 <+82>:    48 8b 05 00 00 00 00    mov    0x0(%rip),%rax        # 0x59 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+89>
   0x0000000000000059 <+89>:    be 2b 00 00 00  mov    $0x2b,%esi
   0x000000000000005e <+94>:    48 8d 54 24 10  lea    0x10(%rsp),%rdx
   0x0000000000000063 <+99>:    ff d0   callq  *%rax
   0x0000000000000065 <+101>:   eb 0b   jmp    0x72 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+114>
   0x0000000000000067 <+103>:   eb c8   jmp    0x31 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+49>
   0x0000000000000069 <+105>:   eb af   jmp    0x1a <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+26>
   0x000000000000006b <+107>:   48 83 c4 28 add    $0x28,%rsp
   0x000000000000006f <+111>:   c3  retq   
   0x0000000000000070 <+112>:   eb f9   jmp    0x6b <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+107>
   0x0000000000000072 <+114>:   0f 0b   ud2    
   0x0000000000000074 <+116>:   48 89 44 24 18  mov    %rax,0x18(%rsp)
   0x0000000000000079 <+121>:   89 54 24 20 mov    %edx,0x20(%rsp)
   0x000000000000007d <+125>:   eb e8   jmp    0x67 <_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E+103>

So, no explicit references to either function, presumably they are relocated in?

$ readelf -r foo.foo.7rcbfp3g-cgu.3.rcgu.o

Relocation section '.rela.text._ZN4core6result19Result$LT$T$C$E$GT$6unwrap17hc6a8d8550c5348d4E' at offset 0x2c8 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000800000004 R_X86_64_PLT32    0000000000000000 _Unwind_Resume - 4
000000000047  000500000002 R_X86_64_PC32     0000000000000000 .rodata..L__unnamed_1 - 4
00000000004e  000600000002 R_X86_64_PC32     0000000000000000 .data.rel.ro..L__unnam - 4
000000000055  000b00000009 R_X86_64_GOTPCREL 0000000000000000 _ZN4core6result13unwra - 4

Relocation section '.rela.data.rel.ro..L__unnamed_2' at offset 0x328 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000000  000a00000001 R_X86_64_64       0000000000000000 _ZN4core3ptr18real_dro + 0
000000000018  000900000001 R_X86_64_64       0000000000000000 _ZN4core3fmt3num52_$LT + 0

Relocation section '.rela.data.DW.ref.rust_eh_personality' at offset 0x358 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000000  000d00000001 R_X86_64_64       0000000000000000 rust_eh_personality + 0

Relocation section '.rela.eh_frame' at offset 0x370 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000013  000700000002 R_X86_64_PC32     0000000000000000 DW.ref.rust_eh_persona + 0
000000000028  000300000002 R_X86_64_PC32     0000000000000000 .text._ZN4core6result1 + 0
000000000031  000400000002 R_X86_64_PC32     0000000000000000 .gcc_except_table + 0

So there's relocation instructions for both functions:

Performing code-flow analysis on the generated code shows that the relocation site for _Unwind_Resume is unreachable (since core::result::unwrap is an external symbol). This explains why the release build is fine, as the unreachable code segments (and thus the relocation instructions) are stripped by LLVM. Similarly (inspecting the LLVM-IR) the function is marked nounwind and thus no .eh_frame section is generated, thus removing the need for the rust_eh_personality relocation.

bossmc commented 4 years ago

More analysis... looking at the LLVM-IR, same source code, new command rustc +nightly foo.rs -C panic=abort -C link-args=-nostartfiles --emit llvm-ir.

...
; core::result::Result<T,E>::unwrap
; Function Attrs: inlinehint nounwind nonlazybind
define internal void @"_ZN4core6result19Result$LT$T$C$E$GT$6unwrap17h1179f71b032e9d7eE"(i64, i64) unnamed_addr #0 personality i32 (...)* @rust_eh_personality {
start:
  ...
bb5:                                              ; preds = %start
  ...
  ; invoke core::result::unwrap_failed
  invoke void @_ZN4core6result13unwrap_failed17hfb1545ecedec37d1E([0 x i8]* noalias nonnull readonly align 1 bitcast (<{ [43 x i8] }>* @0 to [0 x i8]*), i64 43, {}* nonnull align 1 %22, [3 x i64]* noalias readonly align 8 dereferenceable(24) bitcast ({ void (i64*)*, i64, i64, i1 (i64*, %"core::fmt::Formatter"*)* }* @vtable.0 to [3 x i64]*))
          to label %unreachable unwind label %cleanup
...

So it seems that the libcore rlib was built with unwind on panic and thus the expanded template includes the landing pads (and thus implicit references to the stack unwinding infrastructure).

I also tested with xargo (to rebuild libcore) RUSTFLAGS='-C link-arg=-nostartfiles -C panic=abort' xargo run (I had to pass panic=abort in RUSTFLAGS, as [profile.dev] doesn't seem to be applied to core) and the link was successful.

I'm not sure what the right answer is here, either require compiling ones own (non-unwinding) libcore, or rustup shipping two (or more) libcores, or deferring codegen for unwinding until the final build.

Edit - xargo builds libcore in release mode, regardless of passing --release or not. So you can add [profile.dev] panic=abort to Cargo.toml. Support for -nostartfiles is being discussed in https://github.com/rust-lang/rfcs/pull/2735.

haraldh commented 4 years ago

I hit this, too, with no_std, panic=abort and using alloc with x86_64-unknown-linux-musl

rustc 1.43.0-nightly (442ae7f04 2020-02-06)

          ld.lld: error: undefined symbol: _Unwind_Resume
          >>> referenced by string.rs:579 (src/liballoc/string.rs:579)
          >>>               alloc-6bcd594557d0587e.alloc.6cu7tpd8-cgu.0.rcgu.o:(alloc::string::String::from_utf8_lossy::h9a7aba1aef1d966f) in archive /home/harald/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-musl/lib/liballoc-6bcd594557d0587e.rlib

          ld.lld: error: undefined symbol: rust_eh_personality
          >>> referenced by alloc.6cu7tpd8-cgu.0
          >>>               alloc-6bcd594557d0587e.alloc.6cu7tpd8-cgu.0.rcgu.o:(DW.ref.rust_eh_personality) in archive /home/harald/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-musl/lib/liballoc-6bcd594557d0587e.rlib
eloraiby commented 4 years ago

a quickfix for now is to add lto = true in your profile:

[profile.release]
overflow-checks = false
debug-assertions = false
lto = true
incremental = false
panic = "abort"
gmorenz commented 5 months ago

lto=true didn't work for me in a profile without other optimizations.

Just creating an _Unwind_Resume symbol did though. I put this in the module with eh_personality and friends:

/// Workaround for rustc bug: https://github.com/rust-lang/rust/issues/47493
///
/// It shouldn't even be possible to reach this function, thanks to panic=abort,
/// but libcore is compiled with unwinding enabled and that ends up making unreachable
/// references to this.
#[no_mangle]
extern "C" fn _Unwind_Resume() -> ! {
    unreachable!("Unwinding not supported");
}