From 2efbea04f2a8ae744586a4d5d12f10478a5926ec Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 10 Jul 2023 18:10:15 -0400 Subject: [PATCH] integration-test: compile Rust probes using build.rs --- test/README.md | 3 +- test/integration-ebpf/Cargo.toml | 2 +- test/integration-test/Cargo.toml | 3 + test/integration-test/build.rs | 70 ++++++++++++++++- test/integration-test/src/lib.rs | 9 +++ test/integration-test/tests/bpf_probe_read.rs | 5 +- test/integration-test/tests/elf.rs | 4 +- test/integration-test/tests/load.rs | 19 ++--- test/integration-test/tests/log.rs | 5 +- test/integration-test/tests/rbpf.rs | 4 +- test/integration-test/tests/relocations.rs | 6 +- test/integration-test/tests/smoke.rs | 4 +- xtask/src/run.rs | 78 +++---------------- 13 files changed, 109 insertions(+), 103 deletions(-) diff --git a/test/README.md b/test/README.md index bb2e3cef..14184ae2 100644 --- a/test/README.md +++ b/test/README.md @@ -44,7 +44,8 @@ cargo xtask integration-test Tests should follow these guidelines: - Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in - `integration-ebpf/Cargo.toml`. + `integration-ebpf/Cargo.toml` and `integration-test/src/lib.rs` using + `include_bytes_aligned!`. - C eBPF code should live in `integration-test/bpf/${NAME}.bpf.c`. It should be added to the list of files in `integration-test/build.rs` and the list of constants in `integration-test/src/lib.rs` using `include_bytes_aligned!`. diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 73edf5fd..ff35ddb7 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -34,4 +34,4 @@ path = "src/relocations.rs" [[bin]] name = "bpf_probe_read" -path = "src/bpf_probe_read.rs" \ No newline at end of file +path = "src/bpf_probe_read.rs" diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml index 36f8270b..6da87368 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -16,3 +16,6 @@ procfs = "0.15.1" rbpf = "0.2.0" tempfile = "3.3.0" tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] } + +[build-dependencies] +cargo_metadata = "0.15.4" diff --git a/test/integration-test/build.rs b/test/integration-test/build.rs index f8ca9170..a9d8eab6 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -1,4 +1,14 @@ -use std::{env, ffi::OsString, path::PathBuf, process::Command}; +use std::{ + env, + ffi::OsString, + fmt::Write as _, + fs::copy, + io::BufReader, + path::PathBuf, + process::{Command, Stdio}, +}; + +use cargo_metadata::{Artifact, CompilerMessage, Message, Target}; fn main() { let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap(); @@ -57,4 +67,62 @@ fn main() { None => panic!("{cmd:?} terminated by signal"), } } + + let ebpf_dir = manifest_dir.parent().unwrap().join("integration-ebpf"); + let target = format!("{target}-unknown-none"); + + let mut cmd = Command::new("cargo"); + cmd.current_dir(&ebpf_dir).args([ + "build", + "-Z", + "build-std=core", + "--release", + "--message-format=json", + "--target", + &target, + ]); + let mut cmd = cmd + .stdout(Stdio::piped()) + .spawn() + .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}")); + + let reader = BufReader::new(cmd.stdout.take().unwrap()); + let mut executables = Vec::new(); + let mut compiler_messages = String::new(); + for message in Message::parse_stream(reader) { + #[allow(clippy::collapsible_match)] + match message.expect("valid JSON") { + Message::CompilerArtifact(Artifact { + executable, + target: Target { name, .. }, + .. + }) => { + if let Some(executable) = executable { + executables.push((name, executable.into_std_path_buf())); + } + } + Message::CompilerMessage(CompilerMessage { message, .. }) => { + writeln!(&mut compiler_messages, "{message}").unwrap() + } + _ => {} + } + } + + let status = cmd + .wait() + .unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}")); + + match status.code() { + Some(code) => match code { + 0 => {} + code => panic!("{cmd:?} exited with status code {code}:\n{compiler_messages}"), + }, + None => panic!("{cmd:?} terminated by signal"), + } + + for (name, binary) in executables { + let dst = out_dir.join(name); + let _: u64 = copy(&binary, &dst) + .unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}")); + } } diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 2ecb410b..39a87ba2 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -6,3 +6,12 @@ pub const MULTIMAP_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.o")); pub const TEXT_64_64_RELOC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o")); + +pub const LOG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/log")); +pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_test")); +pub const NAME_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/name_test")); +pub const PASS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/pass")); +pub const TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/test")); +pub const RELOCATIONS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/relocations")); +pub const BPF_PROBE_READ: &[u8] = + include_bytes_aligned!(concat!(env!("OUT_DIR"), "/bpf_probe_read")); diff --git a/test/integration-test/tests/bpf_probe_read.rs b/test/integration-test/tests/bpf_probe_read.rs index b8d7aedd..396eed6d 100644 --- a/test/integration-test/tests/bpf_probe_read.rs +++ b/test/integration-test/tests/bpf_probe_read.rs @@ -1,7 +1,6 @@ use std::process::exit; use aya::{ - include_bytes_aligned, maps::Array, programs::{ProgramError, UProbe}, Bpf, @@ -75,7 +74,7 @@ fn set_user_buffer(bytes: &[u8], dest_len: usize) -> Bpf { let bpf = load_and_attach_uprobe( "test_bpf_probe_read_user_str_bytes", "trigger_bpf_probe_read_user", - include_bytes_aligned!("../../../target/bpfel-unknown-none/release/bpf_probe_read"), + integration_test::BPF_PROBE_READ, ); trigger_bpf_probe_read_user(bytes.as_ptr(), dest_len); bpf @@ -85,7 +84,7 @@ fn set_kernel_buffer(bytes: &[u8], dest_len: usize) -> Bpf { let mut bpf = load_and_attach_uprobe( "test_bpf_probe_read_kernel_str_bytes", "trigger_bpf_probe_read_kernel", - include_bytes_aligned!("../../../target/bpfel-unknown-none/release/bpf_probe_read"), + integration_test::BPF_PROBE_READ, ); set_kernel_buffer_element(&mut bpf, bytes); trigger_bpf_probe_read_kernel(dest_len); diff --git a/test/integration-test/tests/elf.rs b/test/integration-test/tests/elf.rs index 3d37ddb4..a51733d7 100644 --- a/test/integration-test/tests/elf.rs +++ b/test/integration-test/tests/elf.rs @@ -1,10 +1,8 @@ -use aya::include_bytes_aligned; use object::{Object, ObjectSymbol}; #[test] fn test_maps() { - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/map_test"); - let obj_file = object::File::parse(bytes).unwrap(); + let obj_file = object::File::parse(integration_test::MAP_TEST).unwrap(); if obj_file.section_by_name("maps").is_none() { panic!("No 'maps' ELF section"); } diff --git a/test/integration-test/tests/load.rs b/test/integration-test/tests/load.rs index 48cd4482..23f71f4e 100644 --- a/test/integration-test/tests/load.rs +++ b/test/integration-test/tests/load.rs @@ -2,7 +2,6 @@ use procfs::KernelVersion; use std::{convert::TryInto as _, thread, time}; use aya::{ - include_bytes_aligned, maps::Array, programs::{ links::{FdLink, PinnedLink}, @@ -16,8 +15,7 @@ const RETRY_DURATION_MS: u64 = 10; #[test] fn long_name() { - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/name_test"); - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::NAME_TEST).unwrap(); let name_prog: &mut Xdp = bpf .program_mut("ihaveaverylongname") .unwrap() @@ -69,8 +67,7 @@ macro_rules! assert_loaded { #[test] fn unload_xdp() { - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/test"); - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::TEST).unwrap(); let prog: &mut Xdp = bpf .program_mut("test_unload_xdp") .unwrap() @@ -99,8 +96,7 @@ fn unload_xdp() { #[test] fn unload_kprobe() { - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/test"); - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::TEST).unwrap(); let prog: &mut KProbe = bpf .program_mut("test_unload_kpr") .unwrap() @@ -135,8 +131,7 @@ fn pin_link() { return; } - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/test"); - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::TEST).unwrap(); let prog: &mut Xdp = bpf .program_mut("test_unload_xdp") .unwrap() @@ -171,11 +166,9 @@ fn pin_lifecycle() { return; } - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); - // 1. Load Program and Pin { - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::PASS).unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); prog.load().unwrap(); prog.pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap(); @@ -209,7 +202,7 @@ fn pin_lifecycle() { // 4. Load a new version of the program, unpin link, and atomically replace old program { - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::PASS).unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); prog.load().unwrap(); diff --git a/test/integration-test/tests/log.rs b/test/integration-test/tests/log.rs index 1c4251db..f4134e98 100644 --- a/test/integration-test/tests/log.rs +++ b/test/integration-test/tests/log.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, LockResult, Mutex, MutexGuard}; -use aya::{include_bytes_aligned, programs::UProbe, Bpf}; +use aya::{programs::UProbe, Bpf}; use aya_log::BpfLogger; use log::{Level, Log, Record}; use tokio::time::{sleep, Duration}; @@ -89,8 +89,7 @@ impl Log for TestingLogger { #[tokio::test] async fn log() { - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/log"); - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::LOG).unwrap(); let (logger, captured_logs) = TestingLogger::with_capacity(5); BpfLogger::init_with_logger(&mut bpf, logger).unwrap(); diff --git a/test/integration-test/tests/rbpf.rs b/test/integration-test/tests/rbpf.rs index d65f8d3f..c9c52af6 100644 --- a/test/integration-test/tests/rbpf.rs +++ b/test/integration-test/tests/rbpf.rs @@ -1,13 +1,11 @@ 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, ProgramSection}; #[test] fn run_with_rbpf() { - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); - let object = Object::parse(bytes).unwrap(); + let object = Object::parse(integration_test::PASS).unwrap(); assert_eq!(object.programs.len(), 1); assert!(matches!( diff --git a/test/integration-test/tests/relocations.rs b/test/integration-test/tests/relocations.rs index 06712eb0..6304966c 100644 --- a/test/integration-test/tests/relocations.rs +++ b/test/integration-test/tests/relocations.rs @@ -1,17 +1,13 @@ use std::{process::exit, time::Duration}; use aya::{ - include_bytes_aligned, programs::{ProgramError, UProbe}, Bpf, }; #[test] fn relocations() { - let bpf = load_and_attach( - "test_64_32_call_relocs", - include_bytes_aligned!("../../../target/bpfel-unknown-none/release/relocations"), - ); + let bpf = load_and_attach("test_64_32_call_relocs", integration_test::RELOCATIONS); trigger_relocations_program(); std::thread::sleep(Duration::from_millis(100)); diff --git a/test/integration-test/tests/smoke.rs b/test/integration-test/tests/smoke.rs index 03e55ca4..8025958e 100644 --- a/test/integration-test/tests/smoke.rs +++ b/test/integration-test/tests/smoke.rs @@ -1,7 +1,6 @@ use procfs::KernelVersion; use aya::{ - include_bytes_aligned, programs::{Extension, Xdp, XdpFlags}, Bpf, BpfLoader, }; @@ -14,8 +13,7 @@ fn xdp() { return; } - let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); - let mut bpf = Bpf::load(bytes).unwrap(); + let mut bpf = Bpf::load(integration_test::PASS).unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); dispatcher.load().unwrap(); dispatcher.attach("lo", XdpFlags::default()).unwrap(); diff --git a/xtask/src/run.rs b/xtask/src/run.rs index fcd10aaa..8cefcf5f 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -9,38 +9,8 @@ use anyhow::{Context as _, Result}; use cargo_metadata::{Artifact, CompilerMessage, Message, Target}; use clap::Parser; -#[derive(Debug, Copy, Clone)] -pub enum Architecture { - BpfEl, - BpfEb, -} - -impl std::str::FromStr for Architecture { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - Ok(match s { - "bpfel-unknown-none" => Architecture::BpfEl, - "bpfeb-unknown-none" => Architecture::BpfEb, - _ => return Err("invalid target"), - }) - } -} - -impl std::fmt::Display for Architecture { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Architecture::BpfEl => "bpfel-unknown-none", - Architecture::BpfEb => "bpfeb-unknown-none", - }) - } -} - #[derive(Debug, Parser)] pub struct Options { - /// Set the endianness of the BPF target - #[clap(default_value = "bpfel-unknown-none", long)] - pub bpf_target: Architecture, /// Build and run the release target #[clap(long)] pub release: bool, @@ -53,7 +23,7 @@ pub struct Options { } /// Build the project -fn build(release: bool) -> Result> { +fn build(release: bool) -> Result> { let mut cmd = Command::new("cargo"); cmd.args([ "build", @@ -77,16 +47,17 @@ fn build(release: bool) -> Result> { match message.context("valid JSON")? { Message::CompilerArtifact(Artifact { executable, - target: Target { src_path, .. }, + target: Target { name, .. }, .. }) => { if let Some(executable) = executable { - executables.push((src_path.into(), executable.into())); + executables.push((name, executable.into())); } } Message::CompilerMessage(CompilerMessage { message, .. }) => { - assert_eq!(writeln!(&mut compiler_messages, "{message}"), Ok(())); + writeln!(&mut compiler_messages, "{message}").context("String write failed")? } + _ => {} } } @@ -109,37 +80,18 @@ fn build(release: bool) -> Result> { /// Build and run the project pub fn run(opts: Options) -> Result<()> { let Options { - bpf_target, release, runner, run_args, } = opts; - let metadata = cargo_metadata::MetadataCommand::new() - .exec() - .context("cargo metadata")?; - let dir = metadata - .workspace_root - .into_std_path_buf() - .join("test") - .join("integration-ebpf"); - - crate::docs::exec( - Command::new("cargo") - .current_dir(&dir) - .args(["+nightly", "build", "--release", "--target"]) - .arg(bpf_target.to_string()) - .args(["-Z", "build-std=core"]) - .current_dir(&dir), - )?; - let binaries = build(release).context("error while building userspace application")?; let mut args = runner.trim().split_terminator(' '); let runner = args.next().ok_or(anyhow::anyhow!("no first argument"))?; let args = args.collect::>(); let mut failures = String::new(); - for (src_path, binary) in binaries { + for (name, binary) in binaries { let mut cmd = Command::new(runner); let cmd = cmd .args(args.iter()) @@ -147,7 +99,7 @@ pub fn run(opts: Options) -> Result<()> { .args(run_args.iter()) .arg("--test-threads=1"); - println!("{} running {cmd:?}", src_path.display()); + println!("{} running {cmd:?}", name); let status = cmd .status() @@ -155,19 +107,11 @@ pub fn run(opts: Options) -> Result<()> { match status.code() { Some(code) => match code { 0 => {} - code => assert_eq!( - writeln!( - &mut failures, - "{} exited with status code {code}", - src_path.display() - ), - Ok(()) - ), + code => writeln!(&mut failures, "{} exited with status code {code}", name) + .context("String write failed")?, }, - None => assert_eq!( - writeln!(&mut failures, "{} terminated by signal", src_path.display()), - Ok(()) - ), + None => writeln!(&mut failures, "{} terminated by signal", name) + .context("String write failed")?, } } if failures.is_empty() {