Unsafe Rust and FFI
13 декември 2022
Unsafe Rust
Разглеждали сме малко unsafe Rust в минали лекции за указатели.
Unsafe Rust
Силната страна на Rust са статичните гаранции за поведението на програмата.
Unsafe Rust
Силната страна на Rust са статичните гаранции за поведението на програмата.
Но тези проверки са консервативни и съществуват програми, които са 'safe', но компилаторът не може да ги верифицира. За да може да пишем такива програми се налага да кажем на компилатора да смекчи ограниченията си.
Unsafe Rust
Силната страна на Rust са статичните гаранции за поведението на програмата.
Но тези проверки са консервативни и съществуват програми, които са 'safe', но компилаторът не може да ги верифицира. За да може да пишем такива програми се налага да кажем на компилатора да смекчи ограниченията си.
За тази цел в Rust има ключовата дума unsafe
, която премахва някои ограничения на компилатора.
Unsafe Rust
Блокове, маркирани с unsafe
.
unsafe {
// Scary stuff...
}
Unsafe Rust
Блокове, маркирани с unsafe
.
unsafe {
// Scary stuff...
}
Това е може би най-често срещаното използване на unsafe
.
Unsafe Rust
unsafe
типажи.
unsafe trait Scary { ... }
Unsafe Rust
Вече сме ги виждали впрочем, най-често използваните са
pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }
Unsafe Rust
Както знаем вече, те се имплементират автоматично върху типове, които компилатора сметне за подходящи.
Но специално Send
и Sync
може да ги деимплементираме с !
, когато не искаме да се имплементират за типа ни.
impl<T> !Sync for Rc<T> where T: ?Sized
Unsafe Rust
Понякога може да имаме обратното - Send
или Sync
да не са имплементирани за наш тип, но всъщност да е правилно да бъдат имплементирани.
Unsafe Rust
Понякога може да имаме обратното - Send
или Sync
да не са имплементирани за наш тип, но всъщност да е правилно да бъдат имплементирани.
Тогава може да си ги имплементираме сами, но това е unsafe
, защото има шанс да нарушим гаранциите на компилатора.
В този случай трябва сами да гарантираме, че семантиките, които репрезентират типажите, са спазени.
Unsafe Rust
Тогава може да ги имплементираме по следния начин.
unsafe impl Scary for i32 { ... }
Unsafe Rust
Ако програмата ни segfault-ва, може да сме сигурни, че това се случва в unsafe
код.
Unsafe Rust
Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…
Unsafe Rust
Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…
- Deadlocks
Unsafe Rust
Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…
- Deadlocks
- Leaks of memory or other resources
Unsafe Rust
Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…
- Deadlocks
- Leaks of memory or other resources
- Exiting without calling destructors
Unsafe Rust
Има неща които не искаме да се случват в програмата ни, но компилаторът не проверява за тях по една или друга причина…
- Deadlocks
- Leaks of memory or other resources
- Exiting without calling destructors
- Integer overflow
Unsafe Rust
…има и неща които може да направим в unsafe
код, но е добре да ги избягваме.
- Data races
- Dereferencing a NULL/dangling raw pointer
- Reads of undef (uninitialized) memory
- Breaking the pointer aliasing rules with raw pointers.
- Mutating an immutable value/reference without
UnsafeCell<U>
- Invoking undefined behavior via compiler intrinsics
- Invalid values in primitive types, even in private fields/locals
- Unwinding into Rust from foreign code or unwinding from Rust into foreign code.
Unsafe Rust
Чрез unsafe
, Rust ни предоставя точно 5 неща които не можем да правим при нормални обстоятелства:
- Четене и писане в static mutable променливи.
- Дереференциране на голи указатели.
- Извикване на
unsafe
функции. - Имплементиране на
unsafe
типажи. - Достъп до полетата на
union
.
Unsafe Rust
Чрез unsafe
, Rust ни предоставя точно 5 неща които не можем да правим при нормални обстоятелства:
- Четене и писане в static mutable променливи.
- Дереференциране на голи указатели.
- Извикване на
unsafe
функции. - Имплементиране на
unsafe
типажи. - Достъп до полетата на
union
.
Когато използватеunsafe
, няма да изключите правила на компилатора като borrow checker.
Union
Синтаксисът е сходен с този на структурите, но полетата на union-ите споделят една и съща памет.
union U {
a: u32,
b: bool,
}
union U { a: u32, b: bool, } fn main() {}
Unsafe Rust
Всички функции които използваме чрез FFI трябва да се маркират като unsafe
.
unsafe fn danger_will_robinson() {
// Scary stuff...
}
Unsafe Rust
FFI
Какво означава FFI?
Unsafe Rust
FFI
Какво означава FFI?
Foreign Function Interface
FFI
За какво се ползва?
FFI
За какво се ползва?
За извикване на функции, които не са написани в нашият код.
Това са най-често функции в библиотеки (.lib/.а
, .dll/.so
и т.н.), върху които нямаме контрол.
Викане на C функции от Rust
int add_in_c(int a, int b) {
return a + b;
}
use std::os::raw::c_int;
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
Викане на C функции от Rust
extern
Викане на C функции от Rust
extern
- extern блок дефинира символи (функции или глобални променливи) към които ще се линква
Викане на C функции от Rust
extern
- extern блок дефинира символи (функции или глобални променливи) към които ще се линква
- типа на дефинираните функции е
extern "<abi>" unsafe fn(...) -> ...
Викане на C функции от Rust
extern
- extern блок дефинира символи (функции или глобални променливи) към които ще се линква
- типа на дефинираните функции е
extern "<abi>" unsafe fn(...) -> ...
- add_in_c:
extern "C" unsafe fn(c_int, c_int) -> c_int
Викане на C функции от Rust
extern
Външни функции са unsafe, защото компилаторът не може да гарантира, че работят правилно.
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
fn main() {
let res = unsafe { add_in_c(1, 2) };
println!("{}", res);
}
Викане на C функции от Rust
Calling convention
Calling conventions се задават на extern блока. По подразбиране е "C"
.
extern "C" {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
extern "system" {
fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> c_int;
}
Викане на C функции от Rust
Calling convention
За какво служи конвенцията на извикване на функции?
Викане на C функции от Rust
Calling convention
За какво служи конвенцията на извикване на функции?
За да уеднакви правилата които трябва да спазват извикващата и извиканата функция, така че да няма разминаване при подаване на параметри и връщане на стойност.
Викане на C функции от Rust
Calling convention
Ако нямаше конвенция, извикването на функции щеше да е хаос.
Най-общо може да сведем правилата, които конвенцията определя до:
Викане на C функции от Rust
Calling convention
Ако нямаше конвенция, извикването на функции щеше да е хаос.
Най-общо може да сведем правилата, които конвенцията определя до:
- как се подават параметрите (в регистри, на стека или и двете)
Викане на C функции от Rust
Calling convention
Ако нямаше конвенция, извикването на функции щеше да е хаос.
Най-общо може да сведем правилата, които конвенцията определя до:
- как се подават параметрите (в регистри, на стека или и двете)
- реда в който се алокират променливите
Викане на C функции от Rust
Calling convention
Ако нямаше конвенция, извикването на функции щеше да е хаос.
Най-общо може да сведем правилата, които конвенцията определя до:
- как се подават параметрите (в регистри, на стека или и двете)
- реда в който се алокират променливите
- по какъв начин се връщат стойности от извиканата функция
Викане на C функции от Rust
Calling convention
Ако нямаше конвенция, извикването на функции щеше да е хаос.
Най-общо може да сведем правилата, които конвенцията определя до:
- как се подават параметрите (в регистри, на стека или и двете)
- реда в който се алокират променливите
- по какъв начин се връщат стойности от извиканата функция
- регистрите, които извиканата функция трябва да запази (callee-saved registers)
Викане на C функции от Rust
Calling convention
Ако нямаше конвенция, извикването на функции щеше да е хаос.
Най-общо може да сведем правилата, които конвенцията определя до:
- как се подават параметрите (в регистри, на стека или и двете)
- реда в който се алокират променливите
- по какъв начин се връщат стойности от извиканата функция
- регистрите, които извиканата функция трябва да запази (callee-saved registers)
- разпределението на подготовката на стека и зачистването му между извикващата и извиканата функция
Викане на C функции от Rust
Calling convention
За любопитните, разпространени x86 конвенции са
cdecl
pascal
fastcall
,thiscall
,stdcall
,vectorcall
- WindowsMicrosoft x64 calling convention
- WindowsSystem V AMD64 ABI
- Unix и Unix-like операционни системи
Викане на C функции от Rust
Calling convention
Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.
Викане на C функции от Rust
Calling convention
Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.
"Rust"
- каквото rustc използва за нормалните функции. Не се използва при FFI.
Викане на C функции от Rust
Calling convention
Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.
"Rust"
- каквото rustc използва за нормалните функции. Не се използва при FFI."C"
- каквото C компилаторът използва по подразбиране. Това почти винаги е правилното, освен ако не е казано друго изрично или библиотеката е компилирана с друг компилатор.
Викане на C функции от Rust
Calling convention
Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката.
"Rust"
- каквото rustc използва за нормалните функции. Не се използва при FFI."C"
- каквото C компилаторът използва по подразбиране. Това почти винаги е правилното, освен ако не е казано друго изрично или библиотеката е компилирана с друг компилатор."system"
- използва се за системни функции (на Windows). Или просто използвайте crate-а winapi.
Викане на C функции от Rust
Нека да пробваме да компилираме
use std::os::raw::c_int;
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
fn main() {
let res = unsafe {
add_in_c(1, 2)
};
println!("{}", res);
}
error: linking with `cc` failed: exit status: 1 | = note: "cc" "-m64" "/tmp/rustc1Howw8/symbols.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.2aezuftndu01cu8m.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.329yrj1mcssjdem5.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.3hdc4c1u78mig5ds.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.4h8glayev5bpfd9g.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.4uz0wvliwhjnnop.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.5485ufl2w968ozg1.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.58tstedgl1ghxxrc.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.2f9m9fz2mpp8p4cg.rcgu.o" "-Wl,--as-needed" "-L" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-05737cf45bd30456.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-9f873b61fdec9b03.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-7f13930fcac1846f.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-098633b847612f3b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-f14b73d282b0245e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-2c5b4433ebc1d822.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-59591a7b405fe395.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-384947c6d5f697ff.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-b08a86c6880b47a8.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-58adeee671f9ba8e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-f156b880fc73e7f0.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-4458c5022988e1ab.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-02e61e5ec4aa9e8b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-a0d9b33b5161957b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-04cec55a79224c36.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-3fb6d8496dc7d6a6.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-7d46c016841a97d4.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-a1f7b8b60464cc57.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-272ca28f0b8538d5.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-znoexecstack" "-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_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro,-znow" "-nodefaultlibs" = note: /usr/bin/ld: /home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_df8618577ecb652a8a0d873977025ed4a2695877-6c177eb70788fc37.58tstedgl1ghxxrc.rcgu.o: in function `main_df8618577ecb652a8a0d873977025ed4a2695877::main': /main_df8618577ecb652a8a0d873977025ed4a2695877.rs:9: undefined reference to `add_in_c' collect2: error: ld returned 1 exit status = help: 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#cargorustc-link-libkindname) error: could not compile `rust` due to previous error
use std::os::raw::c_int; extern { fn add_in_c(a: c_int, b: c_int) -> c_int; } fn main() { let res = unsafe { add_in_c(1, 2) }; println!("{}", res); }
Очаквано, не сме казали на компилатора къде да намери функцията
Linking
Ръчният начин
Static linking
компилираме C кода до math.lib / libmath.a
cargo rustc -- -L . -l math
# или
cargo rustc -- -L . -l static=math
Dynamic linking
компилираме C кода до math.dll / libmath.so
cargo rustc -- -L . -l math
# или
cargo rustc -- -L . -l dylib=math
Linking
Правилният начин
#[link(name="math")]
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
Linking
Правилният начин
#[link(name="math")]
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
#[link(name="math")]
- линква към динамичната библиотекаmath
Linking
Правилният начин
#[link(name="math")]
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
#[link(name="math")]
- линква към динамичната библиотекаmath
#[link(name="math", kind="static")]
- линква към статичната библиотекаmath
Linking
Правилният начин
#[link(name="math")]
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
#[link(name="math")]
- линква към динамичната библиотекаmath
#[link(name="math", kind="static")]
- линква към статичната библиотекаmath
#[link(name="math", kind="framework")]
- линква към MacOS frameworkmath
Linking
Правилният начин
Linking
Правилният начин
- Обикновено динамичните библиотеки се намират в някоя стандартна директория (зависи от операционната система, environment variables и конфигурационни файлове)
Linking
Правилният начин
- Обикновено динамичните библиотеки се намират в някоя стандартна директория (зависи от операционната система, environment variables и конфигурационни файлове)
- Тогава
#[link(name="library_name")]
е достатъчно
Linking
Правилният начин
- Обикновено динамичните библиотеки се намират в някоя стандартна директория (зависи от операционната система, environment variables и конфигурационни файлове)
- Тогава
#[link(name="library_name")]
е достатъчно - За статична библиотека все още трябва да кажем на компилатора къде да я намери
cargo rustc -- -L .
Linking
Странности
Няма значение на кой блок е поставен #[link(...)]
атрибутът.
#[link(name="foo")]
#[link(name="bar")]
extern {}
extern {
fn foo_init();
fn foo_stuff(x: c_int);
}
extern {
fn bar_init();
fn bar_stuff() -> c_int;
}
Build scripts
Cargo предоставя възможност за изпълняване на скрипт преди компилиране на crate-a.
Използва се при FFI, ако искаме сами да си компилираме C кода и да укажем как да се линкнем към него.
Build scripts
[package]
name = "ffi"
version = "0.1.0"
authors = ["..."]
build = "build.rs"
// build.rs
fn main() {
...
}
Build scripts
Build scripts
- build.rs няма достъп до нормалните dependencies
Build scripts
- build.rs няма достъп до нормалните dependencies
- вместо това използва build-dependencies
[build-dependencies]
...
Build scripts
- cargo прихваща stdout на build скрипта
Build scripts
- cargo прихваща stdout на build скрипта
- всеки ред който започва с
cargo:
се разбира като команда
Build scripts
- cargo прихваща stdout на build скрипта
- всеки ред който започва с
cargo:
се разбира като команда - форматът е
cargo:key=value
Build scripts
- cargo прихваща stdout на build скрипта
- всеки ред който започва с
cargo:
се разбира като команда - форматът е
cargo:key=value
cargo:rustc-link-search=.
Build scripts
// build.rs
fn main() {
println!("cargo:rustc-link-search=.");
}
// build.rs fn main() { println!("cargo:rustc-link-search=."); }
Callbacks
// main.c
typedef int (*callback)(int);
int apply(int a, callback op) {
return op(a);
}
// main.rs
#[link(name="math")]
extern "C" {
fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int;
}
fn cube(x: i32) -> i32 { x * x * x }
fn main() {
println!("{}", unsafe { apply(11, cube) });
}
Callbacks
// main.c
typedef int (*callback)(int);
int apply(int a, callback op) {
return op(a);
}
// main.rs
#[link(name="math")]
extern "C" {
fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int;
}
fn cube(x: i32) -> i32 { x * x * x }
fn main() {
println!("{}", unsafe { apply(11, cube) });
}
warning: `extern` block uses type `fn(i32) -> i32`, which is not FFI-safe --> src/bin/main_00831ccc8c58c48de66cefd63726757c274b7239.rs:6:28 | 6 | fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int; | ^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: `#[warn(improper_ctypes)]` on by default = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention error: linking with `cc` failed: exit status: 1 | = note: "cc" "-m64" "/tmp/rustciDqDON/symbols.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.11batfs7n202lm8w.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.1ju26kuvdljyw6g7.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.1l5d7buc69nn894f.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.1xhae7fjenbdce0r.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.414rtkjfrp41iyj1.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.4oh0iml7mm38tmja.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.pfdxr6gnqhbq3pk.rcgu.o" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps/main_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323.5b4be173w1v4szel.rcgu.o" "-Wl,--as-needed" "-L" "/home/andrew/projects/rust-secrets/lectures/slides/output/resources/rustc/target/debug/deps" "-L" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-lmath" "-Wl,-Bstatic" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-05737cf45bd30456.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-9f873b61fdec9b03.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-7f13930fcac1846f.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-098633b847612f3b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-f14b73d282b0245e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-2c5b4433ebc1d822.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-59591a7b405fe395.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-384947c6d5f697ff.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-b08a86c6880b47a8.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-58adeee671f9ba8e.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-f156b880fc73e7f0.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-4458c5022988e1ab.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-02e61e5ec4aa9e8b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-a0d9b33b5161957b.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-04cec55a79224c36.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-3fb6d8496dc7d6a6.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-7d46c016841a97d4.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-a1f7b8b60464cc57.rlib" "/home/andrew/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-272ca28f0b8538d5.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-znoexecstack" "-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_00831ccc8c58c48de66cefd63726757c274b7239-8a9303576a4fb323" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro,-znow" "-nodefaultlibs" = note: /usr/bin/ld: cannot find -lmath: No such file or directory collect2: error: ld returned 1 exit status error: could not compile `rust` due to previous error; 1 warning emitted
use std::os::raw::c_int; #[link(name="math")] extern "C" { fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int; } fn cube(x: i32) -> i32 { x * x * x } fn main() { println!("{}", unsafe { apply(11, cube) }); }
Callbacks
Kомпилаторът ни подсказва:
Callbacks
Kомпилаторът ни подсказва:
apply
очакваint (*callback)(int)
, т.е.extern "C" fn(c_int) -> c_int
Callbacks
Kомпилаторът ни подсказва:
apply
очакваint (*callback)(int)
, т.е.extern "C" fn(c_int) -> c_int
- ние му подаваме
fn(c_int) -> c_int
, т.е.extern "Rust" fn(c_int) -> c_int
Callbacks
// main.rs
#[link(name="math")]
extern "C" {
fn apply(a: c_int, op: extern fn(c_int) -> c_int) -> c_int;
}
extern fn cube(x: i32) -> i32 { x * x * x }
fn main() {
println!("{}", unsafe { apply(11, cube) });
}
Panics
A panic! across an FFI boundary is undefined behavior.
Когато подаваме или експортираме rust функции трябва да се подсигурим, че те не могат да се панират. В тази ситуация е удобно да се използва catch_unwind
.
use std::panic::catch_unwind;
extern fn oh_no() -> i32 {
let result = catch_unwind(|| {
panic!("Oops!");
});
match result {
Ok(_) => 0,
Err(_) => 1,
}
}
fn main() {} use std::panic::catch_unwind; extern fn oh_no() -> i32 { let result = catch_unwind(|| { panic!("Oops!"); }); match result { Ok(_) => 0, Err(_) => 1, } }
Panics
NB! Не го използвайте за generic try/catch block!!
Не е гарантирано, че ще хване всички паници, защото някои от тях са abort.
Exports
За експортиране на функции към C или друг език се налага да използваме #[no_mangle]
, за да предотвратим промяна на името в генерираните символи на библиотеката.
#[no_mangle]
extern fn call_me_from_c() {
}
#[no_mangle] extern fn call_me_from_c() { } fn main() {}
Други неща
Variadic functions
#![feature(c_variadic)]
pub unsafe extern "C" fn add(n: usize, mut args: ...) -> usize {
let mut sum = 0;
for _ in 0..n {
sum += args.arg::<usize>();
}
sum
}
Importing global variables
extern void* magic;
use std::os::raw::c_void;
extern {
#[no_mangle]
static magic: *const c_void;
}
Writing wrappers
Много често е удобно да напишем "rusty" интерфейс към библиотеката
use libc::{c_int, c_size_t};
#[link(name="math")]
extern {
fn math_array_sum(arr: *const c_int, len: c_size_t) -> c_int;
}
/// Safe wrapper
pub fn array_sum(arr: &[c_int]) -> c_int {
unsafe { math_array_sum(arr.as_ptr(), arr.len()) }
}
Writing wrappers
Writing wrappers
- Конвертиране на кодове за грешки до
Option
илиResult
Writing wrappers
- Конвертиране на кодове за грешки до
Option
илиResult
- Методи
Writing wrappers
- Конвертиране на кодове за грешки до
Option
илиResult
- Методи
- Деструктори
Споделяне на структури
Структурите в rust нямат определено подреждане на полетата.
struct FooBar {
int foo;
short bar;
};
void foobar(FooBar x) {
// ...
}
struct FooBar {
foo: c_int,
bar: c_short,
}
extern {
fn foobar(x: FooBar);
}
Споделяне на структури
Структурите в rust нямат определено подреждане на полетата.
struct FooBar {
int foo;
short bar;
};
void foobar(FooBar x) {
// ...
}
struct FooBar {
foo: c_int,
bar: c_short,
}
extern {
fn foobar(x: FooBar);
}
warning: `extern` block uses type `FooBar`, which is not FFI-safe --> src/bin/main_6d02c4ba60eade12e3231c62b2a0c34bf55cf37d.rs:10:18 | 10 | fn foobar(x: FooBar); | ^^^^^^ not FFI-safe | = note: `#[warn(improper_ctypes)]` on by default = 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_6d02c4ba60eade12e3231c62b2a0c34bf55cf37d.rs:4:1 | 4 | struct FooBar { | ^^^^^^^^^^^^^
use std::os::raw::{c_int, c_short}; fn main() {} struct FooBar { foo: c_int, bar: c_short, } extern { fn foobar(x: FooBar); }
Споделяне на структури
За да споделим структура между Rust и C трябва да забраним на компилатора да размества полетата с #[repr(C)]
.
extern {
fn foobar(x: FooBar);
}
#[repr(C)]
struct FooBar {
foo: c_int,
bar: c_short,
}
use std::os::raw::{c_int, c_short}; fn main() {} extern { fn foobar(x: FooBar); } #[repr(C)] struct FooBar { foo: c_int, bar: c_short, }
Споделяне на низове
Споделяне на низове
- Низовете в Rust са utf-8 encoded, нямат терминираща нула и си пазят размера отделно
Споделяне на низове
- Низовете в Rust са utf-8 encoded, нямат терминираща нула и си пазят размера отделно
- Низовете в C могат да имат всякакъв encoding и завършват с терминираща нула
Споделяне на низове
- Низовете в Rust са utf-8 encoded, нямат терминираща нула и си пазят размера отделно
- Низовете в C могат да имат всякакъв encoding и завършват с терминираща нула
- Трябва да конвертираме от единия до другия вид когато изпращаме низове
Споделяне на низове
CString
- Низ със собственост, съвместим със C.
- Не съдържа нулеви байтове
'\0'
във вътрешността и завършва на терминираща нула.
use std::ffi::CString;
// създава се от неща които имплементират Into<Vec<u8>>,
// в това число &str и String
let hello = CString::new("Hello!").unwrap();
unsafe {
print(hello.as_ptr());
}
use std::os::raw::c_char; extern { fn print(s: *const c_char); } use std::ffi::CString; fn main() { // създава се от неща които имплементират Into>, // в това число &str и String let hello = CString::new("Hello!").unwrap(); unsafe { print(hello.as_ptr()); } }
Споделяне на низове
CString
Какъв е проблемът?
extern {
fn print(s: *const c_char);
}
unsafe {
print(CString::new("Hello!").unwrap().as_ptr());
}
warning: getting the inner pointer of a temporary `CString` --> src/bin/main_5e7ecc6a2f055e7fe36ca2bf3ab4a108367d0f0a.rs:10:43 | 10 | print(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: `#[warn(temporary_cstring_as_ptr)]` on by default = 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
use std::os::raw::c_char; use std::ffi::CString; extern { fn print(s: *const c_char); } fn main () { unsafe { print(CString::new("Hello!").unwrap().as_ptr()); } }
Споделяне на низове
CString
Работим с голи указатели, а не с референции. Трябва да се погрижим паметта да живее достатъчно!
let hello = CString::new("Hello!").unwrap();
let ptr = hello.as_ptr(); // `ptr` е валиден докато `hello` е жив
unsafe { print(ptr) }; // Ок
let ptr = CString::new("Hello!").unwrap().as_ptr(); // временния CString се деалокира
unsafe { print(ptr) }; // подаваме dangling pointer
warning: getting the inner pointer of a temporary `CString` --> src/bin/main_d9386e15d088f5ee58864d5e03ea6d1c7a0bce2f.rs:12:43 | 12 | let ptr = CString::new("Hello!").unwrap().as_ptr(); // временния CString се деалокира | ------------------------------- ^^^^^^ 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: `#[warn(temporary_cstring_as_ptr)]` on by default = 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
use std::os::raw::c_char; use std::ffi::CString; extern { fn print(s: *const c_char); } fn main() { let hello = CString::new("Hello!").unwrap(); let ptr = hello.as_ptr(); // `ptr` е валиден докато `hello` е жив unsafe { print(ptr) }; // Ок let ptr = CString::new("Hello!").unwrap().as_ptr(); // временния CString се деалокира unsafe { print(ptr) }; // подаваме dangling pointer }
Споделяне на низове
CStr
Споделяне на низове
CStr
- Borrowed CString
Споделяне на низове
CStr
- Borrowed CString
- удобно ако C код ни даде низ
Option and the "nullable pointer optimization"
typedef int (*callback)(int);
// callback can be a function pointer or NULL
void register(f: callback);
extern "C" {
/// Registers the callback.
fn register(cb: Option<extern "C" fn(c_int) -> c_int>);
}
Opaque types
Често C код използва opaque types.
struct Foo;
Foo* init();
int stuff(Foo* foo);
Opaque types
За да представим такъв тип в rust можем да използваме празен enum
Не можем да създадем променлива от този тип, защото enum-а няма варианти
enum Foo {}
extern {
fn init() -> *const Foo;
fn stuff(foo: *const Foo) -> c_int;
}
use std::os::raw::c_int; fn main() {} enum Foo {} extern { fn init() -> *const Foo; fn stuff(foo: *const Foo) -> c_int; }
Компилиране до библиотека
До сега сме правили библиотеки за Rust чрез cargo new --lib
.
При компилация този вид crate ни дава rlib и има следния Cargo.toml
[package]
name = "project_name"
version = "0.1.0"
authors = ["..."]
[dependencies]
Компилиране до библиотека
Това е достатъчно когато правим crate за Rust екосистемата, но понякога се налага да създадем специфично статична или динамична библиотека.
Компилиране до библиотека
В този случай може да използвате Cargo.toml
, за да укажете това
[package]
name = "project_name"
version = "0.1.0"
[lib]
crate-type = ["..."]
[dependencies]
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binary
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binarylib
- компилатора избира типа на библиотеката
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binarylib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни.rlib
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binarylib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни.rlib
dylib
- динамична Rust библиотека.so
,.dylib
,.dll
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binarylib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни.rlib
dylib
- динамична Rust библиотека.so
,.dylib
,.dll
staticlib
- статична native библиотека.a
,.lib
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binarylib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни.rlib
dylib
- динамична Rust библиотека.so
,.dylib
,.dll
staticlib
- статична native библиотека.a
,.lib
cdylib
- динамична native библиотека предназначена за използване от други езици.so
,.dylib
,.dll
Компилиране до библиотека
На мястото на триеточието може да поставим следните типове:
bin
- binarylib
- компилатора избира типа на библиотекатаrlib
- статична Rust библиотека с метаданни.rlib
dylib
- динамична Rust библиотека.so
,.dylib
,.dll
staticlib
- статична native библиотека.a
,.lib
cdylib
- динамична native библиотека предназначена за използване от други езици.so
,.dylib
,.dll
proc-macro
- процедурен макрос
Компилиране до библиотека
При компилация с cargo файловете се намират в target/${target_type}