Skip to content

Commit

Permalink
docs: Add documentation for BPF targets
Browse files Browse the repository at this point in the history
  • Loading branch information
vadorovsky committed Jan 19, 2025
1 parent 39dc268 commit 33873b5
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/doc/rustc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
- [\*-unknown-fuchsia](platform-support/fuchsia.md)
- [\*-unknown-trusty](platform-support/trusty.md)
- [\*-kmc-solid_\*](platform-support/kmc-solid.md)
- [bpf\*-unknown-none](platform-support/bpf-unknown-none.md)
- [csky-unknown-linux-gnuabiv2\*](platform-support/csky-unknown-linux-gnuabiv2.md)
- [hexagon-unknown-linux-musl](platform-support/hexagon-unknown-linux-musl.md)
- [hexagon-unknown-none-elf](platform-support/hexagon-unknown-none-elf.md)
Expand Down
4 changes: 2 additions & 2 deletions src/doc/rustc/src/platform-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,8 @@ target | std | host | notes
[`armv7s-apple-ios`](platform-support/apple-ios.md) | ✓ | | Armv7-A Apple-A6 Apple iOS
[`armv8r-none-eabihf`](platform-support/armv8r-none-eabihf.md) | * | | Bare Armv8-R, hardfloat
`avr-unknown-gnu-atmega328` | * | | AVR. Requires `-Z build-std=core`
`bpfeb-unknown-none` | * | | BPF (big endian)
`bpfel-unknown-none` | * | | BPF (little endian)
[`bpfeb-unknown-none`](platform-support/bpf-unknown-none.md) | * | | BPF (big endian)
[`bpfel-unknown-none`](platform-support/bpf-unknown-none.md) | * | | BPF (little endian)
`csky-unknown-linux-gnuabiv2` | ✓ | | C-SKY abiv2 Linux (little endian)
`csky-unknown-linux-gnuabiv2hf` | ✓ | | C-SKY abiv2 Linux, hardfloat (little endian)
[`hexagon-unknown-linux-musl`](platform-support/hexagon-unknown-linux-musl.md) | ✓ | | Hexagon Linux with musl 1.2.3
Expand Down
163 changes: 163 additions & 0 deletions src/doc/rustc/src/platform-support/bpf-unknown-none.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# `bpf*-unknown-none`

**Tier: 3**

* `bpfeb-unknown-none` (big endian)
* `bpfel-unknown-none` (little endian)

Targets for the 64-bit [BPF virtual machine][ebpf].

## Target maintainers

- [@alessandrod](https://github.com/alessandrod)
- [@dave-tucker](https://github.com/dave-tucker)
- [@tamird](https://github.com/tamird)
- [@vadorovsky](https://github.com/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][bpf-abi].

Produced binaries use the ELF format.

## Building the target

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

```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`:

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

Or specify it directly in the `cargo build` invocation:

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

BPF has its own debug info format called [BTF][btf].

BPF targets use [bpf-linker], an LLVM bitcode linker, which by
default strips the debug info, but it has an experimental feature of emitting
BTF, which can be enabled by adding `-C link-arg=--btf` to `RUSTFLAGS`. With
that feature enabled, [bpf-linker] does not only link different
crates/modules, but also performs a necessary sanitization of debug info, which
is required to produce valid [BTF][btf] acceptable by the Linux kernel.

## 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 no-op panic handler implementation:

```rust,ignore

Check failure on line 74 in src/doc/rustc/src/platform-support/bpf-unknown-none.md

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

unexplained "```ignore" doctest; try one:
#[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][rbpf]. None of them support running Rust testsuite. 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:

```rust,ignore

Check failure on line 95 in src/doc/rustc/src/platform-support/bpf-unknown-none.md

View workflow job for this annotation

GitHub Actions / PR - mingw-check-tidy

unexplained "```ignore" doctest; try one:
#[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][kprobe], [fprobes][fprobe] and
[uprobes][uprobe] allow dynamic function tracing and lookup into host registers
through the [`pt_regs`][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][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`][aya-ebpf-cty] crate, with type aliases similar
to those provided by [`core:ffi`][core-ffi]. [`aya-ebpf-cty`][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][clang]. It can be done using a `rustc-link-lib`
instruction in `build.rs`. Example:

```rust
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");
```

[ebpf]: https://ebpf.io/
[bpf-linker]: https://github.com/aya-rs/bpf-linker
[bpf-abi]: https://www.kernel.org/doc/html/v6.13-rc5/bpf/standardization/abi.html
[btf]: https://www.kernel.org/doc/html/latest/bpf/btf.html
[rbpf]: https://github.com/qmonnet/rbpf
[kprobe]: https://www.kernel.org/doc/html/latest/trace/kprobes.html
[fprobe]: https://www.kernel.org/doc/html/latest/trace/fprobe.html
[uprobe]: https://www.kernel.org/doc/html/latest/trace/uprobetracer.html
[pt-regs]: https://elixir.bootlin.com/linux/v6.12.6/source/arch/x86/include/uapi/asm/ptrace.h#L44
[aya]: https://aya-rs.dev
[aya-ebpf-cty]: https://github.com/aya-rs/aya/tree/main/ebpf/aya-ebpf-cty
[core-ffi]: https://doc.rust-lang.org/stable/core/ffi/index.html
[clang]: https://clang.llvm.org/

0 comments on commit 33873b5

Please sign in to comment.