Rust in Linux part two
I want to retry using Rust in the linux kernel, which didn't work well before.
Recently, I've come across some new articles about creating LKMs with Rust, so I'm hoping that by following them, it will work well this time.
To-Do
Last time, I tried it on a VM with Fedora, but this time I will use Arch Linux on a physical machine.
Since the default linux package doesn't have the options enabled for using Rust, I need to build the kernel.
grep CONFIG_RUST /usr/src/linux/.config
# No output
I will refer to the article and try to install module, rust_minimal.rs, which is written in the kernel.
Build directory of LKM is as follows:
.
├── Makefile
└── src
├── rust_minimal.rs
└── Makefile
KVER ?= $(shell uname -r)
KDIR ?= /usr/lib/modules/$(KVER)/build
RUSTFMT = rustfmt
RUST_FLAGS = CROSS_COMPILE=x86_64-linux-gnu-
RUST_FLAGS += HOSTRUSTC=rustc
RUST_FLAGS += RUSTC=rustc
RUST_FLAGS += BINDGEN=bindgen
RUST_FLAGS += RUSTFMT=$(RUSTFMT)
RUST_FLAGS += RUST_LIB_SRC=$(shell rustc --print sysroot)/lib/rustlib/src/rust/library
default:
$(MAKE) LLVM=1 $(RUST_FLAGS) -C $(KDIR) M=$$PWD/src
install: default
kmodsign sha512 \
/var/lib/shim-signed/mok/MOK.priv \
/var/lib/shim-signed/mok/MOK.der \
src/hello.ko
$(MAKE) -C $(KDIR) M=$$PWD/src modules_install
depmod -A
fmt:
find . -type f -name '*.rs' | xargs $(RUSTFMT)
clean:
$(MAKE) $(RUNST_FLAGS) -C $(KDIR) M=$$PWD/src clean
// SPDX-License-Identifier: GPL-2.0
//! Rust minimal sample.
use kernel::prelude::*;
module! {
type: RustMinimal,
name: "rust_minimal",
author: "Rust for Linux Contributors",
description: "Rust minimal sample",
license: "GPL",
}
struct RustMinimal {
numbers: Vec<i32>,
}
impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));
let mut numbers = Vec::new();
numbers.try_push(72)?;
numbers.try_push(108)?;
numbers.try_push(200)?;
Ok(RustMinimal { numbers })
}
}
impl Drop for RustMinimal {
fn drop(&mut self) {
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
}
}
obj-m := rust_minimal.o
The following is a record of trial and error.
Method 1: Using AUR's linux-rust (Unsuccessful)
I found a kernel (v6.6) on AUR that seems to have Rust enabled in the kernel build.
It is possible to install the package simply by running paru -S linux-rust
.
However, it had some issues issues such as make not having options for multi-threaded build, and building documentation is included by default, and I don't need it.
So I made some adjustments before building.
Build
Retrieve PKGBUILD:
git clone git@github.com:rnestler/archpkg-linux-rust.git
Edit PKGBUILD:
diff --git a/PKGBUILD b/PKGBUILD
index 7f0db73..b859ccc 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -90,8 +90,8 @@ prepare() {
build() {
cd $_srcname
- make LLVM=1 all
- make LLVM=1 htmldocs
+ make LLVM=1 all -j5
+ # make LLVM=1 htmldocs
}
_package() {
@@ -245,7 +245,7 @@ _package-docs() {
pkgname=(
"$pkgbase"
"$pkgbase-headers"
- "$pkgbase-docs"
+ # "$pkgbase-docs"
)
for _p in "${pkgname[@]}"; do
eval "package_$_p() {
Build:
makepkg -s
During the build, an error occurred in _package-headers()
when creating the headers package.
The error said that target.json
was missing.
_package-headers() {
...
# Rust support
echo "Installing Rust files..."
install -Dt "$builddir/rust" -m644 scripts/target.json
install -Dt "$builddir/rust" -m644 rust/*.rmeta
install -Dt "$builddir/rust" -m644 rust/*.so
install -Dt "$builddir" -m644 ../../rust-toolchain
...
}
Indeed, the scripts/target.json
file was missing in the build directory.
target.json
seems to be a file used for Rust cross-compile settings and should look like the following (from https://github.com/archlinux/linux):
{
"arch": "x86_64",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
"features": "-3dnow,-3dnowa,-mmx,+soft-float,+retpoline-external-thunk",
"llvm-target": "x86_64-linux-gnu",
"target-pointer-width": "64",
"emit-debug-gdb-scripts": false,
"frame-pointer": "may-omit",
"stack-probes": {"kind": "none"}
}
I proceeded with the kernel build again, and this time it completed without issues.
Install the generated *.pkg.tar.zst
:
sudo pacman -U linux-rust-6.6.10.arch1-1-x86_64.pkg.tar.zst linux-rust-headers-6.6.10.arch1-1-x86_64.pkg.tar.zst
Setup bootloader
For systemd-boot, add the entry:
sudo nvim /boot/loader/entries/linux-rust.conf
title Arch Linux (linux-rust)
linux /vmlinuz-linux-rust
initrd /initramfs-linux-rust.img
options <options>
For GRUB:
sudo grub-mkconfig -o /boot/grub/grub.cfg
Reboot.
Building LKM
I installed this kernel and attempted to create an LKM.
However, the module build failed (I forgot to record the error log, but in essence, it couldn't resolve dependencies like the core crate).
In the Makefile, RUST_LIB_SRC
is set as $(shell rustc --print sysroot)/lib/rustlib/src/rust/library
, and checking this directory, I found the core
crate and other essential crates were existing.
I suspected that RUST_LIB_SRC
was not set correctly, so I investigated how it was used.
Searching in the Linux kernel cloned from GitHub, I found that RUST_LIB_SRC
was used in rust/Makefile
as follows:
% fd -tf -x grep -Hni --color=always "RUST_LIB_SRC"
./Makefile:584:ifdef RUST_LIB_SRC
./Makefile:585: export RUST_LIB_SRC
./scripts/rust_is_available_test.py:271: result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
./scripts/rust_is_available.sh:253:rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}
./rust/Makefile:45:RUST_LIB_SRC ?= $(rustc_sysroot)/lib/rustlib/src/rust/library
./rust/Makefile:112:rustdoc-core: $(RUST_LIB_SRC)/core/src/lib.rs FORCE
./rust/Makefile:411: $(RUST_LIB_SRC) $(KBUILD_EXTMOD) > \
./rust/Makefile:431:$(obj)/core.o: $(RUST_LIB_SRC)/core/src/lib.rs scripts/target.json FORCE
However, when searching in /usr/lib/modules/$(KVER)/build
, I couldn't find rust/
directory in the first place.
% fd -tf -x grep -Hni --color=always "RUST_LIB_SRC"
./Makefile:584:ifdef RUST_LIB_SRC
./Makefile:585: export RUST_LIB_SRC
./scripts/rust_is_available_test.py:271: result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
./scripts/rust_is_available.sh:253:rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}
This is likely the cause of the failed LKM build.
I tried copying the rust/
directory and rebuild the kernel, but the issue persisted.
Method 2: Building the Kernel Yourself (failed)
Although it's a bit cumbersome, let's attempt to build the kernel manually.
Clone the archlinux/linux repository to build the kernel.
Build
Firstly, enable CONFIG_RUST in menuconfig.
There are various dependent options, so search with /
to verify and enable them as needed.
make LLVM=1 olddefconfig
make LLVM=1 menuconfig
Once the configuration file is created, proceed with the build.
make LLVM=1 -j5
Install the modules and copy the kernel.
sudo make LLVM=1 modules_install
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-linux-rust-enabled
Setup Bootloader
Configure the bootloader as in Method 1.
sudo nvim /boot/loader/entries/linux-rust-enabled.conf
title Arch Linux (linux-rust-enabled)
linux /vmlinuz-linux-rust-enabled
initrd /initramfs-linux-rust-enabled.img
options <options>
Create the initramfs.
sudo mkinitcpio -p linux-rust-enabled
Reboot...
It doesn't boot.
When selecting linux-rust-enabled with systemd-boot, the following log appears on the screen, and it hangs.
EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
EFI stub: Measured initrd data into PCR 9
For now, revert to the regular kernel for investigation.
Upon closer inspection of the log generated during initramfs creation, the following warning caught my attention:
% sudo mkinitcpio -p linux-rust-enabled
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled.img --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled.img'
==> WARNING: errors were encountered during the build. The image may not be complete.
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled-fallback.img -S autodetect --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled-fallback.img'
==> WARNING: errors were encountered during the build. The image may not be complete.
Although warnings like ==> WARNING: consolefont: no font found in configuration
were present even with the regular kernel, ==> WARNING: errors were encountered during the build. The image may not be complete.
was not present, and ==> Image generation successful
was reported.
It seemed like some modules might be missing, and based on the log like RPM 9
, I suspected that TPM modules might be necessary.
So, I tried searching for related options in menuconfig and enabling them.
The result was the same log, and the system did not boot correctly.
(I checked journalctl later to see if systemd-boot logs were present, but I couldn't find any relevant logs.)
Method 3: Building the Kernel Yourself (Successful Version)
As it seems there might be missing kernel configs, let's try utilizing the config from linux-rust.
While the linux-rust kernel failed to build modules, it could boot, so it should include the necessary drivers for booting.
Copy the .config
file and modify a few options, including the Rust version, then proceed with the build using the archlinux/linux base code.
Build
After placing the config, build again (LLVM=1
is only needed with make -j5
perhaps?).
make LLVM=1 olddefconfig
make LLVM=1 menuconfig
make LLVM=1 -j5
sudo make LLVM=1 modules_install
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-linux-rust-enabled
While reviewing the config, I realized that nvme driver is not enabled in the configuration file that it used when failed. So, I suspect that caused the boot failure.
Setup Bootloader
Configure the bootloader as before.
sudo nvim /boot/loader/entries/linux-rust-enabled.conf
title Arch Linux (linux-rust-enabled)
linux /vmlinuz-linux-rust-enabled
initrd /initramfs-linux-rust-enabled.img
options <options>
Create the initramfs (I omitted creating the fallback image this time because unnecessary kernel config was enabled, and the fallback image size was too large to fit in the /boot partition. There is room for improvement).
% sudo mkinitcpio -p linux-rust-enabled
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled.img --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled.img'
==> Image generation successful
This time, it seems to have been created successfully, and after a reboot, it booted correctly.
Creating LKM
As mentioned earlier, I try to build rust_minimal.rs as a module.
When I run make
in the module directory, it succeeds this time.
% make
make LLVM=1 CROSS_COMPILE=x86_64-linux-gnu- HOSTRUSTC=rustc RUSTC=rustc BINDGEN=bindgen RUSTFMT=rustfmt RUST_LIB_SRC=/home/mori/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library -C /usr/lib/modules/6.7.1-arch1/build M=$PWD/src
make[1]: Entering directory '/home/mori/workspace/archlinux-kernel'
RUSTC [M] /home/mori/workspace/rust-module/module/src/rust_minimal.o
MODPOST /home/mori/workspace/rust-module/module/src/Module.symvers
CC [M] /home/mori/workspace/rust-module/module/src/rust_minimal.mod.o
LD [M] /home/mori/workspace/rust-module/module/src/rust_minimal.ko
BTF [M] /home/mori/workspace/rust-module/module/src/rust_minimal.ko
die__process_class: tag not supported 0x33 (variant_part)!
die__process_class: tag not supported 0x2f (template_type_parameter)!
make[1]: Leaving directory '/home/mori/workspace/archlinux-kernel'
Load and unload the module.
sudo insmod src/rust_minimal.ko
sudo rmmod rust_minimal
[ 160.098129] rust_minimal: loading out-of-tree module taints kernel.
[ 160.098132] rust_minimal: module verification failed: signature and/or required key missing - tainting kernel
[ 160.098596] rust_minimal: Rust minimal sample (init)
[ 160.098598] rust_minimal: Am I built-in? false
[ 180.365691] rust_minimal: My numbers are [72, 108, 200]
[ 180.365696] rust_minimal: Rust minimal sample (exit)
Finally, it worked!
Summary
Looking back, there were three main challenges I encountered while creating the Rust module:
- Using a kernel with the CONFIG_RUST option enabled.
- Writing the Makefile for the Rust-based Loadable Kernel Module (LKM).
- Understanding the usage of Rust's toolchain.
Fortunately, I was able to overcome these hurdles this time.
Moving forward, I am eager to explore and experiment with various aspects of Rust LKMs.
It might be interesting to try this on Ubuntu, considering that recent Ubuntu kernels already have the CONFIG_RUST option enabled.