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
2ec6577 to
54a1461
Compare
| #[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...?
There was a problem hiding this comment.
Yes, that's right. Although instead of saying "is actually external", I would say that the struct definition is a reference point for the runtime checks.
To be fair, I proposed a field projection (like in clang) in my pre-RFC, but given the complexity, I'm not pursuing it for now.
|
Syntax questions and my nits aside, this will want a sponsor from T-lang for proceeding as a nightly experiment (which will later come back to re-informing this RFC). I have nominated it so that if you do not find a sponsor in your own time they eventually see it appear on their agenda and one of them can either take up the gauntlet or they can decline it. And this comment as explanation for why I nominated it, of course. This may be a very eventual eventually, so I do suggest finding a sponsor in your own time. |
|
We talked about this in the lang call today. We were interested to hear what the people working on @rust-lang/rust-for-linux think about this, as well as what those working on reflection (@oli-obk, @scottmcm, rust-lang/rust-project-goals#406) and projection (@BennoLossin, @dingxiangfei2009, @tmandry, rust-lang/rust-project-goals#390) think. If this generally makes sense to people, I'll be happy to champion a lang experiment here. cc @lqd @Nadrieril |
|
Also needs input from sized hierarchy side. cc @davidtwco |
| This overlaps with the accepted [Field Projections project goal][field-projections], | ||
| which is exploring virtual places as a general mechanism for custom field | ||
| projection. This RFC deliberately does not depend on that work: it provides the | ||
| low-level BTF field metadata queries needed for CO-RE today, while leaving a | ||
| future field-projection-based ergonomic surface open. | ||
|
|
||
| Providing the field-info queries proposed in this RFC does not rule out | ||
| exploring this alternative in the future. On the contrary, Clang provides both | ||
| explicit field-info builtins and field projection. It makes sense to treat these | ||
| as separate RFCs. |
There was a problem hiding this comment.
I think this RFC could be written to make it more clear that we expect field access and offset_of! to work in the future. For example, the offset_of! section is phrased as if we could never support offset_of! with btf relocations.
There was a problem hiding this comment.
Good point, I'm going to rephrase this (while still making it clear it's out of scope of this RFC).
|
|
||
| `offset_of!` is intentionally a constant layout query. It does not preserve the | ||
| field identity needed to emit a BTF relocation. Reusing it would either silently | ||
| produce non-relocatable code or require changing the meaning of an existing |
There was a problem hiding this comment.
How is the new macro with the same API change anything? Why isn't that also silently producing wrong things when e.g. a field doesn't exist at all in the relocation table?
There was a problem hiding this comment.
How is the new macro with the same API change anything?
When I submitted the pre-RFC, where I initially proposed emission of BTF relocations in regular field projections and offset_of! , the main feedback was that it goes against the principle that Sized types should have layout known at compile time, which is not the case with BTF-relocatable types.
Then the discussion went towards the hierarchy of Sized types RFC #3729 which would eliminate this problem. But then we also agreed treating BTF-relocatable types as !Sized and allowing access to them only with dedicated macros/intrinsics could be a good way to keep the feature rolling without hard dependency on #3729.
Why isn't that also silently producing wrong things when e.g. a field doesn't exist at all in the relocation table?
Not sure if I got this question right - are you asking about what field_byte_offset! and field_byte_size! macros produce when a field does not exist? That's a great question and actually made me realize that they should probably return an Option:
core::btf::field_byte_offset!(Carrier, field.path) -> Option<usize>
core::btf::field_byte_size!(Carrier, field.path) -> Option<usize>
There was a problem hiding this comment.
Jup, that makes sense now, thx
Thank you, @traviscross, for the discussion and for offering to champion this. I appreciate the lang team's interest. I'm going to modify the RFC according to the comments later today. |
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
It's better to emit a compile error.
54a1461 to
6292c33
Compare
|
This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
Move it under `#relocatable-field-access` section. Make it clear that we don't rule the `offset_of!` support out in the future.
Make the other macros sound by returning `None` when a field does not exist. That makes `field_exists!` redundant, so remove it.
| } | ||
|
|
||
| const PID_OFFSET: usize = core::mem::offset_of!(task_struct, pid); | ||
| // error: cannot use `offset_of!` with a `#[repr(Btf)]` type |
There was a problem hiding this comment.
| // error: cannot use `offset_of!` with a `#[repr(Btf)]` type | |
| // error: cannot use `offset_of!` with a `#[btf_relocatable]` type |
|
|
||
| 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.
| // error: cannot access fields of a `#[repr(Btf)]` type directly | |
| // error: cannot access fields of a `#[btf_relocatable]` type directly |
| Direct field projection from a `#[btf_relocatable]` ADT is rejected. This | ||
| includes projections reached through autoderef: | ||
|
|
||
| ```rust | ||
| task.pid | ||
| ``` |
There was a problem hiding this comment.
I actually feel slightly unclear why this code example is here because it doesn't seem to illustrate the paragraph before it.
View all comments
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