Răsfoiți Sursa

integration-test: compile Rust probes using build.rs

Tamir Duberstein 1 an în urmă
părinte
comite
3d463a3610

+ 2 - 1
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!`.

+ 1 - 1
test/integration-ebpf/Cargo.toml

@@ -34,4 +34,4 @@ path = "src/relocations.rs"
 
 [[bin]]
 name = "bpf_probe_read"
-path = "src/bpf_probe_read.rs"
+path = "src/bpf_probe_read.rs"

+ 3 - 0
test/integration-test/Cargo.toml

@@ -20,3 +20,6 @@ object = { version = "0.31", default-features = false, features = [
 rbpf = "0.2.0"
 tempfile = "3.3.0"
 tokio = { version = "1.24", default-features = false, features = ["time"] }
+
+[build-dependencies]
+cargo_metadata = "0.15.4"

+ 70 - 1
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::{Child, Command, Stdio},
+};
+
+use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
 
 fn main() {
     let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
@@ -85,4 +95,63 @@ 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 child = cmd
+        .stdout(Stdio::piped())
+        .spawn()
+        .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
+    let Child { stdout, .. } = &mut child;
+    let stdout = stdout.take().unwrap();
+    let reader = BufReader::new(stdout);
+    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 = child
+        .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}"));
+    }
 }

+ 9 - 0
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"));

+ 3 - 3
test/integration-test/tests/bpf_probe_read.rs

@@ -1,4 +1,4 @@
-use aya::{include_bytes_aligned, maps::Array, programs::UProbe, Bpf};
+use aya::{maps::Array, programs::UProbe, Bpf};
 
 const RESULT_BUF_LEN: usize = 1024;
 
@@ -68,7 +68,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
@@ -78,7 +78,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);

+ 1 - 3
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");
     }

+ 6 - 13
test/integration-test/tests/load.rs

@@ -1,7 +1,6 @@
 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();
 

+ 2 - 3
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();

+ 1 - 3
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);
     matches::assert_matches!(object.programs["pass"].section, ProgramSection::Xdp { .. });

+ 2 - 5
test/integration-test/tests/relocations.rs

@@ -1,13 +1,10 @@
 use std::time::Duration;
 
-use aya::{include_bytes_aligned, programs::UProbe, Bpf};
+use aya::{programs::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));

+ 1 - 3
test/integration-test/tests/smoke.rs

@@ -1,5 +1,4 @@
 use aya::{
-    include_bytes_aligned,
     programs::{Extension, Xdp, XdpFlags},
     util::KernelVersion,
     Bpf, BpfLoader,
@@ -13,8 +12,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();

+ 11 - 67
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<Self, Self::Err> {
-        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<Vec<(PathBuf, PathBuf)>> {
+fn build(release: bool) -> Result<Vec<(String, PathBuf)>> {
     let mut cmd = Command::new("cargo");
     cmd.args([
         "build",
@@ -77,16 +47,17 @@ fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>> {
         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<Vec<(PathBuf, PathBuf)>> {
 /// 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::<Vec<_>>();
 
     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() {