Skip to content
Open
97 changes: 95 additions & 2 deletions aya-ebpf-macros/src/map.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,63 @@
use std::borrow::Cow;

use proc_macro2::TokenStream;
use quote::quote;
use quote::{format_ident, quote};
use syn::{ItemStatic, Result};

use crate::args::Args;
pub(crate) struct Map {
item: ItemStatic,
name: String,
inner: Option<String>,
}

impl Map {
pub(crate) fn parse(attrs: TokenStream, item: TokenStream) -> Result<Self> {
let item: ItemStatic = syn::parse2(item)?;
let mut args: Args = syn::parse2(attrs)?;
let name = args.pop_name().unwrap_or_else(|| item.ident.to_string());
let inner = args.pop_string("inner");
args.into_error()?;
Ok(Self { item, name })
Ok(Self { item, name, inner })
}

pub(crate) fn expand(&self) -> TokenStream {
let section_name: Cow<'_, _> = "maps".into();
let name = &self.name;
let item = &self.item;

// Aya-specific mechanism for inner map bindings (legacy, NOT libbpf-compatible).
//
// Unlike libbpf which uses BTF relocations within the `.maps` section
// (see https://patchwork.ozlabs.org/comment/2418417/), this legacy `#[map]` macro
// uses a separate `.maps.inner` section containing null-terminated string pairs.
//
// This approach was chosen because the legacy map system doesn't require BTF.
// However, this mechanism is NOT compatible with libbpf loaders.
//
// For libbpf compatibility, use `#[btf_map]` with `aya_ebpf::btf_maps::{ArrayOfMaps, HashOfMaps}`
// which use BTF relocations that both aya and libbpf can process.
//
// Format: "outer_name\0inner_name\0" pairs, parsed by aya-obj.
let inner_binding = self.inner.as_ref().map(|inner| {
let binding_ident = format_ident!("__inner_map_binding_{}", name);
let binding_value = format!("{name}\0{inner}\0");
let binding_len = binding_value.len();
let binding_bytes = binding_value.as_bytes();
quote! {
#[unsafe(link_section = ".maps.inner")]
#[used]
#[allow(non_upper_case_globals)]
static #binding_ident: [u8; #binding_len] = [#(#binding_bytes),*];
}
});

quote! {
#[unsafe(link_section = #section_name)]
#[unsafe(export_name = #name)]
#item

#inner_binding
}
}
}
Expand Down Expand Up @@ -72,4 +103,66 @@ mod tests {
);
assert_eq!(expected.to_string(), expanded.to_string());
}

#[test]
fn test_map_with_inner() {
let map = Map::parse(
parse_quote!(inner = "INNER_TEMPLATE"),
parse_quote!(
static OUTER: Array<u32> = Array::new();
),
)
.unwrap();
let expanded = map.expand();
let binding_bytes: &[u8] = b"OUTER\0INNER_TEMPLATE\0";
let expected = quote!(
#[unsafe(link_section = "maps")]
#[unsafe(export_name = "OUTER")]
static OUTER: Array<u32> = Array::new();

#[unsafe(link_section = ".maps.inner")]
#[used]
#[allow(non_upper_case_globals)]
static __inner_map_binding_OUTER: [u8; 21usize] = [#(#binding_bytes),*];
);
assert_eq!(expected.to_string(), expanded.to_string());
}

#[test]
fn test_map_with_name_and_inner() {
let map = Map::parse(
parse_quote!(name = "my_map", inner = "my_template"),
parse_quote!(
static OUTER: Array<u32> = Array::new();
),
)
.unwrap();
let expanded = map.expand();
let binding_bytes: &[u8] = b"my_map\0my_template\0";
let expected = quote!(
#[unsafe(link_section = "maps")]
#[unsafe(export_name = "my_map")]
static OUTER: Array<u32> = Array::new();

#[unsafe(link_section = ".maps.inner")]
#[used]
#[allow(non_upper_case_globals)]
static __inner_map_binding_my_map: [u8; 19usize] = [#(#binding_bytes),*];
);
assert_eq!(expected.to_string(), expanded.to_string());
}

#[test]
fn test_map_unknown_arg() {
let result = Map::parse(
parse_quote!(unknown = "foo"),
parse_quote!(
static BAR: HashMap<&'static str, u32> = HashMap::new();
),
);
let Err(err) = result else {
panic!("expected parse error for unknown argument")
};
assert_eq!(err.to_string(), "invalid argument");
}
}
21 changes: 21 additions & 0 deletions aya-obj/src/btf/btf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,27 @@ pub enum BtfError {
/// unable to get symbol name
#[error("Unable to get symbol name")]
InvalidSymbolName,

/// Inner map definition cannot be pinned.
#[error("BTF map `{name}`: inner map definition cannot be pinned")]
InnerMapCannotBePinned {
/// The name of the map with the invalid definition.
name: String,
},

/// Multi-level map-in-map is not supported.
#[error("BTF map `{name}`: multi-level map-in-map is not supported")]
MultiLevelMapInMapNotSupported {
/// The name of the map with the invalid definition.
name: String,
},

/// The `values` spec must be a zero-sized array.
#[error("BTF map `{name}`: `values` spec is not a zero-sized array")]
InvalidValuesSpec {
/// The name of the map with the invalid definition.
name: String,
},
}

/// Available BTF features
Expand Down
61 changes: 61 additions & 0 deletions aya-obj/src/maps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,63 @@ impl Map {
Self::Btf(m) => Some(m.symbol_index),
}
}

/// Returns the inner map definition, in case of a map of maps.
pub fn inner(&self) -> Option<Self> {
match self {
Self::Legacy(m) => m.inner_def.as_ref().map(|inner_def| {
Self::Legacy(LegacyMap {
def: *inner_def,
inner_def: None,
// The inner map is a synthetic object with no ELF presence
// of its own, so use neutral metadata values.
section_index: 0,
section_kind: EbpfSectionKind::Undefined,
symbol_index: None,
data: Vec::new(),
})
}),
Self::Btf(m) => m.inner_def.as_ref().map(|inner_def| {
Self::Btf(BtfMap {
def: *inner_def,
inner_def: None,
// The inner map is a synthetic object with no ELF presence
// of its own, so use neutral metadata values.
section_index: 0,
symbol_index: 0,
data: Vec::new(),
})
}),
}
}

/// Creates a new legacy map definition programmatically.
///
/// This is useful for creating inner maps dynamically for map-of-maps types.
pub const fn new_legacy(
map_type: u32,
key_size: u32,
value_size: u32,
max_entries: u32,
flags: u32,
) -> Self {
Self::Legacy(LegacyMap {
def: bpf_map_def {
map_type,
key_size,
value_size,
max_entries,
map_flags: flags,
id: 0,
pinning: PinningType::None,
},
inner_def: None,
section_index: 0,
section_kind: EbpfSectionKind::Undefined,
symbol_index: None,
data: Vec::new(),
})
}
}

/// A map declared with legacy BPF map declaration style, most likely from a `maps` section.
Expand All @@ -261,6 +318,8 @@ impl Map {
pub struct LegacyMap {
/// The definition of the map
pub def: bpf_map_def,
/// The definition of the inner map, in case of a map of maps.
pub inner_def: Option<bpf_map_def>,
/// The section index
pub section_index: usize,
/// The section kind
Expand All @@ -280,6 +339,8 @@ pub struct LegacyMap {
pub struct BtfMap {
/// The definition of the map
pub def: BtfMapDef,
/// The definition of the inner map, in case of a map of maps.
pub(crate) inner_def: Option<BtfMapDef>,
pub(crate) section_index: usize,
pub(crate) symbol_index: usize,
pub(crate) data: Vec<u8>,
Expand Down
Loading