Build kernel modules at the speed of thought
cargo-nok builds out-of-tree Rust Linux kernel modules while keeping Cargo in
charge of compiling the Rust crate.
The name means No Kbuild: module projects do not need their own Makefile or
to invoke the usual external-module Kbuild flow. cargo-nok discovers the
active kernel build tree, configures Cargo and rustc with the kernel target
and flags, and performs the remaining ELF and module metadata steps required to
produce a loadable .ko.
This project is experimental. It depends on Rust-for-Linux build artifacts and internal kernel tools whose interfaces may change between kernel releases.
Supported architectures currently include:
- x86_64
- x86 UML
- arm64
- RISC-V 64
- LoongArch 64
For cargo nok build, the tool:
- Discovers the active Kbuild directory.
- Verifies that the current
rustcexactly matches the compiler used to build the kernel. - Derives the Rust target, architecture flags, kernel cfgs, and precompiled kernel crates from the Kbuild output.
- Runs
cargo buildfor the module'srlibtarget. - Links the module crate and its Rust dependencies into a relocatable object.
- Runs the kernel's
objtoolwhen enabled. - Runs
modpostand compiles the generated module metadata. - Links the final
.kowith the kernel's module linker script.
Cargo remains responsible for dependency resolution and Rust compilation. Kernel-provided tools are still used where the kernel module ABI requires them.
The running kernel must have been built with Rust support. Its Kbuild directory must contain at least:
rust/libkernel.rmetaand the other precompiled kernel Rust cratesinclude/generated/rustc_cfginclude/config/auto.confscripts/mod/modpostscripts/module.ldsscripts/module-common.ctools/objtool/objtoolwhenCONFIG_OBJTOOLis enabled
The host also needs:
- Cargo and the exact
rustcversion recorded by the kernel - a C compiler
- GNU
ldandobjcopy - kernel headers/build artifacts for the target kernel
rust-analyzerfor editor integration
By default, the Kbuild tree is:
/lib/modules/$(uname -r)/build
Override it with NOK_KERNEL_DIR.
Install from the repository:
cargo install --git https://github.com/ardos-os/cargo-nokCargo will then expose the tool as a subcommand:
cargo nok buildThe module must provide one Cargo rlib target:
[package]
name = "hello-kernel"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["rlib"]A minimal crate root can use the normal Rust-for-Linux API:
use kernel::prelude::*;
module! {
type: HelloKernel,
name: "hello_kernel",
authors: ["Example Author"],
description: "Example out-of-tree Rust module",
license: "GPL",
}
struct HelloKernel;
impl kernel::Module for HelloKernel {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("hello_kernel: loaded\n");
Ok(Self)
}
}
impl Drop for HelloKernel {
fn drop(&mut self) {
pr_info!("hello_kernel: unloaded\n");
}
}cargo-nok injects the kernel's no_std and nightly feature attributes during
the real compilation.
When the resolved Cargo workspace uses alloc, cargo-nok compiles the Rust
alloc crate from rust-src against the kernel's precompiled core and
compiler_builtins.
The generated artifacts are stored inside the module's Cargo target directory:
target/<kernel-target>/nok/alloc/liballoc.rlib
target/<kernel-target>/nok/alloc/liballoc.rmeta
The module is then compiled with:
--extern=alloc=<generated liballoc.rlib>-Ldependency=<generated alloc directory>
The rust-analyzer proxy also injects an alloc metadata package pointing at the
rust-src entry point so autocomplete and go-to-definition can resolve
alloc types and functions.
By default, cargo-nok finds rust-src from the selected rustc, matching the
same source layout Cargo uses for -Zbuild-std=alloc:
NOK_RUST_SRCRUST_SRC_PATHrustc --print sysrootrustc --print target-libdir- common distro paths such as
/usr/lib/rustlib/src/rust
The override paths may point at the rust-src root, the library directory, a
sysroot-like root, or directly at library/alloc/src/lib.rs.
export NOK_RUST_SRC=/path/to/rust/srccargo nok buildCargo arguments such as --release, --manifest-path, --package, and
feature options are forwarded:
cargo nok build --release --package hello-kernelThe final .ko is placed beside the Cargo rlib artifact for the selected
profile and kernel target.
The --target option cannot be supplied by the user. Kernel crates such as
core and kernel are compiled for one specific kernel target, so
cargo-nok must control it.
cargo nok checkThis uses the same Cargo build mode, target, kernel crates, cfgs, and rustflags
as cargo nok build, but stops before the ELF, objtool, modpost, and .ko
linking stages. It is intended for fast diagnostics and rust-analyzer.
Requires cargo-expand:
cargo nok expandArguments are forwarded with the same kernel compilation environment.
cargo nok load
cargo nok unload
cargo nok reloadloadbuilds the module and runsinsmod.unloadrunsrmmod.reloadbuilds the module, unloads an existing instance, and loads the new module.
Privilege escalation is attempted with sudo, then doas, then su. These
commands operate on the running kernel and should be used carefully.
cargo nok ra launches rust-analyzer through a Cargo proxy:
cargo nok ra --kernel-source /path/to/linux-sourceThe proxy:
- augments
cargo metadatawith the Linux kernel crates and their source files; - redirects rust-analyzer's normal
cargo checktocargo nok check; - exposes the precompiled kernel proc macros;
- injects kernel cfgs,
MODULE,OBJTREE, andRUST_MODFILE; - preserves normal dependencies from the project's
Cargo.toml.
The Linux source tree must match the Kbuild version exactly. It is discovered from common locations, or can be selected explicitly:
cargo nok ra --kernel-source /usr/src/linuxThe equivalent environment variable is:
export NOK_KERNEL_SOURCE=/usr/src/linuxUse a non-default rust-analyzer executable with:
cargo nok ra \
--kernel-source /usr/src/linux \
--rust-analyzer /path/to/rust-analyzerArguments after -- are passed to rust-analyzer:
cargo nok ra --kernel-source /usr/src/linux -- analysis-stats .VS Code's rust-analyzer.server.path accepts an executable path, not a command
with arguments. Create a launcher such as .vscode/cargo-nok-ra:
#!/bin/sh
exec cargo nok ra \
--kernel-source /path/to/linux-source \
--rust-analyzer /usr/bin/rust-analyzer \
-- "$@"Make it executable and configure .vscode/settings.json:
{
"rust-analyzer.server.path": "/absolute/path/to/project/.vscode/cargo-nok-ra"
}No rust-analyzer.check.overrideCommand is required. The Cargo proxy
intercepts the default check command automatically.
The module crate receives:
#[cfg(MODULE)]
#[cfg(cargo_nok)]MODULE indicates a loadable kernel module. cargo_nok indicates that the
crate is being compiled or analyzed through cargo-nok.
Cargo metadata cannot express rustc's unstable -Zcrate-attr option.
cargo-nok therefore exposes an editor-only cfg:
#[cfg(cargo_nok_ra)]Use it when rust-analyzer needs the same crate attributes that the real build receives through rustflags:
#![cfg_attr(cargo_nok_ra, no_std)]
#![cfg_attr(
cargo_nok_ra,
feature(
asm_const,
asm_goto,
arbitrary_self_types,
lint_reasons,
offset_of_nested,
raw_ref_op,
slice_ptr_len,
strict_provenance,
used_with_arg
)
)]These attributes are active only in rust-analyzer. The real build receives the same attributes directly from the kernel rustflags.
| Variable | Purpose |
|---|---|
NOK_KERNEL_DIR |
Override the Kbuild directory. |
NOK_KERNEL_SOURCE |
Select matching Linux sources for rust-analyzer. |
NOK_RUST_SRC |
Select the Rust source tree used to compile and index alloc. |
RUST_SRC_PATH |
Fallback Rust source tree path compatible with standard Rust tooling. |
NOK_RUST_ANALYZER |
Select the rust-analyzer executable. |
NOK_BTF=1 |
Generate BTF when vmlinux and the kernel helper are available. |
RUSTC |
Select the Rust compiler. Its version must exactly match the kernel. |
CARGO |
Select the Cargo executable. |
CC |
Select the C compiler used for module metadata. |
LD |
Select the linker. |
OBJCOPY |
Select objcopy. |
Variables prefixed with NOK_RA_ are internal to the rust-analyzer proxy and
should not normally be set manually.
BTF generation is opt-in:
NOK_BTF=1 cargo nok buildIt is skipped with a warning if the Kbuild tree does not contain vmlinux.
Run the test suite and lints with:
cargo test
cargo clippy --all-targets -- -D warnings