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

aya: skip map creation and relocation for maps that should be ignored #968

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion aya-obj/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
30 changes: 29 additions & 1 deletion aya-obj/src/relocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,20 @@ impl Object {
&mut self,
maps: I,
text_sections: &HashSet<usize>,
ignored_maps: &HashMap<String, Map>,
) -> 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() {
Expand All @@ -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(),
Expand Down Expand Up @@ -176,13 +189,16 @@ impl Object {
}
}

#[allow(clippy::too_many_arguments)]
fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
fun: &mut Function,
relocations: I,
maps_by_section: &HashMap<usize, (&str, std::os::fd::RawFd, &Map)>,
maps_by_symbol: &HashMap<usize, (&str, std::os::fd::RawFd, &Map)>,
symbol_table: &HashMap<usize, Symbol>,
text_sections: &HashSet<usize>,
ignored_by_section: &HashSet<usize>,
ignored_by_symbol: &HashSet<usize>,
) -> Result<(), RelocationError> {
let section_offset = fun.section_offset;
let instructions = &mut fun.instructions;
Expand Down Expand Up @@ -222,7 +238,9 @@ fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
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 {}",
Expand All @@ -232,6 +250,8 @@ fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
);
debug_assert_eq!(map.symbol_index().unwrap(), rel.symbol_index);
m
} else if ignored_by_section.contains(&section_index) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct, it's just not getting exercised by the test because we do have symbols. We switched to putting all maps in the same section (like libbpf does), so I think that this would skip all the maps?

continue;
} else {
let Some(m) = maps_by_section.get(&section_index) else {
debug!("failed relocating map by section index {}", section_index);
Expand Down Expand Up @@ -580,6 +600,8 @@ mod test {
&maps_by_symbol,
&symbol_table,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.unwrap();

Expand Down Expand Up @@ -636,6 +658,8 @@ mod test {
&maps_by_symbol,
&symbol_table,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.unwrap();

Expand Down Expand Up @@ -675,6 +699,8 @@ mod test {
&maps_by_symbol,
&symbol_table,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.unwrap();

Expand Down Expand Up @@ -731,6 +757,8 @@ mod test {
&maps_by_symbol,
&symbol_table,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.unwrap();

Expand Down
45 changes: 42 additions & 3 deletions aya/src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -175,6 +176,7 @@ impl<'a> EbpfLoader<'a> {
extensions: HashSet::new(),
verifier_log_level: VerifierLogLevel::default(),
allow_unsupported_maps: false,
ignore_maps_by_name: &[],
}
}

Expand Down Expand Up @@ -224,6 +226,32 @@ impl<'a> EbpfLoader<'a> {
self
}

/// Allows programs containing unsupported maps for the current kernel to be loaded
martinsoees marked this conversation as resolved.
Show resolved Hide resolved
/// 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 {
martinsoees marked this conversation as resolved.
Show resolved Hide resolved
self.ignore_maps_by_name = name;
self
}

/// Sets the base directory path for pinned maps.
///
/// Pinned maps will be loaded from `path/MAP_NAME`.
Expand Down Expand Up @@ -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())?;
Expand Down Expand Up @@ -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<u32, EbpfError> {
Ok(possible_cpus()
.map_err(|error| EbpfError::FileError {
Expand Down Expand Up @@ -508,7 +547,6 @@ impl<'a> EbpfLoader<'a> {
map.finalize()?;
maps.insert(name, map);
}

let text_sections = obj
.functions
.keys()
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions test/integration-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
32 changes: 32 additions & 0 deletions test/integration-ebpf/src/ignore_map.rs
Original file line number Diff line number Diff line change
@@ -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<u64> = 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 {}
}
1 change: 1 addition & 0 deletions test/integration-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 2 additions & 0 deletions test/integration-test/src/tests/rbpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
36 changes: 35 additions & 1 deletion test/integration-test/src/tests/relocations.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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();

Expand Down
Loading