Search code examples
cruststatic-linking

Segmentation fault after linking Rust staticlib with C


I'm trying to link statically against library written in Rust:

#![crate_type = "staticlib"]

#[no_mangle]
pub extern "C" fn foo() {
    println!("bork!");
}

Using following code in C:

void foo();
int main()
{
    foo();
    return 0;
}

Compile lib with rustc:

rustc foo.rs

Compile binary and link with library:

gcc -g bar.c libfoo.a -ldl -lpthread -lrt -lgcc_s -lpthread -lc -lm -o bar

Run inside debugger:

(gdb) run
Starting program: /home/kykc/rusttest/bar 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff72117df in __cxa_thread_atexit_impl (func=<optimized out>, obj=<optimized out>, dso_symbol=0x0) at cxa_thread_atexit_impl.c:67
67  cxa_thread_atexit_impl.c: No such file or directory.

gcc:

gcc-4.8.real (Ubuntu 4.8.2-19ubuntu1) 4.8.2

rustc:

rustc 1.0.0-beta (9854143cb 2015-04-02) (built 2015-04-02)

It works completely fine with dylib. What am I doing wrong?


Solution

  • The problem here is that thread-locals with destructors can only be used in position-independent executables (due to a bug) . Fix: pass the -pie flag to gcc, or wait a day or two.

    This is caused by std::io::stdio::_print and std::io::stdio::LOCAL_STDOUT in the standard library:

    /// Stdout used by print! and println! macros
    thread_local! {
        static LOCAL_STDOUT: RefCell<Option<Box<Write + Send>>> = {
            RefCell::new(None)
        }
    }
    
    // ...
    
    pub fn _print(args: fmt::Arguments) {
        let result = LOCAL_STDOUT.with(|s| {
            if s.borrow_state() == BorrowState::Unused {
                if let Some(w) = s.borrow_mut().as_mut() {
                    return w.write_fmt(args);
                }
            }
            stdout().write_fmt(args)
        });
        if let Err(e) = result {
            panic!("failed printing to stdout: {}", e);
        }
    }
    

    The Box in LOCAL_STDOUT has the destructor in that case, so the executable crashes when the thread-local variable is touched. I've filed #24445, and Alex Crichton has diagnosed and fixed the underlying cause already.

    Printing doesn't require initialising the threads or anything; there's fallback to stdout() in the case that LOCAL_STDOUT contains None (which is the default). Just passing -pie is enough to convince the executable to print bork! instead of crashing.