From 311ead6760ce53e9503af00391e6631f7387ab4a Mon Sep 17 00:00:00 2001 From: Shenghui Ye Date: Wed, 28 Dec 2022 12:19:51 +0800 Subject: [PATCH] aya-obj: add integration tests against rbpf --- aya-obj/README.md | 43 ++++++++++- aya-obj/src/lib.rs | 41 ++++++++++ test/integration-test/Cargo.toml | 2 + test/integration-test/src/tests/mod.rs | 1 + test/integration-test/src/tests/rbpf.rs | 99 +++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 test/integration-test/src/tests/rbpf.rs diff --git a/aya-obj/README.md b/aya-obj/README.md index 40a4c5a9..29037565 100644 --- a/aya-obj/README.md +++ b/aya-obj/README.md @@ -1 +1,42 @@ -# aya-obj +# aya-obj - an eBPF object file loading library + +## Overview + +eBPF programs written with [libbpf] or [aya-bpf] are usually compiled +into an ELF object file, using various section to store information +about the eBPF programs. + +`aya-obj` is a library that loads, parses and processes such eBPF +object files. + +[libbpf]: https://github.com/libbpf/libbpf +[aya-bpf]: https://github.com/aya-rs/aya + +## Example + +This example loads a simple eBPF program and runs it with [rbpf]. + +```rust +use aya_bpf::Object; + +// Parse the object file +let bytes = std::fs::read("program.o").unwrap(); +let mut object = Object::parse(bytes).unwrap(); +// Relocate the programs +object.relocate_calls().unwrap(); +object.relocate_maps(std::iter::empty()).unwrap(); + +// Run with rbpf +let program = object.programs.iter().next().unwrap().1; +let instructions = &program.function.instructions; +let data = unsafe { + from_raw_parts( + instructions.as_ptr() as *const u8, + instructions.len() * size_of::(), + ) +}; +let vm = rbpf::EbpfVmNoData::new(Some(data)).unwrap(); +let _return = vm.execute_program().unwrap(); +``` + +[rbpf]: https://github.com/qmonnet/rbpf diff --git a/aya-obj/src/lib.rs b/aya-obj/src/lib.rs index 22ceb1cc..45a781a1 100644 --- a/aya-obj/src/lib.rs +++ b/aya-obj/src/lib.rs @@ -1,4 +1,45 @@ //! A library for loading and relocating eBPF object files. +//! +//! ## Overview +//! +//! eBPF programs written with [libbpf] or [aya-bpf] are usually compiled +//! into an ELF object file, using various section to store information +//! about the eBPF programs. +//! +//! `aya-obj` is a library that loads, parses and processes such eBPF +//! object files. +//! +//! [libbpf]: https://github.com/libbpf/libbpf +//! [aya-bpf]: https://github.com/aya-rs/aya +//! +//! ## Example +//! +//! This example loads a simple eBPF program and runs it with [rbpf]. +//! +//! ```no_run +//! use aya_bpf::Object; +//! +//! // Parse the object file +//! let bytes = std::fs::read("program.o").unwrap(); +//! let mut object = Object::parse(bytes).unwrap(); +//! // Relocate the programs +//! object.relocate_calls().unwrap(); +//! object.relocate_maps(std::iter::empty()).unwrap(); +//! +//! // Run with rbpf +//! let program = object.programs.iter().next().unwrap().1; +//! let instructions = &program.function.instructions; +//! let data = unsafe { +//! from_raw_parts( +//! instructions.as_ptr() as *const u8, +//! instructions.len() * size_of::(), +//! ) +//! }; +//! let vm = rbpf::EbpfVmNoData::new(Some(data)).unwrap(); +//! let _return = vm.execute_program().unwrap(); +//! ``` +//! +//! [rbpf]: https://github.com/qmonnet/rbpf #![doc( html_logo_url = "https://aya-rs.dev/assets/images/crabby.svg", diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml index 94c05cf1..1f1bdf13 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] anyhow = "1" aya = { path = "../../aya" } +aya-obj = { path = "../../aya-obj" } clap = { version = "4", features = ["derive"] } env_logger = "0.10" inventory = "0.2" @@ -15,4 +16,5 @@ lazy_static = "1" libc = { version = "0.2.105" } log = "0.4" object = { version = "0.30", default-features = false, features = ["std", "read_core", "elf"] } +rbpf = "0.1.0" regex = "1" diff --git a/test/integration-test/src/tests/mod.rs b/test/integration-test/src/tests/mod.rs index 53a98abe..0b3e9e95 100644 --- a/test/integration-test/src/tests/mod.rs +++ b/test/integration-test/src/tests/mod.rs @@ -6,6 +6,7 @@ use std::{ffi::CStr, mem}; pub mod elf; pub mod load; +pub mod rbpf; pub mod smoke; pub use integration_test_macros::integration_test; diff --git a/test/integration-test/src/tests/rbpf.rs b/test/integration-test/src/tests/rbpf.rs new file mode 100644 index 00000000..a76bb162 --- /dev/null +++ b/test/integration-test/src/tests/rbpf.rs @@ -0,0 +1,99 @@ +use core::{mem::size_of, ptr::null_mut, slice::from_raw_parts}; +use std::collections::HashMap; + +use aya::include_bytes_aligned; +use aya_obj::{generated::bpf_insn, Object}; + +use super::{integration_test, IntegrationTest}; + +#[integration_test] +fn run_with_rbpf() { + let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass"); + let object = Object::parse(bytes).unwrap(); + assert_eq!(object.programs.len(), 1); + let instructions = &object.programs["pass"].function.instructions; + let data = unsafe { + from_raw_parts( + instructions.as_ptr() as *const u8, + instructions.len() * size_of::(), + ) + }; + // Use rbpf interpreter instead of JIT compiler to ensure platform compatibility. + let vm = rbpf::EbpfVmNoData::new(Some(data)).unwrap(); + const XDP_PASS: u64 = 2; + assert_eq!(vm.execute_program().unwrap(), XDP_PASS); +} + +static mut MULTIMAP_MAPS: [*mut Vec; 2] = [null_mut(), null_mut()]; + +#[integration_test] +fn use_map_with_rbpf() { + let bytes = + include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/multimap-btf.bpf.o"); + let mut object = Object::parse(bytes).unwrap(); + let mut maps = HashMap::new(); + + // Initializes maps: + // - fd: 0xCAFE00 or 0xCAFE01, + // - Note that rbpf does not convert fds into real pointers, + // so we keeps the pointers to our maps in MULTIMAP_MAPS, to be used in helpers. + let mut map_instances = Vec::new(); + for (map_id, (name, map)) in object.maps.iter().enumerate() { + maps.insert(name.to_owned(), (map_id as i32 | 0xCAFE00, map.clone())); + assert_eq!(map.key_size(), size_of::() as u32); + assert_eq!(map.value_size(), size_of::() as u32); + assert_eq!( + map.map_type(), + aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_ARRAY as u32 + ); + map_instances.push(vec![0u64]); + + unsafe { + MULTIMAP_MAPS[if name == "map_1" { 0 } else { 1 }] = + &mut map_instances[map_id] as *mut _; + } + } + + object + .relocate_maps( + maps.iter() + .map(|(s, (fd, map))| (s.as_ref() as &str, Some(*fd), map)), + ) + .expect("Relocation failed"); + // Actually there is no call involved. + object.relocate_calls().unwrap(); + + // Executes the program + assert_eq!(object.programs.len(), 1); + let instructions = &object.programs["tracepoint"].function.instructions; + let data = unsafe { + from_raw_parts( + instructions.as_ptr() as *const u8, + instructions.len() * size_of::(), + ) + }; + let mut vm = rbpf::EbpfVmNoData::new(Some(data)).unwrap(); + vm.register_helper(2, bpf_map_update_elem_multimap) + .expect("Helper failed"); + assert_eq!(vm.execute_program().unwrap(), 0); + + assert_eq!(map_instances[0][0], 24); + assert_eq!(map_instances[1][0], 42); + + unsafe { + MULTIMAP_MAPS[0] = null_mut(); + MULTIMAP_MAPS[1] = null_mut(); + } +} + +fn bpf_map_update_elem_multimap(map: u64, key: u64, value: u64, _: u64, _: u64) -> u64 { + assert!(map == 0xCAFE00 || map == 0xCAFE01); + let key = *unsafe { (key as usize as *const u32).as_ref().unwrap() }; + let value = *unsafe { (value as usize as *const u64).as_ref().unwrap() }; + assert_eq!(key, 0); + unsafe { + let map_instance = MULTIMAP_MAPS[map as usize & 0xFF].as_mut().unwrap(); + map_instance[0] = value; + } + 0 +}