Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bpf*-unknown-none

Tier: 3

  • bpfeb-unknown-none (big endian)
  • bpfel-unknown-none (little endian)

Targets for the 64-bit BPF virtual machine.

Target maintainers

@nagisa @vadorovsky

Requirements

BPF targets require a Rust toolchain with the rust-src component. In addition, you must install the bpf-linker.

They don’t support std and alloc and are meant for a no_std environment.

extern "C" uses the BPF ABI calling convention.

Produced binaries use the ELF format.

BPF virtual machines provide a JIT compiler that compiles the BPF bytecode into the native host architecture.

Running BPF programs on most host architectures requires Linux kernel 4.18, that introduced BTF, or newer. On RISC-V hosts that requirement goes up to 5.7, on PowerPC32 - to [5.13] linux-commit-ppc32, and on LoongArch - to 6.1.

Building the target

You can build Rust with support for BPF targets by adding them to the target list in config.toml:

[build]
target = ["bpfeb-unknown-none", "bpfel-unknown-none"]

Building Rust programs

Rust does not yet ship pre-compiled artifacts for this target. To compile for this target, you will either need to build Rust with the target enabled (see “Building the target” above), or build your own copy of core by using build-std or similar.

Building the BPF target requires specifying it explicitly. Users can either add it to the target list in config.toml:

[build]
target = ["bpfel-unknown-none"]

Or specify it directly in the cargo build invocation:

cargo +nightly build -Z build-std=core --target bpfel-unknown-none

BPF has its own debug info format called BTF.

BPF targets use bpf-linker, an LLVM bitcode linker. In future, they may migrate to the GNU flavor of linker, see the details in the [following issue] bpf-object-linking.

Error handling

There is no concept of stack unwinding in BPF, therefore BPF programs are expected to handle errors in a recoverable manner. Therefore most BPF programs written in Rust use the following panic handler implementation:

#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Infinite loops are forbidden by the BPF verifier. Therefore, if the program contains any code which can panic, the BPF VM refuses to load it.

Testing

BPF bytecode needs to be executed on a BPF virtual machine, like the one provided by the Linux kernel or one of the user-space implementations like rbpf. None of them support running Rust #[test] functions. One of the reasons is the lack of support for panicking.

Therefore, unit tests need to run on the host system. That requirement can be enforced by the following conditional check:

#[cfg(all(not(target_arch = "bpf"), test))]
mod test {}

Cross-compilation toolchains

BPF programs are always cross-compiled from a host (e.g. x86_64-unknown-linux-*) for a BPF target (e.g. bpfel-unknown-none).

The endianness of a chosen BPF target needs to match the endianness of the BPF VM host on which the program is supposed to run.

The architecture of the BPF VM host often has an impact on types that the BPF programs should use. For example kprobes, fprobes and uprobes allow dynamic function tracing and lookup into host registers through the pt_regs struct, which differs across architectures.

That difference is still not a concern of the compiler. Instead, it should be handled by the developers. Aya (the library for writing Linux BPF programs and the main consumer of BPF targets in Rust) handles that by providing the aya-ebpf-cty crate, with type aliases similar to those provided by core:ffi. aya-ebpf-cty allows to specify the VM target through the CARGO_CFG_BPF_TARGET_ARCH environment variable (e.g. CARGO_CFG_BPF_TARGET_ARCH=aarch64).

C code

It’s possible to link a Rust BPF project to bitcode or object files which are built from C code with clang. It can be done using a rustc-link-lib instruction in build.rs. Example:

use std::{env, process::Command};

let out_dir = env::var("OUT_DIR").unwrap();
let c_module = "my_module.bpf.c";
let s = Command::new("clang")
    .arg("-I")
    .arg("src/")
    .arg("-O2")
    .arg("-emit-llvm")
    .arg("-target")
    .arg("bpf")
    .arg("-c")
    .arg("-g")
    .arg(c_module)
    .arg("-o")
    .arg(format!("{out_dir}/my_module.bpf.o"))
    .status()
    .unwrap();
assert!(s.success());
println!("cargo:rustc-link-search=native={out_dir}");
println!("cargo:rustc-link-lib=link-arg={out_dir}/my_module.bpf.o");