FFI rust object file missing linking for c compilation using clang


#1

I trying to work though some simple FFI things to figure out how to get rust to work with c or c++ code. My immediate question is:

It seems when I compile rust code to an object file, it is missing some of the bindings for the clang linker to finalize an executable.

Let me expand on that a little bit. Here are two very simple files that I am using.
clink.c

#include <stdio.h>
#include <stdbool.h>

int rust_int(int);
bool rust_bool(int, int);

/********************************/
/* void rust_char_star(char *); */
/********************************/

int main(void) {
printf("%d\n", rust_int(5));
if (rust_bool(5, 6)) {
	printf("True\n");
}
else { printf("False\n"); }

/******************************/
/* rust_char_star("Testing"); */
/******************************/

return 0;
}

rustlink.rs

use std::os::raw::{c_int, c_char};
// use std::ffi::{CStr, CString};

#[no_mangle]
pub extern "C" fn rust_int(i: c_int) -> c_int {
    i
}

#[no_mangle]
pub extern "C" fn rust_bool(x: c_int, y: c_int) -> bool {
    if x > y {
        true
    }
    else {
        false
    }
}

////////////////////////////////////////////////////////////////////////////
// #[no_mangle]                                                           //
// pub extern "C" fn rust_char_star(c: *const c_char) {                   //
//     let str_printable = unsafe {CString::from_raw(c as *mut c_char) }; //
//                                                                        //
//     println!("{:?}", str_printable);                                   //
// }                                                                      //
////////////////////////////////////////////////////////////////////////////

Now when I compile each of these without the CString used everything works as expected. Nevermind the warning…

nick@Void:~/dev/rust/learn/clink$ rustc --emit obj --crate-type staticlib rustlink.rs
warning: unused import: c_char
–> rustlink.rs:1:27
|
1 | use std::os::raw::{c_int, c_char};
| ^^^^^^
|
= note: #[warn(unused_imports)] on by default

nick@Void:~/dev/rust/learn/clink$ clang clink.c rustlink.o -o test
nick@Void:~/dev/rust/learn/clink$ ./test
5
False
nick@Void:~/dev/rust/learn/clink$

When I uncomment all the code to use the type CString though, thats where I’m missing something. I’m not going to repost the above code with the comments removed. Here is what the output yields though.

nick@Void:~/dev/rust/learn/clink$ rustc --emit obj --crate-type staticlib rustlink.rs 
warning: unused import: `CStr`
 --> rustlink.rs:2:16
  |
2 | use std::ffi::{CStr, CString};
  |                ^^^^
  |
  = note: #[warn(unused_imports)] on by default

nick@Void:~/dev/rust/learn/clink$ clang clink.c rustlink.o -o test
rustlink.o: In function `alloc::alloc::dealloc':
rustlink.3a1fbbbh-cgu.0:(.text._ZN5alloc5alloc7dealloc17hca8aab9ecdf50cafE+0x43): undefined reference to `__rust_dealloc'
rustlink.o: In function `rust_char_star':
rustlink.3a1fbbbh-cgu.0:(.text.rust_char_star+0xa): undefined reference to `std::ffi::c_str::CString::from_raw'
rustlink.3a1fbbbh-cgu.0:(.text.rust_char_star+0x3a): undefined reference to `<std::ffi::c_str::CString as core::fmt::Debug>::fmt'
rustlink.3a1fbbbh-cgu.0:(.text.rust_char_star+0xa9): undefined reference to `std::io::stdio::_print'
rustlink.o:(.data.DW.ref.rust_eh_personality[DW.ref.rust_eh_personality]+0x0): undefined reference to `rust_eh_personality'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
nick@Void:~/dev/rust/learn/clink$ 

So this is where I am a little confused. I thought when you tell rustc that the crate-type is static that it includes all dependencies, but I guess that is not the case.

Finally. Clearly I am missing a step. I would prefer that rustc include all bindings so I can just use a simple command to clang to produce an executable. I would appreciate help in this matter…


#2

Remove the --emit obj and link your C code to the .a archive rustc produces.


#3

I had tried that, having found something similar from a stackexchange q/a, but this is what it yields

nick@Void:~/dev/rust/learn/clink$ rustc --crate-type staticlib rustlink.rs 
warning: unused import: `CStr`
 --> rustlink.rs:2:16
  |
2 | use std::ffi::{CStr, CString};
  |                ^^^^
  |
  = note: #[warn(unused_imports)] on by default

nick@Void:~/dev/rust/learn/clink$ ls
clink.c  librustlink.a  rustlink.o  rustlink.rs
nick@Void:~/dev/rust/learn/clink$ clang clink.c -o test -lrustlink
/usr/bin/ld: cannot find -lrustlink
clang: error: linker command failed with exit code 1 (use -v to see invocation)
nick@Void:~/dev/rust/learn/clink$ clang clink.c -o test -L -lrustlink
/tmp/clink-357672.o: In function `main':
clink.c:(.text+0x15): undefined reference to `rust_int'
clink.c:(.text+0x3a): undefined reference to `rust_bool'
clink.c:(.text+0x83): undefined reference to `rust_char_star'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
nick@Void:~/dev/rust/learn/clink$

#4

You probably don’t even need -lrustlink, just specify the archive directly:

clang clink.c -o test librustlink.a

Or if you want to specify it as a library name, you should make sure -L has the right path to where your .a is located.


#5

Thanks for the input vitalyd. Your response helped, but appears not to have been the full answer. When I ran through everything again it looks like that was a partial solution, but then there are a ton of unresolved bindings that would live in the rust std. A snip of the output is as follows.

nick@Void:~/dev/rust/learn/clink$ clang clink.c -o test -L . -lrustlink
./librustlink.a(std-89cf9eb8d404bb7b.std.c8ao5dpa-cgu.3.rcgu.o): In function `std::sys::unix::mutex::ReentrantMutex::init':
/rustc/b6c32da9b0481e3e9d737153286b3ff8aa39a22c/src/libstd/sys/unix/mutex.rs:109: undefined reference to `pthread_mutexattr_init'
/rustc/b6c32da9b0481e3e9d737153286b3ff8aa39a22c/src/libstd/sys/unix/mutex.rs:111: undefined reference to `pthread_mutexattr_settype'
/rustc/b6c32da9b0481e3e9d737153286b3ff8aa39a22c/src/libstd/sys/unix/mutex.rs:116: undefined reference to `pthread_mutexattr_destroy'

There are about 90 lines of that. What that simply boils down to though is it was missing the standard library bindings. After a little more Googling I found a few links that were helpful.
https://www.reddit.com/r/rust/comments/7slfw9/running_rusts_output_llvm_with_clang/
https://stackoverflow.com/questions/48430136/running-llvm-file-generated-with-rustc-with-clang

It took me a while to find the magic google search incantation to find this info though. Turns out I just needed to know where the rust standard object file was. So simply put running the following is a solution.

nick@Void:~/dev/rust/learn/clink$ rustc --crate-type staticlib rustlink.rs
nick@Void:~/dev/rust/learn/clink$ clang clink.c -o test /home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libstd-89cf9eb8d404bb7b.so -L . -lrustlink
nick@Void:~/dev/rust/learn/clink$ ./test
5
False
"Testing"
Did I get here?
nick@Void:~/dev/rust/learn/clink$ 

I added a line “Did I get here” because when I was originally successful in compilation, the program actually seg faulted before completion. I was trying to figure out if it was the CString printing that was causing it or not. Turns out I had a sort of double free condition. I will add the revised rust code at the very bottom for completeness.

As a second solution, and more to my liking for the moment. You can use an obj.

nick@Void:~/dev/rust/learn/clink$ rustc --emit obj --crate-type staticlib rustlink.rs --verbose
nick@Void:~/dev/rust/learn/clink$ clang clink.c -o test rustlink.o /home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libstd-89cf9eb8d404bb7b.so 
nick@Void:~/dev/rust/learn/clink$ ./test
5
False
"Testing"
Did I get here?
nick@Void:~/dev/rust/learn/clink$ 

They both result in the same output, and I can’t really say if one way is better than the other. It would certainly depend on what your requirements are in development.

Last. My original code above for rustlink.rs was wrong. I learned that using a CString that is bound to a raw pointer is pretty much a bad idea. It needs to allocate its own memory, I think, in most circumstances. This is due to drop. So I changed the rust_char_star function from the original post to the following.

#[no_mangle]
pub extern "C" fn rust_char_star(c: *const c_char) {
    let ch = unsafe { CStr::from_ptr(c) };
    let str_printable = CString::from(ch);

    println!("{:?}", str_printable);
    println!("Did I get here?");
}

That prevents what is effectively a double free.

Anyway. I would be interested to see if anyone has any more viable input in this topic. Pretty much burned a full day in this, but thats how it goes occasionally.