BTF relocations#3966
Conversation
BTF, the BPF Type Format, encodes type information for both the running
Linux kernel and compiled eBPF programs. An eBPF object can carry
relocation records that describe field and aggregate accesses in terms
of BTF types instead of fixed offsets; at load time, the loader compares
the program's BTF with the kernel's BTF and rewrites those accesses to
the correct offsets for the target kernel. This mechanism is often
referred to as "CO-RE relocations" or "BTF relocations".
`offset_of` always folds to a plain layout constant and does not
preserve enough information for BTF CO-RE relocation emission. As a
result, it is not suitable for relocatable field queries on BPF targets.
Add three explicit intrinsics for BTF field metadata queries:
* `btf_field_byte_offset`
* `btf_field_byte_size`
* `btf_field_exists`
The user-facing BTF relocatable type support remains behind the
`btf_relocations` feature gate, so use of this experimental CO-RE
surface requires an explicit nightly opt-in.
These intrinsics provide a frontend surface for CO-RE relocations.
Unlike `offset_of`, they remain visible to backend codegen and can lower
to relocatable field-info queries instead of immediate layout constants.
For LLVM, lower these intrinsics through `@llvm.bpf.preserve.field.info`
with the corresponding query kind. The necessary
`@llvm.preserve.{struct,array,union}.access.index` chain is constructed
internally during lowering, but it is not exposed as part of the
user-facing API.
On targets or backends without BTF relocation support, fall back to the
ordinary layout-computed result: the field offset for
`btf_field_byte_offset`, the field size for `btf_field_byte_size`, and
`true` for `btf_field_exists`.
The language-level design for this feature is proposed in
rust-lang/rfcs#3966.
BTF, the BPF Type Format, encodes type information for both the running
Linux kernel and compiled eBPF programs. An eBPF object can carry
relocation records that describe field and aggregate accesses in terms
of BTF types instead of fixed offsets; at load time, the loader compares
the program's BTF with the kernel's BTF and rewrites those accesses to
the correct offsets for the target kernel. This mechanism is often
referred to as "CO-RE relocations" or "BTF relocations".
`offset_of` always folds to a plain layout constant and does not
preserve enough information for BTF CO-RE relocation emission. As a
result, it is not suitable for relocatable field queries on BPF targets.
Add three intrinsics for BTF field metadata queries:
* `btf_field_byte_offset`
* `btf_field_byte_size`
* `btf_field_exists`
Their availability is hidden behind the `btf_relocations` feature gate.
Unlike `offset_of`, they remain visible to backend codegen and can lower
to relocatable field-info queries instead of immediate layout constants.
For LLVM, lower these intrinsics through `@llvm.bpf.preserve.field.info`
with the corresponding query kind. The necessary
`@llvm.preserve.{struct,array,union}.access.index` chain is constructed
internally during lowering, but it is not exposed as part of the
user-facing API.
On targets or backends without BTF relocation support, fall back to:
* The field offset for `btf_field_byte_offset`.
* The field size for `btf_field_byte_size`.
* `true` for `btf_field_exists`.
The language-level design for this feature is proposed in
rust-lang/rfcs#3966.
0a822d7 to
2ec6577
Compare
The RFC proposes the `btf_relocations` feature gate, which provides a `#[repr(Btf)]` representation and `core::btf` field-info macros that emit BTF (BPF Type Format)[0] CO-RE (Compile Once, Run Everywhere)[1] relocations. It also documents the relationship to `offset_of!`, ordinary field projection, and LLVM BPF lowering. This feature was originally proposed as a pre-RFC[2]. [0] https://docs.kernel.org/bpf/btf.html [1] https://nakryiko.com/posts/bpf-portability-and-co-re/ [2] https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/25
2ec6577 to
54a1461
Compare
| `#[repr(Btf)]` is intentionally not just a layout hint. It marks a type as one | ||
| whose fields should not be accessed through ordinary Rust field projection for | ||
| accesses that are meant to be relocatable. For such types, direct field access | ||
| is rejected: | ||
|
|
||
| ```rust | ||
| #![feature(btf_relocations)] | ||
|
|
||
| #[repr(Btf)] | ||
| pub struct task_struct { | ||
| pub pid: i32, | ||
| } | ||
|
|
||
| fn pid(task: &task_struct) -> i32 { | ||
| task.pid | ||
| // error: cannot access fields of a `#[repr(Btf)]` type directly |
There was a problem hiding this comment.
I would rather we do not use another repr that has the same "feature" as repr(packed), for which we seem to still be encountering new issues: rust-lang/rust#157011
I would prefer that we find some new way to express it syntactically that makes it harder for us to make more such mistakes in the future. Essentially, because these are sort of an Alien Being relative to normal structs, maybe they should not use the syntax of normal structs at all?
But I also wish to acknowledge that I do not have any objection to the fundamental behavior proposed, so this should not block any initial experimentation. It is more of a yet-to-be-resolved-concern type of deal. Unanswered question? "Can anyone think of something better?"
|
|
||
| These restrictions avoid silently producing non-relocatable code for operations | ||
| that appear to query a relocatable type. Code that genuinely wants a normal | ||
| non-relocatable Rust type should not use `#[repr(Btf)]`. |
There was a problem hiding this comment.
It isn't clear to me that this achieves its intended goal? Or I don't understand the goal?
The current proposal includes a "compile-time fallback" to normal layout behavior, several lines back.
On targets or backends without BTF
relocation support, they fall back to the current compilation unit's ordinary
layout information.This is a fallback path that is sometimes permitted based on some variable that can differ based on the circumstances of the compilation. So if we do not trust the compiler to correctly handle the matter, it can always choose a "wrong" path and silently produce directly non-relocatable accesses despite using all these special macros. According to my understanding, we have gone to some length to supposedly prevent silent failure via introducing syntactic noise, and then allowed for silent failure?
Thus we must trust the compiler anyways, right? So what are we trying to guard against?
| A `#[repr(Btf)]` type uses C-compatible field layout. In compiler terms, | ||
| `repr(Btf)` implies the layout constraints of `repr(C)` and also marks the type | ||
| as BTF-relocatable. This gives the backend stable field ordering and offsets for | ||
| the compile-time fallback while preserving a distinct marker for type checking | ||
| and codegen. |
There was a problem hiding this comment.
Is this intended to use actually compatible with C layout or is it intended to simply use a linear layout? See RFC #3845 for a description of that problem and a proposed solution, which is orthogonal to this RFC except insofar as it implies the "what do you really mean?" question.
AFAIK the platforms that this relocatable-fields proposal is relevant for are ones where the notional repr(C_actual) and repr(ordered_fields) are identical... ish... but that sort of statement is prone to breaking down in the face of new discoveries, so we might as well get ahead of that.
| #[inline] | ||
| pub fn pid(&self) -> Option<&i32> { | ||
| self.has_pid().then(|| { | ||
| let offset = core::btf::field_byte_offset!(task_struct, pid); | ||
| let ptr = self as *const task_struct as *const u8; | ||
|
|
||
| // SAFETY: the BTF relocation says that `se.vruntime` exists in the | ||
| // target layout, and the returned offset is relative to `task_struct`. | ||
| Some(unsafe { &*(ptr.add(offset) as *const i32) }) | ||
| }) |
There was a problem hiding this comment.
So a typo in a field name can result in simply turning this into a None and never taking the "active" code path, right? Since the relocation is functionally a set of runtime checks, and we would have to assume we take the field name for granted, and the entire reason to use these relocations is the struct definition per se is actually external to the program...?
| A type that should participate in BTF relocation is written with | ||
| `#[repr(Btf)]`: |
There was a problem hiding this comment.
Something to consider with this as repr: Is this meant to be exclusive with other repr hints?
- align?
- packed?
| A `#[repr(Btf)]` type uses C-compatible field layout. In compiler terms, | ||
| `repr(Btf)` implies the layout constraints of `repr(C)` and also marks the type | ||
| as BTF-relocatable. This gives the backend stable field ordering and offsets for | ||
| the compile-time fallback while preserving a distinct marker for type checking |
There was a problem hiding this comment.
What do we mean here, or what do we achieve, by having a "compile-time fallback"?
There's an implicit alternative here: No fallback. What are we giving up if we choose that instead?
| relocations. This is attractive ergonomically, but it requires a more intrusive | ||
| change to MIR and the design of a proper abstract machine with operational | ||
| semantics. It also has some similarities to the [`Sized` hierarchy | ||
| RFC][sized-hierarchy], which has not yet been accepted. |
There was a problem hiding this comment.
This statement seems to be wearing its shirt backwards, so to speak. We do have an abstract machine, but rather we would need to fit these magic accesses into that semantics, which seems like a lot.
| relocations. This is attractive ergonomically, but it requires a more intrusive | |
| change to MIR and the design of a proper abstract machine with operational | |
| semantics. It also has some similarities to the [`Sized` hierarchy | |
| RFC][sized-hierarchy], which has not yet been accepted. | |
| relocations. This is attractive ergonomically, but it requires a | |
| more intrusive change to MIR and the design of a proper | |
| operational semantics for these relocatable accesses. | |
| It also has some similarities to the [`Sized` hierarchy | |
| RFC][sized-hierarchy], which has not yet been accepted. |
| If the target, backend, or codegen mode cannot emit BTF field relocations, the | ||
| field-info queries fall back to ordinary layout-computed values: | ||
|
|
||
| * `field_byte_offset!` returns the complete field-path offset from the | ||
| current compilation layout. | ||
| * `field_byte_size!` returns the field size from the current compilation | ||
| layout. | ||
| * `field_exists!` returns `true` for a field path present in the current | ||
| compilation layout. |
There was a problem hiding this comment.
This would just become a name resolution question, right? It doesn't have anything to actually do with layout.
The RFC proposes the
btf_relocatable_typesfeature gate, that provides a#[repr(Btf)]representation and low-level field-info intrinsics that emit BTF (BPF TypeFormat) CO-RE (Compile Once, Run Everywhere) relocations.It also documents the relationship to
offset_of!, ordinary field projection and LLVM BPF lowering.This feature was originally proposed as pre-RFC.
Rendered