Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Add documentation for BPF targets #135107

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]
Copy link
Contributor

@niklaskorz niklaskorz Feb 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails building the docs at stage2:

Documenting stage2 library {alloc, core, panic_abort, panic_unwind, proc_macro, std, sysroot, test, unwind} in HTML format (aarch64-apple-darwin -> bpfel-unknown-none)

The errors being, among others:

error[E0277]: the trait bound `System: core::alloc::GlobalAlloc` is not satisfied
   --> std/src/alloc.rs:220:43
    |
220 |             unsafe { GlobalAlloc::dealloc(self, ptr.as_ptr(), layout) }
    |                      -------------------- ^^^^ the trait `core::alloc::GlobalAlloc` is not implemented for `System`
    |                      |
    |                      required by a bound introduced by this call

error[E0277]: the trait bound `System: core::alloc::GlobalAlloc` is not satisfied
   --> std/src/alloc.rs:270:52
    |
270 |                 let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), old_layout, new_size);
    |                               -------------------- ^^^^ the trait `core::alloc::GlobalAlloc` is not implemented for `System`
    |                               |
    |                               required by a bound introduced by this call

error[E0599]: no method named `fetch_add` found for struct `AtomicUsize` in the current scope
   --> std/src/panicking.rs:395:47
    |
395 |         let global_count = GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed);
    |                                               ^^^^^^^^^ method not found in `AtomicUsize`

error[E0599]: no method named `fetch_sub` found for struct `AtomicUsize` in the current scope
   --> std/src/panicking.rs:419:28
    |
419 |         GLOBAL_PANIC_COUNT.fetch_sub(1, Ordering::Relaxed);
    |                            ^^^^^^^^^ method not found in `AtomicUsize`

error[E0599]: no method named `fetch_or` found for struct `AtomicUsize` in the current scope
   --> std/src/panicking.rs:427:28
    |
427 |         GLOBAL_PANIC_COUNT.fetch_or(ALWAYS_ABORT_FLAG, Ordering::Relaxed);
    |                            ^^^^^^^^ method not found in `AtomicUsize

Could you also document how to prevent building docs for std and alloc here, which are not supported by the bpf targets? (without disabling docs entirely through --disable-docs, and without dropping other targets from the same rustc that do successfully build std docs)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug in the bootstrap code, so nothing that should have to be addressed by the documentation, see #137073

```

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted by what, exactly? You mean a specific component of the kernel, right? Does the eBPF verifier also check debuginfo? Or is it that the debugger requires a specific debuginfo format?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK the BPF dynamic linker uses BTF to do things like checking for compatibility of the BPF module with the type layouts of the kernel itself. And through special relocations it can fill in the offsets of fields for kernel types such that the BPF module can run on multiple kernel versions even if the layout of types used by the BPF module changes across those kernel versions. The latter almost certainly will require explicit rustc support to get wired up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@workingjubilee the kernel validates the BTF info as part of calling bpf_program_load. Producing valid BTF right now means... generating whatever clang generates for C code. Non-C symbols in type names are not allowed (eg < in Foo<u8>), and there are some other annoying constraints.

@bjorn3 we discussed implementing BTF relocs on zulip a couple of years ago. Long term, you're correct it requires explicit rustc support if only because what debuginfo rustc generates with -C debuginfo is unspecified, so going from DI => relocs is fragile. In the meantime we think we can probably hack it up in bpf-linker tho.


## 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error: tidy error: /checkout/src/doc/rustc/src/platform-support/bpf-unknown-none.md:74: unexplained "```ignore" doctest; try one:

* make the test actually pass, by adding necessary imports and declarations, or
* use "```text", if the code is not Rust code, or
* use "```compile_fail,Ennnn", if the code is expected to fail at compile time, or
* use "```should_panic", if the code is expected to fail at run time, or
* use "```no_run", if the code should type-check but not necessary linkable/runnable, or
* explain it like "```ignore (cannot-test-this-because-xxxx)", if the annotation cannot be avoided.

#[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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this referring to our UI testsuite, or is it referring to #[test] functions, or both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To both.

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error: tidy error: /checkout/src/doc/rustc/src/platform-support/bpf-unknown-none.md:95: unexplained "```ignore" doctest; try one:

* make the test actually pass, by adding necessary imports and declarations, or
* use "```text", if the code is not Rust code, or
* use "```compile_fail,Ennnn", if the code is expected to fail at compile time, or
* use "```should_panic", if the code is expected to fail at run time, or
* use "```no_run", if the code should type-check but not necessary linkable/runnable, or
* explain it like "```ignore (cannot-test-this-because-xxxx)", if the annotation cannot be avoided.

#[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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this wants a no_run?

  ---- /checkout/src/doc/rustc/src/platform-support/bpf-unknown-none.md - Tier__3::C_code (line 127) stdout ----
  Test executable failed (exit status: 101).
  
  stderr:
  
  thread 'main' panicked at /checkout/src/doc/rustc/src/platform-support/bpf-unknown-none.md:5:35:
  called `Result::unwrap()` on an `Err` value: NotPresent
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

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/
Loading