From 1b2c61fcf49c8975c8421bd92e47200e849fa116 Mon Sep 17 00:00:00 2001 From: Michal R Date: Sun, 7 Sep 2025 12:49:32 +0200 Subject: [PATCH 1/4] aya-ebpf: Take `c_void` instead of `bpf_map_def` in map helpers `bpf_map_def` is a legacy map definition. To be able to introduce BTF map definitions, make the `lookup` and `remove` helpers work with `c_void` and let the callers cast the map types to it. --- ebpf/aya-ebpf/src/lib.rs | 15 +++++---------- ebpf/aya-ebpf/src/maps/array.rs | 4 ++-- ebpf/aya-ebpf/src/maps/hash_map.rs | 18 +++++++++--------- ebpf/aya-ebpf/src/maps/lpm_trie.rs | 6 +++--- ebpf/aya-ebpf/src/maps/per_cpu_array.rs | 2 +- ebpf/aya-ebpf/src/maps/sock_hash.rs | 2 +- ebpf/aya-ebpf/src/maps/sock_map.rs | 2 +- ebpf/aya-ebpf/src/maps/xdp/dev_map.rs | 2 +- ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs | 2 +- ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs | 2 +- 10 files changed, 25 insertions(+), 30 deletions(-) diff --git a/ebpf/aya-ebpf/src/lib.rs b/ebpf/aya-ebpf/src/lib.rs index d842abaf..ced59066 100644 --- a/ebpf/aya-ebpf/src/lib.rs +++ b/ebpf/aya-ebpf/src/lib.rs @@ -28,11 +28,11 @@ pub mod helpers; pub mod maps; pub mod programs; -use core::{ffi::c_void, ptr::NonNull}; +use core::ptr::NonNull; pub use aya_ebpf_cty as cty; pub use aya_ebpf_macros as macros; -use cty::c_long; +use cty::{c_long, c_void}; use helpers::{ bpf_get_current_comm, bpf_get_current_pid_tgid, bpf_get_current_uid_gid, bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem, @@ -142,12 +142,7 @@ pub fn check_bounds_signed(value: i64, lower: i64, upper: i64) -> bool { } #[inline] -fn insert( - def: *mut bindings::bpf_map_def, - key: &K, - value: &V, - flags: u64, -) -> Result<(), c_long> { +fn insert(def: *mut c_void, key: &K, value: &V, flags: u64) -> Result<(), c_long> { let key: *const _ = key; let value: *const _ = value; match unsafe { bpf_map_update_elem(def.cast(), key.cast(), value.cast(), flags) } { @@ -157,7 +152,7 @@ fn insert( } #[inline] -fn remove(def: *mut bindings::bpf_map_def, key: &K) -> Result<(), c_long> { +fn remove(def: *mut c_void, key: &K) -> Result<(), c_long> { let key: *const _ = key; match unsafe { bpf_map_delete_elem(def.cast(), key.cast()) } { 0 => Ok(()), @@ -166,7 +161,7 @@ fn remove(def: *mut bindings::bpf_map_def, key: &K) -> Result<(), c_long> { } #[inline] -fn lookup(def: *mut bindings::bpf_map_def, key: &K) -> Option> { +fn lookup(def: *mut c_void, key: &K) -> Option> { let key: *const _ = key; NonNull::new(unsafe { bpf_map_lookup_elem(def.cast(), key.cast()) }.cast()) } diff --git a/ebpf/aya-ebpf/src/maps/array.rs b/ebpf/aya-ebpf/src/maps/array.rs index d9911547..ca26e48b 100644 --- a/ebpf/aya-ebpf/src/maps/array.rs +++ b/ebpf/aya-ebpf/src/maps/array.rs @@ -65,12 +65,12 @@ impl Array { #[inline(always)] unsafe fn lookup(&self, index: u32) -> Option> { - lookup(self.def.get(), &index) + lookup(self.def.get().cast(), &index) } /// Sets the value of the element at the given index. #[inline(always)] pub fn set(&self, index: u32, value: &T, flags: u64) -> Result<(), c_long> { - insert(self.def.get(), &index, value, flags) + insert(self.def.get().cast(), &index, value, flags) } } diff --git a/ebpf/aya-ebpf/src/maps/hash_map.rs b/ebpf/aya-ebpf/src/maps/hash_map.rs index f6efa8b2..33e015a7 100644 --- a/ebpf/aya-ebpf/src/maps/hash_map.rs +++ b/ebpf/aya-ebpf/src/maps/hash_map.rs @@ -79,12 +79,12 @@ impl HashMap { #[inline] pub fn insert(&self, key: &K, value: &V, flags: u64) -> Result<(), c_long> { - insert(self.def.get(), key, value, flags) + insert(self.def.get().cast(), key, value, flags) } #[inline] pub fn remove(&self, key: &K) -> Result<(), c_long> { - remove(self.def.get(), key) + remove(self.def.get().cast(), key) } } @@ -155,12 +155,12 @@ impl LruHashMap { #[inline] pub fn insert(&self, key: &K, value: &V, flags: u64) -> Result<(), c_long> { - insert(self.def.get(), key, value, flags) + insert(self.def.get().cast(), key, value, flags) } #[inline] pub fn remove(&self, key: &K) -> Result<(), c_long> { - remove(self.def.get(), key) + remove(self.def.get().cast(), key) } } @@ -231,12 +231,12 @@ impl PerCpuHashMap { #[inline] pub fn insert(&self, key: &K, value: &V, flags: u64) -> Result<(), c_long> { - insert(self.def.get(), key, value, flags) + insert(self.def.get().cast(), key, value, flags) } #[inline] pub fn remove(&self, key: &K) -> Result<(), c_long> { - remove(self.def.get(), key) + remove(self.def.get().cast(), key) } } @@ -307,12 +307,12 @@ impl LruPerCpuHashMap { #[inline] pub fn insert(&self, key: &K, value: &V, flags: u64) -> Result<(), c_long> { - insert(self.def.get(), key, value, flags) + insert(self.def.get().cast(), key, value, flags) } #[inline] pub fn remove(&self, key: &K) -> Result<(), c_long> { - remove(self.def.get(), key) + remove(self.def.get().cast(), key) } } @@ -330,7 +330,7 @@ const fn build_def(ty: u32, max_entries: u32, flags: u32, pin: PinningType #[inline] fn get_ptr_mut(def: *mut bpf_map_def, key: &K) -> Option<*mut V> { - lookup(def, key).map(|p| p.as_ptr()) + lookup(def.cast(), key).map(|p| p.as_ptr()) } #[inline] diff --git a/ebpf/aya-ebpf/src/maps/lpm_trie.rs b/ebpf/aya-ebpf/src/maps/lpm_trie.rs index 1e79f30d..45db960e 100644 --- a/ebpf/aya-ebpf/src/maps/lpm_trie.rs +++ b/ebpf/aya-ebpf/src/maps/lpm_trie.rs @@ -64,17 +64,17 @@ impl LpmTrie { #[inline] pub fn get(&self, key: &Key) -> Option<&V> { - lookup(self.def.get(), key).map(|p| unsafe { p.as_ref() }) + lookup(self.def.get().cast(), key).map(|p| unsafe { p.as_ref() }) } #[inline] pub fn insert(&self, key: &Key, value: &V, flags: u64) -> Result<(), c_long> { - insert(self.def.get(), key, value, flags) + insert(self.def.get().cast(), key, value, flags) } #[inline] pub fn remove(&self, key: &Key) -> Result<(), c_long> { - remove(self.def.get(), key) + remove(self.def.get().cast(), key) } } diff --git a/ebpf/aya-ebpf/src/maps/per_cpu_array.rs b/ebpf/aya-ebpf/src/maps/per_cpu_array.rs index da6e7640..500cc596 100644 --- a/ebpf/aya-ebpf/src/maps/per_cpu_array.rs +++ b/ebpf/aya-ebpf/src/maps/per_cpu_array.rs @@ -65,6 +65,6 @@ impl PerCpuArray { #[inline(always)] unsafe fn lookup(&self, index: u32) -> Option> { - lookup(self.def.get(), &index) + lookup(self.def.get().cast(), &index) } } diff --git a/ebpf/aya-ebpf/src/maps/sock_hash.rs b/ebpf/aya-ebpf/src/maps/sock_hash.rs index fe7fc25b..9d25059e 100644 --- a/ebpf/aya-ebpf/src/maps/sock_hash.rs +++ b/ebpf/aya-ebpf/src/maps/sock_hash.rs @@ -93,7 +93,7 @@ impl SockHash { key: impl Borrow, flags: u64, ) -> Result<(), u32> { - let sk = lookup(self.def.get(), key.borrow()).ok_or(1u32)?; + let sk = lookup(self.def.get().cast(), key.borrow()).ok_or(1u32)?; let ret = unsafe { bpf_sk_assign(ctx.as_ptr().cast(), sk.as_ptr(), flags) }; unsafe { bpf_sk_release(sk.as_ptr()) }; match ret { diff --git a/ebpf/aya-ebpf/src/maps/sock_map.rs b/ebpf/aya-ebpf/src/maps/sock_map.rs index 67b01a12..4a01ceef 100644 --- a/ebpf/aya-ebpf/src/maps/sock_map.rs +++ b/ebpf/aya-ebpf/src/maps/sock_map.rs @@ -77,7 +77,7 @@ impl SockMap { index: u32, flags: u64, ) -> Result<(), u32> { - let sk = lookup(self.def.get(), &index).ok_or(1u32)?; + let sk = lookup(self.def.get().cast(), &index).ok_or(1u32)?; let ret = unsafe { bpf_sk_assign(ctx.as_ptr().cast(), sk.as_ptr(), flags) }; unsafe { bpf_sk_release(sk.as_ptr()) }; match ret { diff --git a/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs b/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs index 52afeeef..07ce271a 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs @@ -105,7 +105,7 @@ impl DevMap { /// ``` #[inline(always)] pub fn get(&self, index: u32) -> Option { - let value = lookup(self.def.get(), &index)?; + let value = lookup(self.def.get().cast(), &index)?; let value: &bpf_devmap_val = unsafe { value.as_ref() }; Some(DevMapValue { if_index: value.ifindex, diff --git a/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs b/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs index 3c8ff019..29cc6d75 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs @@ -107,7 +107,7 @@ impl DevMapHash { /// ``` #[inline(always)] pub fn get(&self, key: u32) -> Option { - let value = lookup(self.def.get(), &key)?; + let value = lookup(self.def.get().cast(), &key)?; let value: &bpf_devmap_val = unsafe { value.as_ref() }; Some(DevMapValue { if_index: value.ifindex, diff --git a/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs b/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs index 3e3b7475..5de3816a 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs @@ -124,7 +124,7 @@ impl XskMap { /// ``` #[inline(always)] pub fn get(&self, index: u32) -> Option { - let value = lookup(self.def.get(), &index)?; + let value = lookup(self.def.get().cast(), &index)?; let value: &bpf_xdp_sock = unsafe { value.as_ref() }; Some(value.queue_id) } From 7f65983b2fedca067568a26cd4e0c95f89df225b Mon Sep 17 00:00:00 2001 From: Michal R Date: Sun, 7 Sep 2025 21:00:11 +0200 Subject: [PATCH 2/4] aya-ebpf: Validate the pointer alignment in `Array::get` --- ebpf/aya-ebpf/src/error.rs | 3 +++ ebpf/aya-ebpf/src/lib.rs | 1 + ebpf/aya-ebpf/src/maps/array.rs | 17 ++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 ebpf/aya-ebpf/src/error.rs diff --git a/ebpf/aya-ebpf/src/error.rs b/ebpf/aya-ebpf/src/error.rs new file mode 100644 index 00000000..78c9606b --- /dev/null +++ b/ebpf/aya-ebpf/src/error.rs @@ -0,0 +1,3 @@ +use crate::cty::c_long; + +pub const EINVAL: c_long = 22; diff --git a/ebpf/aya-ebpf/src/lib.rs b/ebpf/aya-ebpf/src/lib.rs index ced59066..44d886a4 100644 --- a/ebpf/aya-ebpf/src/lib.rs +++ b/ebpf/aya-ebpf/src/lib.rs @@ -23,6 +23,7 @@ pub use aya_ebpf_bindings::bindings; mod args; pub use args::{PtRegs, RawTracepointArgs}; +pub mod error; #[expect(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] pub mod helpers; pub mod maps; diff --git a/ebpf/aya-ebpf/src/maps/array.rs b/ebpf/aya-ebpf/src/maps/array.rs index ca26e48b..534aede5 100644 --- a/ebpf/aya-ebpf/src/maps/array.rs +++ b/ebpf/aya-ebpf/src/maps/array.rs @@ -4,6 +4,7 @@ use aya_ebpf_cty::c_long; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_ARRAY}, + error::EINVAL, insert, lookup, maps::PinningType, }; @@ -48,9 +49,19 @@ impl Array { } #[inline(always)] - pub fn get(&self, index: u32) -> Option<&T> { - // FIXME: alignment - unsafe { self.lookup(index).map(|p| p.as_ref()) } + pub fn get(&self, index: u32) -> Result, c_long> { + unsafe { + match self.lookup(index) { + Some(p) => { + if p.is_aligned() { + Ok(Some(p.as_ref())) + } else { + Err(-EINVAL) + } + } + None => Ok(None), + } + } } #[inline(always)] From ddb30ea420752b60c72c0e21cb7204f2de8acd3b Mon Sep 17 00:00:00 2001 From: Michal R Date: Sun, 7 Sep 2025 12:49:46 +0200 Subject: [PATCH 3/4] aya-ebpf: Add BTF array definition Before this change, Aya supported only legacy BPF map definitions, which are instances of the `bpf_map_def` struct and end up in the `maps` ELF section. This change introduces a BTF map definition for arrays, with custom structs indicating the metadata of the map, which end up in the `.maps` section. --- aya-ebpf-macros/src/btf_map.rs | 75 ++++++++++++++++++++ aya-ebpf-macros/src/lib.rs | 11 +++ aya-obj/src/obj.rs | 68 ++++++++++++++---- ebpf/aya-ebpf/src/btf_maps/array.rs | 70 ++++++++++++++++++ ebpf/aya-ebpf/src/btf_maps/mod.rs | 51 ++++++++++++++ ebpf/aya-ebpf/src/lib.rs | 1 + test/integration-common/src/lib.rs | 6 ++ test/integration-ebpf/Cargo.toml | 4 ++ test/integration-ebpf/src/array.rs | 90 ++++++++++++++++++++++++ test/integration-test/src/lib.rs | 1 + test/integration-test/src/tests.rs | 1 + test/integration-test/src/tests/array.rs | 88 +++++++++++++++++++++++ 12 files changed, 453 insertions(+), 13 deletions(-) create mode 100644 aya-ebpf-macros/src/btf_map.rs create mode 100644 ebpf/aya-ebpf/src/btf_maps/array.rs create mode 100644 ebpf/aya-ebpf/src/btf_maps/mod.rs create mode 100644 test/integration-ebpf/src/array.rs create mode 100644 test/integration-test/src/tests/array.rs diff --git a/aya-ebpf-macros/src/btf_map.rs b/aya-ebpf-macros/src/btf_map.rs new file mode 100644 index 00000000..15bf5e95 --- /dev/null +++ b/aya-ebpf-macros/src/btf_map.rs @@ -0,0 +1,75 @@ +use std::borrow::Cow; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ItemStatic, Result}; + +use crate::args::name_arg; + +pub(crate) struct BtfMap { + item: ItemStatic, + name: String, +} + +impl BtfMap { + pub(crate) fn parse(attrs: TokenStream, item: TokenStream) -> Result { + let item: ItemStatic = syn::parse2(item)?; + let mut args = syn::parse2(attrs)?; + let name = name_arg(&mut args).unwrap_or_else(|| item.ident.to_string()); + Ok(BtfMap { item, name }) + } + + pub(crate) fn expand(&self) -> TokenStream { + let section_name: Cow<'_, _> = ".maps".into(); + let name = &self.name; + let item = &self.item; + quote! { + #[unsafe(link_section = #section_name)] + #[unsafe(export_name = #name)] + #item + } + } +} + +#[cfg(test)] +mod tests { + use syn::parse_quote; + + use super::*; + + #[test] + fn test_map_with_name() { + let map = BtfMap::parse( + parse_quote!(name = "foo"), + parse_quote!( + static BAR: HashMap<&'static str, u32> = HashMap::new(); + ), + ) + .unwrap(); + let expanded = map.expand(); + let expected = quote!( + #[unsafe(link_section = ".maps")] + #[unsafe(export_name = "foo")] + static BAR: HashMap<&'static str, u32> = HashMap::new(); + ); + assert_eq!(expected.to_string(), expanded.to_string()); + } + + #[test] + fn test_map_no_name() { + let map = BtfMap::parse( + parse_quote!(), + parse_quote!( + static BAR: HashMap<&'static str, u32> = HashMap::new(); + ), + ) + .unwrap(); + let expanded = map.expand(); + let expected = quote!( + #[unsafe(link_section = ".maps")] + #[unsafe(export_name = "BAR")] + static BAR: HashMap<&'static str, u32> = HashMap::new(); + ); + assert_eq!(expected.to_string(), expanded.to_string()); + } +} diff --git a/aya-ebpf-macros/src/lib.rs b/aya-ebpf-macros/src/lib.rs index d5811d53..91988ea9 100644 --- a/aya-ebpf-macros/src/lib.rs +++ b/aya-ebpf-macros/src/lib.rs @@ -1,4 +1,5 @@ pub(crate) mod args; +mod btf_map; mod btf_tracepoint; mod cgroup_device; mod cgroup_skb; @@ -24,6 +25,7 @@ mod tracepoint; mod uprobe; mod xdp; +use btf_map::BtfMap; use btf_tracepoint::BtfTracePoint; use cgroup_device::CgroupDevice; use cgroup_skb::CgroupSkb; @@ -50,6 +52,15 @@ use tracepoint::TracePoint; use uprobe::{UProbe, UProbeKind}; use xdp::Xdp; +#[proc_macro_attribute] +pub fn btf_map(attrs: TokenStream, item: TokenStream) -> TokenStream { + match BtfMap::parse(attrs.into(), item.into()) { + Ok(prog) => prog.expand(), + Err(err) => err.into_compile_error(), + } + .into() +} + #[proc_macro_attribute] pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { match Map::parse(attrs.into(), item.into()) { diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index ece738ef..0d6fe314 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -758,7 +758,7 @@ impl Object { if type_name == section.name { // each btf_var_secinfo contains a map for info in &datasec.entries { - let (map_name, def) = parse_btf_map_def(btf, info)?; + let (map_name, def) = parse_btf_map(btf, info)?; let symbol_index = maps.get(&map_name) .ok_or_else(|| ParseError::SymbolNotFound { @@ -1240,7 +1240,7 @@ fn parse_map_def(name: &str, data: &[u8]) -> Result { } } -fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDef), BtfError> { +fn parse_btf_map(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDef), BtfError> { let ty = match btf.type_by_id(info.btf_type)? { BtfType::Var(var) => var, other => { @@ -1250,11 +1250,18 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe } }; let map_name = btf.string_at(ty.name_offset)?; - let mut map_def = BtfMapDef::default(); - // Safety: union let root_type = btf.resolve_type(ty.btf_type)?; - let s = match btf.type_by_id(root_type)? { + parse_btf_map_def(btf, &map_name, root_type) +} + +fn parse_btf_map_def( + btf: &Btf, + map_name: &str, + btf_type_id: u32, +) -> Result<(String, BtfMapDef), BtfError> { + // Safety: union + let s = match btf.type_by_id(btf_type_id)? { BtfType::Struct(s) => s, other => { return Err(BtfError::UnexpectedBtfType { @@ -1263,8 +1270,26 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe } }; + let mut map_def = BtfMapDef::default(); + for m in &s.members { + // In aya-ebpf, the BTF map definition types are not used directly. + // Instead, they are wrapped in structs provided by aya-ebpf (e.g. + // `HashMap`, `Array`), which then wrap the definition type in + // `UnsafeCell`. + // To retrieve the definition type, we need to walk through all the + // wrapper types: + // + // - aya-ebpf wrappers like `HashMap`, `Array` etc. They wrap an + // `UnsafeCell` in a tuple struct with one member, hence the field + // name is `__0`. + // - `UnsafeCell`, which wraps the BTF map definition inside a `value` + // field. match btf.string_at(m.name_offset)?.as_ref() { + "__0" => { + let unsafe_cell_id = btf.resolve_type(m.btf_type)?; + return parse_btf_map_def(btf, map_name, unsafe_cell_id); + } "type" => { map_def.map_type = get_map_field(btf, m.btf_type)?; } @@ -1284,14 +1309,31 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe map_def.key_size = get_map_field(btf, m.btf_type)?; } "value" => { - if let BtfType::Ptr(pty) = btf.type_by_id(m.btf_type)? { - let t = pty.btf_type; - map_def.value_size = btf.type_size(t)? as u32; - map_def.btf_value_type_id = t; - } else { - return Err(BtfError::UnexpectedBtfType { - type_id: m.btf_type, - }); + // There are two cases to handle: + // + // 1. We are parsing an actual BTF map type with fields like + // `type`, `value`, `key`. In this case, `value` is a + // pointer. + // 2. We are parsing an `UnsafeCell`, which wraps an actual BTF + // map type, and the `value` field of `UnsafeCell` is a + // struct which we want to parse. + match btf.type_by_id(m.btf_type)? { + // BTF map with `value` as a pointer field. + BtfType::Ptr(pty) => { + let t = pty.btf_type; + map_def.value_size = btf.type_size(t)? as u32; + map_def.btf_value_type_id = t; + } + // `UnsafeCell` wrapping a BTF map in a `value field`. + BtfType::Struct(_) => { + let map_type_id = btf.resolve_type(m.btf_type)?; + return parse_btf_map_def(btf, map_name, map_type_id); + } + _ => { + return Err(BtfError::UnexpectedBtfType { + type_id: m.btf_type, + }); + } } } "value_size" => { diff --git a/ebpf/aya-ebpf/src/btf_maps/array.rs b/ebpf/aya-ebpf/src/btf_maps/array.rs new file mode 100644 index 00000000..458c652e --- /dev/null +++ b/ebpf/aya-ebpf/src/btf_maps/array.rs @@ -0,0 +1,70 @@ +use core::{cell::UnsafeCell, ptr::NonNull}; + +use crate::{ + bindings::bpf_map_type::BPF_MAP_TYPE_ARRAY, btf_map_def, cty::c_long, error::EINVAL, insert, + lookup, +}; + +btf_map_def!(ArrayDef, BPF_MAP_TYPE_ARRAY); + +#[repr(transparent)] +pub struct Array(UnsafeCell>); + +unsafe impl Sync for Array {} + +impl Array { + /// Creates a new [`Array`] instance with elements of type `T`, maximum + /// capacity of `M` and additional flags `F`. + /// + /// # Example + /// + /// ```rust + /// use aya_ebpf::{btf_maps::Array, macros::btf_map}; + /// + /// #[btf_map] + /// static ARRAY: Array = Array::new(); + /// ``` + // BPF maps are always used as static variables, therefore this method has + // to be `const`. `Default::default` is not const. + #[expect(clippy::new_without_default)] + pub const fn new() -> Self { + Array(UnsafeCell::new(ArrayDef::new())) + } + + #[inline(always)] + pub fn get(&self, index: u32) -> Result, c_long> { + unsafe { + match self.lookup(index) { + Some(p) => { + if p.is_aligned() { + Ok(Some(p.as_ref())) + } else { + Err(-EINVAL) + } + } + None => Ok(None), + } + } + } + + #[inline(always)] + pub fn get_ptr(&self, index: u32) -> Option<*const T> { + unsafe { self.lookup(index).map(|p| p.as_ptr() as *const T) } + } + + #[inline(always)] + pub fn get_ptr_mut(&self, index: u32) -> Option<*mut T> { + unsafe { self.lookup(index).map(|p| p.as_ptr()) } + } + + #[inline(always)] + unsafe fn lookup(&self, index: u32) -> Option> { + lookup(self.0.get().cast(), &index) + } + + /// Sets the value of the element at the given index. + #[inline(always)] + pub fn set(&self, index: u32, value: &T, flags: u64) -> Result<(), c_long> { + insert(self.0.get().cast(), &index, value, flags) + } +} diff --git a/ebpf/aya-ebpf/src/btf_maps/mod.rs b/ebpf/aya-ebpf/src/btf_maps/mod.rs new file mode 100644 index 00000000..f36f07c8 --- /dev/null +++ b/ebpf/aya-ebpf/src/btf_maps/mod.rs @@ -0,0 +1,51 @@ +use core::marker::PhantomData; + +pub mod array; + +pub use array::Array; + +/// A marker used to remove names of annotated types in LLVM debug info and +/// therefore also in BTF. +#[repr(transparent)] +pub(crate) struct AyaBtfMapMarker(PhantomData<()>); + +impl AyaBtfMapMarker { + pub(crate) const fn new() -> Self { + Self(PhantomData) + } +} + +#[macro_export] +macro_rules! btf_map_def { + ($name:ident, $t:ident) => { + // These fields exists only for BTF metadata exposure. None of them are + // are actually used. + #[expect(dead_code)] + pub struct $name { + r#type: *const [i32; $t as usize], + key: *const K, + value: *const V, + max_entries: *const [i32; M], + map_flags: *const [i32; F], + + // Anonymize the struct. + _anon: $crate::btf_maps::AyaBtfMapMarker, + } + + // BPF maps are always used as static variables, therefore this method + // has to be `const`. `Default::default` is not const. + #[expect(clippy::new_without_default)] + impl $name { + pub const fn new() -> $name { + $name { + r#type: &[0i32; $t as usize], + key: ::core::ptr::null(), + value: ::core::ptr::null(), + max_entries: &[0i32; M], + map_flags: &[0i32; F], + _anon: $crate::btf_maps::AyaBtfMapMarker::new(), + } + } + } + }; +} diff --git a/ebpf/aya-ebpf/src/lib.rs b/ebpf/aya-ebpf/src/lib.rs index 44d886a4..78e4965b 100644 --- a/ebpf/aya-ebpf/src/lib.rs +++ b/ebpf/aya-ebpf/src/lib.rs @@ -23,6 +23,7 @@ pub use aya_ebpf_bindings::bindings; mod args; pub use args::{PtRegs, RawTracepointArgs}; +pub mod btf_maps; pub mod error; #[expect(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] pub mod helpers; diff --git a/test/integration-common/src/lib.rs b/test/integration-common/src/lib.rs index 8efed81d..78cdd927 100644 --- a/test/integration-common/src/lib.rs +++ b/test/integration-common/src/lib.rs @@ -1,5 +1,11 @@ #![no_std] +pub mod array { + pub const GET_INDEX: u32 = 0; + pub const GET_PTR_INDEX: u32 = 1; + pub const GET_PTR_MUT_INDEX: u32 = 2; +} + pub mod bpf_probe_read { pub const RESULT_BUF_LEN: usize = 1024; diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 78c8b89e..d4d6da59 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -24,6 +24,10 @@ network-types = { workspace = true } which = { workspace = true, features = ["real-sys"] } xtask = { path = "../../xtask" } +[[bin]] +name = "array" +path = "src/array.rs" + [[bin]] name = "bpf_probe_read" path = "src/bpf_probe_read.rs" diff --git a/test/integration-ebpf/src/array.rs b/test/integration-ebpf/src/array.rs new file mode 100644 index 00000000..80a7c25d --- /dev/null +++ b/test/integration-ebpf/src/array.rs @@ -0,0 +1,90 @@ +#![no_std] +#![no_main] + +#[cfg(not(test))] +extern crate ebpf_panic; + +use aya_ebpf::{ + btf_maps::Array, + cty::c_long, + macros::{btf_map, map, uprobe}, + maps::Array as LegacyArray, + programs::ProbeContext, +}; +use integration_common::array::{GET_INDEX, GET_PTR_INDEX, GET_PTR_MUT_INDEX}; + +#[btf_map] +static RESULT: Array = Array::new(); +#[btf_map] +static ARRAY: Array = Array::new(); + +#[map] +static RESULT_LEGACY: LegacyArray = LegacyArray::with_max_entries(3, 0); +#[map] +static ARRAY_LEGACY: LegacyArray = LegacyArray::with_max_entries(10, 0); + +macro_rules! define_array_test { + ( + $result_map:ident, + $array_map:ident, + $result_set_fn:ident, + $set_prog:ident, + $get_prog:ident, + $get_ptr_prog:ident, + $get_ptr_mut_prog:ident + $(,)? + ) => { + #[inline(always)] + fn $result_set_fn(index: u32, value: u32) -> Result<(), c_long> { + let ptr = $result_map.get_ptr_mut(index).ok_or(-1)?; + let dst = unsafe { ptr.as_mut() }; + let dst_res = dst.ok_or(-1)?; + *dst_res = value; + Ok(()) + } + + #[uprobe] + pub fn $set_prog(ctx: ProbeContext) -> Result<(), c_long> { + let index = ctx.arg(0).ok_or(-1)?; + let value = ctx.arg(1).ok_or(-1)?; + $array_map.set(index, &value, 0)?; + Ok(()) + } + + #[uprobe] + pub fn $get_prog(ctx: ProbeContext) -> Result<(), c_long> { + let index = ctx.arg(0).ok_or(-1)?; + let value = $array_map.get(index)?.ok_or(-1)?; + $result_set_fn(GET_INDEX, *value)?; + Ok(()) + } + + #[uprobe] + pub fn $get_ptr_prog(ctx: ProbeContext) -> Result<(), c_long> { + let index = ctx.arg(0).ok_or(-1)?; + let value = $array_map.get_ptr(index).ok_or(-1)?; + $result_set_fn(GET_PTR_INDEX, unsafe { *value })?; + Ok(()) + } + + #[uprobe] + pub fn $get_ptr_mut_prog(ctx: ProbeContext) -> Result<(), c_long> { + let index = ctx.arg(0).ok_or(-1)?; + let ptr = $array_map.get_ptr_mut(index).ok_or(-1)?; + let value = unsafe { *ptr }; + $result_set_fn(GET_PTR_MUT_INDEX, value)?; + Ok(()) + } + }; +} + +define_array_test!(RESULT, ARRAY, result_set, set, get, get_ptr, get_ptr_mut); +define_array_test!( + RESULT_LEGACY, + ARRAY_LEGACY, + result_set_legacy, + set_legacy, + get_legacy, + get_ptr_legacy, + get_ptr_mut_legacy, +); diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 9dbb4d18..f7da1d9f 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -38,6 +38,7 @@ bpf_file!( TEXT_64_64_RELOC => "text_64_64_reloc.o", VARIABLES_RELOC => "variables_reloc.bpf.o", + ARRAY => "array", BPF_PROBE_READ => "bpf_probe_read", LINEAR_DATA_STRUCTURES => "linear_data_structures", LOG => "log", diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index deaa63a5..f9427ab4 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -1,3 +1,4 @@ +mod array; mod bpf_probe_read; mod btf_relocations; mod elf; diff --git a/test/integration-test/src/tests/array.rs b/test/integration-test/src/tests/array.rs new file mode 100644 index 00000000..1b1c6e5c --- /dev/null +++ b/test/integration-test/src/tests/array.rs @@ -0,0 +1,88 @@ +use aya::{EbpfLoader, maps::Array, programs::UProbe}; +use integration_common::array::{GET_INDEX, GET_PTR_INDEX, GET_PTR_MUT_INDEX}; + +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn set(index: u32, value: u32) { + std::hint::black_box((index, value)); +} + +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn get(index: u32) { + std::hint::black_box(index); +} + +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn get_ptr(index: u32) { + std::hint::black_box(index); +} + +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn get_ptr_mut(index: u32) { + std::hint::black_box(index); +} + +#[test_log::test] +fn test_array() { + let mut ebpf = EbpfLoader::new().load(crate::ARRAY).unwrap(); + for (result_map, array_map, progs_and_symbols) in [ + // BTF map definitions. + ( + "RESULT", + "ARRAY", + [ + ("set", "set"), + ("get", "get"), + ("get_ptr", "get_ptr"), + ("get_ptr_mut", "get_ptr_mut"), + ], + ), + // Legacy map definitions. + ( + "RESULT_LEGACY", + "ARRAY_LEGACY", + [ + ("set_legacy", "set"), + ("get_legacy", "get"), + ("get_ptr_legacy", "get_ptr"), + ("get_ptr_mut_legacy", "get_ptr_mut"), + ], + ), + ] { + for (prog_name, symbol) in progs_and_symbols { + let prog: &mut UProbe = ebpf.program_mut(prog_name).unwrap().try_into().unwrap(); + prog.load().unwrap(); + prog.attach(symbol, "/proc/self/exe", None, None).unwrap(); + } + let result_array = ebpf.map(result_map).unwrap(); + let result_array = Array::<_, u32>::try_from(result_array).unwrap(); + let array = ebpf.map(array_map).unwrap(); + let array = Array::<_, u32>::try_from(array).unwrap(); + let seq = 0..9; + for i in seq.clone() { + set(i, i.pow(2)); + } + for i in seq.clone() { + // Assert the value returned by user-space API. + let expected_value = i.pow(2); + let value = array.get(&i, 0).unwrap(); + assert_eq!(value, expected_value); + // Assert the value returned by eBPF in-kernel API. + get(i); + let result = result_array.get(&GET_INDEX, 0).unwrap(); + assert_eq!(result, expected_value); + get_ptr(i); + let result = result_array.get(&GET_PTR_INDEX, 0).unwrap(); + assert_eq!(result, expected_value); + } + for i in seq.clone() { + let value = i.pow(2); + get_ptr_mut(i); + let result = result_array.get(&GET_PTR_MUT_INDEX, 0).unwrap(); + assert_eq!(result, value); + } + } +} From 892a1c466f6110fda8baa428f3e5b6872060871f Mon Sep 17 00:00:00 2001 From: Michal R Date: Sun, 7 Sep 2025 19:35:01 +0200 Subject: [PATCH 4/4] aya: Bless API changes Add the new BTF map macro and BTF array. --- xtask/public-api/aya-ebpf-macros.txt | 1 + xtask/public-api/aya-ebpf.txt | 88 +++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/xtask/public-api/aya-ebpf-macros.txt b/xtask/public-api/aya-ebpf-macros.txt index 484df241..723ec117 100644 --- a/xtask/public-api/aya-ebpf-macros.txt +++ b/xtask/public-api/aya-ebpf-macros.txt @@ -1,4 +1,5 @@ pub mod aya_ebpf_macros +pub proc macro aya_ebpf_macros::#[btf_map] pub proc macro aya_ebpf_macros::#[btf_tracepoint] pub proc macro aya_ebpf_macros::#[cgroup_device] pub proc macro aya_ebpf_macros::#[cgroup_skb] diff --git a/xtask/public-api/aya-ebpf.txt b/xtask/public-api/aya-ebpf.txt index b70493ba..63ed734f 100644 --- a/xtask/public-api/aya-ebpf.txt +++ b/xtask/public-api/aya-ebpf.txt @@ -2,6 +2,91 @@ pub mod aya_ebpf pub use aya_ebpf::bindings pub use aya_ebpf::cty pub use aya_ebpf::macros +pub mod aya_ebpf::btf_maps +pub mod aya_ebpf::btf_maps::array +#[repr(transparent)] pub struct aya_ebpf::btf_maps::array::Array(_) +impl aya_ebpf::btf_maps::array::Array +pub fn aya_ebpf::btf_maps::array::Array::get(&self, index: u32) -> core::option::Option<&T> +pub fn aya_ebpf::btf_maps::array::Array::get_ptr(&self, index: u32) -> core::option::Option<*const T> +pub fn aya_ebpf::btf_maps::array::Array::get_ptr_mut(&self, index: u32) -> core::option::Option<*mut T> +pub const fn aya_ebpf::btf_maps::array::Array::new() -> Self +pub fn aya_ebpf::btf_maps::array::Array::set(&self, index: u32, value: &T, flags: u64) -> core::result::Result<(), aya_ebpf_cty::od::c_long> +impl core::marker::Sync for aya_ebpf::btf_maps::array::Array +impl !core::marker::Freeze for aya_ebpf::btf_maps::array::Array +impl !core::marker::Send for aya_ebpf::btf_maps::array::Array +impl core::marker::Unpin for aya_ebpf::btf_maps::array::Array +impl !core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::btf_maps::array::Array +impl core::panic::unwind_safe::UnwindSafe for aya_ebpf::btf_maps::array::Array where T: core::panic::unwind_safe::RefUnwindSafe +impl core::convert::Into for aya_ebpf::btf_maps::array::Array where U: core::convert::From +pub fn aya_ebpf::btf_maps::array::Array::into(self) -> U +impl core::convert::TryFrom for aya_ebpf::btf_maps::array::Array where U: core::convert::Into +pub type aya_ebpf::btf_maps::array::Array::Error = core::convert::Infallible +pub fn aya_ebpf::btf_maps::array::Array::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya_ebpf::btf_maps::array::Array where U: core::convert::TryFrom +pub type aya_ebpf::btf_maps::array::Array::Error = >::Error +pub fn aya_ebpf::btf_maps::array::Array::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya_ebpf::btf_maps::array::Array where T: 'static + ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::Array::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya_ebpf::btf_maps::array::Array where T: ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::Array::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya_ebpf::btf_maps::array::Array where T: ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::Array::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya_ebpf::btf_maps::array::Array +pub fn aya_ebpf::btf_maps::array::Array::from(t: T) -> T +pub struct aya_ebpf::btf_maps::array::ArrayDef +impl aya_ebpf::btf_maps::array::ArrayDef +pub const fn aya_ebpf::btf_maps::array::ArrayDef::new() -> aya_ebpf::btf_maps::array::ArrayDef +impl core::marker::Freeze for aya_ebpf::btf_maps::array::ArrayDef +impl !core::marker::Send for aya_ebpf::btf_maps::array::ArrayDef +impl !core::marker::Sync for aya_ebpf::btf_maps::array::ArrayDef +impl core::marker::Unpin for aya_ebpf::btf_maps::array::ArrayDef +impl core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::btf_maps::array::ArrayDef where K: core::panic::unwind_safe::RefUnwindSafe, V: core::panic::unwind_safe::RefUnwindSafe +impl core::panic::unwind_safe::UnwindSafe for aya_ebpf::btf_maps::array::ArrayDef where K: core::panic::unwind_safe::RefUnwindSafe, V: core::panic::unwind_safe::RefUnwindSafe +impl core::convert::Into for aya_ebpf::btf_maps::array::ArrayDef where U: core::convert::From +pub fn aya_ebpf::btf_maps::array::ArrayDef::into(self) -> U +impl core::convert::TryFrom for aya_ebpf::btf_maps::array::ArrayDef where U: core::convert::Into +pub type aya_ebpf::btf_maps::array::ArrayDef::Error = core::convert::Infallible +pub fn aya_ebpf::btf_maps::array::ArrayDef::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya_ebpf::btf_maps::array::ArrayDef where U: core::convert::TryFrom +pub type aya_ebpf::btf_maps::array::ArrayDef::Error = >::Error +pub fn aya_ebpf::btf_maps::array::ArrayDef::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya_ebpf::btf_maps::array::ArrayDef where T: 'static + ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::ArrayDef::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya_ebpf::btf_maps::array::ArrayDef where T: ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::ArrayDef::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya_ebpf::btf_maps::array::ArrayDef where T: ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::ArrayDef::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya_ebpf::btf_maps::array::ArrayDef +pub fn aya_ebpf::btf_maps::array::ArrayDef::from(t: T) -> T +#[repr(transparent)] pub struct aya_ebpf::btf_maps::Array(_) +impl aya_ebpf::btf_maps::array::Array +pub fn aya_ebpf::btf_maps::array::Array::get(&self, index: u32) -> core::option::Option<&T> +pub fn aya_ebpf::btf_maps::array::Array::get_ptr(&self, index: u32) -> core::option::Option<*const T> +pub fn aya_ebpf::btf_maps::array::Array::get_ptr_mut(&self, index: u32) -> core::option::Option<*mut T> +pub const fn aya_ebpf::btf_maps::array::Array::new() -> Self +pub fn aya_ebpf::btf_maps::array::Array::set(&self, index: u32, value: &T, flags: u64) -> core::result::Result<(), aya_ebpf_cty::od::c_long> +impl core::marker::Sync for aya_ebpf::btf_maps::array::Array +impl !core::marker::Freeze for aya_ebpf::btf_maps::array::Array +impl !core::marker::Send for aya_ebpf::btf_maps::array::Array +impl core::marker::Unpin for aya_ebpf::btf_maps::array::Array +impl !core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::btf_maps::array::Array +impl core::panic::unwind_safe::UnwindSafe for aya_ebpf::btf_maps::array::Array where T: core::panic::unwind_safe::RefUnwindSafe +impl core::convert::Into for aya_ebpf::btf_maps::array::Array where U: core::convert::From +pub fn aya_ebpf::btf_maps::array::Array::into(self) -> U +impl core::convert::TryFrom for aya_ebpf::btf_maps::array::Array where U: core::convert::Into +pub type aya_ebpf::btf_maps::array::Array::Error = core::convert::Infallible +pub fn aya_ebpf::btf_maps::array::Array::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya_ebpf::btf_maps::array::Array where U: core::convert::TryFrom +pub type aya_ebpf::btf_maps::array::Array::Error = >::Error +pub fn aya_ebpf::btf_maps::array::Array::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya_ebpf::btf_maps::array::Array where T: 'static + ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::Array::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya_ebpf::btf_maps::array::Array where T: ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::Array::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya_ebpf::btf_maps::array::Array where T: ?core::marker::Sized +pub fn aya_ebpf::btf_maps::array::Array::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya_ebpf::btf_maps::array::Array +pub fn aya_ebpf::btf_maps::array::Array::from(t: T) -> T pub mod aya_ebpf::helpers pub use aya_ebpf::helpers::generated pub macro aya_ebpf::helpers::bpf_printk! @@ -2732,6 +2817,7 @@ pub fn aya_ebpf::programs::xdp::XdpContext::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya_ebpf::programs::xdp::XdpContext pub fn aya_ebpf::programs::xdp::XdpContext::from(t: T) -> T pub macro aya_ebpf::bpf_printk! +pub macro aya_ebpf::btf_map_def! pub struct aya_ebpf::PtRegs impl aya_ebpf::PtRegs pub fn aya_ebpf::PtRegs::arg(&self, n: usize) -> core::option::Option @@ -2788,7 +2874,7 @@ impl core::convert::From for aya_ebpf::RawTracepointArgs pub fn aya_ebpf::RawTracepointArgs::from(t: T) -> T pub const aya_ebpf::TASK_COMM_LEN: usize pub trait aya_ebpf::EbpfContext -pub fn aya_ebpf::EbpfContext::as_ptr(&self) -> *mut core::ffi::c_void +pub fn aya_ebpf::EbpfContext::as_ptr(&self) -> *mut aya_ebpf_cty::c_void pub fn aya_ebpf::EbpfContext::command(&self) -> core::result::Result<[u8; 16], aya_ebpf_cty::od::c_long> pub fn aya_ebpf::EbpfContext::gid(&self) -> u32 pub fn aya_ebpf::EbpfContext::pid(&self) -> u32