Ручной вызов динамической библиотеки ржавчины

Я сейчас играю с DynamicLibrary.

Код моей динамической библиотеки (скомпилирован с rustc --crate-type dylib dylib.rs):

// dylib.rs
#[no_mangle]
pub fn minicall() -> u8 {
    3u8
}

И код для его вызова:

// caller.rs
use std::dynamic_lib::DynamicLibrary;

fn main() {
    let mut v = Vec::new();

    DynamicLibrary::prepend_search_path(&::std::os::getcwd());

    match DynamicLibrary::open(Some("./libdylib.so")) {
        Err(e) => panic!("ERROR: {}", e),
        Ok(lib) => {
            println!("Unsafe bloc !");
            let func = unsafe {
                match lib.symbol::< fn() -> u8 >("minicall") {
                        Err(e) => { panic!("ERROR: {}", e) },
                        Ok(f) => { *f },
                }
            };
            println!("call func !");
            let new_value = func();

            println!("extend vec !");
            v.push(new_value);
        }
    }

    println!("v is: {}", v);
}

У меня есть этот вывод:

~> ./caller 
Unsafe bloc !
call func !
Illegal instruction

И здесь я совсем потерялся. Что я делаю неправильно?

Ответ 1

Проблема в том, как работает функция symbol. Он имеет подпись:

unsafe fn symbol<T>(&self, symbol: &str) -> Result<*mut T, String>

Загруженная библиотека в основном представляет собой большой массив в памяти с определенными адресами, помеченными именем (именами символов). Запрос символа ищет адрес и возвращает указатель прямо к нему. Функция в библиотеке - это длинная последовательность инструкций, поэтому запрос имени функции возвращает указатель (функции) непосредственно к началу. Это можно затем назвать нормальным указателем функции. API-интерфейс Rust DynamicLibrary возвращает этот указатель, то есть *mut T указывает непосредственно на кусок памяти в динамической библиотеке (предположительно/надеюсь, что тип T).

Тип fn(...) -> ... - это сам указатель на функцию, то есть 8 байтов (или 4 байта), сохраняющих адрес начала функции, которую он представляет. Следовательно, вызов lib.symbol::< fn() -> u8 >("minicall") говорит "найдите мне адрес вещи под названием minicall (который является указателем на функцию)", он не говорит "найдите мне адрес вещи под названием minicall (который функция)". Возвращаемое значение *mut (fn() -> u8) затем является двунаправленным и разыменовывает его для вызова, это интерпретация первых 8 (или 4) байтов кода функции как указателя (например, случайных машинных команд/прелюдии функции), он не выполняется их.

(Боковое примечание: возможно, это сработает, если в вашей библиотеке есть #[no_mangle] pub static minicall: fn() -> u8 = the_real_minicall;, но вы, вероятно, этого не хотите.)

Вызов lib.symbol::<T>("minicall") возвращает нужный нам указатель функции (т.е. возвращает указатель на начало кода minicall), поэтому просто возникает вопрос об этом выражении компилятору, К сожалению, в настоящее время нет типа T, который делает *mut T указателем на функцию, поэтому сначала нужно установить T = u8 (т.е. lib.symbol::<u8>("minicall")), а затем применить возвращаемое значение к соответствующему типу указателя функции через transmute::<_, fn() -> u8>(pointer).

(Я отвечаю на это даже после того, как был принят другой ответ, потому что я не думаю, что он объяснил причину очень хорошо, просто дал решение.)


Последнее, в этом случае это не проблема, но он много путешествует: Rust ABI (вызывающее соглашение, используемое для функций типа fn(...) -> ...), не совпадает с C ABI, поэтому функции загруженные из динамических библиотек C, должны иметь тип extern "C" fn(...) -> ..., а не fn(...) -> ....

Ответ 2

Я думаю, проблема связана с тем фактом, что вы производите несовместимые типы. В частности, разыменование *f будет указывать на неправильное место. Я просмотрел код Rust, чтобы увидеть, как должна использоваться библиотека, и нашел пример в src/librustc/plugin/load.rs. Я применил этот код к вашему примеру:

let func = unsafe {
    // Let this return a `*mut u8`, a very generic pointer
    match lib.symbol("minicall") {
        Err(e) => { fail!("ERROR: {}", e) },
        // And then cast that pointer a function
        Ok(f) => { std::mem::transmute::<*mut u8, fn() -> u8>(f) },
    }
};
println!("call func !");
let new_value = func();

Выход:

$ ./caller
Unsafe bloc !
call func !
extend vec !
v is: [3]