Skip to content

Commit

Permalink
aya: Support multiple maps in map sections
Browse files Browse the repository at this point in the history
This commit uses the symbol table to discover all maps inside an ELF
section. Instead of doing what libbpf does - divide the section data
in to equal sized chunks - we read in to section data using the
symbol address and offset, thus allowing us to support definitions
of varying lengths.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
  • Loading branch information
dave-tucker committed Apr 29, 2022
1 parent 2e494a1 commit f357be7
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 5 deletions.
103 changes: 99 additions & 4 deletions aya/src/obj/mod.rs
Expand Up @@ -519,6 +519,36 @@ impl Object {
Ok(())
}

fn parse_map_section(
&mut self,
section: &Section,
symbols: Vec<Symbol>,
) -> Result<(), ParseError> {
if symbols.is_empty() {
return Err(ParseError::NoSymbolsInMapSection {});
}
for (i, sym) in symbols.iter().enumerate() {
let start = sym.address as usize;
let end = start + sym.size as usize;
let data = &section.data[start..end];
let name = sym
.name
.as_ref()
.ok_or(ParseError::MapSymbolNameNotFound { i })?;
let def = parse_map_def(name, data)?;
self.maps.insert(
name.to_string(),
Map {
section_index: section.index.0,
def,
data: Vec::new(),
kind: MapKind::Other,
},
);
}
Ok(())
}

fn parse_section(&mut self, mut section: Section) -> Result<(), BpfError> {
let mut parts = section.name.rsplitn(2, '/').collect::<Vec<_>>();
parts.reverse();
Expand All @@ -542,9 +572,19 @@ impl Object {
BpfSectionKind::Btf => self.parse_btf(&section)?,
BpfSectionKind::BtfExt => self.parse_btf_ext(&section)?,
BpfSectionKind::Maps => {
let name = section.name.splitn(2, '/').last().unwrap();
self.maps
.insert(name.to_string(), parse_map(&section, name)?);
let symbols: Vec<Symbol> = self
.symbols_by_index
.values()
.filter(|s| {
if let Some(idx) = s.section_index {
idx == section.index.0
} else {
false
}
})
.cloned()
.collect();
self.parse_map_section(&section, symbols)?
}
BpfSectionKind::Program => {
let program = self.parse_program(&section)?;
Expand Down Expand Up @@ -625,6 +665,12 @@ pub enum ParseError {

#[error("map for section with index {index} not found")]
MapNotFound { index: usize },

#[error("the map number {i} in the `maps` section doesn't have a symbol name")]
MapSymbolNameNotFound { i: usize },

#[error("no symbols found for the maps included in the maps section")]
NoSymbolsInMapSection {},
}

#[derive(Debug)]
Expand Down Expand Up @@ -873,6 +919,22 @@ mod tests {
}
}

fn fake_sym(obj: &mut Object, section_index: usize, address: u64, name: &str, size: u64) {
let idx = obj.symbols_by_index.len();
obj.symbols_by_index.insert(
idx + 1,
Symbol {
index: idx + 1,
section_index: Some(section_index),
name: Some(name.to_string()),
address,
size,
is_definition: false,
kind: SymbolKind::Data,
},
);
}

fn bytes_of<T>(val: &T) -> &[u8] {
// Safety: This is for testing only
unsafe { crate::util::bytes_of(val) }
Expand Down Expand Up @@ -1113,7 +1175,7 @@ mod tests {
#[test]
fn test_parse_section_map() {
let mut obj = fake_obj();

fake_sym(&mut obj, 0, 0, "foo", mem::size_of::<bpf_map_def>() as u64);
assert_matches!(
obj.parse_section(fake_section(
BpfSectionKind::Maps,
Expand All @@ -1132,6 +1194,39 @@ mod tests {
assert!(obj.maps.get("foo").is_some());
}

#[test]
fn test_parse_section_multiple_maps() {
let mut obj = fake_obj();
fake_sym(&mut obj, 0, 0, "foo", mem::size_of::<bpf_map_def>() as u64);
fake_sym(&mut obj, 0, 28, "bar", mem::size_of::<bpf_map_def>() as u64);
fake_sym(&mut obj, 0, 60, "baz", mem::size_of::<bpf_map_def>() as u64);
let def = &bpf_map_def {
map_type: 1,
key_size: 2,
value_size: 3,
max_entries: 4,
map_flags: 5,
..Default::default()
};
let map_data = bytes_of(def).to_vec();
let mut buf = vec![];
buf.extend(&map_data);
buf.extend(&map_data);
// throw in some padding
buf.extend(&[0, 0, 0, 0]);
buf.extend(&map_data);
assert_matches!(
obj.parse_section(fake_section(BpfSectionKind::Maps, "maps", buf.as_slice(),)),
Ok(())
);
assert!(obj.maps.get("foo").is_some());
assert!(obj.maps.get("bar").is_some());
assert!(obj.maps.get("baz").is_some());
for (_, m) in &obj.maps {
assert_eq!(&m.def, def);
}
}

#[test]
fn test_parse_section_data() {
let mut obj = fake_obj();
Expand Down
2 changes: 1 addition & 1 deletion test/README.md
Expand Up @@ -7,7 +7,7 @@ common usage behaviours work on real Linux distros

This assumes you have a working Rust and Go toolchain on the host machine

1. `rustup toolchain add x86_64-unknown-linux-musl`
1. `rustup target add x86_64-unknown-linux-musl`
1. Install [`rtf`](https://github.com/linuxkit/rtf): `go install github.com/linuxkit/rtf@latest`
1. Install rust-script: `cargo install rust-script`
1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds`
Expand Down
47 changes: 47 additions & 0 deletions test/cases/010_load/010_multiple_maps/multimap.bpf.c
@@ -0,0 +1,47 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

const int XDP_ACTION_MAX = (XDP_TX + 1);

struct datarec {
__u64 rx_packets;
};

// stats keyed by XDP Action
struct bpf_map_def SEC("maps") xdp_stats_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct datarec),
.max_entries = XDP_ACTION_MAX,
};

// tracks number of times called
struct bpf_map_def SEC("maps") prog_stats_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u64),
.max_entries = 1,
};

SEC("xdp/stats")
int xdp_stats(struct xdp_md *ctx)
{
__u64 *stats;
struct datarec *rec;
__u32 key = XDP_PASS;
__u32 k1 = 0;

stats = bpf_map_lookup_elem(&prog_stats_map, &k1);
if (!stats)
return XDP_ABORTED;
__sync_fetch_and_add(stats, 1);

rec = bpf_map_lookup_elem(&xdp_stats_map, &key);
if (!rec)
return XDP_ABORTED;
__sync_fetch_and_add(&rec->rx_packets, 1);

return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
33 changes: 33 additions & 0 deletions test/cases/010_load/010_multiple_maps/multimap.rs
@@ -0,0 +1,33 @@
//! ```cargo
//! [dependencies]
//! log = "0.4"
//! simplelog = "0.11"
//! aya = { path = "../../../../aya" }
//! ```

use aya::{
Bpf,
programs::{Xdp, XdpFlags},
};
use log::info;
use std::convert::TryInto;

use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode};

fn main() {
TermLogger::init(
LevelFilter::Debug,
ConfigBuilder::new()
.set_target_level(LevelFilter::Error)
.set_location_level(LevelFilter::Error)
.build(),
TerminalMode::Mixed,
ColorChoice::Auto,
).unwrap();
info!("Loading XDP program");
let mut bpf = Bpf::load_file("multimap.o").unwrap();
let pass: &mut Xdp = bpf.program_mut("stats").unwrap().try_into().unwrap();
pass.load().unwrap();
pass.attach("eth0", XdpFlags::default()).unwrap();
info!("Success...");
}
29 changes: 29 additions & 0 deletions test/cases/010_load/010_multiple_maps/test.sh
@@ -0,0 +1,29 @@
#!/bin/sh
# SUMMARY: Check that a program with multiple maps in the maps section loads
# LABELS:

set -e

# Source libraries. Uncomment if needed/defined
#. "${RT_LIB}"
. "${RT_PROJECT_ROOT}/_lib/lib.sh"

NAME=multimap

clean_up() {
rm -rf ${NAME}.o ${NAME}
exec_vm rm -f ${NAME}.o ${NAME}
}

trap clean_up EXIT

# Test code goes here
compile_c_ebpf "$(pwd)/${NAME}.bpf.c"
compile_user "$(pwd)/${NAME}.rs"

scp_vm ${NAME}.o
scp_vm ${NAME}

exec_vm sudo ./${NAME}

exit 0

0 comments on commit f357be7

Please sign in to comment.