Вручную вызвать динамическую библиотеку rust

В настоящее время я играю с 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

И тут я совсем потерялся. Что я делаю не так ?


person Levans    schedule 01.11.2014    source источник


Ответы (3)


Проблема здесь в том, как работает функция 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(...) -> ....

person huon    schedule 03.11.2014

Я думаю, что проблема связана с тем, что вы выполняете приведение между несовместимыми типами. В частности, разыменование *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]
person Shepmaster    schedule 02.11.2014
comment
О, понятно, спасибо. Документация вообще ничего не говорит об этом. - person Levans; 02.11.2014
comment
Это очень благотворительно; на данный момент нет никаких документов... Я даже видел #![allow(missing_docs)] во время погружения с исходным кодом ^_^. - person Shepmaster; 02.11.2014

После этого вопроса/ответа std::dynamic_lib API, похоже, исчез. На момент написания этой статьи казалось, что загрузка библиотек — самый популярный способ динамической загрузки библиотек на crates.io. .

person djc    schedule 29.08.2018