External Publication
Visit Post

[Pre-RFC] BTF relocations

Rust Internals [Unofficial] April 10, 2026
Source

alice:

vad:

I do agree that offset_of! on relocatable types/fields should fail to compile

Why? Can't it just compile down to emitting a relocation so that it evaluates to the correct offset?

Because the offset_of intrinsic is const:

pub const fn offset_of<T: PointeeSized>(variant: u32, field: u32) -> usize;

and I'm under the impression that we want to treat BTF-relocatable field access as non-const. Nobody said this explicitly, but if we want #[repr(btf)] types to be Sized but not const Sized, then emitting a relocation in a const intrinsic seems inconsistent with that principle. That's why I'm proposing a new BTF-aware intrinsic:

pub fn btf_field_byte_offset<T: PointeeSized>(variant: u32, field: u32) -> usize;

Do you think relocation emission could still be modeled as a const intrinsic somehow?

alice:

As a more general comment, I do think the sized hierarchy work is the right way to approach this issue.

The sized hierarchy idea definitely seems very helpful for this RFC. I'll try to help make some progress on it.

alice:

Perhaps one could expose some entirely different way to access btf data? For example, a macro that takes a struct name and field name and returns the offset. The program can then use that value on a raw pointer explicitly.

Yes, I think that aligns with my idea of the btf_field_byte_offset intrinsic. It could definitely be exposed through a macro.

Jules-Bertholet:

Can a relocation affect whether two fields overlap (e.g. if a struct changes to a union)?

No. The type of the container (struct, union) containing the field is encoded in the relocation.

Jules-Bertholet:

Can a relocation change an array index?

No. For a type:

struct foo {
    int arr[3];
}

An access to a specific index (e.g. foo->arr[3]) emits a relocation with that index encoded. What the loader does when reading such a relocation is find the 3rd element of the array field in the target layout and apply whatever offset adjustment is needed if new fields were inserted before arr.

Jules-Bertholet:

Can a relocation affect the number of pointer dereferences necessary to access a field?

No. A BTF relocation only adjusts layout-dependent properties along a fixed access path; it does not rewrite the path itself. So it can change field offsets, sizes, or compatibility checks, but it cannot change how many pointer dereferences are required to perform the access.

If a target type evolved in a way that would require a different number of dereferences, that would be a compatibility break rather than something BTF relocations can adapt to.

Jules-Bertholet:

Is it ever possible to construct one of these structs? Or does the potential for additional fields Rust doesn't know about mean construction must be forbidden?

Excellent question.

I tried compiling a C program that initializes a struct annotated with __attribute__((preserve_access_index)), and it compiles. I haven't tested it against a real kernel yet, but it seems unsound.

If the program constructs such a value using one kernel layout and then gets loaded on a kernel with a different layout, relocated accesses could interpret that locally constructed value using the target kernel's layout instead of the layout it was actually initialized with.

Forbidding the construction in Rust absolutely makes sense to me.

Jules-Bertholet:

Do we need an analogue of #[link_name] for struct and field names?

Good question.

It's not strictly necessary, because matching container type, its name and matching field name are sufficient for the relocation to work.

But given that this is all done on C kernel types with C-style snake_case names, defining such structures in Rust code will often trigger lint issues. For someone who really wants to keep Rust-style type names, such an attribute could be helpful.

I'm still leaning towards saying "no", since using C-like type names and suppressing these lints is a pretty common thing to do in bindgen, and implementing a dedicated attribute sounds like overkill to me.

Jules-Bertholet:

What is the failure mode like? If you make a mistake or run on an unsupported kernel version, and the relocations aren't sufficient to make offsets or types line up, is the verifier guaranteed to reject the program?

There are two possible failure modes.

If the relocated access ends up outside the valid bounds that the verifier can prove, the verifier should reject the program.

But if the relocated access still stays within some valid memory region while pointing to the wrong field, then this can become a silent logic bug instead. So I would not claim that the verifier is guaranteed to reject every semantically wrong relocation outcome.

Jules-Bertholet:

Is there any connection to/similarity with Swift stable ABI?

Not really. Actually I don't even think Rust ABI should be used for BTF relocatable types. #[repr(btf)] should enforce the C ABI, I'm going to update the RFC and examples accordingly.

RalfJung:

I didn't mean to suggest that this should actually be implemented in Miri before an RFC can be accepted, sorry if I was unclear. What I meant is that the semantics of this type need to be described at a level of abstraction and detail that it is clear how they would be implemented in Miri. This is crucial; we should not add new pieces to the language that we don't know how to describe at the level of the Abstract Machine. "It emits this kind of machine code" is not an acceptable specification for a language feature.

I am saying this with my t-opsem hat on. It is literally the responsibility of my team to ensure we have a proper Abstract Machine with an operational semantics for Rust. We're happy to help you work this out (come find us on Zulip), but an RFC without a proper opsem discussion is not going to be accepted.

Thanks for the explanation. I must be honest: I hadn't thought about the Abstract Machine description while writing the RFC and working on the prototype. I need some time to reason about it, and I will definitely reach out once I'm ready.

RalfJung:

FWIW this will be very non-trivial because Rust has generics. Today one can write a generic function that takes an arbitrary T (implicitly: T: Sized) and then obtain its size as a compile-time constant. This is not something we can break. That's why people are saying that the Sized hierarchy work is a prerequisite for supporting this kind of a type.

I'm fine with making support for BTF relocations depend on the Sized hierarchy work if there is strong opposition to introducing #[repr(btf)] types that don't work with generics.

Discussion in the ATmosphere

Loading comments...