Skip to content

ACP: Add BTF relocation macros to core #846

Description

@vadorovsky

Proposal

Problem statement

BTF is the type metadata format used by the Linux kernel and eBPF tooling. eBPF loaders such as Aya and libbpf use BTF for relocations: the compiled program records which field or array element it intended to access, and the loader rewrites the bytecode to match the layout of the kernel it is about to run on. These relocations are often referred to as Compile Once, Run Everywhere (CO-RE) relocations, or BTF relocations.

Clang and GCC are capable of emitting such relocations. Rust can already target eBPF, but it does not currently have a way to emit these BTF access relocations.

Ordinary field projection and offset_of! use the layout known when the program is compiled. The resulting fixed offsets may be incorrect when the program is loaded on a kernel whose corresponding types have a different layout. They also cannot represent the case where a field does not exist in the target kernel's BTF.

These operations require compiler and codegen support, so they cannot be implemented entirely by an external crate.

Motivating examples or use cases

Rust BPF programs commonly access fields in Linux kernel types such as task_struct. The layout of these types can vary between kernel versions, configurations, and distributions.

For example, a program may need the offset and size of task_struct::pid:

#[btf_relocatable]
#[repr(C)]
struct task_struct {
    pid: i32,
}

Using offset_of!(task_struct, pid) would embed the offset from the Rust definition used at compile time. A CO-RE-capable BPF program loader instead needs a relocation identifying task_struct::pid, allowing it to substitute the offset from the target kernel's BTF.

Programs may also need to handle fields that are absent on some kernels. The API therefore needs to represent both the relocated value and whether the requested field exists.

Clang provides equivalent functionality through __builtin_preserve_access_index and __builtin_preserve_field_info intrinsics. Rust eBPF projects currently need to rebuild against a specific kernel layout, encode offsets manually, or use C code to emit these relocations.

Solution sketch

Add a core::arch::bpf module containing two field metadata query macros.

The proposed public surface is shown below using signature-only notation. Declarative macros do not have standalone Rust signature syntax, so the expansion types are specified separately.

// core::arch::bpf
pub macro field_byte_offset($Carrier:ty, $($fields:expr)+ $(,)?);
pub macro field_byte_size($Carrier:ty, $($fields:expr)+ $(,)?);

Both macros expand to an expression of type Option<usize>.

Given the following example of the task_struct type, with nested BTF-relocatable types:

#[btf_relocatable]
#[repr(C)]
pub struct load_weight {
    pub weight: usize,
}

#[btf_relocatable]
#[repr(C)]
pub struct sched_entity {
    pub load: load_weight,
    pub vruntime: u64,
}

#[btf_relocatable]
#[repr(C)]
pub struct task_struct {
    pub pid: i32,
    pub se: sched_entity,
}

field_byte_offset! returns the byte offset of the complete field path relative to the root carrier type:

let offset: Option<usize> = core::btf::field_byte_offset!(task_struct, pid);

And works for a nested field as well:

let offset: Option<usize> =
    core::arch::bpf::field_byte_offset!(task_struct, sched_entity.vruntime);

field_byte_size! returns the byte size of the terminal field:

let size: Option<usize> =
    core::arch::bpf::field_byte_size!(task_struct, sched_entity.vruntime);

Both return None when the requested field does not exist in the target BTF.

The carrier argument is a Rust type. The second argument is a dot-separated field path beginning at that type. The compiler type-checks the complete path.

The module and macros are unstable under #![feature(btf_relocations)]. They are intended to be used with the #[btf_relocatable] language feature proposed by rust-lang/rfcs#3966.

The initial implementation supports LLVM BPF targets and requires debug info. Invoking the macros with an unsupported target, backend, or codegen configuration produces a compile error.

The #[btf_relocatable] attribute, restrictions on ordinary field access, and backend lowering are part of the language experiment and are outside the scope of this ACP.

Alternatives

Use offset_of!

offset_of! intentionally produces a compile-time layout constant. Once that constant has been produced, codegen no longer knows which source field was queried and cannot emit the corresponding BTF relocation.

Changing offset_of! to have relocatable behavior for selected types would also change its existing semantics and depends on broader work around relocatable field projection.

Make ordinary field access relocatable

The compiler could make ordinary field projection on selected types emit CO-RE relocations, similarly to Clang's preserve_access_index behavior.

This could provide a more ergonomic API, but requires operational semantics for relocatable places and memory accesses. It overlaps with ongoing Sized hierarchy work. Explicit metadata queries provide a smaller initial experiment without preventing relocatable field access from being added later.

Links and related work

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions