aya: Support multiple maps in map sections

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>
pull/181/head
Dave Tucker 3 years ago
parent 2e494a1a29
commit f357be7db4

@ -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();
@ -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)?;
@ -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)]
@ -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) }
@ -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,
@ -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();

@ -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`

@ -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";

@ -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...");
}

@ -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
Loading…
Cancel
Save