[SOLVED] Segfault with extern C function

Hello all

I have a strange bug. I can use a foreign function I load just after load, but can't no more after returning it from a function .

extern crate dylib;
extern crate glob;

struct AudioPlugin {
    pub generate: Box<extern "C" fn() -> u8>,
}

fn load() -> Option<AudioPlugin> {
    // fn load() -> Option<extern "C" fn() -> u8> {
    // fn load() -> Option<u8> {
    for path in glob::glob("plugins/lib*.so").unwrap() {
        match dylib::DynamicLibrary::open(Some(&path.unwrap())) {
            Ok(handle) => {
                let f: extern "C" fn() -> u8;
                unsafe {
                    match handle.symbol::<u8>("generate") {
                        Ok(ptr) => f = std::mem::transmute::<*mut u8, extern "C" fn() -> u8>(ptr),
                        Err(_) => {
                            println!("can't open");
                            return None;
                        }
                    }
                    let a = AudioPlugin {
                        generate: Box::new(f),
                    };
                    println!("{}", (a.generate)());
//// FINE
                    return Some(a);
                }
            }
            Err(_) => {
                println!("can't open");
            }
        }
    }
    None
}

fn main() {
    let a = load().unwrap();
    let res = (a.generate)();
SEGFAULT !!!!
 
}

Can't see what happens here.
The loaded function is trivial, only returns a number.
and I compiled it as rust code

#[no_mangle]
pub extern "C" fn generate() -> u8 {
    8
}

Any help is welcome

Cheers :slight_smile:

The opened DynamicLibrary handle is closed when dropped. If you don't keep it around, your function pointer pointing into the library becomes dangling.

Great, I was suspecting something like that. So I need to keep the dyLib instance inside my struct ? Or is it a more rusty way ?
thx

You probably only want to open the library once. Perhaps do that in main() or put it in a shared cache (e.g. a HashMap wrapped in a lazy_static!) ensuring that any symbols obtained from the library remain valid for the duration of the program.

Also, consider using libloading, which associates lifetimes with libs and symbols, so it helps prevent this particular kind of error.

Oh great, I was confused when i saw the same kind of lib in the air.

But Maybe for something like hot reload is it simpler to get the dylib ?

Thx

If you need hot reloading, then there's probably no simple way, I'd say. Once you reload the library, the function pointers are invalidated again. So you need to refresh any stored function pointers any time you reload the library. That sounds error-prone to me, so what I'd actually suggest is instead of storing function pointers, store the library handles only – even if they are stored in a global cache, you could just swap them out when you need a reload. Then, you could get one-off function pointers each time you want call one, which would ensure no dangling pointers.

3 Likes

Yes, the situation with dynamic loading in Rust is quite disappointing: libloading is the best option out there, but even that lib is unsound (it offers a Deref impl for extern fn targets, but since those are Copy, one can extract them out of the lifetime bound).

  • Here is a prototype of how libloading ought to be changed to offer a sound (lifetime-wise) API: Rust Playground

    • I have integrated a basic memoization strategy for it not to be too costly to load a symbol.

In your case, then, the simplest solution would be to have something like:

use ::parking_lot::Mutex;
static LIB: Mutex<Option<Library>> = Mutex::new(None);

So that your load function can LIB.lock().replace(Library::new(...).unwrap()) to "update" the loaded library;

Then, everytime you need something like "generate", you lock against library reloads with:

let lock_guard = LIB.lock();
let lib = lock_guard.as_ref().expect("Library not loaded yet");

and then can get, for instance, a Symbol<fn() -> u8> with lib.get(c!("generate")), that is guaranteed to be soundly usable while that lock is held / that lock_guard is in scope, while also being guaranteed to be using the "last" version of the loaded library (I am saying this because another solution would be for each "symbol" to hold an owned ref-counted instance of the library, but that would lead to symbols referring to out-of-date / stale libraries (it would be sound, but probably not what you want)).

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.