diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 9ed72d3a..621b2e33 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -1,6 +1,7 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, + convert::TryFrom, error::Error, ffi::CString, fs, io, @@ -73,14 +74,43 @@ pub(crate) struct bpf_map_def { pub(crate) pinning: PinningType, } +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub(crate) struct BtfMapDef { + pub(crate) map_type: u32, + pub(crate) key_size: u32, + pub(crate) value_size: u32, + pub(crate) max_entries: u32, + pub(crate) map_flags: u32, + pub(crate) pinning: PinningType, + pub(crate) btf_key_type_id: u32, + pub(crate) btf_value_type_id: u32, +} + #[repr(u32)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum PinningType { None = 0, - #[allow(dead_code)] // ByName is constructed from the BPF side ByName = 1, } +#[derive(Debug, Error)] +pub(crate) enum PinningError { + #[error("unsupported pinning type")] + Unsupported, +} + +impl TryFrom for PinningType { + type Error = PinningError; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(PinningType::None), + 1 => Ok(PinningType::ByName), + _ => Err(PinningError::Unsupported), + } + } +} + impl Default for PinningType { fn default() -> Self { PinningType::None @@ -340,21 +370,23 @@ impl<'a> BpfLoader<'a> { let mut maps = HashMap::new(); for (name, mut obj) in obj.maps.drain() { - if obj.def.map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32 && obj.def.max_entries == 0 - { - obj.def.max_entries = possible_cpus() - .map_err(|error| BpfError::FileError { - path: PathBuf::from(POSSIBLE_CPUS), - error, - })? - .len() as u32; + if obj.map_type() == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32 && obj.max_entries() == 0 { + obj.set_max_entries( + possible_cpus() + .map_err(|error| BpfError::FileError { + path: PathBuf::from(POSSIBLE_CPUS), + error, + })? + .len() as u32, + ); } let mut map = Map { obj, fd: None, pinned: false, + btf_fd, }; - let fd = match map.obj.def.pinning { + let fd = match map.obj.pinning() { PinningType::ByName => { let path = match &self.map_pin_path { Some(p) => p, @@ -375,16 +407,15 @@ impl<'a> BpfLoader<'a> { } PinningType::None => map.create(&name)?, }; - if !map.obj.data.is_empty() && map.obj.kind != MapKind::Bss { - bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data.as_mut_ptr(), 0).map_err( - |(code, io_error)| MapError::SyscallError { + if !map.obj.data().is_empty() && map.obj.kind() != MapKind::Bss { + bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data_mut().as_mut_ptr(), 0) + .map_err(|(code, io_error)| MapError::SyscallError { call: "bpf_map_update_elem".to_owned(), code, io_error, - }, - )?; + })?; } - if map.obj.kind == MapKind::Rodata { + if map.obj.kind() == MapKind::Rodata { bpf_map_freeze(fd).map_err(|(code, io_error)| MapError::SyscallError { call: "bpf_map_freeze".to_owned(), code, @@ -804,6 +835,10 @@ pub enum BpfError { error: Box, }, + /// No BTF parsed for object + #[error("no BTF parsed for object")] + NoBTF, + #[error("map error")] /// A map error MapError(#[from] MapError), diff --git a/aya/src/maps/array/array.rs b/aya/src/maps/array/array.rs index 6b5e5fa2..557e9ce5 100644 --- a/aya/src/maps/array/array.rs +++ b/aya/src/maps/array/array.rs @@ -40,20 +40,20 @@ pub struct Array, V: Pod> { impl, V: Pod> Array { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_ARRAY as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = mem::size_of::(); - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let expected = mem::size_of::(); - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); } @@ -69,7 +69,7 @@ impl, V: Pod> Array { /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { - self.inner.obj.def.max_entries + self.inner.obj.max_entries() } /// Returns the value stored at the given index. @@ -99,8 +99,8 @@ impl, V: Pod> Array { } fn check_bounds(&self, index: u32) -> Result<(), MapError> { - let max_entries = self.inner.obj.def.max_entries; - if index >= self.inner.obj.def.max_entries { + let max_entries = self.inner.obj.max_entries(); + if index >= self.inner.obj.max_entries() { Err(MapError::OutOfBounds { index, max_entries }) } else { Ok(()) diff --git a/aya/src/maps/array/per_cpu_array.rs b/aya/src/maps/array/per_cpu_array.rs index ed91dfaf..2344a708 100644 --- a/aya/src/maps/array/per_cpu_array.rs +++ b/aya/src/maps/array/per_cpu_array.rs @@ -59,20 +59,20 @@ pub struct PerCpuArray, V: Pod> { impl, V: Pod> PerCpuArray { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_PERCPU_ARRAY as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = mem::size_of::(); - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let expected = mem::size_of::(); - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); } @@ -88,7 +88,7 @@ impl, V: Pod> PerCpuArray { /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { - self.inner.obj.def.max_entries + self.inner.obj.max_entries() } /// Returns a slice of values - one for each CPU - stored at the given index. @@ -118,8 +118,8 @@ impl, V: Pod> PerCpuArray { } fn check_bounds(&self, index: u32) -> Result<(), MapError> { - let max_entries = self.inner.obj.def.max_entries; - if index >= self.inner.obj.def.max_entries { + let max_entries = self.inner.obj.max_entries(); + if index >= self.inner.obj.max_entries() { Err(MapError::OutOfBounds { index, max_entries }) } else { Ok(()) diff --git a/aya/src/maps/array/program_array.rs b/aya/src/maps/array/program_array.rs index ae0a28e0..127da90f 100644 --- a/aya/src/maps/array/program_array.rs +++ b/aya/src/maps/array/program_array.rs @@ -57,20 +57,20 @@ pub struct ProgramArray> { impl> ProgramArray { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_PROG_ARRAY as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = mem::size_of::(); - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let expected = mem::size_of::(); - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); } @@ -86,8 +86,8 @@ impl> ProgramArray { } fn check_bounds(&self, index: u32) -> Result<(), MapError> { - let max_entries = self.inner.obj.def.max_entries; - if index >= self.inner.obj.def.max_entries { + let max_entries = self.inner.obj.max_entries(); + if index >= self.inner.obj.max_entries() { Err(MapError::OutOfBounds { index, max_entries }) } else { Ok(()) diff --git a/aya/src/maps/bloom_filter.rs b/aya/src/maps/bloom_filter.rs index ae2f5faf..fd38ada3 100644 --- a/aya/src/maps/bloom_filter.rs +++ b/aya/src/maps/bloom_filter.rs @@ -41,7 +41,7 @@ pub struct BloomFilter, V: Pod> { impl, V: Pod> BloomFilter { pub(crate) fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); // validate the map definition if map_type != BPF_MAP_TYPE_BLOOM_FILTER as u32 { @@ -51,7 +51,7 @@ impl, V: Pod> BloomFilter { } let size = mem::size_of::(); - let expected = map.obj.def.value_size as usize; + let expected = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); }; @@ -140,7 +140,7 @@ mod tests { use std::io; fn new_obj_map() -> obj::Map { - obj::Map { + obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_BLOOM_FILTER as u32, key_size: 4, @@ -152,7 +152,7 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: obj::MapKind::Other, - } + }) } fn sys_error(value: i32) -> SysResult { @@ -165,6 +165,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( BloomFilter::<_, u16>::new(&map), @@ -178,7 +179,7 @@ mod tests { #[test] fn test_try_from_wrong_map() { let map = Map { - obj: obj::Map { + obj: obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32, key_size: 4, @@ -190,9 +191,10 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: obj::MapKind::Other, - }, + }), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( @@ -207,6 +209,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( @@ -221,6 +224,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(BloomFilter::<_, u32>::new(&mut map).is_ok()); @@ -232,6 +236,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(BloomFilter::<_, u32>::try_from(&map).is_ok()) } @@ -244,6 +249,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap(); @@ -267,6 +273,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap(); @@ -280,6 +287,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap(); @@ -302,6 +310,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap(); diff --git a/aya/src/maps/hash_map/hash_map.rs b/aya/src/maps/hash_map/hash_map.rs index bb454acf..df11da03 100644 --- a/aya/src/maps/hash_map/hash_map.rs +++ b/aya/src/maps/hash_map/hash_map.rs @@ -42,7 +42,7 @@ pub struct HashMap, K, V> { impl, K: Pod, V: Pod> HashMap { pub(crate) fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); // validate the map definition if map_type != BPF_MAP_TYPE_HASH as u32 && map_type != BPF_MAP_TYPE_LRU_HASH as u32 { @@ -159,7 +159,7 @@ mod tests { use super::*; fn new_obj_map() -> obj::Map { - obj::Map { + obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_HASH as u32, key_size: 4, @@ -171,7 +171,7 @@ mod tests { data: Vec::new(), kind: obj::MapKind::Other, symbol_index: 0, - } + }) } fn sys_error(value: i32) -> SysResult { @@ -184,6 +184,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( HashMap::<_, u8, u32>::new(&map), @@ -200,6 +201,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( HashMap::<_, u32, u16>::new(&map), @@ -213,7 +215,7 @@ mod tests { #[test] fn test_try_from_wrong_map() { let map = Map { - obj: obj::Map { + obj: obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32, key_size: 4, @@ -225,9 +227,10 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: obj::MapKind::Other, - }, + }), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( @@ -242,6 +245,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( @@ -256,6 +260,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(HashMap::<_, u32, u32>::new(&mut map).is_ok()); @@ -267,6 +272,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok()) } @@ -274,7 +280,7 @@ mod tests { #[test] fn test_try_from_ok_lru() { let map = Map { - obj: obj::Map { + obj: obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_LRU_HASH as u32, key_size: 4, @@ -286,9 +292,10 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: obj::MapKind::Other, - }, + }), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok()) @@ -302,6 +309,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); @@ -325,6 +333,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); @@ -339,6 +348,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); @@ -362,6 +372,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); @@ -375,6 +386,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); @@ -397,6 +409,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); @@ -433,6 +446,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let keys = hm.keys().collect::, _>>(); @@ -477,6 +491,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); @@ -505,6 +520,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); @@ -535,6 +551,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let items = hm.iter().collect::, _>>().unwrap(); @@ -568,6 +585,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); @@ -602,6 +620,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); @@ -642,6 +661,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); diff --git a/aya/src/maps/hash_map/mod.rs b/aya/src/maps/hash_map/mod.rs index 349f67b3..e878e2d4 100644 --- a/aya/src/maps/hash_map/mod.rs +++ b/aya/src/maps/hash_map/mod.rs @@ -15,12 +15,12 @@ pub use per_cpu_hash_map::*; pub(crate) fn check_kv_size(map: &Map) -> Result<(), MapError> { let size = mem::size_of::(); - let expected = map.obj.def.key_size as usize; + let expected = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let size = mem::size_of::(); - let expected = map.obj.def.value_size as usize; + let expected = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); }; diff --git a/aya/src/maps/hash_map/per_cpu_hash_map.rs b/aya/src/maps/hash_map/per_cpu_hash_map.rs index a76d1c23..32a616e4 100644 --- a/aya/src/maps/hash_map/per_cpu_hash_map.rs +++ b/aya/src/maps/hash_map/per_cpu_hash_map.rs @@ -52,7 +52,7 @@ pub struct PerCpuHashMap, K: Pod, V: Pod> { impl, K: Pod, V: Pod> PerCpuHashMap { pub(crate) fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); // validate the map definition if map_type != BPF_MAP_TYPE_PERCPU_HASH as u32 diff --git a/aya/src/maps/lpm_trie.rs b/aya/src/maps/lpm_trie.rs index 473d92e4..4b7262c7 100644 --- a/aya/src/maps/lpm_trie.rs +++ b/aya/src/maps/lpm_trie.rs @@ -101,7 +101,7 @@ unsafe impl Pod for Key {} impl, K: Pod, V: Pod> LpmTrie { pub(crate) fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); // validate the map definition if map_type != BPF_MAP_TYPE_LPM_TRIE as u32 { @@ -110,12 +110,12 @@ impl, K: Pod, V: Pod> LpmTrie { }); } let size = mem::size_of::>(); - let expected = map.obj.def.key_size as usize; + let expected = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let size = mem::size_of::(); - let expected = map.obj.def.value_size as usize; + let expected = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); }; @@ -230,7 +230,7 @@ mod tests { use std::{io, mem, net::Ipv4Addr}; fn new_obj_map() -> obj::Map { - obj::Map { + obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_LPM_TRIE as u32, key_size: mem::size_of::>() as u32, @@ -242,7 +242,7 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: obj::MapKind::Other, - } + }) } fn sys_error(value: i32) -> SysResult { @@ -255,6 +255,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( LpmTrie::<_, u16, u32>::new(&map), @@ -271,6 +272,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( LpmTrie::<_, u32, u16>::new(&map), @@ -284,7 +286,7 @@ mod tests { #[test] fn test_try_from_wrong_map() { let map = Map { - obj: obj::Map { + obj: obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32, key_size: 4, @@ -296,8 +298,9 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: obj::MapKind::Other, - }, + }), fd: None, + btf_fd: None, pinned: false, }; @@ -313,6 +316,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, }; assert!(matches!( @@ -327,6 +331,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(LpmTrie::<_, u32, u32>::new(&mut map).is_ok()); @@ -338,6 +343,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; assert!(LpmTrie::<_, u32, u32>::try_from(&map).is_ok()) } @@ -350,6 +356,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); @@ -374,6 +381,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); @@ -390,6 +398,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); @@ -414,6 +423,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); @@ -428,6 +438,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let trie = LpmTrie::<_, u32, u32>::new(&map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); @@ -452,6 +463,7 @@ mod tests { obj: new_obj_map(), fd: Some(42), pinned: false, + btf_fd: None, }; let trie = LpmTrie::<_, u32, u32>::new(&map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index a432a615..8e390681 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -266,6 +266,7 @@ fn maybe_warn_rlimit() { pub struct Map { pub(crate) obj: obj::Map, pub(crate) fd: Option, + pub(crate) btf_fd: Option, /// Indicates if this map has been pinned to bpffs pub pinned: bool, } @@ -279,7 +280,7 @@ impl Map { let c_name = CString::new(name).map_err(|_| MapError::InvalidName { name: name.into() })?; - let fd = bpf_create_map(&c_name, &self.obj.def).map_err(|(code, io_error)| { + let fd = bpf_create_map(&c_name, &self.obj, self.btf_fd).map_err(|(code, io_error)| { let k_ver = kernel_version().unwrap(); if k_ver < (5, 11, 0) { maybe_warn_rlimit(); @@ -327,7 +328,7 @@ impl Map { /// Returns the [`bpf_map_type`] of this map pub fn map_type(&self) -> Result { - bpf_map_type::try_from(self.obj.def.map_type) + bpf_map_type::try_from(self.obj.map_type()) } pub(crate) fn fd_or_err(&self) -> Result { @@ -625,7 +626,7 @@ mod tests { use super::*; fn new_obj_map() -> obj::Map { - obj::Map { + obj::Map::Legacy(obj::LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_HASH as u32, key_size: 4, @@ -637,7 +638,7 @@ mod tests { symbol_index: 0, data: Vec::new(), kind: MapKind::Other, - } + }) } fn new_map() -> Map { @@ -645,6 +646,7 @@ mod tests { obj: new_obj_map(), fd: None, pinned: false, + btf_fd: None, } } diff --git a/aya/src/maps/perf/perf_event_array.rs b/aya/src/maps/perf/perf_event_array.rs index eede6009..e6aa2e8d 100644 --- a/aya/src/maps/perf/perf_event_array.rs +++ b/aya/src/maps/perf/perf_event_array.rs @@ -164,7 +164,7 @@ pub struct PerfEventArray> { impl> PerfEventArray { pub(crate) fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, diff --git a/aya/src/maps/queue.rs b/aya/src/maps/queue.rs index a6257807..0c26d93c 100644 --- a/aya/src/maps/queue.rs +++ b/aya/src/maps/queue.rs @@ -39,20 +39,20 @@ pub struct Queue, V: Pod> { impl, V: Pod> Queue { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_QUEUE as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = 0; - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let expected = mem::size_of::(); - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); } @@ -68,7 +68,7 @@ impl, V: Pod> Queue { /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn capacity(&self) -> u32 { - self.inner.obj.def.max_entries + self.inner.obj.max_entries() } } diff --git a/aya/src/maps/sock/sock_hash.rs b/aya/src/maps/sock/sock_hash.rs index 59f3f54a..82ce91cd 100644 --- a/aya/src/maps/sock/sock_hash.rs +++ b/aya/src/maps/sock/sock_hash.rs @@ -69,7 +69,7 @@ pub struct SockHash, K> { impl, K: Pod> SockHash { pub(crate) fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); // validate the map definition if map_type != BPF_MAP_TYPE_SOCKHASH as u32 { diff --git a/aya/src/maps/sock/sock_map.rs b/aya/src/maps/sock/sock_map.rs index d9624a7e..9b870069 100644 --- a/aya/src/maps/sock/sock_map.rs +++ b/aya/src/maps/sock/sock_map.rs @@ -47,20 +47,20 @@ pub struct SockMap> { impl> SockMap { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_SOCKMAP as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = mem::size_of::(); - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let expected = mem::size_of::(); - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); } @@ -76,8 +76,8 @@ impl> SockMap { } fn check_bounds(&self, index: u32) -> Result<(), MapError> { - let max_entries = self.inner.obj.def.max_entries; - if index >= self.inner.obj.def.max_entries { + let max_entries = self.inner.obj.max_entries(); + if index >= self.inner.obj.max_entries() { Err(MapError::OutOfBounds { index, max_entries }) } else { Ok(()) diff --git a/aya/src/maps/stack.rs b/aya/src/maps/stack.rs index b22b049c..8268480f 100644 --- a/aya/src/maps/stack.rs +++ b/aya/src/maps/stack.rs @@ -39,20 +39,20 @@ pub struct Stack, V: Pod> { impl, V: Pod> Stack { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_STACK as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = 0; - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let expected = mem::size_of::(); - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); } @@ -68,7 +68,7 @@ impl, V: Pod> Stack { /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn capacity(&self) -> u32 { - self.inner.obj.def.max_entries + self.inner.obj.max_entries() } } diff --git a/aya/src/maps/stack_trace.rs b/aya/src/maps/stack_trace.rs index e4aeac8b..af3c64f9 100644 --- a/aya/src/maps/stack_trace.rs +++ b/aya/src/maps/stack_trace.rs @@ -73,14 +73,14 @@ pub struct StackTraceMap { impl> StackTraceMap { fn new(map: T) -> Result, MapError> { - let map_type = map.obj.def.map_type; + let map_type = map.obj.map_type(); if map_type != BPF_MAP_TYPE_STACK_TRACE as u32 { return Err(MapError::InvalidMapType { map_type: map_type as u32, }); } let expected = mem::size_of::(); - let size = map.obj.def.key_size as usize; + let size = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } @@ -93,7 +93,7 @@ impl> StackTraceMap { io_error, } })?; - let size = map.obj.def.value_size as usize; + let size = map.obj.value_size() as usize; if size > max_stack_depth * mem::size_of::() { return Err(MapError::InvalidValueSize { size, expected }); } diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 9094986e..9375383d 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod btf; mod relocation; +use log::debug; use object::{ read::{Object as ElfObject, ObjectSection, Section as ObjSection}, Endianness, ObjectSymbol, ObjectSymbolTable, RelocationTarget, SectionIndex, SectionKind, @@ -19,14 +20,14 @@ use relocation::*; use crate::{ bpf_map_def, - generated::{bpf_insn, bpf_map_type::BPF_MAP_TYPE_ARRAY, BPF_F_RDONLY_PROG}, - obj::btf::{Btf, BtfError, BtfExt}, + generated::{bpf_insn, bpf_map_type::BPF_MAP_TYPE_ARRAY, btf_var_secinfo, BPF_F_RDONLY_PROG}, + obj::btf::{Btf, BtfError, BtfExt, BtfType}, programs::{CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType}, - BpfError, + BpfError, BtfMapDef, PinningType, }; use std::slice::from_raw_parts_mut; -use self::btf::{FuncSecInfo, LineSecInfo}; +use self::btf::{BtfKind, FuncSecInfo, LineSecInfo}; const KERNEL_VERSION_ANY: u32 = 0xFFFF_FFFE; /// The first five __u32 of `bpf_map_def` must be defined. @@ -51,7 +52,7 @@ pub struct Object { pub(crate) text_section_index: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum MapKind { Bss, Data, @@ -74,7 +75,99 @@ impl From<&str> for MapKind { } #[derive(Debug, Clone)] -pub struct Map { +pub enum Map { + Legacy(LegacyMap), + Btf(BtfMap), +} + +impl Map { + pub(crate) fn map_type(&self) -> u32 { + match self { + Map::Legacy(m) => m.def.map_type, + Map::Btf(m) => m.def.map_type, + } + } + + pub(crate) fn key_size(&self) -> u32 { + match self { + Map::Legacy(m) => m.def.key_size, + Map::Btf(m) => m.def.key_size, + } + } + + pub(crate) fn value_size(&self) -> u32 { + match self { + Map::Legacy(m) => m.def.value_size, + Map::Btf(m) => m.def.value_size, + } + } + + pub(crate) fn max_entries(&self) -> u32 { + match self { + Map::Legacy(m) => m.def.max_entries, + Map::Btf(m) => m.def.max_entries, + } + } + + pub(crate) fn set_max_entries(&mut self, v: u32) { + match self { + Map::Legacy(m) => m.def.max_entries = v, + Map::Btf(m) => m.def.max_entries = v, + } + } + + pub(crate) fn map_flags(&self) -> u32 { + match self { + Map::Legacy(m) => m.def.map_flags, + Map::Btf(m) => m.def.map_flags, + } + } + + pub(crate) fn pinning(&self) -> PinningType { + match self { + Map::Legacy(m) => m.def.pinning, + Map::Btf(m) => m.def.pinning, + } + } + + pub(crate) fn data(&self) -> &[u8] { + match self { + Map::Legacy(m) => &m.data, + Map::Btf(m) => &m.data, + } + } + + pub(crate) fn data_mut(&mut self) -> &mut Vec { + match self { + Map::Legacy(m) => m.data.as_mut(), + Map::Btf(m) => m.data.as_mut(), + } + } + + pub(crate) fn kind(&self) -> MapKind { + match self { + Map::Legacy(m) => m.kind, + Map::Btf(m) => m.kind, + } + } + + pub(crate) fn section_index(&self) -> usize { + match self { + Map::Legacy(m) => m.section_index, + Map::Btf(m) => m.section_index, + } + } + + pub(crate) fn symbol_index(&self) -> usize { + match self { + Map::Legacy(m) => m.symbol_index, + Map::Btf(m) => m.symbol_index, + } + } +} + +#[derive(Debug, Clone)] +pub struct LegacyMap { pub(crate) def: bpf_map_def, pub(crate) section_index: usize, pub(crate) symbol_index: usize, @@ -82,6 +175,15 @@ pub struct Map { pub(crate) kind: MapKind, } +#[derive(Debug, Clone)] +pub struct BtfMap { + pub(crate) def: BtfMapDef, + pub(crate) section_index: usize, + pub(crate) symbol_index: usize, + pub(crate) kind: MapKind, + pub(crate) data: Vec, +} + #[derive(Debug, Clone)] pub(crate) struct Program { pub(crate) license: CString, @@ -519,20 +621,20 @@ impl Object { .iter_mut() // assumption: there is only one map created per section where we're trying to // patch data. this assumption holds true for the .rodata section at least - .find(|(_, m)| symbol.section_index == Some(m.section_index)) + .find(|(_, m)| symbol.section_index == Some(m.section_index())) .ok_or_else(|| ParseError::MapNotFound { index: symbol.section_index.unwrap_or(0), })?; let start = symbol.address as usize; let end = start + symbol.size as usize; - if start > end || end > map.data.len() { + if start > end || end > map.data().len() { return Err(ParseError::InvalidGlobalData { name: name.to_string(), sym_size: symbol.size, data_size: data.len(), }); } - map.data.splice(start..end, data.iter().cloned()); + map.data_mut().splice(start..end, data.iter().cloned()); } else { return Err(ParseError::SymbolNotFound { name: name.to_owned(), @@ -706,18 +808,63 @@ impl Object { let def = parse_map_def(name, data)?; self.maps.insert( name.to_string(), - Map { + Map::Legacy(LegacyMap { section_index: section.index.0, symbol_index: sym.index, def, data: Vec::new(), kind: MapKind::Other, - }, + }), ); } Ok(()) } + fn parse_btf_maps( + &mut self, + section: &Section, + symbols: HashMap, + ) -> Result<(), BpfError> { + if self.btf.is_none() { + return Err(BpfError::NoBTF); + } + let btf = self.btf.as_ref().unwrap(); + + for t in btf.types() { + if let BtfType::DataSec(_, sec_info) = &t { + let type_name = match btf.type_name(t) { + Ok(Some(name)) => name, + _ => continue, + }; + if type_name == section.name { + // each btf_var_secinfo contains a map + for info in sec_info { + let (map_name, def) = parse_btf_map_def(btf, info)?; + let symbol_index = symbols + .get(&map_name) + .ok_or_else(|| { + BpfError::ParseError(ParseError::SymbolNotFound { + name: map_name.to_string(), + }) + })? + .index; + self.maps.insert( + map_name, + Map::Btf(BtfMap { + def, + section_index: section.index.0, + symbol_index, + kind: MapKind::Other, + data: Vec::new(), + }), + ); + } + } + } + } + Ok(()) + } + fn parse_section(&mut self, mut section: Section) -> Result<(), BpfError> { let mut parts = section.name.rsplitn(2, '/').collect::>(); parts.reverse(); @@ -740,6 +887,22 @@ impl Object { BpfSectionKind::Text => self.parse_text_section(section)?, BpfSectionKind::Btf => self.parse_btf(§ion)?, BpfSectionKind::BtfExt => self.parse_btf_ext(§ion)?, + BpfSectionKind::BtfMaps => { + let symbols: HashMap = self + .symbols_by_index + .values() + .filter(|s| { + if let Some(idx) = s.section_index { + idx == section.index.0 && s.name.is_some() + } else { + false + } + }) + .cloned() + .map(|s| (s.name.as_ref().unwrap().to_string(), s)) + .collect(); + self.parse_btf_maps(§ion, symbols)? + } BpfSectionKind::Maps => { let symbols: Vec = self .symbols_by_index @@ -770,10 +933,7 @@ impl Object { ); } } - BpfSectionKind::Undefined - | BpfSectionKind::BtfMaps - | BpfSectionKind::License - | BpfSectionKind::Version => {} + BpfSectionKind::Undefined | BpfSectionKind::License | BpfSectionKind::Version => {} } Ok(()) @@ -977,6 +1137,30 @@ fn parse_version(data: &[u8], endianness: object::Endianness) -> Result Result { + let pty = match &btf.type_by_id(type_id)? { + BtfType::Ptr(pty) => pty, + other => { + return Err(BtfError::UnexpectedBtfType { + type_id: other.kind()?.unwrap_or(BtfKind::Unknown) as u32, + }) + } + }; + // Safety: union + let arr = match &btf.type_by_id(unsafe { pty.__bindgen_anon_1.type_ })? { + BtfType::Array(_, arr) => arr, + other => { + return Err(BtfError::UnexpectedBtfType { + type_id: other.kind()?.unwrap_or(BtfKind::Unknown) as u32, + }) + } + }; + Ok(arr.nelems) +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum KernelVersion { Version(u32), @@ -1014,13 +1198,13 @@ fn parse_map(section: &Section, name: &str) -> Result { } MapKind::Other => (parse_map_def(name, section.data)?, Vec::new()), }; - Ok(Map { + Ok(Map::Legacy(LegacyMap { section_index: section.index.0, symbol_index: 0, def, data, kind, - }) + })) } fn parse_map_def(name: &str, data: &[u8]) -> Result { @@ -1043,6 +1227,82 @@ fn parse_map_def(name: &str, data: &[u8]) -> Result { } } +fn parse_btf_map_def(btf: &Btf, info: &btf_var_secinfo) -> Result<(String, BtfMapDef), BtfError> { + let ty = match btf.type_by_id(info.type_)? { + BtfType::Var(ty, _) => ty, + other => { + return Err(BtfError::UnexpectedBtfType { + type_id: other.kind()?.unwrap_or(BtfKind::Unknown) as u32, + }) + } + }; + let map_name = btf.string_at(ty.name_off)?; + let mut map_def = BtfMapDef::default(); + + // Safety: union + let root_type = btf.resolve_type(unsafe { ty.__bindgen_anon_1.type_ })?; + let members = match btf.type_by_id(root_type)? { + BtfType::Struct(_, members) => members, + other => { + return Err(BtfError::UnexpectedBtfType { + type_id: other.kind()?.unwrap_or(BtfKind::Unknown) as u32, + }) + } + }; + + for m in members { + match btf.string_at(m.name_off)?.as_ref() { + "type" => { + map_def.map_type = get_map_field(btf, m.type_)?; + } + "key" => { + if let BtfType::Ptr(pty) = btf.type_by_id(m.type_)? { + // Safety: union + let t = unsafe { pty.__bindgen_anon_1.type_ }; + map_def.key_size = btf.type_size(t)? as u32; + map_def.btf_key_type_id = t; + } else { + return Err(BtfError::UnexpectedBtfType { type_id: m.type_ }); + } + } + "key_size" => { + map_def.key_size = get_map_field(btf, m.type_)?; + } + "value" => { + if let BtfType::Ptr(pty) = btf.type_by_id(m.type_)? { + // Safety: union + let t = unsafe { pty.__bindgen_anon_1.type_ }; + map_def.value_size = btf.type_size(t)? as u32; + map_def.btf_value_type_id = t; + } else { + return Err(BtfError::UnexpectedBtfType { type_id: m.type_ }); + } + } + "value_size" => { + map_def.value_size = get_map_field(btf, m.type_)?; + } + "max_entries" => { + map_def.max_entries = get_map_field(btf, m.type_)?; + } + "map_flags" => { + map_def.map_flags = get_map_field(btf, m.type_)?; + } + "pinning" => { + let pinning = get_map_field(btf, m.type_)?; + map_def.pinning = PinningType::try_from(pinning).unwrap_or_else(|_| { + debug!("{} is not a valid pin type. using PIN_NONE", pinning); + PinningType::None + }); + } + other => { + debug!("skipping unknown map section: {}", other); + continue; + } + } + } + Ok((map_name.to_string(), map_def)) +} + pub(crate) fn copy_instructions(data: &[u8]) -> Result, ParseError> { if data.len() % mem::size_of::() > 0 { return Err(ParseError::InvalidProgramCode); @@ -1187,6 +1447,7 @@ mod tests { map_flags: 5, id: 0, pinning: PinningType::None, + ..Default::default() }; assert_eq!( @@ -1205,6 +1466,7 @@ mod tests { map_flags: 5, id: 6, pinning: PinningType::ByName, + ..Default::default() }; assert_eq!(parse_map_def("foo", bytes_of(&def)).unwrap(), def); @@ -1220,6 +1482,7 @@ mod tests { map_flags: 5, id: 6, pinning: PinningType::ByName, + ..Default::default() }; let mut buf = [0u8; 128]; unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, def) }; @@ -1250,11 +1513,12 @@ mod tests { map_flags: 5, id: 0, pinning: PinningType::None, + ..Default::default() }) ), "foo" ), - Ok(Map { + Ok(Map::Legacy(LegacyMap{ section_index: 0, def: bpf_map_def { map_type: 1, @@ -1267,7 +1531,7 @@ mod tests { }, data, .. - }) if data.is_empty() + })) if data.is_empty() )) } @@ -1283,7 +1547,7 @@ mod tests { ), ".bss" ), - Ok(Map { + Ok(Map::Legacy(LegacyMap { section_index: 0, symbol_index: 0, def: bpf_map_def { @@ -1297,7 +1561,7 @@ mod tests { }, data, kind - }) if data == map_data && value_size == map_data.len() as u32 && kind == MapKind::Bss + })) if data == map_data && value_size == map_data.len() as u32 && kind == MapKind::Bss )) } @@ -1393,8 +1657,12 @@ mod tests { 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.values() { - assert_eq!(&m.def, def); + for map in obj.maps.values() { + if let Map::Legacy(m) = map { + assert_eq!(&m.def, def); + } else { + panic!("expected a BTF map") + } } } @@ -1905,7 +2173,7 @@ mod tests { let mut obj = fake_obj(); obj.maps.insert( ".rodata".to_string(), - Map { + Map::Legacy(LegacyMap { def: bpf_map_def { map_type: BPF_MAP_TYPE_ARRAY as u32, key_size: mem::size_of::() as u32, @@ -1914,12 +2182,13 @@ mod tests { map_flags: BPF_F_RDONLY_PROG, id: 1, pinning: PinningType::None, + ..Default::default() }, section_index: 1, symbol_index: 1, data: vec![0, 0, 0], kind: MapKind::Rodata, - }, + }), ); obj.symbols_by_index.insert( 1, @@ -1939,6 +2208,103 @@ mod tests { .unwrap(); let map = obj.maps.get(".rodata").unwrap(); - assert_eq!(test_data, map.data); + assert_eq!(test_data, map.data()); + } + + #[test] + fn test_parse_btf_map_section() { + let mut obj = fake_obj(); + fake_sym(&mut obj, 0, 0, "map_1", 0); + fake_sym(&mut obj, 0, 0, "map_2", 0); + // generated from: + // objcopy --dump-section .BTF=test.btf ./target/bpfel-unknown-none/debug/multimap-btf.bpf.o + // hexdump -v -e '7/1 "0x%02X, " 1/1 " 0x%02X,\n"' test.btf + let data: &[u8] = &[ + 0x9F, 0xEB, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x01, + 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0xCC, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x07, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x09, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0A, 0x00, + 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0xC0, 0x00, + 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0D, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x20, 0x00, + 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x4A, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x4E, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0E, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x0D, 0x02, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x70, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x12, 0x00, 0x00, 0x00, 0xB0, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xB5, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0E, 0x15, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xBE, 0x01, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xC4, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x69, 0x6E, 0x74, 0x00, 0x5F, 0x5F, 0x41, 0x52, 0x52, 0x41, 0x59, + 0x5F, 0x53, 0x49, 0x5A, 0x45, 0x5F, 0x54, 0x59, 0x50, 0x45, 0x5F, 0x5F, 0x00, 0x5F, + 0x5F, 0x75, 0x33, 0x32, 0x00, 0x75, 0x6E, 0x73, 0x69, 0x67, 0x6E, 0x65, 0x64, 0x20, + 0x69, 0x6E, 0x74, 0x00, 0x5F, 0x5F, 0x75, 0x36, 0x34, 0x00, 0x75, 0x6E, 0x73, 0x69, + 0x67, 0x6E, 0x65, 0x64, 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x6C, 0x6F, 0x6E, 0x67, + 0x00, 0x74, 0x79, 0x70, 0x65, 0x00, 0x6B, 0x65, 0x79, 0x00, 0x76, 0x61, 0x6C, 0x75, + 0x65, 0x00, 0x6D, 0x61, 0x78, 0x5F, 0x65, 0x6E, 0x74, 0x72, 0x69, 0x65, 0x73, 0x00, + 0x6D, 0x61, 0x70, 0x5F, 0x31, 0x00, 0x6D, 0x61, 0x70, 0x5F, 0x32, 0x00, 0x63, 0x74, + 0x78, 0x00, 0x62, 0x70, 0x66, 0x5F, 0x70, 0x72, 0x6F, 0x67, 0x00, 0x74, 0x72, 0x61, + 0x63, 0x65, 0x70, 0x6F, 0x69, 0x6E, 0x74, 0x00, 0x2F, 0x76, 0x61, 0x72, 0x2F, 0x68, + 0x6F, 0x6D, 0x65, 0x2F, 0x64, 0x61, 0x76, 0x65, 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x61, + 0x79, 0x61, 0x2D, 0x72, 0x73, 0x2F, 0x61, 0x79, 0x61, 0x2F, 0x74, 0x65, 0x73, 0x74, + 0x2F, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2D, 0x65, + 0x62, 0x70, 0x66, 0x2F, 0x73, 0x72, 0x63, 0x2F, 0x62, 0x70, 0x66, 0x2F, 0x6D, 0x75, + 0x6C, 0x74, 0x69, 0x6D, 0x61, 0x70, 0x2D, 0x62, 0x74, 0x66, 0x2E, 0x62, 0x70, 0x66, + 0x2E, 0x63, 0x00, 0x69, 0x6E, 0x74, 0x20, 0x62, 0x70, 0x66, 0x5F, 0x70, 0x72, 0x6F, + 0x67, 0x28, 0x76, 0x6F, 0x69, 0x64, 0x20, 0x2A, 0x63, 0x74, 0x78, 0x29, 0x00, 0x09, + 0x5F, 0x5F, 0x75, 0x33, 0x32, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x3D, 0x20, 0x30, 0x3B, + 0x00, 0x09, 0x5F, 0x5F, 0x75, 0x36, 0x34, 0x20, 0x74, 0x77, 0x65, 0x6E, 0x74, 0x79, + 0x5F, 0x66, 0x6F, 0x75, 0x72, 0x20, 0x3D, 0x20, 0x32, 0x34, 0x3B, 0x00, 0x09, 0x5F, + 0x5F, 0x75, 0x36, 0x34, 0x20, 0x66, 0x6F, 0x72, 0x74, 0x79, 0x5F, 0x74, 0x77, 0x6F, + 0x20, 0x3D, 0x20, 0x34, 0x32, 0x3B, 0x00, 0x20, 0x20, 0x20, 0x20, 0x62, 0x70, 0x66, + 0x5F, 0x6D, 0x61, 0x70, 0x5F, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5F, 0x65, 0x6C, + 0x65, 0x6D, 0x28, 0x26, 0x6D, 0x61, 0x70, 0x5F, 0x31, 0x2C, 0x20, 0x26, 0x6B, 0x65, + 0x79, 0x2C, 0x20, 0x26, 0x74, 0x77, 0x65, 0x6E, 0x74, 0x79, 0x5F, 0x66, 0x6F, 0x75, + 0x72, 0x2C, 0x20, 0x42, 0x50, 0x46, 0x5F, 0x41, 0x4E, 0x59, 0x29, 0x3B, 0x00, 0x20, + 0x20, 0x20, 0x20, 0x62, 0x70, 0x66, 0x5F, 0x6D, 0x61, 0x70, 0x5F, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x5F, 0x65, 0x6C, 0x65, 0x6D, 0x28, 0x26, 0x6D, 0x61, 0x70, 0x5F, + 0x32, 0x2C, 0x20, 0x26, 0x6B, 0x65, 0x79, 0x2C, 0x20, 0x26, 0x66, 0x6F, 0x72, 0x74, + 0x79, 0x5F, 0x74, 0x77, 0x6F, 0x2C, 0x20, 0x42, 0x50, 0x46, 0x5F, 0x41, 0x4E, 0x59, + 0x29, 0x3B, 0x00, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6E, 0x20, 0x30, 0x3B, 0x00, + 0x63, 0x68, 0x61, 0x72, 0x00, 0x5F, 0x6C, 0x69, 0x63, 0x65, 0x6E, 0x73, 0x65, 0x00, + 0x2E, 0x6D, 0x61, 0x70, 0x73, 0x00, 0x6C, 0x69, 0x63, 0x65, 0x6E, 0x73, 0x65, 0x00, + ]; + + let btf_section = fake_section(BpfSectionKind::Btf, ".BTF", data); + obj.parse_section(btf_section).unwrap(); + + let map_section = fake_section(BpfSectionKind::BtfMaps, ".maps", &[]); + obj.parse_section(map_section).unwrap(); + + let map = obj.maps.get("map_1").unwrap(); + if let Map::Btf(m) = map { + assert_eq!(m.def.key_size, 4); + assert_eq!(m.def.value_size, 8); + assert_eq!(m.def.max_entries, 1); + } else { + panic!("expected a BTF map") + } } } diff --git a/aya/src/obj/relocation.rs b/aya/src/obj/relocation.rs index af316a84..978faab8 100644 --- a/aya/src/obj/relocation.rs +++ b/aya/src/obj/relocation.rs @@ -65,12 +65,12 @@ impl Object { pub fn relocate_maps(&mut self, maps: &HashMap) -> Result<(), BpfError> { let maps_by_section = maps .iter() - .map(|(name, map)| (map.obj.section_index, (name.as_str(), map))) + .map(|(name, map)| (map.obj.section_index(), (name.as_str(), map))) .collect::>(); let maps_by_symbol = maps .iter() - .map(|(name, map)| (map.obj.symbol_index, (name.as_str(), map))) + .map(|(name, map)| (map.obj.symbol_index(), (name.as_str(), map))) .collect::>(); let functions = self @@ -189,7 +189,7 @@ fn relocate_maps<'a, I: Iterator>( section_index, })?; - if !map.obj.data.is_empty() { + if !map.obj.data().is_empty() { instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_VALUE as u8); instructions[ins_index + 1].imm = instructions[ins_index].imm + sym.address as i32; } else { @@ -433,3 +433,268 @@ fn insn_is_call(ins: &bpf_insn) -> bool { && ins.dst_reg() == 0 && ins.off == 0 } + +#[cfg(test)] +mod test { + use crate::{ + bpf_map_def, + obj::{self, BtfMap, LegacyMap, MapKind}, + BtfMapDef, + }; + + use super::*; + + fn fake_sym(index: usize, section_index: usize, address: u64, name: &str, size: u64) -> Symbol { + Symbol { + index, + section_index: Some(section_index), + name: Some(name.to_string()), + address, + size, + is_definition: false, + kind: SymbolKind::Data, + } + } + + fn ins(bytes: &[u8]) -> bpf_insn { + unsafe { std::ptr::read_unaligned(bytes.as_ptr() as *const _) } + } + + fn fake_legacy_map(fd: i32, symbol_index: usize) -> Map { + Map { + obj: obj::Map::Legacy(LegacyMap { + def: bpf_map_def { + ..Default::default() + }, + section_index: 0, + symbol_index, + data: Vec::new(), + kind: MapKind::Other, + }), + fd: Some(fd), + btf_fd: None, + pinned: false, + } + } + + fn fake_btf_map(fd: i32, symbol_index: usize) -> Map { + Map { + obj: obj::Map::Btf(BtfMap { + def: BtfMapDef { + ..Default::default() + }, + section_index: 0, + symbol_index, + data: Vec::new(), + kind: MapKind::Other, + }), + fd: Some(fd), + btf_fd: None, + pinned: false, + } + } + + fn fake_func(name: &str, instructions: Vec) -> Function { + Function { + address: Default::default(), + name: name.to_string(), + section_index: SectionIndex(0), + section_offset: Default::default(), + instructions, + func_info: Default::default(), + line_info: Default::default(), + func_info_rec_size: Default::default(), + line_info_rec_size: Default::default(), + } + } + + #[test] + fn test_single_legacy_map_relocation() { + let mut fun = fake_func( + "test", + vec![ins(&[ + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ])], + ); + + let symbol_table = HashMap::from([(1, fake_sym(1, 0, 0, "test_map", 0))]); + + let relocations = vec![Relocation { + offset: 0x0, + symbol_index: 1, + }]; + let maps_by_section = HashMap::new(); + + let map = fake_legacy_map(1, 1); + let maps_by_symbol = HashMap::from([(1, ("test_map", &map))]); + + relocate_maps( + &mut fun, + relocations.iter(), + &maps_by_section, + &maps_by_symbol, + &symbol_table, + None, + ) + .unwrap(); + + assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8); + assert_eq!(fun.instructions[0].imm, 1); + + mem::forget(map); + } + + #[test] + fn test_multiple_legacy_map_relocation() { + let mut fun = fake_func( + "test", + vec![ + ins(&[ + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ]), + ins(&[ + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ]), + ], + ); + + let symbol_table = HashMap::from([ + (1, fake_sym(1, 0, 0, "test_map_1", 0)), + (2, fake_sym(2, 0, 0, "test_map_2", 0)), + ]); + + let relocations = vec![ + Relocation { + offset: 0x0, + symbol_index: 1, + }, + Relocation { + offset: mem::size_of::() as u64, + symbol_index: 2, + }, + ]; + let maps_by_section = HashMap::new(); + + let map_1 = fake_legacy_map(1, 1); + let map_2 = fake_legacy_map(2, 2); + let maps_by_symbol = + HashMap::from([(1, ("test_map_1", &map_1)), (2, ("test_map_2", &map_2))]); + + relocate_maps( + &mut fun, + relocations.iter(), + &maps_by_section, + &maps_by_symbol, + &symbol_table, + None, + ) + .unwrap(); + + assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8); + assert_eq!(fun.instructions[0].imm, 1); + + assert_eq!(fun.instructions[1].src_reg(), BPF_PSEUDO_MAP_FD as u8); + assert_eq!(fun.instructions[1].imm, 2); + + mem::forget(map_1); + mem::forget(map_2); + } + + #[test] + fn test_single_btf_map_relocation() { + let mut fun = fake_func( + "test", + vec![ins(&[ + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ])], + ); + + let symbol_table = HashMap::from([(1, fake_sym(1, 0, 0, "test_map", 0))]); + + let relocations = vec![Relocation { + offset: 0x0, + symbol_index: 1, + }]; + let maps_by_section = HashMap::new(); + + let map = fake_btf_map(1, 1); + let maps_by_symbol = HashMap::from([(1, ("test_map", &map))]); + + relocate_maps( + &mut fun, + relocations.iter(), + &maps_by_section, + &maps_by_symbol, + &symbol_table, + None, + ) + .unwrap(); + + assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8); + assert_eq!(fun.instructions[0].imm, 1); + + mem::forget(map); + } + + #[test] + fn test_multiple_btf_map_relocation() { + let mut fun = fake_func( + "test", + vec![ + ins(&[ + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ]), + ins(&[ + 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ]), + ], + ); + + let symbol_table = HashMap::from([ + (1, fake_sym(1, 0, 0, "test_map_1", 0)), + (2, fake_sym(2, 0, 0, "test_map_2", 0)), + ]); + + let relocations = vec![ + Relocation { + offset: 0x0, + symbol_index: 1, + }, + Relocation { + offset: mem::size_of::() as u64, + symbol_index: 2, + }, + ]; + let maps_by_section = HashMap::new(); + + let map_1 = fake_btf_map(1, 1); + let map_2 = fake_btf_map(2, 2); + let maps_by_symbol = + HashMap::from([(1, ("test_map_1", &map_1)), (2, ("test_map_2", &map_2))]); + + relocate_maps( + &mut fun, + relocations.iter(), + &maps_by_section, + &maps_by_symbol, + &symbol_table, + None, + ) + .unwrap(); + + assert_eq!(fun.instructions[0].src_reg(), BPF_PSEUDO_MAP_FD as u8); + assert_eq!(fun.instructions[0].imm, 1); + + assert_eq!(fun.instructions[1].src_reg(), BPF_PSEUDO_MAP_FD as u8); + assert_eq!(fun.instructions[1].imm, 2); + + mem::forget(map_1); + mem::forget(map_2); + } +} diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index d26fb5e2..16a5023f 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -17,26 +17,34 @@ use std::{ }; use crate::{ - bpf_map_def, generated::{ bpf_attach_type, bpf_attr, bpf_btf_info, bpf_cmd, bpf_insn, bpf_prog_info, bpf_prog_type, }, maps::PerCpuValues, - obj::btf::{FuncSecInfo, LineSecInfo}, + obj::{ + self, + btf::{FuncSecInfo, LineSecInfo}, + }, sys::{kernel_version, syscall, SysResult, Syscall}, util::VerifierLog, Pod, BPF_OBJ_NAME_LEN, }; -pub(crate) fn bpf_create_map(name: &CStr, def: &bpf_map_def) -> SysResult { +pub(crate) fn bpf_create_map(name: &CStr, def: &obj::Map, btf_fd: Option) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_1 }; - u.map_type = def.map_type; - u.key_size = def.key_size; - u.value_size = def.value_size; - u.max_entries = def.max_entries; - u.map_flags = def.map_flags; + u.map_type = def.map_type(); + u.key_size = def.key_size(); + u.value_size = def.value_size(); + u.max_entries = def.max_entries(); + u.map_flags = def.map_flags(); + + if let obj::Map::Btf(m) = def { + u.btf_key_type_id = m.def.btf_key_type_id; + u.btf_value_type_id = m.def.btf_value_type_id; + u.btf_fd = btf_fd.unwrap() as u32; + } // https://github.com/torvalds/linux/commit/ad5b177bd73f5107d97c36f56395c4281fb6f089 // The map name was added as a parameter in kernel 4.15+ so we skip adding it on diff --git a/test/integration-ebpf/src/bpf/multimap-btf.bpf.c b/test/integration-ebpf/src/bpf/multimap-btf.bpf.c new file mode 100644 index 00000000..955a91d3 --- /dev/null +++ b/test/integration-ebpf/src/bpf/multimap-btf.bpf.c @@ -0,0 +1,30 @@ +#include +#include + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u64); + __uint(max_entries, 1); +} map_1 SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u64); + __uint(max_entries, 1); +} map_2 SEC(".maps"); + + +SEC("tracepoint") +int bpf_prog(void *ctx) +{ + __u32 key = 0; + __u64 twenty_four = 24; + __u64 forty_two = 42; + bpf_map_update_elem(&map_1, &key, &twenty_four, BPF_ANY); + bpf_map_update_elem(&map_2, &key, &forty_two, BPF_ANY); + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index bacbf317..472ce62b 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -1,8 +1,13 @@ -use std::{convert::TryInto, process::Command}; +use std::{ + convert::{TryFrom, TryInto}, + process::Command, + thread, time, +}; use aya::{ include_bytes_aligned, - programs::{Xdp, XdpFlags}, + maps::{Array, MapRefMut}, + programs::{TracePoint, Xdp, XdpFlags}, Bpf, }; @@ -34,6 +39,31 @@ fn multiple_maps() -> anyhow::Result<()> { Ok(()) } +#[integration_test] +fn multiple_btf_maps() -> anyhow::Result<()> { + let bytes = + include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/multimap-btf.bpf.o"); + let mut bpf = Bpf::load(bytes)?; + + let map_1: Array = Array::try_from(bpf.map_mut("map_1")?)?; + let map_2: Array = Array::try_from(bpf.map_mut("map_2")?)?; + + let prog: &mut TracePoint = bpf.program_mut("tracepoint").unwrap().try_into().unwrap(); + prog.load().unwrap(); + prog.attach("sched", "sched_switch").unwrap(); + + thread::sleep(time::Duration::from_secs(3)); + + let key = 0; + let val_1 = map_1.get(&key, 0)?; + let val_2 = map_2.get(&key, 0)?; + + assert_eq!(val_1, 24); + assert_eq!(val_2, 42); + + Ok(()) +} + fn is_loaded() -> bool { let output = Command::new("bpftool").args(&["prog"]).output().unwrap(); let stdout = String::from_utf8(output.stdout).unwrap();