Browse Source

aya-obj: add integration tests against rbpf

Shenghui Ye 2 years ago
parent
commit
311ead6

+ 42 - 1
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::<bpf_insn>(),
+    )
+};
+let vm = rbpf::EbpfVmNoData::new(Some(data)).unwrap();
+let _return = vm.execute_program().unwrap();
+```
+
+[rbpf]: https://github.com/qmonnet/rbpf

+ 41 - 0
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::<bpf_insn>(),
+//!     )
+//! };
+//! 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",

+ 2 - 0
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"

+ 1 - 0
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;

+ 99 - 0
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::<bpf_insn>(),
+        )
+    };
+    // 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<u64>; 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::<u32>() as u32);
+        assert_eq!(map.value_size(), size_of::<u64>() 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::<bpf_insn>(),
+        )
+    };
+    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
+}