integration-test: compile Rust probes using build.rs

pull/644/head
Tamir Duberstein 1 year ago
parent bc9f059d53
commit 3d463a3610
No known key found for this signature in database

@ -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!`.

@ -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"

@ -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"

@ -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}"));
}
}

@ -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"));

@ -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,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");
}

@ -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();

@ -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,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 { .. });

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

@ -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() {

Loading…
Cancel
Save