From dff419aec824b5cebc1afb104e7292265f00a6bf Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 16 Oct 2024 14:10:51 +0100 Subject: [PATCH] lisa._assets.kmodules.lisa: Add Rust runtime FEATURE Add a basic runtime for Rust code: * Bindings to printk() (pr_info!() and pr_err!() macros). * Bindings to kmalloc/kfree heap-allocated values for interop with the rest of the kernel (runtime::kbox::KBox). The standard Box could be re-used for that, but that would tie us to always returning an unmodified kmalloc() pointer. By separating the 2 uses, we allow the GlobalAllocator instance Box uses to allocate extra memory to satisfy stronger alignments. * Binding of the panic handler to kernel panic. * Inline C function: this allows writing C function inside Rust code for easy interop with the C world: #[cfunc] fn alloc(size: usize) -> *mut u8 { "#include "; "return kmalloc(size, GFP_KERNEL);" } The function is annotated with #[cfunc], and the body is made of: * An optional first string that will be written before the C function definition. This is a good place for includes. * A mandatory second string with the C code. This prevents any error-prone function prototype duplication and keeps small helpers close to the Rust code. --- lisa/_assets/kmodules/lisa/Makefile | 36 ++- lisa/_assets/kmodules/lisa/main.c | 4 +- lisa/_assets/kmodules/lisa/rust/cbindgen.h | 29 -- .../lisa/rust/{ => lisakmod}/Cargo.toml | 2 + .../kmodules/lisa/rust/lisakmod/cbindgen.h | 11 + .../lisa/rust/{ => lisakmod}/cbindgen.toml | 2 +- .../kmodules/lisa/rust/lisakmod/runtime.c | 8 + .../kmodules/lisa/rust/lisakmod/rust.lds | 7 + .../{ => lisakmod}/rustc_targets/README.md | 0 .../rustc_targets/arm64/target.json | 0 .../rustc_targets/x86_64/target.json | 0 .../lisa/rust/lisakmod/src/inlinec.rs | 20 ++ .../lisa/rust/{ => lisakmod}/src/lib.rs | 6 +- .../lisa/rust/lisakmod/src/prelude.rs | 9 + .../lisa/rust/lisakmod/src/runtime/alloc.rs | 105 ++++++ .../lisa/rust/lisakmod/src/runtime/kbox.rs | 184 +++++++++++ .../lisa/rust/lisakmod/src/runtime/mod.rs | 6 + .../lisa/rust/lisakmod/src/runtime/panic.rs | 87 +++++ .../lisa/rust/lisakmod/src/runtime/printk.rs | 101 ++++++ .../kmodules/lisa/rust/lisakmod/src/tests.rs | 154 +++++++++ .../kmodules/lisa/rust/lisakmod/tests.c | 17 + .../rust/{validate.h => lisakmod/tests.h} | 4 +- .../proc_macros/lisakmodprocmacro/Cargo.toml | 8 + .../proc_macros/lisakmodprocmacro/src/lib.rs | 6 + .../lisakmodprocmacro/src/private/inlinec.rs | 188 +++++++++++ .../lisakmodprocmacro/src/private/mod.rs | 5 + .../lisakmodprocmacro/tests/inlinec.rs | 45 +++ .../lisakmodprocmacro_macro/Cargo.toml | 13 + .../lisakmodprocmacro_macro/src/inlinec.rs | 300 ++++++++++++++++++ .../lisakmodprocmacro_macro/src/lib.rs | 11 + lisa/_assets/kmodules/lisa/rust/runtime.c | 47 --- lisa/_assets/kmodules/lisa/rust/rust.lds | 2 - .../_assets/kmodules/lisa/rust/src/runtime.rs | 88 ----- .../kmodules/lisa/rust/src/validate.rs | 35 -- lisa/_assets/kmodules/lisa/rust/validate.c | 35 -- tools/tests.sh | 25 +- 36 files changed, 1336 insertions(+), 264 deletions(-) delete mode 100644 lisa/_assets/kmodules/lisa/rust/cbindgen.h rename lisa/_assets/kmodules/lisa/rust/{ => lisakmod}/Cargo.toml (73%) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h rename lisa/_assets/kmodules/lisa/rust/{ => lisakmod}/cbindgen.toml (99%) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds rename lisa/_assets/kmodules/lisa/rust/{ => lisakmod}/rustc_targets/README.md (100%) rename lisa/_assets/kmodules/lisa/rust/{ => lisakmod}/rustc_targets/arm64/target.json (100%) rename lisa/_assets/kmodules/lisa/rust/{ => lisakmod}/rustc_targets/x86_64/target.json (100%) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs rename lisa/_assets/kmodules/lisa/rust/{ => lisakmod}/src/lib.rs (68%) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c rename lisa/_assets/kmodules/lisa/rust/{validate.h => lisakmod/tests.h} (66%) create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs delete mode 100644 lisa/_assets/kmodules/lisa/rust/runtime.c delete mode 100644 lisa/_assets/kmodules/lisa/rust/rust.lds delete mode 100644 lisa/_assets/kmodules/lisa/rust/src/runtime.rs delete mode 100644 lisa/_assets/kmodules/lisa/rust/src/validate.rs delete mode 100644 lisa/_assets/kmodules/lisa/rust/validate.c diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index e5de3a395..11693a284 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -71,12 +71,14 @@ clean-files := $(GENERATED) RUST_VERSION ?= nightly RUSTFLAGS := -Clinker=rust-lld -Clink-self-contained=y -Clto=n -Crelocation-model=static -Cno-redzone=y -Csoft-float=y -Ctarget-cpu=generic -Cforce-frame-pointers=y -Ccodegen-units=1 -Zfunction-sections=y -RUST_SRC := $(MODULE_SRC)/rust +RUST_SRC := $(MODULE_SRC)/rust/lisakmod RUST_LDS := $(RUST_SRC)/rust.lds RUST_GENERATED := $(GENERATED)/rust RUST_OBJECT := $(RUST_GENERATED)/rust.o RUST_OBJECT_CMD := $(RUST_GENERATED)/.rust.o.cmd -RUST_CBINDGEN_HEADER := $(RUST_SRC)/cbindgen.h +RUST_CBINDGEN_H := $(RUST_SRC)/cbindgen.h +RUST_C_SHIMS_H := $(RUST_GENERATED)/rust_c_shims.h +RUST_C_SHIMS_DIR := $(RUST_GENERATED)/rust_c_shims RUSTUP_HOME ?= $(HOME)/.rustup CARGO_HOME ?= $(HOME)/.cargo @@ -104,22 +106,36 @@ $(RUST_TARGET_DIR): $(RUST_BUILD_DIR) # Build the rust code into a static archive, then prelink it into an object # file and filter the symbols to only keep as GLOBAL the exported pub # #[no_mangle] ones. This avoids having tons of GLOBAL symbols from core lib. -$(RUST_OBJECT): $(RUST_TARGET_DIR) +$(RUST_OBJECT) $(RUST_C_SHIMS_H): $(RUST_TARGET_DIR) # Build the crate into a static library cd $(RUST_SRC) && export RUSTFLAGS="$(RUSTFLAGS)" && $(call cargo_cmd,build --target-dir $(RUST_TARGET_DIR) --release --target=$(RUST_SRC)/rustc_targets/$(ARCH)/target.json -Zbuild-std=core$(comma)alloc) # Generate the documentation JSON that we will use to extract symbols to keep - cd $(RUST_SRC) && $(call cargo_cmd,rustdoc --target-dir $(RUST_TARGET_DIR) -- -Zunstable-options --output-format json) + cd $(RUST_SRC) && $(call cargo_cmd,rustdoc --release --target-dir $(RUST_TARGET_DIR) -- -Zunstable-options --output-format json) # Get the list of exported symbols (pub with #[no_mangle] attribute) from the rustdoc JSON - python3 "$(MODULE_SRC)/rustdoc_symbols.py" --rustdoc-json $(RUST_TARGET_DIR)/doc/lisakmod.json --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) + python3 "$(MODULE_SRC)/rustdoc_symbols.py" --rustdoc-json $(RUST_TARGET_DIR)/doc/lisakmod.json --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) cat $(RUST_SYMBOLS) # Prelink the archive into a single object file. - $(LD) --version + $(LD) $(KBUILD_LDFLAGS) -nostdlib -r -o $(RUST_OBJECT) --whole-archive $(RUST_TARGET_DIR)/target/release/liblisakmod.a + + # Extract the C shims created by our inlinec Rust module from the + # binary, so they can be compiled and linked later. + mkdir -p "$(RUST_C_SHIMS_DIR)" + touch $(RUST_C_SHIMS_DIR)/empty + $(OBJDUMP) -h $(RUST_OBJECT) | awk '{ print $$2 }' | grep '^.binstore.c_shims.' | xargs -n1 sh -c '$(OBJCOPY) --dump-section=$$0=$(RUST_C_SHIMS_DIR)/shim_$$0 $(RUST_OBJECT)' + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/* > $(RUST_C_SHIMS_H) + cat $(RUST_C_SHIMS_H) | if which clang-format 2>&1 1>/dev/null; then clang-format; else cat; fi + + # Garbage collect unused sections in the object file, after we have + # extracted the binstore sections (otherwise they get discarded). + # # Use --undefined CLI parameters in $(RUST_SYMBOLS_CLI) instead of # EXTERN() command in a linker script since older GNU ld version seem # to ignore the EXTERN() command with --gc-sections. - $(LD) $(KBUILD_LDFLAGS) $$(cat $(RUST_SYMBOLS_CLI)) --gc-sections -nostdlib -r -o $(RUST_OBJECT) --whole-archive $(RUST_TARGET_DIR)/target/release/liblisakmod.a + $(LD) --version + $(LD) $(KBUILD_LDFLAGS) $$(cat $(RUST_SYMBOLS_CLI)) --gc-sections -nostdlib -r -o $(RUST_OBJECT)_gced $(RUST_OBJECT) + mv $(RUST_OBJECT)_gced $(RUST_OBJECT) # Only keep as GLOBAL symbols the ones that are to be exported (and the # undefined ones to be provided by C code) @@ -143,7 +159,7 @@ $(RUST_CBINDGEN_BIN): $(CARGO_HOME) # This is for the benefit of the user and CI to generate the file to be # committed if it does not exist yet. refresh-rust-bindings: $(RUST_CBINDGEN_BIN) - cd $(RUST_SRC) && $(call rust_cmd,cbindgen --config $(RUST_SRC)/cbindgen.toml --lang c --crate lisakmod --output $(RUST_CBINDGEN_HEADER)) + cd $(RUST_SRC) && $(call rust_cmd,cbindgen --config $(RUST_SRC)/cbindgen.toml --lang c --crate lisakmod --output $(RUST_CBINDGEN_H)) $(RUST_SYMBOLS): $(RUST_OBJECT) @@ -153,7 +169,7 @@ ifneq ($(KERNELRELEASE),) LISA_KMOD_NAME ?= lisa obj-m := $(LISA_KMOD_NAME).o -$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o rust/runtime.o rust/validate.o generated/rust/rust.o +$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o rust/lisakmod/runtime.o rust/lisakmod/tests.o generated/rust/rust.o # -fno-stack-protector is needed to possibly undefined __stack_chk_guard symbol ccflags-y := "-I$(MODULE_SRC)" -std=gnu11 -fno-stack-protector -Wno-declaration-after-statement -Wno-error @@ -247,7 +263,7 @@ $(KALLSYMS): $(GENERATED) cat $(MODULE_SRC)/kallsyms > $@ || $(NM) $(_REAL_VMLINUX) > $@ || touch $@ # Make all object files depend on the generated sources -$(addprefix $(MODULE_OBJ)/,$($(LISA_KMOD_NAME)-y)): $(INTROSPECTION_DATA_H) $(SYMBOL_NAMESPACES_H) $(MODULE_VERSION_H) $(SYMBOLS_LDS) $(RUST_CBINDGEN_HEADER) +$(addprefix $(MODULE_OBJ)/,$($(LISA_KMOD_NAME)-y)): $(INTROSPECTION_DATA_H) $(SYMBOL_NAMESPACES_H) $(MODULE_VERSION_H) $(SYMBOLS_LDS) $(RUST_CBINDGEN_H) $(RUST_C_SHIMS_H) # Non-Kbuild part else diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index 4054ef1b5..ba99fc7b6 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -4,7 +4,7 @@ #include "main.h" #include "features.h" #include "introspection.h" -#include "rust/validate.h" +#include "rust/lisakmod/tests.h" #include "generated/module_version.h" /* Import all the symbol namespaces that appear to be defined in the kernel * sources so that we won't trigger any warning @@ -34,7 +34,7 @@ static int __init modinit(void) { return -EPROTO; } - ret = rust_validate(); + ret = rust_tests(); if (ret) { pr_err("Lisa module Rust support validation failed: %i\n", ret); return -EINVAL; diff --git a/lisa/_assets/kmodules/lisa/rust/cbindgen.h b/lisa/_assets/kmodules/lisa/rust/cbindgen.h deleted file mode 100644 index 19f5b9d4b..000000000 --- a/lisa/_assets/kmodules/lisa/rust/cbindgen.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/* Generated with cbindgen:0.27.0 */ - -/* Warning, this file is autogenerated by Rust cbindgen. Don't modify this manually. */ - - - -extern uint8_t *__lisa_rust_alloc(size_t size); - -extern uint8_t *__lisa_rust_alloc_zeroed(size_t size); - -extern void __lisa_rust_dealloc(uint8_t *ptr); - -extern void __lisa_rust_panic(const uint8_t *msg, size_t len); - -extern void __lisa_rust_pr_info(const uint8_t *msg, size_t len); - -extern uint8_t *__lisa_rust_realloc(uint8_t *ptr, size_t size); - -extern uint64_t myc_callback(uint64_t x); - - uint64_t test_1(uint64_t left, uint64_t right) ; - - uint64_t test_2(uint64_t left, uint64_t right) ; - - uint64_t test_3(uint64_t left, uint64_t right) ; - - diff --git a/lisa/_assets/kmodules/lisa/rust/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml similarity index 73% rename from lisa/_assets/kmodules/lisa/rust/Cargo.toml rename to lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml index 4e53266d5..dccabab03 100644 --- a/lisa/_assets/kmodules/lisa/rust/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml @@ -8,6 +8,8 @@ crate-type = ["staticlib"] [dependencies] # hashbrown = "0.15" +lisakmodprocmacro = { path = "../proc_macros/lisakmodprocmacro" } +paste = "1.0" [profile.release] panic = 'abort' diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h b/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h new file mode 100644 index 000000000..0027b64af --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Generated with cbindgen:0.27.0 */ + +/* Warning, this file is autogenerated by Rust cbindgen. Don't modify this manually. */ + + +extern uint64_t myc_callback(uint64_t x); + +uint64_t do_rust_tests(void) ; + diff --git a/lisa/_assets/kmodules/lisa/rust/cbindgen.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.toml similarity index 99% rename from lisa/_assets/kmodules/lisa/rust/cbindgen.toml rename to lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.toml index 5ef56d61b..7bac36e5f 100644 --- a/lisa/_assets/kmodules/lisa/rust/cbindgen.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.toml @@ -192,7 +192,7 @@ prefix = "" # * "functions": # # default: [] -item_types = [] +item_types = ["enums", "structs", "unions", "typedefs", "opaque", "functions"] # Whether applying rules in export.rename prevents export.prefix from applying. # diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c b/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c new file mode 100644 index 000000000..4b07c98ae --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "main.h" +#include "rust/lisakmod/cbindgen.h" + +/* Basic glue between Rust runtime and kernel functions */ + +/* C shims generated by the lisakmodprocmacro::inlinec module */ +#include "generated/rust/rust_c_shims.h" diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds b/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds new file mode 100644 index 000000000..cda52d315 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds @@ -0,0 +1,7 @@ +/* Workaround this issue: https://github.com/rust-lang/rust/issues/125619 */ +PROVIDE(__builtin_copysignq = 0); + +SECTIONS { + /* Remove binstore sections that we created in lisakmodprocmacro::inlinec Rust module */ + /DISCARD/ : { *(.binstore.*) } +} diff --git a/lisa/_assets/kmodules/lisa/rust/rustc_targets/README.md b/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/README.md similarity index 100% rename from lisa/_assets/kmodules/lisa/rust/rustc_targets/README.md rename to lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/README.md diff --git a/lisa/_assets/kmodules/lisa/rust/rustc_targets/arm64/target.json b/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/arm64/target.json similarity index 100% rename from lisa/_assets/kmodules/lisa/rust/rustc_targets/arm64/target.json rename to lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/arm64/target.json diff --git a/lisa/_assets/kmodules/lisa/rust/rustc_targets/x86_64/target.json b/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/x86_64/target.json similarity index 100% rename from lisa/_assets/kmodules/lisa/rust/rustc_targets/x86_64/target.json rename to lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/x86_64/target.json diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs new file mode 100644 index 000000000..23ed47c85 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +pub use lisakmodprocmacro::cfunc; + +macro_rules! get_c_macro { + ($header:expr, $macro:ident, $ty:ty) => {{ + ::paste::paste! { + // Emit the C function code that will be extracted from the Rust object file and then + // compiled as C. + #[::lisakmodprocmacro::cfunc] + #[allow(non_snake_case)] + fn [<__macro_getter_ $macro>]() -> $ty { + concat!("#include<", $header, ">"); + concat!("return (", stringify!($macro), ");") + } + [<__macro_getter_ $macro>]() + } + }}; +} +pub(crate) use get_c_macro; diff --git a/lisa/_assets/kmodules/lisa/rust/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs similarity index 68% rename from lisa/_assets/kmodules/lisa/rust/src/lib.rs rename to lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index 16ccb34d1..16dc8d945 100644 --- a/lisa/_assets/kmodules/lisa/rust/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0 */ - #![no_std] #![no_builtins] + extern crate alloc; +pub mod inlinec; +pub mod prelude; pub mod runtime; -pub mod validate; +pub mod tests; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs new file mode 100644 index 000000000..55f82218d --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +pub use crate::{inlinec::cfunc, runtime::kbox::KBox}; + +#[allow(unused_imports)] +pub(crate) use crate::{ + inlinec::get_c_macro, + runtime::printk::{pr_err, pr_info}, +}; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs new file mode 100644 index 000000000..289188c70 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::alloc::handle_alloc_error; +use core::alloc::{GlobalAlloc, Layout}; + +use crate::{ + inlinec::{cfunc, get_c_macro}, + runtime::printk::pr_err, +}; + +struct KernelAllocator; + +#[inline] +fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { + let minalign = || get_c_macro!("linux/slab.h", ARCH_KMALLOC_MINALIGN, usize); + + let size = layout.size(); + let align = layout.align(); + // For sizes which are a power of two, the kmalloc() alignment is also guaranteed to be at + // least the respective size. + if (size.is_power_of_two() && align <= size) || (align <= minalign()) { + let ptr = f(layout.size()); + assert_eq!((ptr as usize % align), 0); + ptr + } else { + // Do not panic as this would create UB + pr_err!("Rust: cannot allocate memory with alignment > {minalign} and a size {size} that is not a power of two", minalign=minalign()); + core::ptr::null_mut() + } +} + +/// This function is guaranteed to return the pointer given by the kernel's kmalloc() without +/// re-aligning it in any way. This makes it suitable to pass to kfree() without knowing the +/// original layout. +#[inline] +pub fn kmalloc(layout: Layout) -> *mut u8 { + #[cfunc] + fn alloc(size: usize) -> *mut u8 { + "#include "; + + "return kmalloc(size, GFP_KERNEL);" + } + with_size(layout, alloc) +} + +#[inline] +pub unsafe fn kfree(ptr: *mut T) { + #[cfunc] + unsafe fn dealloc(ptr: *mut u8) { + "#include "; + + "return kfree(ptr);" + } + unsafe { dealloc(ptr as *mut u8) } +} + +unsafe impl GlobalAlloc for KernelAllocator { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + kmalloc(layout) + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + unsafe { kfree(ptr) }; + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + #[cfunc] + fn alloc_zeroed(size: usize) -> *mut u8 { + "#include "; + + "return kzalloc(size, GFP_KERNEL);" + } + with_size(layout, alloc_zeroed) + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + #[cfunc] + unsafe fn realloc(ptr: *mut u8, size: usize) -> *mut u8 { + "#include "; + + r#" + if (!size) { + // Do not feed a size=0 to krealloc() as it will free it, + // leading to a double-free. + size = 1; + } + return krealloc(ptr, size, GFP_KERNEL); + "# + } + let new_layout = Layout::from_size_align(new_size, layout.align()); + let new_layout = match new_layout { + Ok(layout) => layout, + Err(_) => handle_alloc_error(layout), + }; + with_size(new_layout, |size| unsafe { realloc(ptr, size) }) + } +} + +#[global_allocator] +/// cbindgen:ignore +static GLOBAL: KernelAllocator = KernelAllocator; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs new file mode 100644 index 000000000..7b630ca8e --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use core::{ + alloc::Layout, + borrow::{Borrow, BorrowMut}, + cmp::Ordering, + convert::{AsMut, AsRef}, + fmt, + hash::{Hash, Hasher}, + ops::{Deref, DerefMut}, + ptr, + ptr::NonNull, +}; + +use crate::runtime::alloc::{kfree, kmalloc}; + +pub struct KBox { + ptr: NonNull, +} + +impl KBox { + #[inline] + pub fn new(x: T) -> Self + where + T: Sized, + { + let layout = Layout::new::(); + let ptr = kmalloc(layout); + let ptr = ptr::NonNull::new(ptr as *mut T).expect("Allocation failed"); + unsafe { + ptr::write(ptr.as_ptr(), x); + } + KBox { ptr } + } + + #[inline] + pub fn from_ptr(ptr: NonNull) -> Self { + KBox { ptr } + } + + #[inline] + pub fn as_ptr(&self) -> NonNull { + self.ptr + } +} + +impl Drop for KBox { + #[inline] + fn drop(&mut self) { + unsafe { + self.ptr.drop_in_place(); + kfree(self.ptr.as_ptr()); + } + } +} + +impl From for KBox { + #[inline] + fn from(t: T) -> Self { + Self::new(t) + } +} + +impl From> for KBox { + #[inline] + fn from(t: NonNull) -> Self { + Self::from_ptr(t) + } +} + +unsafe impl Send for KBox {} +unsafe impl Sync for KBox {} + +impl Deref for KBox { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for KBox { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.ptr.as_mut() } + } +} + +impl AsRef for KBox { + #[inline] + fn as_ref(&self) -> &T { + self.deref() + } +} + +impl AsMut for KBox { + #[inline] + fn as_mut(&mut self) -> &mut T { + self.deref_mut() + } +} + +impl Borrow for KBox { + #[inline] + fn borrow(&self) -> &T { + self.deref() + } +} + +impl BorrowMut for KBox { + #[inline] + fn borrow_mut(&mut self) -> &mut T { + self.deref_mut() + } +} + +impl Clone for KBox { + #[inline] + fn clone(&self) -> Self { + let x: T = ::clone(self.deref()); + Self::new(x) + } +} + +impl fmt::Debug for KBox { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Debug::fmt(self.deref(), f) + } +} + +impl Default for KBox { + #[inline] + fn default() -> Self { + KBox::new(::default()) + } +} + +impl fmt::Display for KBox { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(self.deref(), f) + } +} + +impl PartialEq for KBox { + #[inline] + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(&**self, &**other) + } +} + +impl Eq for KBox {} + +impl PartialOrd for KBox { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + PartialOrd::partial_cmp(&**self, &**other) + } +} + +impl Ord for KBox { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + Ord::cmp(&**self, &**other) + } +} + +impl Hash for KBox { + #[inline] + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +impl fmt::Pointer for KBox { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.ptr, f) + } +} + +impl Unpin for KBox {} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs new file mode 100644 index 000000000..9c5f80751 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +pub mod alloc; +pub mod kbox; +pub mod panic; +pub mod printk; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs new file mode 100644 index 000000000..9eaeb012b --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use core::{ + cmp::min, + fmt::Write, + fmt::{self}, + ops::{Deref, DerefMut}, +}; + +use crate::inlinec::cfunc; + +struct SliceWriter<'a> { + slice: &'a mut [u8], + cursor: usize, +} + +impl<'a> SliceWriter<'a> { + fn new(slice: &'a mut [u8]) -> Self { + SliceWriter { slice, cursor: 0 } + } +} + +impl Deref for SliceWriter<'_> { + type Target = [u8]; + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.slice[self.cursor..] + } +} + +impl DerefMut for SliceWriter<'_> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.slice[self.cursor..] + } +} + +impl fmt::Write for SliceWriter<'_> { + #[inline(always)] + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + let src = s.as_bytes(); + let dst = &mut *self; + + // Crop to our size, so we don't risk panicking. + let len = min(src.len(), dst.len()); + dst[0..len].clone_from_slice(&src[0..len]); + self.cursor += len; + Ok(()) + } +} + +#[allow(dead_code)] +fn _panic(info: &core::panic::PanicInfo) -> ! { + #[cfunc] + unsafe fn panic(msg: *const u8, len: usize) { + r#" + if (msg && len) { + panic("Rust panic: %.*s", (int)len, msg); + } else { + panic("Rust panic with no message"); + } + "# + } + + let mut buf = [0; 128]; + let msg = info.message(); + let out: &[u8] = match msg.as_str() { + Some(s) => s.as_bytes(), + None => { + let mut out = SliceWriter::new(buf.as_mut_slice()); + match write!(out, "{}", msg) { + Ok(()) => &buf, + Err(_) => "".as_bytes(), + } + } + }; + unsafe { panic(out.as_ptr(), out.len()) }; + + #[allow(clippy::empty_loop)] + loop {} +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + _panic(info) +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs new file mode 100644 index 000000000..924343727 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use crate::inlinec::cfunc; + +enum DmesgWriterState { + Init, + Cont, +} + +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum DmesgLevel { + Emerg = 0, + Alert = 1, + Crit = 2, + Err = 3, + Warning = 4, + Notice = 5, + Info = 6, + Debug = 7, + Cont = 8, +} + +pub struct __DmesgWriter { + state: DmesgWriterState, + level: DmesgLevel, +} + +impl __DmesgWriter { + pub fn new(level: DmesgLevel) -> Self { + __DmesgWriter { + level, + state: DmesgWriterState::Init, + } + } +} + +impl core::fmt::Write for __DmesgWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let level = match self.state { + DmesgWriterState::Init => self.level, + DmesgWriterState::Cont => DmesgLevel::Cont, + }; + self.state = DmesgWriterState::Cont; + + #[cfunc] + unsafe fn printk(level: u8, msg: *const u8, len: usize) { + "#include "; + + r#" + #define HANDLE(level, f) case level: f("%.*s", (int)len, msg); break; + switch (level) { + HANDLE(0, pr_emerg); + HANDLE(1, pr_alert); + HANDLE(2, pr_crit); + HANDLE(3, pr_err); + HANDLE(4, pr_warn); + HANDLE(5, pr_notice); + HANDLE(6, pr_info); + HANDLE(7, pr_debug); + HANDLE(8, pr_cont); + } + #undef HANDLE + "# + } + + unsafe { printk(level as u8, s.as_ptr(), s.len()) } + Ok(()) + } +} + +macro_rules! __pr_level { + ($level:expr, $($arg:tt)*) => {{ + use ::core::fmt::Write as _; + ::core::write!( + $crate::runtime::printk::__DmesgWriter::new($level), + $($arg)* + ).expect("Could not write to dmesg") + }} +} +pub(crate) use __pr_level; + +macro_rules! pr_info { + ($($arg:tt)*) => {{ + $crate::runtime::printk::__pr_level!( + $crate::runtime::printk::DmesgLevel::Info, + $($arg)* + ) + }} +} +pub(crate) use pr_info; + +macro_rules! pr_err { + ($($arg:tt)*) => {{ + $crate::runtime::printk::__pr_level!( + $crate::runtime::printk::DmesgLevel::Err, + $($arg)* + ) + }} +} +pub(crate) use pr_err; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs new file mode 100644 index 000000000..81bb43d14 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::ffi::CStr; + +use crate::prelude::*; + +extern "C" { + fn myc_callback(x: u64) -> u64; +} + +#[no_mangle] +pub extern "C" fn do_rust_tests() -> u64 { + test_2(); + test_3(); + test_4(); + test_5(); + test_6(); + + pr_info!("Rust: tests finished"); + 0 +} + +fn test_2() { + pr_info!("Rust: test_2"); + let x = unsafe { myc_callback(42) }; + assert_eq!(x, 43); +} + +fn test_3() { + let left = 1; + let right = 3; + + pr_info!("Rust: test_3"); + let v: Vec = vec![left, right]; + let mut mymap = BTreeMap::new(); + mymap.insert(left, right); + let val = mymap.get(&left).unwrap(); + let b = Arc::new(v); + let x = val + b[1]; + assert_eq!(x, 6); +} + +fn test_4() { + pr_info!("Rust: test_4"); + + let minalign = get_c_macro!("linux/slab.h", ARCH_KMALLOC_MINALIGN, usize); + // Check we don't get any C compilation error with duplicated code. + let minalign2 = get_c_macro!("linux/slab.h", ARCH_KMALLOC_MINALIGN, usize); + assert_eq!(minalign, minalign2); + assert!(minalign >= 8); +} + +fn test_5() { + pr_info!("Rust: test_5"); + + #[cfunc] + fn my_cfunc_1() { + "return;" + } + my_cfunc_1(); + + #[cfunc] + fn my_cfunc_2(x: u32) -> u64 { + "return x * 2;" + } + assert_eq!(my_cfunc_2(42u32), 84u64); + + #[cfunc] + unsafe fn my_cfunc_3(x: &CStr) -> &str { + "return x;" + } + assert_eq!(unsafe { my_cfunc_3(c"hello") }, "hello"); + + #[cfunc] + fn my_cfunc_4() -> &'static str { + r#" + static const char *mystring = "hello world"; + return mystring; + "# + } + assert_eq!(my_cfunc_4(), "hello world"); + + #[cfunc] + unsafe fn my_cfunc_5(x: &CStr) -> bool { + "#include "; + + r#"return strcmp(x, "hello") == 0;"# + } + assert!(unsafe { my_cfunc_5(c"hello") }); + + #[cfunc] + unsafe fn my_cfunc_6(x: Option<&CStr>) -> bool { + "#include "; + + r#"return x == NULL;"# + } + assert!(unsafe { my_cfunc_6(None) }); + assert!(!unsafe { my_cfunc_6(Some(c"hello")) }); + + #[cfunc] + fn my_cfunc_7() -> Option<&'static CStr> { + r#" + static const char *mystring = "hello world"; + return mystring; + "# + } + assert_eq!(my_cfunc_7(), Some(c"hello world")); + + #[cfunc] + fn my_cfunc_8() -> Option<&'static str> { + r#" + static const char *mystring = "hello world"; + return mystring; + "# + } + assert_eq!(my_cfunc_8(), Some("hello world")); + + #[cfunc] + fn my_cfunc_9() -> Option<&'static str> { + r#" + return NULL; + "# + } + assert_eq!(my_cfunc_9(), None); + + #[cfunc] + unsafe fn my_cfunc_10<'a>() -> Option<&'a str> { + r#" + return NULL; + "# + } + assert_eq!(unsafe { my_cfunc_10() }, None); +} + +fn test_6() { + pr_info!("Rust: test_6"); + + { + let b = KBox::new(42u8); + assert_eq!(*b, 42); + drop(b); + } + + { + let zst_addr = get_c_macro!("linux/slab.h", ZERO_SIZE_PTR, *const u8); + let b = KBox::new(()); + assert_eq!(b.as_ptr().as_ptr() as usize, zst_addr as usize); + drop(b); + } +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c new file mode 100644 index 000000000..f789a7e33 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "main.h" +#include "rust/lisakmod/tests.h" + +u64 myc_callback(u64 x) { + return x + 1; +} + +// Use of a function pointer with a global variable forces the compiler to +// issue an indirect call, which exhibits issues with kernels compiled with +// CONFIG_CFI_CLANG +typedef uint64_t fnptr(void); +fnptr *volatile myptr = &do_rust_tests; + +int __attribute__((no_sanitize("kcfi"))) rust_tests(void) { + return (int)myptr(); +} diff --git a/lisa/_assets/kmodules/lisa/rust/validate.h b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.h similarity index 66% rename from lisa/_assets/kmodules/lisa/rust/validate.h rename to lisa/_assets/kmodules/lisa/rust/lisakmod/tests.h index 442efa984..3b79dc08e 100644 --- a/lisa/_assets/kmodules/lisa/rust/validate.h +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.h @@ -2,8 +2,8 @@ #ifndef RUST_VALIDATE_H #define RUST_VALIDATE_H -#include "rust/cbindgen.h" +#include "rust/lisakmod//cbindgen.h" -int rust_validate(void); +int rust_tests(void); #endif /* RUST_VALIDATE_H */ diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml new file mode 100644 index 000000000..c86753867 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lisakmodprocmacro" +version = "0.1.0" +edition = "2021" + +[dependencies] +lisakmodprocmacro_macro = { path = "../lisakmodprocmacro_macro" } +const_format = "0.2" diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs new file mode 100644 index 000000000..dc55aa3de --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#![no_std] + +pub mod private; + +pub use lisakmodprocmacro_macro::*; diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs new file mode 100644 index 000000000..6d73e4610 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use core::{ + ffi::{c_char, CStr}, + ptr::{null, null_mut, NonNull}, +}; + +pub trait FfiType { + // TODO: if and when Rust gains const trait methods, we can just define a const function to + // build a type rather than providing a C macro body and C preprocessor machinery to build full + // type names. + const C_DECL: &'static str; + type FfiType; +} + +pub trait FromFfi: FfiType { + unsafe fn from_ffi(x: Self::FfiType) -> Self; +} + +pub trait IntoFfi: FfiType { + fn into_ffi(self) -> Self::FfiType; +} + +macro_rules! transparent_cff { + ($ty:ty, $c_decl:expr) => { + impl FfiType for $ty { + const C_DECL: &'static str = $c_decl; + type FfiType = $ty; + } + + impl FromFfi for $ty { + unsafe fn from_ffi(x: Self::FfiType) -> Self { + x + } + } + + impl IntoFfi for $ty { + fn into_ffi(self) -> Self::FfiType { + self + } + } + }; +} + +transparent_cff!(u8, "BUILTIN_TY_DECL(uint8_t, DECLARATOR)"); +transparent_cff!(u16, "BUILTIN_TY_DECL(uint16_t, DECLARATOR)"); +transparent_cff!(u32, "BUILTIN_TY_DECL(uint32_t, DECLARATOR)"); +transparent_cff!(u64, "BUILTIN_TY_DECL(uint64_t, DECLARATOR)"); + +transparent_cff!(i8, "BUILTIN_TY_DECL(signed char, DECLARATOR)"); +transparent_cff!(i16, "BUILTIN_TY_DECL(int16_t, DECLARATOR)"); +transparent_cff!(i32, "BUILTIN_TY_DECL(int32_t, DECLARATOR)"); +transparent_cff!(i64, "BUILTIN_TY_DECL(int64_t, DECLARATOR)"); + +transparent_cff!(usize, "BUILTIN_TY_DECL(size_t, DECLARATOR)"); +transparent_cff!(isize, "BUILTIN_TY_DECL(ssize_t, DECLARATOR)"); + +transparent_cff!(bool, "BUILTIN_TY_DECL(_Bool, DECLARATOR)"); +transparent_cff!((), "BUILTIN_TY_DECL(void, DECLARATOR)"); + +transparent_cff!( + *const u8, + "BUILTIN_TY_DECL(const uint8_t, PTR_TY_DECL(DECLARATOR))" +); + +transparent_cff!(*mut u8, "BUILTIN_TY_DECL(uint8_t, PTR_TY_DECL(DECLARATOR))"); +transparent_cff!( + *const c_char, + "BUILTIN_TY_DECL(const char, PTR_TY_DECL(DECLARATOR))" +); + +impl FfiType for Option> { + const C_DECL: &'static str = "BUILTIN_TY_DECL(const uint8_t, PTR_TY_DECL(DECLARATOR))"; + type FfiType = *mut u8; +} + +impl IntoFfi for Option> { + #[inline] + fn into_ffi(self) -> Self::FfiType { + match self { + None => null_mut(), + Some(p) => p.as_ptr(), + } + } +} + +impl FromFfi for Option> { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + if x.is_null() { + None + } else { + Some(NonNull::new(x).unwrap()) + } + } +} + +impl FfiType for NonNull { + const C_DECL: &'static str = > as FfiType>::C_DECL; + type FfiType = > as FfiType>::FfiType; +} + +impl IntoFfi for NonNull { + fn into_ffi(self) -> Self::FfiType { + Some(self).into_ffi() + } +} + +impl FromFfi for NonNull { + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let x = > as FromFfi>::from_ffi(x); + x.expect("NULL pointer was passed to NonNull") + } +} + +impl FfiType for Option<&CStr> { + const C_DECL: &'static str = <*const c_char as FfiType>::C_DECL; + type FfiType = <*const c_char as FfiType>::FfiType; +} + +impl IntoFfi for Option<&CStr> { + fn into_ffi(self) -> Self::FfiType { + match self { + None => null(), + Some(s) => s.as_ptr(), + } + } +} + +impl FromFfi for Option<&CStr> { + #[allow(clippy::not_unsafe_ptr_arg_deref)] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + if x.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(x) }) + } + } +} + +impl<'a> FfiType for &'a CStr { + const C_DECL: &'static str = as FfiType>::C_DECL; + type FfiType = as FfiType>::FfiType; +} + +impl IntoFfi for &CStr { + fn into_ffi(self) -> Self::FfiType { + Some(self).into_ffi() + } +} + +impl FromFfi for &CStr { + unsafe fn from_ffi(x: Self::FfiType) -> Self { + Option::<&CStr>::from_ffi(x) + .expect("NULL pointer was returned as a &CStr, use Option<&CStr> to allow that.") + } +} + +impl<'a> FfiType for &'a str { + const C_DECL: &'static str = <&'a CStr as FfiType>::C_DECL; + type FfiType = <&'a CStr as FfiType>::FfiType; +} + +impl FromFfi for &str { + unsafe fn from_ffi(x: Self::FfiType) -> Self { + <&CStr>::from_ffi(x) + .to_str() + .expect("Invalid UTF-8 content in C string") + } +} + +// No IntoFfi instance for &str as we cannot provide NULL-terminated string out of an &str. Use +// &CStr for that. + +impl<'a> FfiType for Option<&'a str> { + const C_DECL: &'static str = <&'a CStr as FfiType>::C_DECL; + type FfiType = <&'a CStr as FfiType>::FfiType; +} + +impl FromFfi for Option<&str> { + unsafe fn from_ffi(x: Self::FfiType) -> Self { + Some( + >::from_ffi(x)? + .to_str() + .expect("Invalid UTF-8 content in C string"), + ) + } +} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs new file mode 100644 index 000000000..b8116cb73 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +pub use const_format; + +pub mod inlinec; diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs new file mode 100644 index 000000000..30f79e0f1 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use lisakmodprocmacro::cfunc; + +#[cfunc] +fn myfunc(x: u64, y: u64) -> u8 { + "#include "; + + r#" + #ifdef FOOBAR + if (x == 3) { + return 1; + } else { + return y; + } + #endif + "# +} + +#[cfunc] +fn myfunc2(x: u64, y: u64) { + "#include "; + + r#" + return; + "# +} + +use core::ffi::CStr; +#[cfunc] +unsafe fn myfunc3<'a, 'b>(x: &'b CStr) -> &'a str +where + 'a: 'b, +{ + "#include "; + + r#" + return; + "# +} + +#[test] +fn test_cfunc() { + assert_eq!(myfunc(1, 2), 2); +} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml new file mode 100644 index 000000000..971db75ba --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "lisakmodprocmacro_macro" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn = { version = "2.0", default-features = false, features = ["proc-macro", "parsing", "printing", "full", "clone-impls", "extra-traits"]} +quote = { version = "1.0", default-features = false } +proc-macro2 = { version = "1.0", default-features = false, features = ["span-locations"]} +getrandom = {version = "0.2", default-features = false } + +[lib] +proc-macro = true diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs new file mode 100644 index 000000000..0976edcc1 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use proc_macro::TokenStream as RustcTokenStream; +use quote::{format_ident, quote}; +use syn::{ + parse::Parse, spanned::Spanned, token, Attribute, Error, FnArg, Generics, Ident, ItemFn, Pat, + ReturnType, Stmt, StmtMacro, Type, +}; + +struct CFuncInput { + name: Ident, + c_code: (Stmt, Stmt, Stmt), + f_args: Vec<(Ident, Type)>, + f_attrs: Vec, + f_ret_ty: Type, + f_generics: Generics, + f_unsafety: Option, +} + +impl Parse for CFuncInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let item_fn: ItemFn = input.parse()?; + let span = item_fn.span(); + let f_attrs = item_fn.attrs; + let name = item_fn.sig.ident; + let f_ret_ty = match item_fn.sig.output { + ReturnType::Type(_, ty) => *ty, + ReturnType::Default => syn::parse_str::("()")?, + }; + + let mut snippets = Vec::new(); + for stmt in item_fn.block.stmts { + let snippet = match &stmt { + Stmt::Expr(expr, _) => Ok(Stmt::Expr(expr.clone(), None)), + Stmt::Macro(mac) => { + let mac = mac.clone(); + Ok(Stmt::Macro(StmtMacro { + semi_token: None, + ..mac + })) + } + stmt => Err(Error::new( + stmt.span(), + "An inline C function must contain string expressions.", + )), + }?; + snippets.push(snippet.clone()); + } + + let empty = || Stmt::Expr(syn::parse_str("\"\"").unwrap(), None); + + let c_code = match snippets.len() { + 1 => Ok(( + empty(), + snippets[0].clone(), + empty(), + )), + 2 => Ok(( + snippets[0].clone(), + snippets[1].clone(), + empty(), + )), + 3 => Ok(( + snippets[0].clone(), + snippets[1].clone(), + snippets[2].clone(), + )), + _ => Err(Error::new(span, "An inline C function must contain either a single string expression with the C code in it or two string expressions (one output before the function and one for the body).")) + }?; + + let f_args: Vec<_> = item_fn + .sig + .inputs + .into_iter() + .map(|arg| match arg { + FnArg::Typed(arg) => { + let ident = match *arg.pat { + Pat::Ident(ident) => Ok(ident.ident), + _ => Err(Error::new( + arg.span(), + "An inline C function argument must be an identifier.", + )), + }?; + Ok((ident, *arg.ty)) + } + _ => Err(Error::new( + arg.span(), + "An inline C function argument must have a type.", + )), + }) + .collect::>()?; + + let f_generics = item_fn.sig.generics.clone(); + let f_unsafety = item_fn.sig.unsafety; + + Ok(Self { + name, + c_code, + f_args, + f_attrs, + f_ret_ty, + f_generics, + f_unsafety, + }) + } +} + +pub fn cfunc(_attrs: RustcTokenStream, code: RustcTokenStream) -> Result { + let input = syn::parse::(code)?; + let (pre_c_code, c_code, post_c_code) = input.c_code; + + // TODO: Due to this issue, the span reported by syn is currently always going to have + // span.line == 0, so we workaround that by just emitting a line!() call. This is not as + // precise though. + // https://github.com/dtolnay/proc-macro2/issues/402 + // let pre_c_code_line = pre_c_code.span().start().line; + // let c_code_line = c_code.span().start().line; + // let post_c_code_line = post_c_code.span().start().line; + let pre_c_code_line = quote! { line!() }; + let c_code_line = quote! { line!() }; + let post_c_code_line = quote! { line!() }; + + let name = input.name; + let f_ret_ty = input.f_ret_ty; + let f_attrs = input.f_attrs; + let f_args = input.f_args; + let f_unsafety = input.f_unsafety; + let f_generics = input.f_generics; + let f_where = f_generics.where_clause.clone(); + + let section_str = format!(".binstore.c_shims.{}", name); + + // Use getrandom instead of e.g. uuid crate as it has far fewer dependencies, so faster build + // time. + fn get_random() -> u128 { + let mut buf: [u8; 128 / 8] = [0; 128 / 8]; + getrandom::getrandom(&mut buf).expect("Could not get random number"); + u128::from_le_bytes(buf) + } + let c_name_str = format!("__lisa_c_shim_{name}_{}", get_random()); + let c_name = format_ident!("{}", c_name_str); + let c_proto = format!("{c_name_str}_proto"); + let c_ret_ty = format!("{c_name_str}_ret_ty"); + let (c_args, c_args_ty_macros, rust_args, rust_extern_args, rust_extern_call_args) = if f_args + .is_empty() + { + ( + quote! { "void" }, + quote! { "" }, + quote! {}, + quote! {}, + quote! {}, + ) + } else { + let c_nr_args = f_args.len(); + let (arg_names, arg_tys): (Vec<_>, Vec<_>) = f_args.into_iter().unzip(); + let c_arg_names: Vec<_> = arg_names.iter().map(|name| name.to_string()).collect(); + let c_args_commas: Vec<_> = c_arg_names + .iter() + .enumerate() + .map(|(i, _)| if i == (c_nr_args - 1) { "" } else { ", " }) + .collect(); + let c_arg_ty_names: Vec<_> = arg_names + .iter() + .map(|arg| format!("{c_name_str}_arg_ty_{arg}")) + .collect(); + + // Argument types are encoded as a function-like macro. Calling this macro with an + // identifier declares a variable (or function argument) of that type. This allows using + // any type, including more complex ones like arrays and function pointers. + let c_args_ty_macros = quote! { + ::lisakmodprocmacro::private::const_format::concatcp!( + #( + "\n", + ::lisakmodprocmacro::private::const_format::concatcp!( + "#define ", #c_arg_ty_names, "(DECLARATOR)", + { + // Use a const fn to introduce f_generics and f_where + const fn get #f_generics() -> &'static str #f_where { + <#arg_tys as ::lisakmodprocmacro::private::inlinec::FfiType>::C_DECL + } + get() + }, + ) + ),* + ) + }; + + let c_args = quote! { + ::lisakmodprocmacro::private::const_format::concatcp!( + #( + #c_arg_ty_names, + "(", #c_arg_names, ")", #c_args_commas + ),* + ) + }; + let rust_args = quote! { + #( + #arg_names : #arg_tys + ),* + }; + let rust_extern_args = quote! { + #( + #arg_names : <#arg_tys as ::lisakmodprocmacro::private::inlinec::FfiType>::FfiType + ),* + }; + let rust_extern_call_args = quote! { + #( + ::lisakmodprocmacro::private::inlinec::IntoFfi::into_ffi(#arg_names) + ),* + }; + ( + c_args, + c_args_ty_macros, + rust_args, + rust_extern_args, + rust_extern_call_args, + ) + }; + + let out = quote! { + // Store the C function in a section of the binary, that will be extracted by the + // module Makefile and compiled separately as C code. + const _: () = { + // Keep this out of the concatcp!() call so we can have the generic parameters for + // f_ret_ty in scope. This is possible with the "generic_const_item" unstable feature. + const CODE_SLICE: &[u8] = ::lisakmodprocmacro::private::const_format::concatcp!( + r#" + #include + + #define BUILTIN_TY_DECL(ty, declarator) ty (declarator) + #define PTR_TY_DECL(declarator) *(declarator) + #define ARR_TY_DECL(N, declarator) (declarator)[N] + #define FN_PTR_TY_DECL(args, declarator) (*(declarator))args + #define FN_TY_DECL(args, declarator) (declarator)args + "#, + + "#line ", line!(), " \"", file!(), "\"\n", + #c_args_ty_macros, + + // See comment on how arguments type are handled, as we do the same for the return + // type. + "\n#define ", #c_ret_ty, "(DECLARATOR)", + { + // Use a const fn to introduce f_generics and f_where + const fn get #f_generics() -> &'static str #f_where { + <#f_ret_ty as ::lisakmodprocmacro::private::inlinec::FfiType>::C_DECL + } + get() + }, + "\n#define ", #c_proto, " ", #c_ret_ty, "(FN_TY_DECL((", #c_args, "), ", #c_name_str, "))", + "\n", + + "#line ", #pre_c_code_line, " \"", file!(), "\"\n", + #pre_c_code, + "\n", + + // Prototype + "#line ", #c_code_line, " \"", file!(), "\"\n", + #c_proto, ";\n", + // Definition + #c_proto, + "{\n", #c_code, "\n}", + "\n", + "#line ", #post_c_code_line, " \"", file!(), "\"\n", + #post_c_code, + "\n", + ).as_bytes(); + const CODE_LEN: usize = CODE_SLICE.len(); + + #[link_section = #section_str ] + #[used] + static CODE: [u8; CODE_LEN] = { + let mut arr = [0u8; CODE_LEN]; + let mut idx: usize = 0; + while idx < CODE_LEN { + arr[idx] = CODE_SLICE[idx]; + idx += 1; + } + arr + }; + }; + + extern "C" { + fn #c_name #f_generics(#rust_extern_args) -> <#f_ret_ty as ::lisakmodprocmacro::private::inlinec::FfiType>::FfiType #f_where; + } + + #[inline] + #(#f_attrs)* + #f_unsafety fn #name #f_generics(#rust_args) -> #f_ret_ty #f_where { + unsafe { + ::lisakmodprocmacro::private::inlinec::FromFfi::from_ffi( + #c_name(#rust_extern_call_args) + ) + } + } + }; + // eprintln!("{}", &out.to_string()); + Ok(out.into()) +} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs new file mode 100644 index 000000000..2cec4287f --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use proc_macro::TokenStream; +use syn::Error; + +mod inlinec; + +#[proc_macro_attribute] +pub fn cfunc(attrs: TokenStream, code: TokenStream) -> TokenStream { + inlinec::cfunc(attrs, code).unwrap_or_else(|err| Error::into_compile_error(err).into()) +} diff --git a/lisa/_assets/kmodules/lisa/rust/runtime.c b/lisa/_assets/kmodules/lisa/rust/runtime.c deleted file mode 100644 index 8b66b0a83..000000000 --- a/lisa/_assets/kmodules/lisa/rust/runtime.c +++ /dev/null @@ -1,47 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include "main.h" -#include "rust/cbindgen.h" - -/* Basic glue between Rust runtime and kernel functions */ - - -u8 *__lisa_rust_alloc(size_t size) { - return kmalloc(size, GFP_KERNEL); -} - -void __lisa_rust_dealloc(u8 *ptr) { - kfree(ptr); -} - -u8 *__lisa_rust_alloc_zeroed(size_t size) { - return kzalloc(size, GFP_KERNEL); -} - -u8 *__lisa_rust_realloc(u8 *ptr, size_t size) { - if (!size) { - // Do not feed a size=0 to krealloc() as it will free it, - // leading to a double-free. - size = 1; - } - return krealloc(ptr, size, GFP_KERNEL); -} - -void __lisa_rust_panic(const u8 *msg, size_t len) { - if (msg && len) { - panic("Rust panic: %.*s", (int)len, msg); - } else { - panic("Rust panic with no message"); - } -} - -void __lisa_rust_pr_info(const u8 *msg, size_t len) { - if (msg) { - if (len) { - pr_info("%.*s", (int)len, msg); - } else { - pr_info(""); - } - } else { - pr_info("(null)"); - } -} diff --git a/lisa/_assets/kmodules/lisa/rust/rust.lds b/lisa/_assets/kmodules/lisa/rust/rust.lds deleted file mode 100644 index 0f465a6ff..000000000 --- a/lisa/_assets/kmodules/lisa/rust/rust.lds +++ /dev/null @@ -1,2 +0,0 @@ -/* Workaround this issue: https://github.com/rust-lang/rust/issues/125619 */ -PROVIDE(__builtin_copysignq = 0); diff --git a/lisa/_assets/kmodules/lisa/rust/src/runtime.rs b/lisa/_assets/kmodules/lisa/rust/src/runtime.rs deleted file mode 100644 index 826729f99..000000000 --- a/lisa/_assets/kmodules/lisa/rust/src/runtime.rs +++ /dev/null @@ -1,88 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -use core::{ - alloc::{GlobalAlloc, Layout}, -}; - -extern "C" { - // All these low-level functions need to have the __lisa prefix, otherwise we will clash with - // the ones provided by the Rust toolchain. - fn __lisa_rust_alloc(size: usize) -> *mut u8; - fn __lisa_rust_dealloc(ptr: *mut u8); - fn __lisa_rust_alloc_zeroed(size: usize) -> *mut u8; - fn __lisa_rust_realloc(ptr: *mut u8, size: usize) -> *mut u8; - - fn __lisa_rust_panic(msg: *const u8, len: usize); - fn __lisa_rust_pr_info(msg: *const u8, len: usize); -} - -#[cfg(not(test))] -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - match info.message().as_str() { - Some(s) => unsafe { __lisa_rust_panic(s.as_ptr(), s.len()) }, - None => unsafe { __lisa_rust_panic(core::ptr::null(), 0) }, - }; - loop {} -} - -struct KernelAllocator; - -fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { - let size = layout.size(); - let align = layout.align(); - // For sizes which are a power of two, the kmalloc() alignment is also guaranteed to be at - // least the respective size. - if align <= 8 || (size.is_power_of_two() && align <= size) { - let ptr = f(layout.size()); - assert!((ptr as usize % align) == 0); - ptr - } else { - pr_info!("Rust: cannot allocate memory with alignment > 8"); - core::ptr::null_mut() - } -} - -unsafe impl GlobalAlloc for KernelAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - with_size(layout, |size| unsafe { __lisa_rust_alloc(size) }) - } - - unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - unsafe { __lisa_rust_dealloc(ptr) } - } - unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - with_size(layout, |size| unsafe { __lisa_rust_alloc_zeroed(size) }) - } - - unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { - with_size(layout, |_| unsafe { __lisa_rust_realloc(ptr, new_size) }) - } -} - -#[global_allocator] -/// cbindgen:ignore -static GLOBAL: KernelAllocator = KernelAllocator; - -// FIXME: Find a way to issue pr_cont() calls, otherwise each __lisa_rust_pr_info() call will -// create a newline, so each fragment of the write!() format string will be printed on a separated -// line. -pub struct __DmesgWriter {} - -impl core::fmt::Write for __DmesgWriter { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - unsafe { __lisa_rust_pr_info(s.as_ptr(), s.len()) } - Ok(()) - } -} - -macro_rules! pr_info { - ($($arg:tt)*) => {{ - use ::core::fmt::Write as _; - ::core::write!( - $crate::runtime::__DmesgWriter {}, - $($arg)* - ).expect("Could not write to dmesg") - }} -} -pub(crate) use pr_info; diff --git a/lisa/_assets/kmodules/lisa/rust/src/validate.rs b/lisa/_assets/kmodules/lisa/rust/src/validate.rs deleted file mode 100644 index b5d244335..000000000 --- a/lisa/_assets/kmodules/lisa/rust/src/validate.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -use alloc::collections::BTreeMap; -use alloc::sync::Arc; -use alloc::vec; -use alloc::vec::Vec; - -use crate::runtime::pr_info; - -extern "C" { - fn myc_callback(x: u64) -> u64; -} - -#[no_mangle] -pub extern "C" fn test_1(left: u64, right: u64) -> u64 { - pr_info!("Rust: test_1"); - left + right -} - -#[no_mangle] -pub extern "C" fn test_2(left: u64, right: u64) -> u64 { - pr_info!("Rust: test_2"); - left + unsafe { myc_callback(right) } -} - -#[no_mangle] -pub extern "C" fn test_3(left: u64, right: u64) -> u64 { - pr_info!("Rust: test_3"); - let v: Vec = vec![left, right]; - let mut mymap = BTreeMap::new(); - mymap.insert(left, right); - let val = mymap.get(&left).unwrap(); - let b = Arc::new(v); - val + b[1] -} diff --git a/lisa/_assets/kmodules/lisa/rust/validate.c b/lisa/_assets/kmodules/lisa/rust/validate.c deleted file mode 100644 index 29485d8ad..000000000 --- a/lisa/_assets/kmodules/lisa/rust/validate.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include "main.h" -#include "rust/validate.h" - -u64 myc_callback(u64 x) { - return x + 1; -} - -// Use of a function pointer with a global variable forces the compiler to -// issue an indirect call, which exhibits issues with kernels compiled with -// CONFIG_CFI_CLANG -typedef u64 fnptr(u64, u64); -fnptr *myptr = test_1; - -int __attribute__((no_sanitize("kcfi"))) rust_validate(void) { - int ret = 0; - - if (myptr(1, 2) != 3) { - pr_err("Rust test_1 failed"); - ret |= 1; - } - - if (test_2(1, 2) != 4) { - pr_err("Rust test_2 failed"); - ret |= 1; - } - - if (test_3(1, 3) != 6) { - pr_err("Rust test_3 failed"); - ret |= 1; - } - - pr_info("Rust: tests finished"); - return ret; -} diff --git a/tools/tests.sh b/tools/tests.sh index e8e770e6b..4a906e4ef 100755 --- a/tools/tests.sh +++ b/tools/tests.sh @@ -44,15 +44,18 @@ if ! git diff --exit-code doc/man1/; then exit 1 fi -echo "Checking that the kernel module Rust bindings are up to date ..." -( - cd "$LISA_HOME/lisa/_assets/kmodules/lisa/" || exit 1 - make_rust_bindings="make refresh-rust-bindings" - - $make_rust_bindings - if ! git diff --exit-code .; then - echo "Please regenerate Rust bindings: $make_rust_bindings" - exit 1 - fi -) +# FIXME: cbindgen does not work on our source anymore due to that issue: +# https://github.com/mozilla/cbindgen/issues/993 +# +# echo "Checking that the kernel module Rust bindings are up to date ..." +# ( + # cd "$LISA_HOME/lisa/_assets/kmodules/lisa/" || exit 1 + # make_rust_bindings="make refresh-rust-bindings" + + # $make_rust_bindings + # if ! git diff --exit-code .; then + # echo "Please regenerate Rust bindings: $make_rust_bindings" + # exit 1 + # fi +# ) -- GitLab