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

Add BTF relocation support in eBPF programs #349

Open
v-thakkar opened this issue Jul 25, 2022 · 11 comments
Open

Add BTF relocation support in eBPF programs #349

v-thakkar opened this issue Jul 25, 2022 · 11 comments

Comments

@v-thakkar
Copy link

v-thakkar commented Jul 25, 2022

To be able to support BPF CO-RE, rustc needs to be able to generate the relocations of BTF types across different kernel versions. First part of this work requires adding core::intrinsics::{preserve_access_index, preserve_field_info, preserve_type_info, preserve_enum_value} for the BPF target in rustc. And then making sure/testing that the relocations work well with the userspace part of Aya.

@alessandrod
Copy link
Collaborator

Just to clarify: aya supports BTF relocations, see: https://github.com/aya-rs/aya/blob/main/aya/src/obj/btf/relocation.rs. So if you load an ebpf object file with BTF relocations, aya will be able to resolve them.

What doesn't support emitting BTF relocations yet is the rust compiler. So if you compile a rust program for the bpf*-unknown-none targets it will not emit BTF relocations.

@alessandrod alessandrod changed the title Add support for relocations in BTF types Add BTF relocation support to rustc Jul 25, 2022
@GermanCoding
Copy link

What would be needed to get the preserve_access_index, preserve_field_info, preserve_type_info, preserve_enum_value builtins into rustc? I guess some issue/PR over on https://github.com/rust-lang/rust? I would really like to see this feature in Rust, so as to have a really complete CORE solution pretty much equivalent to what you can do in C today. However I don't feel qualified enough to open an issue on the Rust project myself, so maybe someone here has more expertise with this?

@l2dy
Copy link
Contributor

l2dy commented Sep 7, 2024

First part of this work requires adding core::intrinsics::{preserve_access_index, preserve_field_info, preserve_type_info, preserve_enum_value} for the BPF target in rustc.

Is it possible to experiment with the new intrinsics with the link_llvm_intrinsics feature in unstable?

What would be needed to get the preserve_access_index, preserve_field_info, preserve_type_info, preserve_enum_value builtins into rustc? I guess some issue/PR over on https://github.com/rust-lang/rust?

Rust language feature requests should be discussed on the internals forum first, and then follow the RFC process if asked to. @GermanCoding If you are really interested in this feature, feel free to ask on the forum.

@v-thakkar
Copy link
Author

@l2dy @GermanCoding I think @vadorovsky had some discussions regarding this with the Rust community and there has been some WIP regarding the same. Maybe he can shed more light onto it.

@vadorovsky vadorovsky changed the title Add BTF relocation support to rustc Add BTF relocation support in eBPF programs Sep 11, 2024
@vadorovsky
Copy link
Member

vadorovsky commented Sep 11, 2024

I'm currently trying to achieve that without any changes in Rust compiler - I updated the issue title to generalize it.

Instead, I'm trying to emit these intrinsic calls in https://github.com/aya-rs/bpf-linker, which as a bitcode linker, is alreaady capable of modifying LLVM IR before producing the actual BPF binary.

Clang is producing the @llvm.preserve.array.access.index, @llvm.preserve.struct.access.index and @llvm.preserve.array.access.index intrinsics by performing regular calls of LLVM API, the code is here:

https://github.com/llvm/llvm-project/blob/cb6a62369a353f506a1dde087eeaf5ebea5d5c26/llvm/lib/IR/IRBuilder.cpp#L1260-L1285

The idea is to do the same in bpf-linker whenever we find a getelementptr instruction followed by load which is done on a type, for which we would like to emit BTF relocations.

Clang has two ways of enabling BTF relocations:

  • For the whole type, by annotating it with __attribute__((preserve_access_index)), e.g.
struct foo {
  int a;
  int b;
  int c;
  int d;
} __attribute__((preserve_access_index));

int get_a(struct foo *foo) {
  return foo->a;  // <-- this already does BTF relocations
}
  • For a single field, if it's being accessed with __builtin_preserve_access_index, e.g.
struct foo {
  int a;
  int b;
  int c;
  int d;
}

int get_a(struct foo *foo) {
  return __builtin_preserve_access_index(foo->a); // <- this does BTF relocation, but the
                                                  // intrinsics are emitted just for field `a`
}

How we could achieve equivalent way of notifying bpf-linker about necessity of emitting the intrinsic for the given type or field? We can use a custom PhantomData field which won't affect the layout of the type.

  • For the whole type, we could look for _btf_marker: PhantomData<()>, e.g.
#[repr(C)]
pub struct Foo {
    a: i32,
    b: i32,
    c: i32,
    d: i32,
    _btf_marker: PhantomData<()>,
}
  • For a single field, we could look for _btf_marker_FIELD_NAME: PhantomData<()>, e.g.
#[repr(C)]
pub struct Foo {
    a: i32,
    b: i32,
    c: i32,
    d: i32,
    _btf_marker_a: PhantomData<()>,
}

Of course the code examples above are ugly and we don't want users to write such code. We could hide them with macros and make the final code look like:

// This would emit BTF relocs for the whole type
#[repr(C)]
#[derive(Btf)]
pub struct 
    a: i32,
    b: i32,
    c: i32,
    d: i32,
}
// This would emit BTF relocs only for annotated fields
#[repr(C)]
#[derive(Btf)]
pub struct 
    a: i32,
    b: i32,
    c: i32,
    #[btf]
    d: i32,
}

To be more explicit, the plan going forward is:

@canoriz
Copy link

canoriz commented Sep 27, 2024

I'm new to aya. Does this mean an eBPF ELF object compiled from rust may not be CO-RE (because lack of index in ELF), and we are working on it now?

@vadorovsky
Copy link
Member

I'm new to aya. Does this mean an eBPF ELF object compiled from rust may not be CO-RE (because lack of index in ELF), and we are working on it now?

Sorry for late reply. Yes, that exactly the case.

@fhilgers
Copy link

@vadorovsky Do you mind sharing what's the state on this one? Is there a POC for an implementation? I have seen your branch (https://github.com/vadorovsky/bpf-linker/tree/preserve-access-index-v2), however there has been no updates in a while. Did you hit some roadblock that makes this harder than previously thought.

I'm currently writing all my CO:RE stuff in C, compiling it to llvm bitcode and linking it together with bpf-linker, however thats very cumbersome to maintain. Before I hop into writing proc macros to make writing ebpf programs in a mix of rust and C (for relocations) more convenient, my time might be better spent helping to get this through.

Let me know if there are any updates and how/where one can help, thanks!

@hepek
Copy link

hepek commented Jan 31, 2025

I'm currently trying to achieve that without any changes in Rust compiler - I updated the issue title to generalize it.

Instead, I'm trying to emit these intrinsic calls in https://github.com/aya-rs/bpf-linker, which as a bitcode linker, is alreaady capable of modifying LLVM IR before producing the actual BPF binary.

@vadorovsky, any updates on this approach?

I like the macro idea. What do you think about putting all logic in user code? No changes to linkers or LLVM hacking.
Users would declare structs and offsets they use with a macro like this:

btf_use!(sock.__sock_common.skc_family);

Which would declare a static variable:

#[no_mangle]
static mut BTF_OFF_SOCK__SOCK_COMMON_SKC_FAMILY: u32 = 0;

in their ebpf program.

ebpf probe can then use this offset when accessing kernel code via bpf_probe_read_kernel - this can also be wrapped in a macro for easier understanding, something like:

bpf_read_kernel!(&sock, "sock.__sock_common.skc_family").

In the user program we parse the ebpf program ELF binary and find all globals that match our naming scheme. Then read the BTF from "/sys/kernel/btf/vmlinux", get struct offsets and other info and populate the memory pointed to by the globals before we even load the binary. Then load the modified ELF binary with aya::Ebpf::load.

I know it sounds hacky, but it could generate CO-RE ebpf probe that is also free of any generated bindings.

Is this worth trying?

@vadorovsky
Copy link
Member

vadorovsky commented Feb 1, 2025

Sorry for slow progress here. I was mostly preoccupied with other things, including BTF maps support (#1117) and statically linking libLLVM to bpf-linker. I think the approach I described is still going to work and I didn't hit anything which would prove me otherwise, it's mostly time issue on my side. 😢

@hepek @fhilgers About the macro idea, I think it could work, although not exactly in the form you described.

btf_use!(sock.__sock_common.skc_family); wouldn't work, because I don't think there is a way you could declare a global variable if you call such macro from inside a function.

However, I think that some kind of combination of:

  • derive macro to annotate the struct
  • macro for accessing the struct member (we can keep calling it btf_use!)

would work.

Regarding the first derive macro, let's call the macro #[btf]. The code could look like:

#[derive(Btf)]
pub struct kuid_t {
    val: u32,
}

#[derive(Btf)]
pub struct kgid_t {
    val: gid_t,
}

#[derive(Btf)]
pub struct cred {
    suid: kuid_t,
    sgid: kgid_t,
}

#[derive(Btf)]
pub struct task_struct {
    pid: i32,
    tgid: u32,
    cred: *const cred,
}

The macro would iterate over the fields and define globals for each of them:

pub const BTF_OFF__KUID_T__VAL: usize = 0;

pub const BTF_OFF__KGID_T__VAL: usize = 0;

pub const BTF_OFF__CRED__SUID: usize = 0;
pub const BTF_OFF__CRED__SGID: usize = 0;

pub const BTF_OFF__TASK_STRUCT__PID: usize = 0;
pub const BTF_OFF__TASK_STRUCT__TGID: usize = 0;

Then btf_read_kernel! could use these variables to figure out the correct offset.

I think that's worth trying, so if you were thinking to do that go ahead. Thanks for coming up with the idea!

@hepek
Copy link

hepek commented Feb 4, 2025

@vadorovsky, definitely. I already have some code that validates the basic idea, however none of the procedural macro niceties is there yet. Will let you know when I come up with something that's usable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants