Работа със системни библиотеки

foreign function interface (FFI)

11 декември 2024

Библиотеки

Езиците, които се компилират до машинен код имат две основни фази на компилация

Единицата сорс код, който се компилира до отделен "обект", се нарича translation unit

Библиотеки

Библиотеките са начин за преизползване на код между различни програми

Има два основни подхода за линкване на библиотеки към програма:

Библиотеки

Статично линкване

Библиотеки

Статично линкване

Предимства

Библиотеки

Динамично линкване

Библиотеки

Динамично линкване

При линкване със програмата

Системни библиотеки

Системни библиотеки

Защо се използват динамични библиотеки?

Системни библиотеки

Защо се използват динамични библиотеки?

Системни библиотеки

Защо се използват динамични библиотеки?

Системни библиотеки

Защо се използват динамични библиотеки?

Системни библиотеки

Защо се използват динамични библиотеки?

Системни библиотеки

Системни библиотеки

Системни библиотеки

Системни библиотеки

Под Unix/Linux, ldd показва списъка от всички нужни библиотеки (рекурсивно)

1 2 3 4 5 6
$ ldd /usr/bin/bash
    linux-vdso.so.1 (0x000075e7daf61000)
    libreadline.so.8 => /usr/lib/libreadline.so.8 (0x000075e7dadbc000)
    libc.so.6 => /usr/lib/libc.so.6 (0x000075e7dabcb000)
    libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x000075e7dab5c000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x000075e7daf63000)

Използване на динамични библиотеки

Пример

Използване на динамични библиотеки

Пример

(използваме Lua, защото е достатъчно малка и проста C библиотека)

Използване на динамични библиотеки

Пример

Искаме да напишем ръстски еквивалент на следния код на C.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int main() {
    char buff[256] = /* file contents */;

    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    int error;
    error = luaL_loadbuffer(L, buff, strlen(buff), "file");
    if (error) { /* error handling */ }

    error = lua_pcall(L, 0, 0, 0);
    if (error) { /* error handling */ }

    lua_close(L);
    return 0;
}

FFI

FFI

FFI

FFI

FFI

FFI

Символи

Динамичната библиотека съдържа символи за

Списъка от символи може да се провери

1 2 3 4 5 6 7 8
$ nm --dynamic --defined-only /usr/lib64/liblua.so.5.4
0000000000006750 T lua_absindex
0000000000006e00 T lua_arith
0000000000006720 T lua_atpanic
000000000000dea0 T lua_callk
000000000000de20 T lua_checkstack
0000000000014500 T lua_close
...

Символи

Съдържанието на символ за функция е assembly кода за тази функция

Няма информация за това как се извиква функцията:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Disassembly of section .text:

0000000000028050 <luaL_newstate@@Base>:
   28050:    f3 0f 1e fa             endbr64
   28054:    55                      push   %rbp
   28055:    31 f6                   xor    %esi,%esi
   28057:    48 8d 3d 12 5a ff ff    lea    -0xa5ee(%rip),%rdi        # 1da70 <lua_close@@Base+0x9570>
   2805e:    48 89 e5                mov    %rsp,%rbp
   28061:    53                      push   %rbx
   28062:    48 83 ec 08             sub    $0x8,%rsp
   28066:    ff 15 24 ac 01 00       call   *0x1ac24(%rip)        # 42c90 <lua_newstate@@Base+0x2ea30>
   2806c:    48 89 c3                mov    %rax,%rbx
   2806f:    48 85 c0                test   %rax,%rax
   28072:    74 23                   je     28097 <luaL_newstate@@Base+0x47>
   28074:    48 8d 35 d5 ce ff ff    lea    -0x312b(%rip),%rsi        # 24f50 <lua_close@@Base+0x10a50>
   2807b:    48 89 c7                mov    %rax,%rdi
   2807e:    ff 15 74 ac 01 00       call   *0x1ac74(%rip)        # 42cf8 <lua_atpanic@@Base+0x3c5d8>
   28084:    48 89 da                mov    %rbx,%rdx
   28087:    48 8d 35 a2 f0 ff ff    lea    -0xf5e(%rip),%rsi        # 27130 <luaL_requiref@@Base+0x110>
   2808e:    48 89 df                mov    %rbx,%rdi
   28091:    ff 15 19 ae 01 00       call   *0x1ae19(%rip)        # 42eb0 <lua_setwarnf@@Base+0x3a2b0>
   28097:    48 89 d8                mov    %rbx,%rax
   2809a:    48 8b 5d f8             mov    -0x8(%rbp),%rbx
   2809e:    c9                      leave
   2809f:    c3                      ret

Символи

За да разберем как се извиква функция, трябва да видим C header файла

1 2 3 4 5 6
lua_State *luaL_newstate(void);

int luaL_loadbuffer(lua_State *L,
                    const char *buff,
                    size_t sz,
                    const char *name);

Извикване на C код от Rust

Декларациите на C функциите трябва да се напишат в extern блок.
Важно - няма никакви проверки!
Грешки при тип на аргумент / тип на резултат / calling convention ще доведат до крашове по време на изпълнение.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
use std::ffi::{c_void, c_char, c_int};

#[repr(transparent)]
pub struct LuaState(pub *mut c_void);

extern "C" {
    fn luaL_newstate() -> LuaState;
    fn luaL_openlibs(state: LuaState);
    fn luaL_loadbuffer(
        state: LuaState,
        buff: *const c_char,
        sz: usize,
        name: *const c_char
    ) -> c_int;
    fn lua_pcall(state: LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}
use std::ffi::{c_void, c_char, c_int};

#[repr(transparent)]
pub struct LuaState(pub *mut c_void);

extern "C" {
    fn luaL_newstate() -> LuaState;
    fn luaL_openlibs(state: LuaState);
    fn luaL_loadbuffer(
        state: LuaState,
        buff: *const c_char,
        sz: usize,
        name: *const c_char
    ) -> c_int;
    fn lua_pcall(state: LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}
fn main() {}

Извикване на C код от Rust

За типа LuaState може да се използва и празна енумерация.
Но трябва да е *const LuaState, не &LuaState.
(някой ден Rust ще получи начин да декларира opaque структури коректно)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use std::ffi::c_int;

pub enum LuaState {}

extern "C" {
    fn luaL_newstate() -> *mut LuaState;
    fn luaL_openlibs(state: *mut LuaState);
    fn luaL_loadbuffer(
        state: *mut LuaState,
        buff: *const u8,
        sz: usize,
        name: *const i8
    ) -> c_int;
    fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}
use std::ffi::c_int;

pub enum LuaState {}

extern "C" {
    fn luaL_newstate() -> *mut LuaState;
    fn luaL_openlibs(state: *mut LuaState);
    fn luaL_loadbuffer(
        state: *mut LuaState,
        buff: *const u8,
        sz: usize,
        name: *const i8
    ) -> c_int;
    fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}
fn main() {}

Извикване на C код от Rust

Calling convention

1 2 3 4 5 6 7 8 9 10 11 12 13
extern "C" {
    fn add_in_c(a: c_int, b: c_int) -> c_int;
}

// може да се пропусне - подразбира се "C".
extern {
    fn mul_in_c(a: c_int, b: c_int) -> c_int;
}

// някои функции могат да имат различна конвенция за извикване - например winapi функциите.
extern "system" {
    fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> c_int;
}

Извикване на C код от Rust

Calling convention

Може да се окаже точното име на конвенция (напр. "vectorcall")
Но обикновнно се използват тези псевдоними:

Извикване на C код от Rust

Calling convention

Може да се окаже точното име на конвенция (напр. "vectorcall")
Но обикновнно се използват тези псевдоними:

Извикване на C код от Rust

Calling convention

Може да се окаже точното име на конвенция (напр. "vectorcall")
Но обикновнно се използват тези псевдоними:

Извикване на C код от Rust

Calling convention

Може да се окаже точното име на конвенция (напр. "vectorcall")
Но обикновнно се използват тези псевдоними:

Извикване на C код от Rust

След това можем да си напишем програмата.
Външните функции трябва да се извикват в unsafe блок, защото Rust няма как да гарантира, че външния код не предизвиква undefined behaviour.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
fn main() {
    let file_name = "my_file.lua";
    let file_contents = std::fs::read_to_string(file_name).unwrap();

    unsafe {
        let lua_state = luaL_newstate();
        luaL_openlibs(lua_state);

        let error = luaL_loadbuffer(
            lua_state,
            file_contents.as_bytes().as_ptr(),
            file_contents.as_bytes().len(),
            b"file\0".as_ptr() as *const i8,
        );
        if error != 0 {
            panic!("failed loading lua code");
        }

        let error = lua_pcall(lua_state, 0, 0, 0);
        if error != 0 {
            panic!("failed running lua code");
        }
    }
}
use std::ffi::c_int;
pub enum LuaState {}
extern "C" {
fn luaL_newstate() -> *mut LuaState;
fn luaL_openlibs(state: *mut LuaState);
fn luaL_loadbuffer(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8) -> c_int;
fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}
fn main() {
    let file_name = "my_file.lua";
    let file_contents = std::fs::read_to_string(file_name).unwrap();

    unsafe {
        let lua_state = luaL_newstate();
        luaL_openlibs(lua_state);

        let error = luaL_loadbuffer(
            lua_state,
            file_contents.as_bytes().as_ptr(),
            file_contents.as_bytes().len(),
            b"file\0".as_ptr() as *const i8,
        );
        if error != 0 {
            panic!("failed loading lua code");
        }

        let error = lua_pcall(lua_state, 0, 0, 0);
        if error != 0 {
            panic!("failed running lua code");
        }
    }
}

Извикване на C код от Rust

Работа със c-низове

Много често C библиотеки работят с nul terminated низове.
За такива низове Rust има типовете CStr и CString

Извикване на C код от Rust

Работа със c-низове

Конструиране на CStr

1 2 3 4
use std::ffi::CStr;

let hello = CStr::from_bytes_with_nul(b"hello, world!\0").unwrap();
// hello: &'static CStr
fn main() {
use std::ffi::CStr;

let hello = CStr::from_bytes_with_nul(b"hello, world!\0").unwrap();
// hello: &'static CStr
}

Кратък синтаксис за литерал

1 2
let hello = c"hello, world!";
// hello: &'static CStr

Извикване на C код от Rust

Работа със c-низове

Трябва да се внимава с lifetimes, когато се взимат указатели към съдържанието на CString.
CString::as_ptr() връща гол указател, не референция.

1 2 3 4 5 6 7 8
// Do this:
let cstr = CString::new("Hello").unwrap();
unsafe { c_func(cstr.as_ptr()) };

// Do not do this:
unsafe {
    c_func(CString::new("Hello").unwrap().as_ptr()); 
}
warning: getting the inner pointer of a temporary `CString` --> src/bin/main_ef58ef4005da829e2ce5723e373cc68815cad1a1.rs:11:43 | 11 | c_func(CString::new("Hello").unwrap().as_ptr()); | ------------------------------ ^^^^^^ this pointer will be invalid | | | this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime | = note: pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned = help: for more information, see https://doc.rust-lang.org/reference/destructors.html = note: `#[warn(temporary_cstring_as_ptr)]` on by default
use std::ffi::CString;
unsafe fn c_func(name: *const i8) {}
fn main() {
// Do this:
let cstr = CString::new("Hello").unwrap();
unsafe { c_func(cstr.as_ptr()) };

// Do not do this:
unsafe {
    c_func(CString::new("Hello").unwrap().as_ptr()); 
}
}

Извикване на C код от Rust

Програмата се компилира, но гърми на стъпката за линкване.
Защото не сме казали на Rust към коя библиотека да се линк-не, за да намери външните функции.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
fn main() {
    let file_name = "my_file.lua";
    let file_contents = std::fs::read_to_string(file_name).unwrap();

    unsafe {
        let lua_state = luaL_newstate();
        luaL_openlibs(lua_state);

        let error = luaL_loadbuffer(
            lua_state,
            file_contents.as_bytes().as_ptr(),
            file_contents.as_bytes().len(),
            b"file\0".as_ptr() as *const i8,
        );
        if error != 0 {
            panic!("failed loading lua code");
        }

        let error = lua_pcall(lua_state, 0, 0, 0);
        if error != 0 {
            panic!("failed running lua code");
        }
    }
}
error: linking with `cc` failed: exit status: 1 | = note: LC_ALL="C" PATH="/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin:/opt/miniconda3/condabin:/home/andrew/.gem/ruby/3.2.2/bin:/home/andrew/.rubies/ruby-3.2.2/lib/ruby/gems/3.2.0/bin:/home/andrew/.rubies/ruby-3.2.2/bin:/home/andrew/bin:/usr/lib/ccache/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/usr/local/bin:/usr/local/heroku/bin:/home/andrew/.cabal/bin:/home/andrew/.cargo/bin:/home/andrew/.yarn/bin:/home/andrew/.local/bin:/opt/ucsf-chimera/bin/:/opt/diet/bin:/opt/nginx/sbin" VSLANG="1033" "cc" "-m64" "/tmp/rustcYVU5gd/symbols.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.01iwd25puqw1u8e9jgeyqjlr0.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.0dz67w5g66ys7uml9gmnzf34w.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.0j7f86q123p1riicj3ckntuxu.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.1aj1qplgpv4opdujscu8sjc00.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.1k4lopsa0dwkc8b104zvpt932.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.1n4nz8a3kftsm0atjti6toc9k.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.2eo7q949h8oqiqq1mbhuwdei2.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.2qykj712faelxtjs0c9vdvmb4.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.2v0gxg6p2xr2gc2wxmgyuo84j.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.4hjgznmvrvkd61565nu882sbw.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.4y0jjgtjiow369uk864q4v088.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.5ah2lb0pjxkpm0sbj9tag7q37.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.5xlpdq46z5jm83pr6tfddkdt4.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.6f49yswhrwszm3yxekwyphp8r.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.7djaysh20clfqwtne94uwt66l.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.854rthmvp0npk2b494bppe1ke.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.8u9v2qr2gbndh57opohwxbpwg.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.99di8l48xcrde9ty1n8f9vjas.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.9q4sfyiny9fqckqco0pv0vhmf.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.a80rdzd35dzjdba3v0bwaqp60.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.5hngth86in3f3eg5sv3x9pz49.rcgu.o" "-Wl,--as-needed" "-Wl,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-ca74a2d9c5166d9f.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-e31ab23316ed5080.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-27dc4aa955912662.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-bd0d6cccce077b99.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-8d001680935b5e3c.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-ba8ce71964f984f4.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-99a73526abcec14b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-63ac0d22cff92579.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-9057355c92c922d5.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-358be9bc1f6bab04.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-aca15549d5bff974.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-8251d2cef7072448.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-7d50b86011c66411.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-51ea098fce5006bf.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-5a14e0d0b712e731.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-8b83dbf3a7b8f999.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-c6fd227bdc7b39ff.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-959d3389fa3da8a5.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-abe05db089cc2c62.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" = note: /usr/bin/ld: /home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_51019d70c8b163e99a924f926730745f98e5d747-198c1967215e0f45.854rthmvp0npk2b494bppe1ke.rcgu.o: in function `main_51019d70c8b163e99a924f926730745f98e5d747::main': /main_51019d70c8b163e99a924f926730745f98e5d747.rs:14:(.text._ZN45main_51019d70c8b163e99a924f926730745f98e5d7474main17h0167acaf7e602accE+0xe3): undefined reference to `luaL_newstate' /usr/bin/ld: /main_51019d70c8b163e99a924f926730745f98e5d747.rs:15:(.text._ZN45main_51019d70c8b163e99a924f926730745f98e5d7474main17h0167acaf7e602accE+0xfc): undefined reference to `luaL_openlibs' /usr/bin/ld: /main_51019d70c8b163e99a924f926730745f98e5d747.rs:17:(.text._ZN45main_51019d70c8b163e99a924f926730745f98e5d7474main17h0167acaf7e602accE+0x198): undefined reference to `luaL_loadbuffer' /usr/bin/ld: /main_51019d70c8b163e99a924f926730745f98e5d747.rs:27:(.text._ZN45main_51019d70c8b163e99a924f926730745f98e5d7474main17h0167acaf7e602accE+0x1b5): undefined reference to `lua_pcall' collect2: error: ld returned 1 exit status = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified = note: use the `-l` flag to specify native libraries to link = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib) error: could not compile `rust` (bin "main_51019d70c8b163e99a924f926730745f98e5d747") due to 1 previous error
use std::ffi::c_int;
pub enum LuaState {}
extern "C" {
fn luaL_newstate() -> *mut LuaState;
fn luaL_openlibs(state: *mut LuaState);
fn luaL_loadbuffer(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8) -> c_int;
fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}
fn main() {
    let file_name = "my_file.lua";
    let file_contents = std::fs::read_to_string(file_name).unwrap();

    unsafe {
        let lua_state = luaL_newstate();
        luaL_openlibs(lua_state);

        let error = luaL_loadbuffer(
            lua_state,
            file_contents.as_bytes().as_ptr(),
            file_contents.as_bytes().len(),
            b"file\0".as_ptr() as *const i8,
        );
        if error != 0 {
            panic!("failed loading lua code");
        }

        let error = lua_pcall(lua_state, 0, 0, 0);
        if error != 0 {
            panic!("failed running lua code");
        }
    }
}

Извикване на C код от Rust

Единия вариант е това да се направи с #[link] атрибут на extern блока.

Указва се само името на библиотеката ("lua").
Това автоматично се разпъва до пълното име на файла, спрямо конвенциите на ОС ("liblua.so", "lua.dll")

1 2 3 4 5 6 7 8 9 10 11
use std::ffi::c_int;

pub enum LuaState {}

#[link(name = "lua")]
extern "C" {
    fn luaL_newstate() -> *mut LuaState;
    fn luaL_openlibs(state: *mut LuaState);
    fn luaL_loadbuffer(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8) -> c_int;
    fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}

Извикване на C код от Rust

#[link] атрибута служи като документация от коя библиотека идват функциите.

Но това е просто конвенция. Няма проверка дали функциите реално идват от тази библиотека.
Списъка с библиотеки за линкване е глобално свойство на програмата.
Няма значение на кой блок ще се сложи #[link] атрибута

1 2 3 4 5 6 7 8 9 10 11 12 13
use std::ffi::c_int;

pub enum LuaState {}

#[link(name = "lua")]
extern {}

extern "C" {
    fn luaL_newstate() -> *mut LuaState;
    fn luaL_openlibs(state: *mut LuaState);
    fn luaL_loadbuffer(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8) -> c_int;
    fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int;
}

Извикване на C код от Rust

Друг начин е да се подаде като флаг на компилатора

1
cargo rustc -- -l lua

Може и през .config/cargo.toml - но така се прилага и на rust dependecy-тата

1 2
[build]
rustflags = ["-l", "lua"]

Има и трети начин - през билд скрипт: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib

Извикване на C код от Rust

Това не е достатъчно - все още получаваме грешки:

1 2
undefined reference to `luaL_loadbuffer'
undefined reference to `lua_pcall'

Причината е, че гледаме C API документацията.
C API ≠ library ABI

Специфично няма символи luaL_loadbuffer и lua_pcall, защото не са функции, а C макроси.

1 2
#define luaL_loadbuffer(L,s,sz,n)    luaL_loadbufferx(L,s,sz,n,NULL)
#define lua_pcall(L,n,r,f)    lua_pcallk(L, (n), (r), (f), 0, NULL)

Извикване на C код от Rust

Решението - трябва ръчно да разпишем макрото на Rust

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
use std::ffi::c_int;
use std::ptr;

pub enum LuaState {}
type LuaKContext = isize;
type LuaKFunction = Option<extern "C" fn (*const LuaState, c_int, LuaKContext) -> c_int>;

#[allow(non_snake_case)]
unsafe fn luaL_loadbuffer(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8) -> c_int {
    luaL_loadbufferx(state, buff, sz, name, ptr::null())
}

unsafe fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int {
    lua_pcallk(state, nargs, nresults, msgh, 0, None)
}

#[link(name = "lua")]
extern "C" {
    fn luaL_newstate() -> *mut LuaState;
    fn luaL_openlibs(state: *mut LuaState);
    fn luaL_loadbufferx(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8, mode: *const i8) -> c_int;
    fn lua_pcallk(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int, ctx: LuaKContext, k: LuaKFunction) -> c_int;
}

Извикване на C код от Rust

Финален код

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
use std::ffi::c_int;
use std::ptr;

pub enum LuaState {}
type LuaKContext = isize;
type LuaKFunction = Option<extern "C" fn (*const LuaState, c_int, LuaKContext) -> c_int>;

#[allow(non_snake_case)]
unsafe fn luaL_loadbuffer(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8) -> c_int {
    luaL_loadbufferx(state, buff, sz, name, ptr::null())
}

unsafe fn lua_pcall(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int) -> c_int {
    lua_pcallk(state, nargs, nresults, msgh, 0, None)
}

#[link(name = "lua")]
extern "C" {
    fn luaL_newstate() -> *mut LuaState;
    fn luaL_openlibs(state: *mut LuaState);
    fn luaL_loadbufferx(state: *mut LuaState, buff: *const u8, sz: usize, name: *const i8, mode: *const i8) -> c_int;
    fn lua_pcallk(state: *mut LuaState, nargs: c_int, nresults: c_int, msgh: c_int, ctx: LuaKContext, k: LuaKFunction) -> c_int;
}

fn main() {
    let file_name = "my_file.lua";
    let file_contents = std::fs::read_to_string(file_name).unwrap();

    unsafe {
        let lua_state = luaL_newstate();
        luaL_openlibs(lua_state);

        let error = luaL_loadbuffer(
            lua_state,
            file_contents.as_bytes().as_ptr(),
            file_contents.as_bytes().len(),
            b"file\0".as_ptr(),
        );
        if error != 0 {
            panic!("failed loading lua code");
        }

        let error = lua_pcall(lua_state, 0, 0, 0);
        if error != 0 {
            panic!("failed running lua code");
        }
    }
}

Извикване на C код от Rust

Bindgen

Извикване на C код от Rust

Интерфейс

Обикновенно, binding-ите не се използват директно от Rust код.
Вместо това се постоява безопасен Rust-ски интерфейс над тях.

Често ще виждате в crates.io

Извикване на C код от Rust

Интерфейс - пример

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
use std::ffi::CStr;

mod ffi {
    /* генерирани binding-и */
}

#[derive(Debug)]
pub enum LuaError {
    RuntimeError,
    InvalidSyntax,
    OutOfMemory,
    Unknown(i32),
}

// ownership - няма clone/copy
pub struct LuaState {
    state: *mut ffi::LuaState,
}

impl LuaState {
    // error handling
    pub fn new() -> Option<Self> {
        let state_ptr = unsafe { ffi::luaL_newstate() };
        if state_ptr.is_null() {
            None
        } else {
            Some(LuaState { state: state_ptr })
        }
    }

    // използване на конкретни типове, носещи допълнителна информация:
    // - `&[u8]` вместо `*const u8, usize` - буфер от байтове, няма терминираща нула
    // - `&CStr` вместо `*const i8` - низа трябва да завършва с терминираща нула
    //
    // конвертиране на резултата до ръстски Result
    pub fn load_buffer(&mut self, buff: &[u8], name: &CStr) -> Result<(), LuaError> {
        let res = unsafe { ffi::luaL_loadbuffer(self.state, buff.as_ptr(), buff.len(), name.as_ptr()) };
        match res {
            ffi::LUA_OK => Ok(()),
            ffi::LUA_ERRSYNTAX => Err(LuaError::InvalidSyntax),
            ffi::LUA_ERRMEM => Err(LuaError::OutOfMemory),
            n => Err(LuaError::Unknown(n)),
        }
    }

    // връща низ с коректния lifetime (низа се държи от lua_State)
    // и коректното ограничение (lua_State не трябва да се модифицира, докато се използва низа)
    pub fn to_string(&mut self, index: i32) -> Option<&CStr> {
        unsafe {
            let mut out_len = 0;
            let ptr = ffi::lua_tolstring(self.state, index, &mut out_len);

            if ptr.is_null() {
                None
            } else {
                let cstr_bytes = std::slice::from_raw_parts(ptr as *const u8, out_len + 1);
                Some(CStr::from_bytes_with_nul_unchecked(cstr_bytes))
            }
        }
    }
}

// автоматично деструктиране
impl Drop for LuaState {
    fn drop(&mut self) {
        unsafe { ffi::lua_close(self.state) };
    }
}

Компилиране на Rust като библиотека

1 2 3 4 5 6 7 8
[package]
name = "project-name"
version = "0.1.0"

[lib]
crate-type = ["..."]

[dependencies]

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

Възможни са следните типове:

Компилиране на Rust като библиотека

За да можем да извикаме Rust кода от друг език ни трябва staticlib или cdylib

1 2 3 4 5 6 7 8
[package]
name = "project-name"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]

[dependencies]

Резултатът ще бъде target/debug/libproject_name.so / target/release/libproject_name.so

Компилиране на Rust като библиотека

Компилиране на Rust като библиотека

Rust прилага name mangling за символите, които генерира за въртрешно ползване.

Например core::panicking::panic_fmt
става _ZN4core9panicking9panic_fmt17h0c3082644d1bf418E

Компилиране на Rust като библиотека

За да се export-не публично символ, трябва да му се даде фиксирано име

1 2 3 4 5 6 7 8 9
#[no_mangle]
pub extern "C" fn add_from_rust(left: i32, right: i32) -> i32 {
    left + right
}

#[export_name = "mul_from_rust"]
pub extern "C" fn my_mul(left: i32, right: i32) -> i32 {
    left * right
}
fn main() {}
#[no_mangle]
pub extern "C" fn add_from_rust(left: i32, right: i32) -> i32 {
    left + right
}

#[export_name = "mul_from_rust"]
pub extern "C" fn my_mul(left: i32, right: i32) -> i32 {
    left * right
}
1 2 3
$ nm --dynamic --defined-only target/debug/libdylib_test.so
0000000000006330 T add_from_rust
0000000000006370 T mul_from_rust

Компилиране на Rust като библиотека

Тези атрибути могат да доведат до UB при колизия на имена.
Затова е позволено да се дефинират като #[unsafe(attribute)]
В бъдеща версия ще е задължително.

1 2 3 4 5 6 7 8 9
#[unsafe(no_mangle)]
pub extern "C" fn add_from_rust(left: i32, right: i32) -> i32 {
    left + right
}

#[unsafe(export_name = "mul_from_rust")]
pub extern "C" fn my_mul(left: i32, right: i32) -> i32 {
    left * right
}
fn main() {}
#[unsafe(no_mangle)]
pub extern "C" fn add_from_rust(left: i32, right: i32) -> i32 {
    left + right
}

#[unsafe(export_name = "mul_from_rust")]
pub extern "C" fn my_mul(left: i32, right: i32) -> i32 {
    left * right
}

Компилиране на Rust като библиотека

Веднъж компилирана, тази библиотека може да се използва от C код.

1 2 3 4 5 6 7
int32_t add_from_rust(int32_t a, int32_t b);
int32_t mul_from_rust(int32_t a, int32_t b);

int main() {
    int seven = add_from_rust(5, 2);
    int ten = mul_from_rust(5, 2);
}

Компилиране на Rust като библиотека

Cbindgen

Генерирането на C header файл от Rust код може да се автоматизира с cbindgen

Споделяне на структури

Структурите в Rust нямат фиксирана подредба на полетата.
Компилатора е свободен да ги размества с цел оптимизации.

За да споделяме структури по стойност м/у C и Rust, трябва да добавим #[repr(C)].
Това фиксира наредбата на полетата в реда на дефинирането им.

1 2 3 4 5 6 7 8 9 10
// на C

struct FooBar {
    int foo;
    short bar;
};

void foobar(FooBar foobar) {
    // ...
}

1 2 3 4 5 6 7 8 9 10 11
// на Rust

#[repr(C)]
struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern "C" {
    fn foobar(foobar: FooBar); 
}

use std::ffi::{c_int, c_short};
fn main() {}
// на Rust

#[repr(C)]
struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern "C" {
    fn foobar(foobar: FooBar); 
}

Споделяне на структури

Забравянето на #[repr(C)] води до warning

1 2 3 4 5 6 7 8 9 10
// на Rust

struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern "C" {
    fn foobar(foobar: FooBar); 
}
warning: `extern` block uses type `FooBar`, which is not FFI-safe --> src/bin/main_64469259a6c0af8ae3a464b5fafbadffdbe5d3ea.rs:12:23 | 12 | fn foobar(foobar: FooBar); | ^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here --> src/bin/main_64469259a6c0af8ae3a464b5fafbadffdbe5d3ea.rs:6:1 | 6 | struct FooBar { | ^^^^^^^^^^^^^ = note: `#[warn(improper_ctypes)]` on by default
use std::ffi::{c_int, c_short};
fn main() {}
// на Rust

struct FooBar {
    foo: c_int,
    bar: c_short,
}

extern "C" {
    fn foobar(foobar: FooBar); 
}

Споделяне на указатели

Споделяне на указатели

Споделяне на указатели

Споделяне на указатели

Интеграция със C++ библиотеки

Интеграция със C++ библиотеки

Интеграция със C++ библиотеки

Интеграция със C++ библиотеки

Други материали

Въпроси