diff --git a/install_base.sh b/install_base.sh index 28fd7b47e7a1716f38bbc77b65668800959b1178..dfc2254b6d9842a6167237fed355a9c5dff83b19 100755 --- a/install_base.sh +++ b/install_base.sh @@ -254,7 +254,11 @@ for arg in "${args[@]}"; do ;;& "--install-kernel-build-dependencies" | "--install-all") - apt_packages+=(build-essential gcc bc bison flex libssl-dev libncurses5-dev libelf-dev) + apt_packages+=(build-essential gcc bc bison flex libssl-dev libncurses5-dev libelf-dev pahole) + + # For the LISA kernel module + apt_packages+=(jq) + handled=1 ;;& diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 661a825460cc9bc3232a038bc617fca189e0875d..550b8698fbf812b8a9fcc7e4d0fd26b42be4fa33 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -26,6 +26,17 @@ else IN_TREE_BUILD := 1 endif +# Kbuild +ifneq ($(KERNELRELEASE),) + MODULE_OBJ := $(obj) + KERNEL_SRC ?= $(srctree) +# Kbuild in-tree build +ifeq ($(IN_TREE_BUILD),1) + MODULE_SRC ?= $(srctree)/$(src) +# Kbuild out-of-tree build +else + MODULE_SRC ?= $(src) +endif # This did not lie: # https://tooling.sites.arm.com/lisa/latest/setup.html#manual-module-setup-warning2 # Maintaining a patch to get in-tree build will be the least of your worries @@ -44,17 +55,6 @@ endif # maintain the patch if you are motivated enough to deal with that. Pull # requests to fix issues related to in-tree build are welcome, *provided that # they were tested in normal flow as well*. - -# Kbuild -ifneq ($(KERNELRELEASE),) - MODULE_OBJ := $(obj) - KERNEL_SRC ?= $(srctree) -ifeq ($(IN_TREE_BUILD),1) - MODULE_SRC ?= $(srctree)/$(src) -# Kbuild out-of-tree build -else - MODULE_SRC ?= $(src) -endif # non-Kbuild else MODULE_SRC ?= $(PWD) @@ -62,13 +62,95 @@ else KERNEL_SRC ?= /lib/modules/`uname -r`/build endif +GENERATED := $(MODULE_OBJ)/generated +$(GENERATED): + rm -r "$@" 2>&1 || true + mkdir -p "$@" +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_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 + +RUSTUP_HOME ?= $(HOME)/.rustup +CARGO_HOME ?= $(HOME)/.cargo + +RUST_BUILD_DIR := $(RUST_GENERATED)/build +RUST_SYMBOLS := $(RUST_BUILD_DIR)/exported.list +RUST_SYMBOLS_LDS := $(RUST_BUILD_DIR)/exported.lds +RUST_TARGET_DIR := $(RUST_BUILD_DIR)/target +RUST_CBINDGEN_BIN := $(CARGO_HOME)/bin/cbindgen + +# Enable RUSTC_BOOTSTRAP=1 so we can use nightly features on any toolchain, +# including stable ones. This allows using the more tested stable binaries. +rust_cmd = export RUSTC_BOOTSTRAP=1 PATH="$(CARGO_HOME)/bin:$$PATH" RUSTUP_HOME=$(RUSTUP_HOME) CARGO_HOME=$(CARGO_HOME) && $(1) +cargo_cmd = $(call rust_cmd,cargo +$(RUST_VERSION) $(1)) + +$(RUST_GENERATED): $(GENERATED) + mkdir -p "$@" + +$(RUST_BUILD_DIR): $(RUST_GENERATED) + mkdir -p "$@" + +$(RUST_TARGET_DIR): $(RUST_BUILD_DIR) + mkdir -p "$@" + +# 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) + # 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) + + # Get the list of exported symbols (pub with #[no_mangle] attribute) from the rustdoc JSON + cat $(RUST_TARGET_DIR)/doc/lisakmod.json | jq '.index[] | select(.attrs[] | contains("#[no_mangle]")) | .name' -r > $(RUST_SYMBOLS) + cat $(RUST_SYMBOLS) + + # Prelink the archive into a single object file. + sed -ne 's/\(.*\)/EXTERN(\1)/p' $(RUST_SYMBOLS) > $(RUST_SYMBOLS_LDS) + $(LD) $(KBUILD_LDFLAGS) --gc-sections -T $(RUST_SYMBOLS_LDS) -nostdlib -r -o $(RUST_OBJECT) --whole-archive $(RUST_TARGET_DIR)/target/release/liblisakmod.a + + # Only keep as GLOBAL symbols the ones that are to be exported (and the + # undefined ones to be provided by C code) + $(OBJCOPY) --keep-global-symbols $(RUST_SYMBOLS) $(RUST_OBJECT) + + # Provide the pre-built .o to Kbuild + cp $(RUST_OBJECT) $(RUST_OBJECT)_shipped + touch $(RUST_OBJECT_CMD) + +genbin: $(RUST_OBJECT) + +$(RUSTUP_HOME): + $(call rust_cmd,curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain none) + +$(CARGO_HOME): $(RUSTUP_HOME) + $(call rust_cmd,rustup install nightly --profile=minimal) + +$(RUST_CBINDGEN_BIN): $(CARGO_HOME) + $(call cargo_cmd,install --locked cbindgen) + +# 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)) + +$(RUST_SYMBOLS): $(RUST_OBJECT) + # kbuild part of makefile. Only Kbuild-related targets should be used here to # avoid any sort of clash. 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 +$(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 # -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 @@ -77,20 +159,14 @@ ccflags-y := "-I$(MODULE_SRC)" -std=gnu11 -fno-stack-protector -Wno-declaration- # file regardless of the kernel config. CFLAGS_introspection_data.o := -g3 -gno-split-dwarf -gdwarf -fno-eliminate-unused-debug-types -GENERATED := $(MODULE_OBJ)/generated FEATURES_LDS := $(MODULE_SRC)/features.lds SYMBOLS_LDS := $(GENERATED)/symbols.lds -clean-files := $(GENERATED) - -$(GENERATED): - mkdir -p "$@" - SYMBOL_NAMESPACES_H := $(GENERATED)/symbol_namespaces.h MODULE_VERSION_H := $(GENERATED)/module_version.h KALLSYMS := $(GENERATED)/kallsyms -ldflags-y += -T $(FEATURES_LDS) +ldflags-y += -T $(FEATURES_LDS) -T $(RUST_LDS) ifeq ($(IN_TREE_BUILD),1) ccflags-y += -I$(srctree) -D_IN_TREE_BUILD @@ -160,21 +236,20 @@ $(MODULE_VERSION_H): $(GENERATED) printf "#define LISA_MODULE_VERSION \"" >> $@ # Show the content of the folder to debug mismatching sha1sum issues ls -laR $(MODULE_SRC) - export LC_ALL=C && (cd $(MODULE_SRC) && find -type f '(' -name '*.c' -or -name '*.h' -or -name '*.txt' -or -name '*.json' -or -name '*.py' -or -name '*.sh' -or -name '*.lds' -or -name 'Makefile' -or -name 'vmlinux' -or -name 'kallsyms' ')' -and -not -path './generated/*' -and -not -path './__pycache__/*' | xargs sha1sum) | sort | sha1sum | cut -d' ' -f1 | tr -d '\n' >> $@ + export LC_ALL=C && (cd $(MODULE_SRC) && find -type f '(' -name '*.c' -or -name '*.h' -or -name '*.rs' -or -name '*.toml' -or -name '*.txt' -or -name '*.json' -or -name '*.md' -or -name '*.py' -or -name '*.sh' -or -name '*.lds' -or -name 'Makefile' -or -name 'vmlinux' -or -name 'kallsyms' ')' -and -not -path './generated/*' -and -not -path './__pycache__/*' | xargs sha1sum) | sort | sha1sum | cut -d' ' -f1 | tr -d '\n' >> $@ printf "\"\n" >> $@ $(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) +$(addprefix $(MODULE_OBJ)/,$($(LISA_KMOD_NAME)-y)): $(INTROSPECTION_DATA_H) $(SYMBOL_NAMESPACES_H) $(MODULE_VERSION_H) $(SYMBOLS_LDS) $(RUST_CBINDGEN_HEADER) # Non-Kbuild part else -.PHONY: all build install clean +.PHONY: all build install clean refresh-rust-bindings all: install diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index 05ec9b2cfcb167a5af51d3aba0a428d9746caca1..4054ef1b5809ba0b950127595e1db373d484081b 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -4,6 +4,7 @@ #include "main.h" #include "features.h" #include "introspection.h" +#include "rust/validate.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 @@ -33,6 +34,12 @@ static int __init modinit(void) { return -EPROTO; } + ret = rust_validate(); + if (ret) { + pr_err("Lisa module Rust support validation failed: %i\n", ret); + return -EINVAL; + } + pr_info("Kernel features detected. This will impact the module features that are available:\n"); const char *kernel_feature_names[] = {__KERNEL_FEATURE_NAMES}; const bool kernel_feature_values[] = {__KERNEL_FEATURE_VALUES}; diff --git a/lisa/_assets/kmodules/lisa/main.h b/lisa/_assets/kmodules/lisa/main.h index e3691166dfbfaeb0b0552aac271fe4625219ae25..f6b9f5b3ab9493c2436a515ba3594ef2cedcd15e 100644 --- a/lisa/_assets/kmodules/lisa/main.h +++ b/lisa/_assets/kmodules/lisa/main.h @@ -3,6 +3,7 @@ #define _MAIN_H #include +#include #undef pr_fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/lisa/_assets/kmodules/lisa/rust/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4e53266d55c2e997f63afc1f68315d238020acd2 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "lisakmod" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +# hashbrown = "0.15" + +[profile.release] +panic = 'abort' +strip = "debuginfo" + +[profile.dev] +panic = 'abort' diff --git a/lisa/_assets/kmodules/lisa/rust/cbindgen.h b/lisa/_assets/kmodules/lisa/rust/cbindgen.h new file mode 100644 index 0000000000000000000000000000000000000000..19f5b9d4b12d78e2a641ec7dc4f2e6cab4743ec6 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/cbindgen.h @@ -0,0 +1,29 @@ +/* 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/cbindgen.toml b/lisa/_assets/kmodules/lisa/rust/cbindgen.toml new file mode 100644 index 0000000000000000000000000000000000000000..5ef56d61be49b877ced7d212d4491810f47a88a6 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/cbindgen.toml @@ -0,0 +1,698 @@ +# The language to output bindings in +# +# possible values: "C", "C++", "Cython" +# +# default: "C++" +language = "C" + + +# Options for wrapping the contents of the header: + +# An optional string of text to output at the beginning of the generated file +# default: doesn't emit anything +header = "/* SPDX-License-Identifier: GPL-2.0 */" + +# An optional string of text to output at the end of the generated file +# default: doesn't emit anything +trailer = "" + +# An optional name to use as an include guard +# default: doesn't emit an include guard +# include_guard = "" + +# Whether to add a `#pragma once` guard +# default: doesn't emit a `#pragma once` +pragma_once = false + +# An optional string of text to output between major sections of the generated +# file as a warning against manual editing +# +# default: doesn't emit anything +autogen_warning = "/* Warning, this file is autogenerated by Rust cbindgen. Don't modify this manually. */" + +# Whether to include a comment with the version of cbindgen used to generate the file +# default: false +include_version = true + +# An optional namespace to output around the generated bindings +# default: doesn't emit a namespace +namespace = "" + +# An optional list of namespaces to output around the generated bindings +# default: [] +namespaces = [] + +# An optional list of namespaces to declare as using with "using namespace" +# default: [] +using_namespaces = [] + +# A list of sys headers to #include (with angle brackets) +# default: [] +sys_includes = [] + +# A list of headers to #include (with quotes) +# default: [] +includes = [] + +# Whether cbindgen's default C/C++ standard imports should be suppressed. These +# imports are included by default because our generated headers tend to require +# them (e.g. for uint32_t). Currently, the generated imports are: +# +# * for C: , , , , +# +# * for C++: , , , , (depending on config) +# +# default: false +no_includes = true + +# Whether to make a C header C++ compatible. +# These will wrap generated functions into a `extern "C"` block, e.g. +# +# #ifdef __cplusplus +# extern "C" { +# #endif // __cplusplus +# +# // Generated functions. +# +# #ifdef __cplusplus +# } // extern "C" +# #endif // __cplusplus +# +# If the language is not C this option won't have any effect. +# +# default: false +cpp_compat = false + +# A list of lines to add verbatim after the includes block +after_includes = "" + + + +# Code Style Options + +# The style to use for curly braces +# +# possible values: "SameLine", "NextLine" +# +# default: "SameLine" +braces = "SameLine" + +# The desired length of a line to use when formatting lines +# default: 100 +line_length = 80 + +# The amount of spaces to indent by +# default: 2 +tab_width = 4 + +# Include doc comments from Rust as documentation +documentation = true + +# How the generated documentation should be commented. +# +# possible values: +# * "c": /* like this */ +# * "c99": // like this +# * "c++": /// like this +# * "doxy": like C, but with leading *'s on each line +# * "auto": "c++" if that's the language, "doxy" otherwise +# +# default: "auto" +documentation_style = "c" + +# How much of the documentation for each item is output. +# +# possible values: +# * "short": Only the first line. +# * "full": The full documentation. +# +# default: "full" +documentation_length = "full" + + + + +# Codegen Options + +# When generating a C header, the kind of declaration style to use for structs +# or enums. +# +# possible values: +# * "type": typedef struct { ... } MyType; +# * "tag": struct MyType { ... }; +# * "both": typedef struct MyType { ... } MyType; +# +# default: "both" +style = "both" + +# If this option is true `usize` and `isize` will be converted into `size_t` and `ptrdiff_t` +# instead of `uintptr_t` and `intptr_t` respectively. +usize_is_size_t = true + +# A list of substitutions for converting cfg's to ifdefs. cfgs which aren't +# defined here will just be discarded. +# +# e.g. +# `#[cfg(target = "freebsd")] ...` +# becomes +# `#if defined(DEFINE_FREEBSD) ... #endif` +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + + + + + +[export] +# A list of additional items to always include in the generated bindings if they're +# found but otherwise don't appear to be used by the public API. +# +# default: [] +include = [] + +# A list of items to not include in the generated bindings +# default: [] +exclude = [] + +# A prefix to add before the name of every item +# default: no prefix is added +prefix = "" + +# Types of items that we'll generate. If empty, then all types of item are emitted. +# +# possible items: (TODO: explain these in detail) +# * "constants": +# * "globals": +# * "enums": +# * "structs": +# * "unions": +# * "typedefs": +# * "opaque": +# * "functions": +# +# default: [] +item_types = [] + +# Whether applying rules in export.rename prevents export.prefix from applying. +# +# e.g. given this toml: +# +# [export] +# prefix = "capi_" +# [export.rename] +# "MyType" = "my_cool_type" +# +# You get the following results: +# +# renaming_overrides_prefixing = true: +# "MyType" => "my_cool_type" +# +# renaming_overrides_prefixing = false: +# "MyType => capi_my_cool_type" +# +# default: false +renaming_overrides_prefixing = true + +# Table of name conversions to apply to item names (lhs becomes rhs) +[export.rename] +# "MyType" = "my_cool_type" +# "my_function" = "BetterFunctionName" + +# Table of things to prepend to the body of any struct, union, or enum that has the +# given name. This can be used to add things like methods which don't change ABI, +# mark fields private, etc +[export.pre_body] +# "MyType" = """ + # MyType() = delete; +# private: +# """ + +# Table of things to append to the body of any struct, union, or enum that has the +# given name. This can be used to add things like methods which don't change ABI. +[export.body] +# "MyType" = """ + # void cppMethod() const; +# """ + +# Configuration for name mangling +[export.mangle] +# Whether the types should be renamed during mangling, for example +# c_char -> CChar, etc. +# rename_types = "PascalCase" +# Whether the underscores from the mangled name should be omitted. +# remove_underscores = false + +[layout] +# A string that should come before the name of any type which has been marked +# as `#[repr(packed)]`. For instance, "__attribute__((packed))" would be a +# reasonable value if targeting gcc/clang. A more portable solution would +# involve emitting the name of a macro which you define in a platform-specific +# way. e.g. "PACKED" +# +# default: `#[repr(packed)]` types will be treated as opaque, since it would +# be unsafe for C callers to use a incorrectly laid-out union. +packed = "__packed" + +# A string that should come before the name of any type which has been marked +# as `#[repr(align(n))]`. This string must be a function-like macro which takes +# a single argument (the requested alignment, `n`). For instance, a macro +# `#define`d as `ALIGNED(n)` in `header` which translates to +# `__attribute__((aligned(n)))` would be a reasonable value if targeting +# gcc/clang. +# +# default: `#[repr(align(n))]` types will be treated as opaque, since it +# could be unsafe for C callers to use a incorrectly-aligned union. +aligned_n = "__aligned" + + +[fn] +# An optional prefix to put before every function declaration +# default: no prefix added +prefix = "" + +# An optional postfix to put after any function declaration +# default: no postix added +postfix = "" + +# How to format function arguments +# +# possible values: +# * "horizontal": place all arguments on the same line +# * "vertical": place each argument on its own line +# * "auto": only use vertical if horizontal would exceed line_length +# +# default: "auto" +args = "horizontal" + +# An optional string that should prefix function declarations which have been +# marked as `#[must_use]`. For instance, "__attribute__((warn_unused_result))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "MUST_USE_FUNC" +# default: nothing is emitted for must_use functions +must_use = "__must_check" + +# An optional string that should prefix function declarations which have been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_FUNC" +# default: nothing is emitted for deprecated functions +deprecated = "__deprecated" + +# An optional string that should prefix function declarations which have been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_FUNC_WITH_NOTE(note)" +# default: nothing is emitted for deprecated functions +# deprecated_with_notes = "DEPRECATED_FUNC_WITH_NOTE" + +# An optional string that will be used in the attribute position for functions +# that don't return (that return `!` in Rust). +# +# For instance, `__attribute__((noreturn))` would be a reasonable value if +# targeting gcc/clang. +no_return = "__noreturn" + +# An optional string that, if present, will be used to generate Swift function +# and method signatures for generated functions, for example "CF_SWIFT_NAME". +# If no such macro is available in your toolchain, you can define one using the +# `header` option in cbindgen.toml +# default: no swift_name function attributes are generated +# swift_name_macro = "CF_SWIFT_NAME" + +# A rule to use to rename function argument names. The renaming assumes the input +# is the Rust standard snake_case, however it accepts all the different rename_args +# inputs. This means many options here are no-ops or redundant. +# +# possible values (that actually do something): +# * "CamelCase": my_arg => myArg +# * "PascalCase": my_arg => MyArg +# * "GeckoCase": my_arg => aMyArg +# * "ScreamingSnakeCase": my_arg => MY_ARG +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose here): +# * "SnakeCase": apply no renaming +# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?) +# * "UpperCase": same as ScreamingSnakeCase in this context +# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context +# +# default: "None" +# rename_args = "PascalCase" + +# This rule specifies the order in which functions will be sorted. +# +# "Name": sort by the name of the function +# "None": keep order in which the functions have been parsed +# +# default: "None" +sort_by = "Name" + +[struct] +# A rule to use to rename struct field names. The renaming assumes the input is +# the Rust standard snake_case, however it acccepts all the different rename_args +# inputs. This means many options here are no-ops or redundant. +# +# possible values (that actually do something): +# * "CamelCase": my_arg => myArg +# * "PascalCase": my_arg => MyArg +# * "GeckoCase": my_arg => mMyArg +# * "ScreamingSnakeCase": my_arg => MY_ARG +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose here): +# * "SnakeCase": apply no renaming +# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?) +# * "UpperCase": same as ScreamingSnakeCase in this context +# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context +# +# default: "None" +rename_fields = "None" + +# An optional string that should come before the name of any struct which has been +# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "MUST_USE_STRUCT" +# +# default: nothing is emitted for must_use structs +must_use = "__must_check" + +# An optional string that should come before the name of any struct which has been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_STRUCT" +# default: nothing is emitted for deprecated structs +deprecated = "__deprecated" + +# An optional string that should come before the name of any struct which has been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_STRUCT_WITH_NOTE(note)" +# default: nothing is emitted for deprecated structs +# deprecated_with_notes = "DEPRECATED_STRUCT_WITH_NOTE" + +# Whether a Rust type with associated consts should emit those consts inside the +# type's body. Otherwise they will be emitted trailing and with the type's name +# prefixed. This does nothing if the target is C, or if +# [const]allow_static_const = false +# +# default: false +# associated_constants_in_body: false + +# Whether to derive a simple constructor that takes a value for every field. +# default: false +derive_constructor = false + +# Whether to derive an operator== for all structs +# default: false +derive_eq = false + +# Whether to derive an operator!= for all structs +# default: false +derive_neq = false + +# Whether to derive an operator< for all structs +# default: false +derive_lt = false + +# Whether to derive an operator<= for all structs +# default: false +derive_lte = false + +# Whether to derive an operator> for all structs +# default: false +derive_gt = false + +# Whether to derive an operator>= for all structs +# default: false +derive_gte = false + + + + + +[enum] +# A rule to use to rename enum variants, and the names of any fields those +# variants have. This should probably be split up into two separate options, but +# for now, they're the same! See the documentation for `[struct]rename_fields` +# for how this applies to fields. Renaming of the variant assumes that the input +# is the Rust standard PascalCase. In the case of QualifiedScreamingSnakeCase, +# it also assumed that the enum's name is PascalCase. +# +# possible values (that actually do something): +# * "CamelCase": MyVariant => myVariant +# * "SnakeCase": MyVariant => my_variant +# * "ScreamingSnakeCase": MyVariant => MY_VARIANT +# * "QualifiedScreamingSnakeCase": MyVariant => ENUM_NAME_MY_VARIANT +# * "LowerCase": MyVariant => myvariant +# * "UpperCase": MyVariant => MYVARIANT +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose for the variants): +# * "PascalCase": apply no renaming +# * "GeckoCase": apply no renaming +# +# default: "None" +rename_variants = "None" + +# Whether an extra "sentinel" enum variant should be added to all generated enums. +# Firefox uses this for their IPC serialization library. +# +# WARNING: if the sentinel is ever passed into Rust, behaviour will be Undefined. +# Rust does not know about this value, and will assume it cannot happen. +# +# default: false +add_sentinel = false + +# Whether enum variant names should be prefixed with the name of the enum. +# default: false +prefix_with_name = true + +# Whether to emit enums using "enum class" when targeting C++. +# default: true +enum_class = true + +# Whether to generate static `::MyVariant(..)` constructors and `bool IsMyVariant()` +# methods for enums with fields. +# +# default: false +derive_helper_methods = false + +# Whether to generate `const MyVariant& AsMyVariant() const` methods for enums with fields. +# default: false +derive_const_casts = false + +# Whether to generate `MyVariant& AsMyVariant()` methods for enums with fields +# default: false +derive_mut_casts = false + +# The name of the macro/function to use for asserting `IsMyVariant()` in the body of +# derived `AsMyVariant()` cast methods. +# +# default: "assert" (but also causes `` to be included by default) +cast_assert_name = "" + +# An optional string that should come before the name of any enum which has been +# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "MUST_USE_ENUM" +# +# Note that this refers to the *output* type. That means this will not apply to an enum +# with fields, as it will be emitted as a struct. `[struct]must_use` will apply there. +# +# default: nothing is emitted for must_use enums +must_use = "__must_check" + +# An optional string that should come before the name of any enum which has been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_ENUM" +# default: nothing is emitted for deprecated enums +deprecated = "__deprecated" + +# An optional string that should come before the name of any enum which has been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_ENUM_WITH_NOTE(note)" +# default: nothing is emitted for deprecated enums +# deprecated_with_notes = "DEPRECATED_ENUM_WITH_NOTE" + +# An optional string that should come after the name of any enum variant which has been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution would +# involve emitting the name of a macro which you define in a platform-specific +# way. e.g. "DEPRECATED_ENUM_VARIANT" +# default: nothing is emitted for deprecated enum variants +deprecated_variant = "__deprecated" + +# An optional string that should come after the name of any enum variant which has been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" would be a +# reasonable value if targeting gcc/clang. A more portable solution would involve +# emitting the name of a macro which you define in a platform-specific +# way. e.g. "DEPRECATED_ENUM_WITH_NOTE(note)" +# default: nothing is emitted for deprecated enum variants +# deprecated_variant_with_notes = "DEPRECATED_ENUM_VARIANT_WITH_NOTE({})" + +# Whether enums with fields should generate destructors. This exists so that generic +# enums can be properly instantiated with payloads that are C++ types with +# destructors. This isn't necessary for structs because C++ has rules to +# automatically derive the correct constructors and destructors for those types. +# +# Care should be taken with this option, as Rust and C++ cannot +# properly interoperate with eachother's notions of destructors. Also, this may +# change the ABI for the type. Either your destructor-full enums must live +# exclusively within C++, or they must only be passed by-reference between +# C++ and Rust. +# +# default: false +derive_tagged_enum_destructor = false + +# Whether enums with fields should generate copy-constructor. See the discussion on +# derive_tagged_enum_destructor for why this is both useful and very dangerous. +# +# default: false +derive_tagged_enum_copy_constructor = false +# Whether enums with fields should generate copy-assignment operators. +# +# This depends on also deriving copy-constructors, and it is highly encouraged +# for this to be set to true. +# +# default: false +derive_tagged_enum_copy_assignment = false + +# Whether enums with fields should generate an empty, private destructor. +# This allows the auto-generated constructor functions to compile, if there are +# non-trivially constructible members. This falls in the same family of +# dangerousness as `derive_tagged_enum_copy_constructor` and co. +# +# default: false +private_default_tagged_enum_constructor = false + + + + + +[const] +# Whether a generated constant can be a static const in C++ mode. I have no +# idea why you would turn this off. +# +# default: true +allow_static_const = true + +# Whether a generated constant can be constexpr in C++ mode. +# +# default: true +allow_constexpr = true + +# This rule specifies the order in which constants will be sorted. +# +# "Name": sort by the name of the constant +# "None": keep order in which the constants have been parsed +# +# default: "None" +sort_by = "Name" + + + + +[macro_expansion] +# Whether bindings should be generated for instances of the bitflags! macro. +# default: false +bitflags = true + + + + + + +# Options for how your Rust library should be parsed + +[parse] +# Whether to parse dependent crates and include their types in the output +# default: false +parse_deps = true + +# A white list of crate names that are allowed to be parsed. If this is defined, +# only crates found in this list will ever be parsed. +# +# default: there is no whitelist (NOTE: this is the opposite of []) +# include = [] + +# A black list of crate names that are not allowed to be parsed. +# default: [] +exclude = [] + +# Whether to use a new temporary target directory when running `rustc -Zunpretty=expanded`. +# This may be required for some build processes. +# +# default: false +clean = false + +# Which crates other than the top-level binding crate we should generate +# bindings for. +# +# default: [] +extra_bindings = [] + +[parse.expand] +# A list of crate names that should be run through `cargo expand` before +# parsing to expand any macros. Note that if a crate is named here, it +# will always be parsed, even if the blacklist/whitelist says it shouldn't be. +# +# default: [] +crates = [] + +# If enabled, use the `--all-features` option when expanding. Ignored when +# `features` is set. For backwards-compatibility, this is forced on if +# `expand = ["euclid"]` shorthand is used. +# +# default: false +all_features = false + +# When `all_features` is disabled and this is also disabled, use the +# `--no-default-features` option when expanding. +# +# default: true +default_features = true + +# A list of feature names that should be used when running `cargo expand`. This +# combines with `default_features` like in your `Cargo.toml`. Note that the features +# listed here are features for the current crate being built, *not* the crates +# being expanded. The crate's `Cargo.toml` must take care of enabling the +# appropriate features in its dependencies +# +# default: [] +features = [] + +[ptr] +# An optional string to decorate all pointers that are +# required to be non null. Nullability is inferred from the Rust type: `&T`, +# `&mut T` and `NonNull` all require a valid pointer value. +# non_null_attribute = "_Nonnull" + +# Options specific to Cython bindings. + +[cython] + +# Header specified in the top level `cdef extern from header:` declaration. +# +# default: * +# header = '"my_header.h"' + +# `from module cimport name1, name2` declarations added in the same place +# where you'd get includes in C. +[cython.cimports] +module = [] diff --git a/lisa/_assets/kmodules/lisa/rust/runtime.c b/lisa/_assets/kmodules/lisa/rust/runtime.c new file mode 100644 index 0000000000000000000000000000000000000000..8b66b0a835879f71e63a2600bee354683a545cb8 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/runtime.c @@ -0,0 +1,47 @@ +/* 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 new file mode 100644 index 0000000000000000000000000000000000000000..0f465a6ff5aa47fad9413609634bb49ce858079f --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/rust.lds @@ -0,0 +1,2 @@ +/* Workaround this issue: https://github.com/rust-lang/rust/issues/125619 */ +PROVIDE(__builtin_copysignq = 0); diff --git a/lisa/_assets/kmodules/lisa/rust/rustc_targets/README.md b/lisa/_assets/kmodules/lisa/rust/rustc_targets/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6915e0bf5f6442e7ca65ce139d0c7daac338bdce --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/rustc_targets/README.md @@ -0,0 +1,13 @@ + +This folder contains rustc target JSON definitions. They are equivalent to a +custom --target triplet passed to rustc. + +In order to establish one, the easiest way is start from an existing target +that is close enough (typically a bare-metal target) and adapt it for kernel +use, e.g.: + +$ rustc +nightly -Z unstable-options --print target-spec-json --target=$TRIPLET + +Note that these JSON targets are barebone on purpose to be easily maintainable. +Any architecture-agnostic options such as -Cno-redzone=y or the +relocation-model are passed as RUSTFLAGS in the Makefile. diff --git a/lisa/_assets/kmodules/lisa/rust/rustc_targets/arm64/target.json b/lisa/_assets/kmodules/lisa/rust/rustc_targets/arm64/target.json new file mode 100644 index 0000000000000000000000000000000000000000..37a7d69d2a1ffe100ff59ea8d04485fb6936e944 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/rustc_targets/arm64/target.json @@ -0,0 +1,15 @@ +{ + "abi": "softfloat", + "arch": "aarch64", + "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", + "features": "+v8a,+strict-align,-neon,-fp-armv8", + "llvm-target": "aarch64-unknown-none", + "max-atomic-width": 128, + "stack-probes": { + "kind": "none" + }, + "target-pointer-width": "64", + "supported-sanitizers": [ + "kcfi" + ] +} diff --git a/lisa/_assets/kmodules/lisa/rust/rustc_targets/x86_64/target.json b/lisa/_assets/kmodules/lisa/rust/rustc_targets/x86_64/target.json new file mode 100644 index 0000000000000000000000000000000000000000..5efdbe352768f19a44578739b599a4d7c376e59b --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/rustc_targets/x86_64/target.json @@ -0,0 +1,13 @@ +{ + "arch": "x86_64", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float,+retpoline-external-thunk", + "llvm-target": "x86_64-linux-gnu", + "target-pointer-width": "64", + "stack-probes": {"kind": "none"}, + "code-model": "kernel", + "supported-sanitizers": [ + "kcfi" + ] +} + diff --git a/lisa/_assets/kmodules/lisa/rust/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..16ccb34d1a7e3b14c02c1de4ab20af50f15dee6d --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/src/lib.rs @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#![no_std] +#![no_builtins] +extern crate alloc; + +pub mod runtime; +pub mod validate; diff --git a/lisa/_assets/kmodules/lisa/rust/src/runtime.rs b/lisa/_assets/kmodules/lisa/rust/src/runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e3721ca21bda509242cd6bc9e161e69596851b5 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/src/runtime.rs @@ -0,0 +1,86 @@ +/* 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) { + f(layout.size()) + } 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 new file mode 100644 index 0000000000000000000000000000000000000000..b5d244335f34c6987a0f461dcad7004a44b6f2dd --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/src/validate.rs @@ -0,0 +1,35 @@ +/* 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 new file mode 100644 index 0000000000000000000000000000000000000000..29485d8ad2c3caf95ce5ac46077661b41aa2aa5b --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/validate.c @@ -0,0 +1,35 @@ +/* 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/lisa/_assets/kmodules/lisa/rust/validate.h b/lisa/_assets/kmodules/lisa/rust/validate.h new file mode 100644 index 0000000000000000000000000000000000000000..442efa9842199364cff9d00896d2647f7ebf175e --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/validate.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef RUST_VALIDATE_H +#define RUST_VALIDATE_H + +#include "rust/cbindgen.h" + +int rust_validate(void); + +#endif /* RUST_VALIDATE_H */ diff --git a/lisa/_kmod.py b/lisa/_kmod.py index ae994f1ccc546730ba12aa6568713c1512d37d02..471f1bce6cfdf21c4a51029866f1147cc2284802 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -176,7 +176,7 @@ class KmodVersionError(Exception): pass -_ALPINE_DEFAULT_VERSION = '3.20.0' +_ALPINE_DEFAULT_VERSION = '3.20.3' _ALPINE_ROOTFS_URL = 'https://dl-cdn.alpinelinux.org/alpine/v{minor}/releases/{arch}/alpine-minirootfs-{version}-{arch}.tar.gz' _ALPINE_PACKAGE_INFO_URL = 'https://pkgs.alpinelinux.org/package/v{version}/{repo}/{arch}/{package}' @@ -320,7 +320,7 @@ def _kbuild_make_cmd(path, targets, cc, make_vars): ) ] - nr_cpus = int(os.cpu_count() * 1.5) + nr_cpus = os.cpu_count() cmd = ['make', f'-j{nr_cpus}', '-C', path, '--', *formatted_vars, *targets] @@ -354,6 +354,36 @@ def _clang_version(cc, env): raise ValueError(f'Could not determine version of {cc}') +def _install_rust(rust_spec, run_cmd): + rust_version = rust_spec['version'] + components = sorted(rust_spec.get('components', [])) + crates = rust_spec.get('crates', {}) + + rustup_home = str(rust_spec['rustup_home']) + cargo_home = str(rust_spec['cargo_home']) + cargo_bin = str(Path(cargo_home) / 'bin') + + def run(cmd): + cmd = f'export PATH={cargo_bin}:$PATH RUSTC_BOOTSTRAP=1 RUSTUP_INIT_SKIP_PATH_CHECK=yes RUSTUP_HOME={quote(rustup_home)} CARGO_HOME={quote(cargo_home)} && {cmd}' + run_cmd(['sh', '-c', cmd]) + + # Install rustup + with urllib.request.urlopen('https://sh.rustup.rs') as response: + script = response.read() + script = script.decode('utf-8') + run(f'sh -c {quote(script)} -- -y --no-modify-path --default-toolchain none') + + # Install rust toolchain + components = ' '.join( + f'--component={quote(compo)}' + for compo in components + ) + run(f"rustup toolchain install {quote(rust_version)} --profile minimal {components}") + + for crate, crate_spec in sorted(crates.items()): + run(f"cargo +{rust_version} install --locked --version {quote(crate_spec['version'])} {quote(crate)}") + + def _resolve_alpine_version(version): version = version or _ALPINE_DEFAULT_VERSION @@ -364,7 +394,7 @@ def _resolve_alpine_version(version): @destroyablecontextmanager -def _make_build_chroot(cc, cross_compile, abi, bind_paths=None, version=None, overlay_backend=None, packages=None): +def _make_build_chroot(cc, cross_compile, abi, bind_paths=None, version=None, overlay_backend=None, packages=None, rust_spec=None): """ Create a chroot folder ready to be used to build a kernel. """ @@ -405,6 +435,9 @@ def _make_build_chroot(cc, cross_compile, abi, bind_paths=None, version=None, ov 'perl', 'pahole', 'git', + + # This is needed by the LISA kernel module + 'jq', ] if is_clang(cc): @@ -456,6 +489,7 @@ def _make_build_chroot(cc, cross_compile, abi, bind_paths=None, version=None, ov packages=packages, bind_paths=bind_paths, overlay_backend=overlay_backend, + rust_spec=rust_spec, ) as chroot: try: yield chroot @@ -464,7 +498,7 @@ def _make_build_chroot(cc, cross_compile, abi, bind_paths=None, version=None, ov @destroyablecontextmanager -def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overlay_backend='overlayfs'): +def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overlay_backend='overlayfs', rust_spec=None): logger = logging.getLogger(f'{__name__}.alpine_chroot') def mount_binds(chroot, bind_paths, mount=True): @@ -509,12 +543,23 @@ def _make_alpine_chroot(version, packages=None, abi=None, bind_paths=None, overl shutil.copy('/etc/resolv.conf', path / 'etc' / 'resolv.conf') + def run_cmd(cmd): + cmd = _make_build_chroot_cmd(path, cmd) + _subprocess_log(cmd, logger=logger, level=logging.DEBUG) + def install_packages(packages): if packages: - cmd = _make_build_chroot_cmd(path, ['apk', 'add', *sorted(set(packages))]) - _subprocess_log(cmd, logger=logger, level=logging.DEBUG) + run_cmd(['apk', 'add', *sorted(set(packages))]) + + def install_rust(): + if rust_spec: + _install_rust( + rust_spec=rust_spec, + run_cmd=run_cmd, + ) install_packages(packages) + install_rust() abi = abi or LISA_HOST_ABI use_qemu = abi != LISA_HOST_ABI @@ -2222,6 +2267,13 @@ class KmodSrc(Loggable): will be created using a checksum of the sources. :type name: str or None """ + + _RUST_SPEC = None + """ + Rust version and components to install in the build environment. + """ + + def __init__(self, src, name=None): def encode(x): if isinstance(x, str): @@ -2242,7 +2294,10 @@ class KmodSrc(Loggable): return { name: content for name, content in self.src.items() - if name.endswith('.c') or name.endswith('.h') + if any( + name.endswith(extension) + for extension in ('.c', '.h', '.rs') + ) } @property @@ -2347,7 +2402,7 @@ class KmodSrc(Loggable): for name, content in src.items(): file_path = mod_path / name - file_path.parent.mkdir(exist_ok=True) + file_path.parent.mkdir(parents=True, exist_ok=True) with open(file_path, 'wb') as f: f.write(content) @@ -2380,6 +2435,7 @@ class KmodSrc(Loggable): else: return filenames[0] + rust_spec = self._RUST_SPEC or {} if build_conf['build-env'] == 'alpine': settings = build_conf['build-env-settings']['alpine'] alpine_version = settings.get('version', None) @@ -2387,6 +2443,24 @@ class KmodSrc(Loggable): @contextlib.contextmanager def cmd_cm(): + if rust_spec: + rust_home = Path('/opt/rust') + rustup_home = rust_home / 'rustup' + cargo_home = rust_home / 'cargo' + _rust_spec = { + **rust_spec, + 'rustup_home': rustup_home, + 'cargo_home': cargo_home, + } + rust_env = { + 'RUSTUP_HOME': rustup_home, + 'CARGO_HOME': cargo_home, + 'RUST_VERSION': rust_spec['version'], + } + else: + rust_env = {} + _rust_spec = None + with _make_build_chroot( cc=cc.name, cross_compile=cross_compile, @@ -2395,6 +2469,7 @@ class KmodSrc(Loggable): overlay_backend=build_conf['overlay-backend'], version=alpine_version, packages=alpine_packages, + rust_spec=_rust_spec, ) as chroot: # Do not use a CM here to avoid choking on permission # issues. Since the chroot itself will be entirely @@ -2403,17 +2478,53 @@ class KmodSrc(Loggable): cmd = make_cmd( tree_path=tree_path, mod_path=f'/{mod_path.relative_to(chroot)}', - make_vars=make_vars, + make_vars={ + **make_vars, + **rust_env, + } ) yield (mod_path, _make_build_chroot_cmd(chroot, cmd)) elif build_conf['build-env'] == 'host': + def install_rust(rust_spec): + def populate(key, path): + rust_spec = { + **dict(key), + 'rustup_home': path / 'rustup', + 'cargo_home': path / 'cargo', + } + _install_rust( + rust_spec=rust_spec, + run_cmd=lambda cmd: _subprocess_log(cmd, logger=logger, level=logging.DEBUG) + ) + + dir_cache = DirCache( + category='rust_home', + populate=populate, + ) + key = sorted(rust_spec.items()) + cache_path = dir_cache.get_entry(key) + return cache_path + @contextlib.contextmanager def cmd_cm(): + if rust_spec: + rust_home = install_rust(rust_spec) + rust_env = { + 'RUSTUP_HOME': rust_home / 'rustup', + 'CARGO_HOME': rust_home / 'cargo', + 'RUST_VERSION': rust_spec['version'], + } + else: + rust_env = {} + with tempfile.TemporaryDirectory() as mod_path: cmd = make_cmd( tree_path=tree_path, mod_path=mod_path, - make_vars=make_vars, + make_vars={ + **make_vars, + **rust_env, + }, ) yield (mod_path, cmd) @@ -2744,7 +2855,7 @@ class DynamicKmod(Loggable): Unload the module from the target. """ mod = quote(self.mod_name) - execute = self.target.execute + execute = lambda cmd: self.target.execute(cmd, as_root=True) try: execute(f'rmmod {mod}') @@ -2845,6 +2956,19 @@ class FtraceDynamicKmod(DynamicKmod): }) +class _LISADynamicKmodSrc(KmodSrc): + _RUST_SPEC = dict( + version='1.81.0', + components=[ + # rust-docs to list the C public API (pub #[no_mangle] functions) + # to avoid exporting a thousand symbols from Rust libcore. + 'rust-docs', + # rust-src for -Zbuild-std + 'rust-src', + ] + ) + + class LISADynamicKmod(FtraceDynamicKmod): """ Module providing ftrace events used in various places by :mod:`lisa`. @@ -2918,7 +3042,7 @@ class LISADynamicKmod(FtraceDynamicKmod): ) logger.debug(f'Variable sources checksum of the {cls.__qualname__} module: {extra_checksum}') - src = KmodSrc.from_path(path, extra=extra, name=mod_name) + src = _LISADynamicKmodSrc.from_path(path, extra=extra, name=mod_name) return cls( target=target, src=src, diff --git a/lisa/utils.py b/lisa/utils.py index 6076d9f2679a09e92a8c4e7262cc3bb3ce750d62..471d506cc3e9f8a2b444731028ef722557882302 100644 --- a/lisa/utils.py +++ b/lisa/utils.py @@ -4248,14 +4248,22 @@ def subprocess_log(cmd, level=None, name=None, logger=None, **kwargs): :Variable keyword arguments: Forwarded to :class:`subprocess.Popen`. """ + def crop(max_len, s, cropped_len=None): + cropped_len = cropped_len or max_len // 2 + if len(s) > max_len: + middle = ' [...] ' + half = (cropped_len - len(middle)) // 2 + return s[:half] + middle + s[-half:] + else: + return s + if isinstance(cmd, str): pretty_cmd = cmd else: pretty_cmd = ' '.join(map(shlex.quote, map(str, cmd))) - if not name: - crop = 20 - name = pretty_cmd[:crop] + ('...' if len(pretty_cmd) > crop else '') + name = name or crop(20, pretty_cmd) + pretty_cmd = crop(80 * 10, pretty_cmd, cropped_len=80) logger = logger or logging.getLogger() logger = logger.getChild(name) diff --git a/tools/kmodules/lisa/__init__.py b/tools/kmodules/lisa/__init__.py new file mode 120000 index 0000000000000000000000000000000000000000..46c626429e47fd638450ed5d7e2db5a30f53a207 --- /dev/null +++ b/tools/kmodules/lisa/__init__.py @@ -0,0 +1 @@ +../../../lisa/_assets/kmodules/lisa/__init__.py \ No newline at end of file diff --git a/tools/kmodules/lisa/lisa b/tools/kmodules/lisa/lisa deleted file mode 120000 index bc9aaba04213387df121db043b115bce662e89d0..0000000000000000000000000000000000000000 --- a/tools/kmodules/lisa/lisa +++ /dev/null @@ -1 +0,0 @@ -../../../lisa/_assets/kmodules/lisa/lisa/ \ No newline at end of file diff --git a/tools/kmodules/lisa/lisa/__init__.py b/tools/kmodules/lisa/lisa/__init__.py new file mode 120000 index 0000000000000000000000000000000000000000..05a000d343b792347ed83ff01fa8e88359fbd4e5 --- /dev/null +++ b/tools/kmodules/lisa/lisa/__init__.py @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/lisa/__init__.py \ No newline at end of file diff --git a/tools/kmodules/lisa/lisa/_btf.py b/tools/kmodules/lisa/lisa/_btf.py new file mode 120000 index 0000000000000000000000000000000000000000..b587ad4471124e336822ecf7be1609cdc7356c93 --- /dev/null +++ b/tools/kmodules/lisa/lisa/_btf.py @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/lisa/_btf.py \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/Cargo.toml b/tools/kmodules/lisa/rust/Cargo.toml new file mode 120000 index 0000000000000000000000000000000000000000..6ae4b4b63643e30e8bf6db05f866222370dbc138 --- /dev/null +++ b/tools/kmodules/lisa/rust/Cargo.toml @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/Cargo.toml \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/cbindgen.h b/tools/kmodules/lisa/rust/cbindgen.h new file mode 120000 index 0000000000000000000000000000000000000000..fd07c8fd8e88ad9f07f5e1acf1040a50b9170e87 --- /dev/null +++ b/tools/kmodules/lisa/rust/cbindgen.h @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/cbindgen.h \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/cbindgen.toml b/tools/kmodules/lisa/rust/cbindgen.toml new file mode 120000 index 0000000000000000000000000000000000000000..b23765cc6554755685a8b3cad71ecb012b55cd4f --- /dev/null +++ b/tools/kmodules/lisa/rust/cbindgen.toml @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/cbindgen.toml \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/runtime.c b/tools/kmodules/lisa/rust/runtime.c new file mode 120000 index 0000000000000000000000000000000000000000..738c2c23be3de6ddb4f60c1d5c80757455de0fb1 --- /dev/null +++ b/tools/kmodules/lisa/rust/runtime.c @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/runtime.c \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/rust.lds b/tools/kmodules/lisa/rust/rust.lds new file mode 120000 index 0000000000000000000000000000000000000000..f35eea9970ff886dbb0f9b44ed589a14ae12a79b --- /dev/null +++ b/tools/kmodules/lisa/rust/rust.lds @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/rust.lds \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/rustc_targets/README.md b/tools/kmodules/lisa/rust/rustc_targets/README.md new file mode 120000 index 0000000000000000000000000000000000000000..45758693066edbe159f19443c1e4a12dafee376a --- /dev/null +++ b/tools/kmodules/lisa/rust/rustc_targets/README.md @@ -0,0 +1 @@ +../../../../../lisa/_assets/kmodules/lisa/rust/rustc_targets/README.md \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/rustc_targets/arm64/target.json b/tools/kmodules/lisa/rust/rustc_targets/arm64/target.json new file mode 120000 index 0000000000000000000000000000000000000000..4806882441ff1c8df2c6c3095867e11ce083a73a --- /dev/null +++ b/tools/kmodules/lisa/rust/rustc_targets/arm64/target.json @@ -0,0 +1 @@ +../../../../../../lisa/_assets/kmodules/lisa/rust/rustc_targets/arm64/target.json \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/rustc_targets/x86_64/target.json b/tools/kmodules/lisa/rust/rustc_targets/x86_64/target.json new file mode 120000 index 0000000000000000000000000000000000000000..5118601603ace52f40ac21c78566824081f9fcf0 --- /dev/null +++ b/tools/kmodules/lisa/rust/rustc_targets/x86_64/target.json @@ -0,0 +1 @@ +../../../../../../lisa/_assets/kmodules/lisa/rust/rustc_targets/x86_64/target.json \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/src/lib.rs b/tools/kmodules/lisa/rust/src/lib.rs new file mode 120000 index 0000000000000000000000000000000000000000..b97a26decf775858758d2da4f14a3016d79f935d --- /dev/null +++ b/tools/kmodules/lisa/rust/src/lib.rs @@ -0,0 +1 @@ +../../../../../lisa/_assets/kmodules/lisa/rust/src/lib.rs \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/src/runtime.rs b/tools/kmodules/lisa/rust/src/runtime.rs new file mode 120000 index 0000000000000000000000000000000000000000..ba5779561df9308e65c622d2ae9f8a8989173a3a --- /dev/null +++ b/tools/kmodules/lisa/rust/src/runtime.rs @@ -0,0 +1 @@ +../../../../../lisa/_assets/kmodules/lisa/rust/src/runtime.rs \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/src/validate.rs b/tools/kmodules/lisa/rust/src/validate.rs new file mode 120000 index 0000000000000000000000000000000000000000..20cd74f5d0b008df781736aff10e01c56d24a780 --- /dev/null +++ b/tools/kmodules/lisa/rust/src/validate.rs @@ -0,0 +1 @@ +../../../../../lisa/_assets/kmodules/lisa/rust/src/validate.rs \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/validate.c b/tools/kmodules/lisa/rust/validate.c new file mode 120000 index 0000000000000000000000000000000000000000..c3d4e8f9c74261e583243fb3991142406f080d4d --- /dev/null +++ b/tools/kmodules/lisa/rust/validate.c @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/validate.c \ No newline at end of file diff --git a/tools/kmodules/lisa/rust/validate.h b/tools/kmodules/lisa/rust/validate.h new file mode 120000 index 0000000000000000000000000000000000000000..20431510757ea9e7bb5d88944be6d8498e681313 --- /dev/null +++ b/tools/kmodules/lisa/rust/validate.h @@ -0,0 +1 @@ +../../../../lisa/_assets/kmodules/lisa/rust/validate.h \ No newline at end of file diff --git a/tools/kmodules/lisa/utils.h b/tools/kmodules/lisa/utils.h new file mode 120000 index 0000000000000000000000000000000000000000..ea4728519b85910d0d12ffe27196d53721b9ba89 --- /dev/null +++ b/tools/kmodules/lisa/utils.h @@ -0,0 +1 @@ +../../../lisa/_assets/kmodules/lisa/utils.h \ No newline at end of file diff --git a/tools/tests.sh b/tools/tests.sh index 357d85fe4881c784725cf98388333156ef6018d3..e8e770e6baf477daa67d5ad244962c0b5ddb5b60 100755 --- a/tools/tests.sh +++ b/tools/tests.sh @@ -39,8 +39,20 @@ echo "Starting documentation pedantic build ..." lisa-doc-build echo "Checking that the man pages are up to date ..." - if ! git diff --exit-code doc/man1/; then echo "Please regenerate man pages in doc/man1 and commit them" 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 +) +