diff --git a/aya-obj/src/lib.rs b/aya-obj/src/lib.rs index ea0e56702..460d64240 100644 --- a/aya-obj/src/lib.rs +++ b/aya-obj/src/lib.rs @@ -39,10 +39,14 @@ //! // Relocate the programs //! #[cfg(feature = "std")] //! let text_sections = std::collections::HashSet::new(); +//! #[cfg(feature = "std")] +//! let ignore_maps = std::collections::HashMap::new(); //! #[cfg(not(feature = "std"))] //! let text_sections = hashbrown::HashSet::new(); +//! #[cfg(not(feature = "std"))] +//! let ignore_maps = hashbrown::HashMap::new(); //! object.relocate_calls(&text_sections).unwrap(); -//! object.relocate_maps(std::iter::empty(), &text_sections).unwrap(); +//! object.relocate_maps(std::iter::empty(), &text_sections, &ignore_maps).unwrap(); //! //! // Run with rbpf //! let function = object.functions.get(&object.programs["prog_name"].function_key()).unwrap(); diff --git a/aya-obj/src/relocation.rs b/aya-obj/src/relocation.rs index b05648ba4..bbf7f1d3a 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -108,9 +108,20 @@ impl Object { &mut self, maps: I, text_sections: &HashSet, + ignored_maps: &HashMap, ) -> Result<(), EbpfRelocationError> { let mut maps_by_section = HashMap::new(); let mut maps_by_symbol = HashMap::new(); + let mut ignored_by_section = HashSet::new(); + let mut ignored_by_symbol = HashSet::new(); + + for map in ignored_maps.values() { + ignored_by_section.insert(map.section_index()); + if let Some(index) = map.symbol_index() { + ignored_by_symbol.insert(index); + } + } + for (name, fd, map) in maps { maps_by_section.insert(map.section_index(), (name, fd, map)); if let Some(index) = map.symbol_index() { @@ -127,6 +138,8 @@ impl Object { &maps_by_symbol, &self.symbol_table, text_sections, + &ignored_by_section, + &ignored_by_symbol, ) .map_err(|error| EbpfRelocationError { function: function.name.clone(), @@ -176,6 +189,7 @@ impl Object { } } +#[allow(clippy::too_many_arguments)] fn relocate_maps<'a, I: Iterator>( fun: &mut Function, relocations: I, @@ -183,6 +197,8 @@ fn relocate_maps<'a, I: Iterator>( maps_by_symbol: &HashMap, symbol_table: &HashMap, text_sections: &HashSet, + ignored_by_section: &HashSet, + ignored_by_symbol: &HashSet, ) -> Result<(), RelocationError> { let section_offset = fun.section_offset; let instructions = &mut fun.instructions; @@ -222,7 +238,9 @@ fn relocate_maps<'a, I: Iterator>( continue; } - let (_name, fd, map) = if let Some(m) = maps_by_symbol.get(&rel.symbol_index) { + let (_name, fd, map) = if ignored_by_symbol.contains(&rel.symbol_index) { + continue; + } else if let Some(m) = maps_by_symbol.get(&rel.symbol_index) { let map = &m.2; debug!( "relocating map by symbol index {:?}, kind {:?} at insn {ins_index} in section {}", @@ -232,6 +250,8 @@ fn relocate_maps<'a, I: Iterator>( ); debug_assert_eq!(map.symbol_index().unwrap(), rel.symbol_index); m + } else if ignored_by_section.contains(§ion_index) { + continue; } else { let Some(m) = maps_by_section.get(§ion_index) else { debug!("failed relocating map by section index {}", section_index); @@ -580,6 +600,8 @@ mod test { &maps_by_symbol, &symbol_table, &HashSet::new(), + &HashSet::new(), + &HashSet::new(), ) .unwrap(); @@ -636,6 +658,8 @@ mod test { &maps_by_symbol, &symbol_table, &HashSet::new(), + &HashSet::new(), + &HashSet::new(), ) .unwrap(); @@ -675,6 +699,8 @@ mod test { &maps_by_symbol, &symbol_table, &HashSet::new(), + &HashSet::new(), + &HashSet::new(), ) .unwrap(); @@ -731,6 +757,8 @@ mod test { &maps_by_symbol, &symbol_table, &HashSet::new(), + &HashSet::new(), + &HashSet::new(), ) .unwrap(); diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 42a9a4899..6ec4c5c37 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -21,8 +21,8 @@ use thiserror::Error; use crate::{ generated::{ - bpf_map_type, bpf_map_type::*, AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, - AYA_PERF_EVENT_IOC_SET_BPF, + bpf_map_type::{self, *}, + AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, AYA_PERF_EVENT_IOC_SET_BPF, }, maps::{Map, MapData, MapError}, obj::{ @@ -137,6 +137,7 @@ pub struct EbpfLoader<'a> { extensions: HashSet<&'a str>, verifier_log_level: VerifierLogLevel, allow_unsupported_maps: bool, + ignore_maps_by_name: &'a [&'a str], } /// Builder style API for advanced loading of eBPF programs. @@ -175,6 +176,7 @@ impl<'a> EbpfLoader<'a> { extensions: HashSet::new(), verifier_log_level: VerifierLogLevel::default(), allow_unsupported_maps: false, + ignore_maps_by_name: &[], } } @@ -224,6 +226,32 @@ impl<'a> EbpfLoader<'a> { self } + /// Allows programs containing unsupported maps for the current kernel to be loaded + /// by skipping map creation and relocation before loading. + /// + /// This is useful when you have a single ebpf program containing e.g. a `RingBuf` + /// and a `PerfEventArray` and you decide which one to use before loading the bytecode. + /// + /// Must be used with [`EbpfLoader::set_global`] to signal if the map is supported in the ebpf program. + /// + /// # Example + /// + /// ```no_run + /// use aya::EbpfLoader; + /// + /// let ringbuf_supported = 0; + /// let ebpf = EbpfLoader::new() + /// .ignore_maps_by_name(&["RINGBUF"]) + /// .set_global("RINGBUF_SUPPORTED", &ringbuf_supported, true) + /// .load_file("file.o")?; + /// # Ok::<(), aya::EbpfError>(()) + /// ``` + /// + pub fn ignore_maps_by_name(&mut self, name: &'a [&'a str]) -> &mut Self { + self.ignore_maps_by_name = name; + self + } + /// Sets the base directory path for pinned maps. /// /// Pinned maps will be loaded from `path/MAP_NAME`. @@ -394,6 +422,7 @@ impl<'a> EbpfLoader<'a> { extensions, verifier_log_level, allow_unsupported_maps, + ignore_maps_by_name, } = self; let mut obj = Object::parse(data)?; obj.patch_map_data(globals.clone())?; @@ -459,12 +488,22 @@ impl<'a> EbpfLoader<'a> { obj.relocate_btf(btf)?; } let mut maps = HashMap::new(); + let mut ignored_maps = HashMap::new(); + for (name, mut obj) in obj.maps.drain() { if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) = (FEATURES.bpf_global_data(), obj.section_kind()) { continue; } + + if ignore_maps_by_name.contains(&name.as_str()) { + ignored_maps.insert(name, obj); + // ignore map creation. The map is saved in `ignored_maps` and filtered out + // in `relocate_maps()` later on + continue; + } + let num_cpus = || -> Result { Ok(possible_cpus() .map_err(|error| EbpfError::FileError { @@ -508,7 +547,6 @@ impl<'a> EbpfLoader<'a> { map.finalize()?; maps.insert(name, map); } - let text_sections = obj .functions .keys() @@ -519,6 +557,7 @@ impl<'a> EbpfLoader<'a> { maps.iter() .map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj())), &text_sections, + &ignored_maps, )?; obj.relocate_calls(&text_sections)?; obj.sanitize_functions(&FEATURES); diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 2c5e7b2e2..4932ef1f0 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -61,6 +61,10 @@ path = "src/xdp_sec.rs" name = "ring_buf" path = "src/ring_buf.rs" +[[bin]] +name = "ignore_map" +path = "src/ignore_map.rs" + [[bin]] name = "memmove_test" path = "src/memmove_test.rs" diff --git a/test/integration-ebpf/src/ignore_map.rs b/test/integration-ebpf/src/ignore_map.rs new file mode 100644 index 000000000..4255d1c94 --- /dev/null +++ b/test/integration-ebpf/src/ignore_map.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use aya_ebpf::{ + macros::{map, uprobe}, + maps::{PerfEventArray, RingBuf}, + programs::ProbeContext, +}; + +#[no_mangle] +pub static RINGBUF_SUPPORTED: i32 = 0; + +#[map] +static mut RINGBUF: RingBuf = RingBuf::with_byte_size(0, 0); + +#[map] +static mut PERFBUF: PerfEventArray = PerfEventArray::with_max_entries(1, 0); + +#[uprobe] +pub fn test_ignored_map_relocation(ctx: ProbeContext) { + if unsafe { core::ptr::read_volatile(&RINGBUF_SUPPORTED) == 1 } { + let _ = unsafe { RINGBUF.output(&1, 0).map_err(|_| 1u32) }; + } else { + unsafe { PERFBUF.output(&ctx, &1, 0) }; + } +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 4b55f3a1e..a53cb68a4 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -22,6 +22,7 @@ pub const BPF_PROBE_READ: &[u8] = pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/redirect")); pub const XDP_SEC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/xdp_sec")); pub const RING_BUF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ring_buf")); +pub const IGNORE_MAP: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ignore_map")); pub const MEMMOVE_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/memmove_test")); #[cfg(test)] diff --git a/test/integration-test/src/tests/rbpf.rs b/test/integration-test/src/tests/rbpf.rs index 900d1462b..8a90697df 100644 --- a/test/integration-test/src/tests/rbpf.rs +++ b/test/integration-test/src/tests/rbpf.rs @@ -81,11 +81,13 @@ fn use_map_with_rbpf() { .iter() .map(|((section_index, _), _)| *section_index) .collect(); + let disable_maps = HashMap::new(); object .relocate_maps( maps.iter() .map(|(s, (fd, map))| (s.as_ref() as &str, *fd, map)), &text_sections, + &disable_maps, ) .expect("Relocation failed"); // Actually there is no local function call involved. diff --git a/test/integration-test/src/tests/relocations.rs b/test/integration-test/src/tests/relocations.rs index eb43466a6..2c0326711 100644 --- a/test/integration-test/src/tests/relocations.rs +++ b/test/integration-test/src/tests/relocations.rs @@ -1,6 +1,19 @@ -use aya::{programs::UProbe, util::KernelVersion, Ebpf}; +use aya::{programs::UProbe, util::KernelVersion, Ebpf, EbpfLoader}; use test_log::test; +#[test] +fn ignored_map_relocation_by_name() { + let mut ebpf = + relocation_load_and_attach("test_ignored_map_relocation", crate::IGNORE_MAP, "RINGBUF"); + + let perf = ebpf.take_map("PERFBUF"); + + let ring = ebpf.take_map("RINGBUF"); + + assert!(perf.is_some()); + assert!(ring.is_none()); +} + #[test] fn relocations() { let bpf = load_and_attach("test_64_32_call_relocs", crate::RELOCATIONS); @@ -33,6 +46,27 @@ fn text_64_64_reloc() { assert_eq!(m.get(&1, 0).unwrap(), 3); } +fn relocation_load_and_attach(name: &str, bytes: &[u8], disable_map_name: &str) -> Ebpf { + let mut ebpf = EbpfLoader::new() + .ignore_maps_by_name(&[disable_map_name]) + .set_global("RINGBUF_SUPPORTED", &0, true) + .load(bytes) + .unwrap(); + + let prog: &mut UProbe = ebpf.program_mut(name).unwrap().try_into().unwrap(); + prog.load().unwrap(); + + prog.attach( + Some("trigger_relocations_program"), + 0, + "/proc/self/exe", + None, + ) + .unwrap(); + + ebpf +} + fn load_and_attach(name: &str, bytes: &[u8]) -> Ebpf { let mut bpf = Ebpf::load(bytes).unwrap();