0

I am currently playing with POSIX functions defined in dlfcn.h with Rust, with the goal of calling a function in a separated .so file.

The project actually contains 2 crates:

  • The binary loading the shared object and calling the function.
  • The dynamic library.

The dynamic library is 4 lines long and here is the code:

#[no_mangle]
pub extern "Rust" fn hello_world() -> String {
    String::from("Hello World !")
}

But the executable binary is a bit more complex and looks like this:

#![feature(str_from_raw_parts)]

fn open_dl(path: &str) -> Result<*mut libc::c_void, String> {
    let path = format!("{path}\0");

    let path: *const libc::c_char = unsafe { std::mem::transmute(path.as_ptr()) };
    let handle = unsafe { libc::dlopen(path, libc::RTLD_NOW) };

    if handle.is_null() {
        let dl_error: &str = unsafe {
            let ptr = libc::dlerror();
            let len = libc::strlen(ptr);
            std::str::from_raw_parts(std::mem::transmute(ptr), len)
        };

        return Err(String::from(dl_error));
    }
    Ok(handle)
}

fn get_symbol<T>(handle: *mut libc::c_void, symbol: &str) -> Result<&T, String> {
    let symbol_name = format!("{symbol}\0");
    let symbol = unsafe { libc::dlsym(handle, std::mem::transmute(symbol_name.as_ptr())) };

    if symbol.is_null() {
        let dl_error: &str = unsafe {
            let ptr = libc::dlerror();
            let len = libc::strlen(ptr);
            std::str::from_raw_parts(std::mem::transmute(ptr), len)
        };

        return Err(String::from(dl_error));
    }
    return unsafe { Ok(std::mem::transmute(symbol)) };
}

fn main() -> Result<(), String> {
    let handle = open_dl("./libfoo.so")?;

    // Tricky part, get the symbol with the correct signature and calling it
    let symbol: &fn() -> String = get_symbol(handle, "hello_world")?;
    let val = symbol(); // Blows up

    println!("{val}");

    Ok(())
}

My problem is that get_symbol seems to work fine, the obtained pointer is not null, so I would expect that calling the function behind it would cause no issue, but I get a SEGFAULT when I try to. Do you wonder why it does SEGFAULT ?

6
  • 1
    get_symbol returning a reference looks wrong especially since fn is already a pointer type. Thus your symbol in main() has two levels of indirection when it should only have one. Commented May 15, 2024 at 0:45
  • 3
    do not rely on extern "Rust", the ABI is not stable and may break for arbitrary reasons Commented May 15, 2024 at 0:45
  • 2
    You should not need unsafe and transmute for pointer-to-pointer casts, there is a .cast() method for that which is much safer. Commented May 15, 2024 at 0:46
  • 1
    You can convert an &str to &CStr using let c_path = CString::new(path)?; c_path.as_c_str(), which handles allocating new memory, appending '\0' and checking for inner zeros, and it doesn't require unsafe or pointer casts. Commented May 15, 2024 at 8:17
  • Consider using libloading. Commented May 16, 2024 at 13:35

1 Answer 1

1

The return type of get_symbol is wrong.

dlsym returns *mut c_void which (in the case of function pointers) contains Option<extern "C" fn(...)> internally.

You have to cast the return value to Option directly, not to a reference.

Remember that in Rust function pointers do not imply pointer semantics, much less reference semantics.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.