Работа със системни библиотеки
foreign function interface (FFI)
11 декември 2024
Библиотеки
Езиците, които се компилират до машинен код имат две основни фази на компилация
- компилиране на сорс код до "обект", съдържащ машинен код
- линкване на няколко "обекта" във финален артефакт (програма или библиотека)
Единицата сорс код, който се компилира до отделен "обект", се нарича translation unit
- в C/C++ e на ниво сорс файл
- в Rust е на ниво crate
- но в основни щрихи схемата е подобна
Библиотеки
Библиотеките са начин за преизползване на код между различни програми
Има два основни подхода за линкване на библиотеки към програма:
- статично линкване
- динамично линкване
Библиотеки
Статично линкване
- обикновенно имаме пълния сорс код на библиотеката
- библиотеката се компилира до "обекти" заедно с нашия код
- всични "обекти" се линват заедно в едно binary
Библиотеки
Статично линкване
- обикновенно имаме пълния сорс код на библиотеката
- библиотеката се компилира до "обекти" заедно с нашия код
- всични "обекти" се линват заедно в едно binary
Предимства
- възможност за повече оптимизации
- произвежда се self contained binary
- библиотеки съдържащи generic-и могат да се линкват само статично (освен ако не сте Swift)
Библиотеки
Динамично линкване
- библиотеката се компилира самостоянотелно
- произвежда се shared library файл (.so / .dll), съдържащ изпълнимия код на библиотеката
- .so / .dll файла се разпространява самостоятелно
Библиотеки
Динамично линкване
- библиотеката се компилира самостоянотелно
- произвежда се shared library файл (.so / .dll), съдържащ изпълнимия код на библиотеката
- .so / .dll файла се разпространява самостоятелно
При линкване със програмата
- кодът на библиотеката не се копира във финалното binary
- само се добавя информация как да се заредят необходимите функции от динамичната библиотека
Системни библиотеки
- обикновенно динамичните библиотеки се инсталират в глобална системна директория
- напр
/usr/lib64/libfoo.so.1
- напр
C:\Windows\System32\foo.dll
Системни библиотеки
Защо се използват динамични библиотеки?
Системни библиотеки
Защо се използват динамични библиотеки?
- една библиотека (
.so
/.dll
файл) може да се използва от множество програми
Системни библиотеки
Защо се използват динамични библиотеки?
- една библиотека (
.so
/.dll
файл) може да се използва от множество програми - пести се място на диска
- общия код е записан само веднъж на файловата система
Системни библиотеки
Защо се използват динамични библиотеки?
- една библиотека (
.so
/.dll
файл) може да се използва от множество програми - пести се място на диска
- общия код е записан само веднъж на файловата система
- пести се оперативна памет
- кода на библиотеката се зарежда в сегмент споделена памет
- може да се преизползва от няколко процеса
Системни библиотеки
Защо се използват динамични библиотеки?
- една библиотека (
.so
/.dll
файл) може да се използва от множество програми - пести се място на диска
- общия код е записан само веднъж на файловата система
- пести се оперативна памет
- кода на библиотеката се зарежда в сегмент споделена памет
- може да се преизползва от няколко процеса
- позволява ъпдейт на библиотеката независимо от приложението
- при security vulnerability може да се ъпдейтне системната библиотека
- и това автоматично афектира всички програми, които я използват
- без да се чака всяка програма да се ъпдейтне индивидуално
Системни библиотеки
- всяко binary (програма или библиотека) съдържа списък от кои динамични библиотеки зависи
Системни библиотеки
- всяко binary (програма или библиотека) съдържа списък от кои динамични библиотеки зависи
- при пускане на програмата тези библиотеки се намират в системните директории и зареждат
Системни библиотеки
- всяко binary (програма или библиотека) съдържа списък от кои динамични библиотеки зависи
- при пускане на програмата тези библиотеки се намират в системните директории и зареждат
- ако някоя библиотека не съществува - програмата не може да стартира
Системни библиотеки
Под Unix/Linux, ldd
показва списъка от всички нужни библиотеки (рекурсивно)
$ 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)
bash
зависи отlibc.so.6
иlibreadline.so.8
libc.so.6
е намерено като/usr/lib/libc.so.6
libreadline.so.8
е намерено като/usr/lib/libreadline.so.8
Използване на динамични библиотеки
Пример
- ще напишем програма, която зарежда и изпълнява файл, съдържащ код на Lua
- интерпретатора на Lua е имплементиран на C
- и е инсталиран като библиотека
/usr/lib/liblua.so.5.4
Използване на динамични библиотеки
Пример
- ще напишем програма, която зарежда и изпълнява файл, съдържащ код на Lua
- интерпретатора на Lua е имплементиран на C
- и е инсталиран като библиотека
/usr/lib/liblua.so.5.4
(използваме Lua, защото е достатъчно малка и проста C библиотека)
Използване на динамични библиотеки
Пример
Искаме да напишем ръстски еквивалент на следния код на C.
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
- foreign function interface
FFI
- foreign function interface
- механизма, чрез който Rust може да комуникира с други езици
FFI
- foreign function interface
- механизма, чрез който Rust може да комуникира с други езици
- свежда се до извикване на "C" функции
FFI
- foreign function interface
- механизма, чрез който Rust може да комуникира с други езици
- свежда се до извикване на "C" функции
- Rust може да извиква "C" код директно
FFI
- foreign function interface
- механизма, чрез който Rust може да комуникира с други езици
- свежда се до извикване на "C" функции
- Rust може да извиква "C" код директно
- Rust може да извиква код на друг език, ако той е представен чрез "C" интерфейс
FFI
- foreign function interface
- механизма, чрез който Rust може да комуникира с други езици
- свежда се до извикване на "C" функции
- Rust може да извиква "C" код директно
- Rust може да извиква код на друг език, ако той е представен чрез "C" интерфейс
- Rust може да предостави "C" интерфейс, така че да може да се извика от друг език
Символи
Динамичната библиотека съдържа символи за
- функции
- глобални променливи
Списъка от символи може да се провери
$ 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 кода за тази функция
Няма информация за това как се извиква функцията:
- типа на аргументите
- типа на резултата
- calling конвенцията
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 файла
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 ще доведат до крашове по време на изпълнение.
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 структури коректно)
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
- "C"-то в
extern "C" {}
e calling конвенцията, с която се извикват функциите. - правила как се извиква функция
- как се подават аргументи (в регистри, на стека, в какъв ред)
- как се връща резултата
- подготовка и зачистване на стека и регистрите между извикващата и извиканата функция
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" - стандартната конвенция, използвана от C компилатора на системата
- обикновенно е правилното, освен ако в C header-а изрично не пише друго
Извикване на C код от Rust
Calling convention
Може да се окаже точното име на конвенция (напр. "vectorcall")
Но обикновнно се използват тези псевдоними:
- "C" - стандартната конвенция, използвана от C компилатора на системата
- обикновенно е правилното, освен ако в C header-а изрично не пише друго
- "system" - използва се за системни функции.
- под windows "system" е различно от "C"
- съвет: просто използвайте https://docs.rs/winapi
Извикване на C код от Rust
Calling convention
Може да се окаже точното име на конвенция (напр. "vectorcall")
Но обикновнно се използват тези псевдоними:
- "C" - стандартната конвенция, използвана от C компилатора на системата
- обикновенно е правилното, освен ако в C header-а изрично не пише друго
- "system" - използва се за системни функции.
- под windows "system" е различно от "C"
- съвет: просто използвайте https://docs.rs/winapi
- "Rust" - каквото rustc използва за нормалните функции. Не се използва при FFI
- не е стабилна конвенция - няма спецификация
- технически, може да се промени в следваща версия на компилатора
- (на практика, за момента съвпада с "C")
Извикване на C код от Rust
След това можем да си напишем програмата.
Външните функции трябва да се извикват в unsafe
блок, защото Rust няма как да гарантира, че външния код не предизвиква undefined behaviour.
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
- поредица от байтове - не е задължително да са utf8
- завършват с терминираща нула
- не съдържат терминираща нулев байт във вътрешността си
&CStr
- аналог на&str
CString
- аналог наString
Извикване на C код от Rust
Работа със c-низове
Конструиране на CStr
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 }
Кратък синтаксис за литерал
let hello = c"hello, world!";
// hello: &'static CStr
Извикване на C код от Rust
Работа със c-низове
Трябва да се внимава с lifetimes, когато се взимат указатели към съдържанието на CString
.
CString::as_ptr()
връща гол указател, не референция.
// 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 към коя библиотека да се линк-не, за да намери външните функции.
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")
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]
атрибута
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
Друг начин е да се подаде като флаг на компилатора
cargo rustc -- -l lua
Може и през .config/cargo.toml
- но така се прилага и на rust dependecy-тата
[build]
rustflags = ["-l", "lua"]
Има и трети начин - през билд скрипт: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib
Извикване на C код от Rust
Това не е достатъчно - все още получаваме грешки:
undefined reference to `luaL_loadbuffer'
undefined reference to `lua_pcall'
Причината е, че гледаме C API документацията.
C API ≠ library ABI
Специфично няма символи luaL_loadbuffer
и lua_pcall
, защото не са функции, а C макроси.
#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
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
Финален код
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 header файл генерира binding-и
- (външни функции, дефиниции на функции, константи и т.н.)
- https://github.com/rust-lang-nursery/rust-bindgen
Извикване на C код от Rust
Интерфейс
Обикновенно, binding-ите не се използват директно от Rust код.
Вместо това се постоява безопасен Rust-ски интерфейс над тях.
Често ще виждате в crates.io
- пакет
libfoo-sys
- binding-и, автоматично генерирани сbindgen
- пакет
libfoo
- ръчно написана безопасна абстракция над binding-ите
Извикване на C код от Rust
Интерфейс - пример
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 като библиотека
- ако проекта е библиотека (
cargo new --lib
) - в
Cargo.toml
може да се добави опцияlib.crate-type
- до какъв формат да се компилира библиотеката
[package]
name = "project-name"
version = "0.1.0"
[lib]
crate-type = ["..."]
[dependencies]
Компилиране на Rust като библиотека
Възможни са следните типове:
Компилиране на Rust като библиотека
Възможни са следните типове:
lib
- компилатора избира типа на библиотеката
Компилиране на Rust като библиотека
Възможни са следните типове:
lib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни -.rlib
Компилиране на Rust като библиотека
Възможни са следните типове:
lib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни -.rlib
dylib
- динамична Rust библиотека (не знам какво значи това)
Компилиране на Rust като библиотека
Възможни са следните типове:
lib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни -.rlib
dylib
- динамична Rust библиотека (не знам какво значи това)staticlib
- статична native библиотека -.a
,.lib
Компилиране на Rust като библиотека
Възможни са следните типове:
lib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни -.rlib
dylib
- динамична Rust библиотека (не знам какво значи това)staticlib
- статична native библиотека -.a
,.lib
cdylib
- динамична native библиотека -.so
,.dylib
,.dll
Компилиране на Rust като библиотека
Възможни са следните типове:
lib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни -.rlib
dylib
- динамична Rust библиотека (не знам какво значи това)staticlib
- статична native библиотека -.a
,.lib
cdylib
- динамична native библиотека -.so
,.dylib
,.dll
proc-macro
- процедурен макрос
Компилиране на Rust като библиотека
За да можем да извикаме Rust кода от друг език ни трябва staticlib
или cdylib
[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 няма стабилен Appliaction Binary Interface (ABI)
- ако искаме кода да се ползва от отвън, трябва да му направим C интерфейс
- дори и това "от отвън" да е друг Rust-ски проект
Компилиране на Rust като библиотека
Rust прилага name mangling за символите, които генерира за въртрешно ползване.
Например core::panicking::panic_fmt
става _ZN4core9panicking9panic_fmt17h0c3082644d1bf418E
Компилиране на Rust като библиотека
За да се export-не публично символ, трябва да му се даде фиксирано име
#[no_mangle]
- името на символа е същото като името на функцията#[export_name = ...]
- името на символа е зададеното име
#[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 }
$ nm --dynamic --defined-only target/debug/libdylib_test.so
0000000000006330 T add_from_rust
0000000000006370 T mul_from_rust
Компилиране на Rust като библиотека
Тези атрибути могат да доведат до UB при колизия на имена.
Затова е позволено да се дефинират като #[unsafe(attribute)]
В бъдеща версия ще е задължително.
#[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 код.
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)]
.
Това фиксира наредбата на полетата в реда на дефинирането им.
// на C
struct FooBar {
int foo;
short bar;
};
void foobar(FooBar foobar) {
// ...
}
// на 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
// на 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); }
Споделяне на указатели
- non-null оптимизацията за
Option
е гарантирана
Споделяне на указатели
- non-null оптимизацията за
Option
е гарантирана - опционални референции могат да се използват във външни функции, ако от другата страна стои указател
Споделяне на указатели
- non-null оптимизацията за
Option
е гарантирана - опционални референции могат да се използват във външни функции, ако от другата страна стои указател
Option<&Foo>
има същия layout катоFoo*
в C&Foo
има същия layout катоFoo*
в C
Споделяне на указатели
- non-null оптимизацията за
Option
е гарантирана - опционални референции могат да се използват във външни функции, ако от другата страна стои указател
Option<&Foo>
има същия layout катоFoo*
в C&Foo
има същия layout катоFoo*
в COption<extern "C" fn()>
има същия layout като function pointer в C (void(*)()
)extern "C" fn()
има същия layout като function pointer в C (void(*)()
)
Интеграция със C++ библиотеки
- https://docs.rs/cxx/latest/cxx/
- Safe interop between Rust and C++
Интеграция със C++ библиотеки
- https://docs.rs/cxx/latest/cxx/
- Safe interop between Rust and C++
- opinianated и за съжаление недовършена
Интеграция със C++ библиотеки
- https://docs.rs/cxx/latest/cxx/
- Safe interop between Rust and C++
- opinianated и за съжаление недовършена
- каквото е имплементирано е мощно, но няма много опции за mock-ване на липсваща функционалност
Интеграция със C++ библиотеки
- https://docs.rs/cxx/latest/cxx/
- Safe interop between Rust and C++
- opinianated и за съжаление недовършена
- каквото е имплементирано е мощно, но няма много опции за mock-ване на липсваща функционалност
- ако ви върши работа е супер, иначе …