generated from amosproj/amos202Xss0Y-projname
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #192 from amosproj/161-testing-ebpf
161 testing ebpf
- Loading branch information
Showing
8 changed files
with
293 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# SPDX-FileCopyrightText: 2024 Felix Hilgers <[email protected]> | ||
# | ||
# SPDX-License-Identifier: MIT | ||
|
||
[package] | ||
name = "backend-ebpf-test" | ||
version = "0.1.0" | ||
license.workspace = true | ||
repository.workspace = true | ||
edition.workspace = true | ||
|
||
[dependencies] | ||
backend-common = { workspace = true } | ||
aya-ebpf = { workspace = true } | ||
aya-log-ebpf = { workspace = true } | ||
|
||
aya = { workspace = true } | ||
libc = { workspace = true } | ||
aya-obj = { workspace = true } | ||
|
||
[build-dependencies] | ||
which = { workspace = true } | ||
cargo_metadata = { workspace = true } | ||
|
||
[[test]] | ||
name = "prog-test-run" | ||
path = "tests/prog_test_run.rs" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// SPDX-FileCopyrightText: 2024 Benedikt Zinn <[email protected]> | ||
// SPDX-FileCopyrightText: 2024 Felix Hilgers <[email protected]> | ||
// SPDX-FileCopyrightText: 2024 Luca Bretting <[email protected]> | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
// TODO: this is a verbatim copy of backend-daemon/build.rs with just another build variant of the ebpf program | ||
|
||
use std::{ | ||
env, fs, | ||
io::{BufRead as _, BufReader}, | ||
path::PathBuf, | ||
process::{Child, Command, Stdio}, | ||
}; | ||
|
||
use cargo_metadata::{ | ||
Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target, TargetKind, | ||
}; | ||
|
||
/// This crate has a runtime dependency on artifacts produced by the `example-ebpf` crate. | ||
/// This would be better expressed as one or more [artifact-dependencies][bindeps] but issues such | ||
/// as: | ||
/// | ||
/// * https://github.com/rust-lang/cargo/issues/12374 | ||
/// * https://github.com/rust-lang/cargo/issues/12375 | ||
/// * https://github.com/rust-lang/cargo/issues/12385 | ||
/// | ||
/// prevent their use for the time being. | ||
/// | ||
/// This file, along with the xtask crate, allows analysis tools such as `cargo check`, `cargo | ||
/// clippy`, and even `cargo build` to work as users expect. Prior to this file's existence, this | ||
/// crate's undeclared dependency on artifacts from `example-ebpf` would cause build (and | ||
/// `cargo check`, and `cargo clippy`) failures until the user ran certain other commands in the | ||
/// workspace. Conversely, those same tools (e.g. cargo test --no-run) would produce stale results | ||
/// if run naively because they'd make use of artifacts from a previous build of | ||
/// `example-ebpf`. | ||
/// | ||
/// Note that this solution is imperfect: in particular it has to balance correctness with | ||
/// performance; an environment variable is used to replace true builds of `example-ebpf` | ||
/// with stubs to preserve the property that code generation and linking (in | ||
/// `example-ebpf`) do not occur on metadata-only actions such as `cargo check` or `cargo | ||
/// clippy` of this crate. This means that naively attempting to `cargo test --no-run` this crate | ||
/// will produce binaries that fail at runtime because the stubs are inadequate for actually running | ||
/// the tests. | ||
/// | ||
/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies | ||
fn main() { | ||
let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap(); | ||
let ebpf_package = packages | ||
.into_iter() | ||
.find(|Package { name, .. }| name == "backend-ebpf") | ||
.unwrap(); | ||
|
||
let out_dir = env::var_os("OUT_DIR").unwrap(); | ||
let out_dir = PathBuf::from(out_dir); | ||
|
||
let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap(); | ||
let target = if endian == "big" { | ||
"bpfeb" | ||
} else if endian == "little" { | ||
"bpfel" | ||
} else { | ||
panic!("unsupported endian={:?}", endian) | ||
}; | ||
|
||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap(); | ||
|
||
let target = format!("{target}-unknown-none"); | ||
|
||
let Package { manifest_path, .. } = ebpf_package; | ||
let ebpf_dir = manifest_path.parent().unwrap(); | ||
|
||
// We have a build-dependency on `example-ebpf`, so cargo will automatically rebuild us | ||
// if `example-ebpf`'s *library* target or any of its dependencies change. Since we | ||
// depend on `example-ebpf`'s *binary* targets, that only gets us half of the way. This | ||
// stanza ensures cargo will rebuild us on changes to the binaries too, which gets us the | ||
// rest of the way. | ||
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str()); | ||
|
||
let mut cmd = Command::new("cargo"); | ||
cmd.args([ | ||
"build", | ||
"-Z", | ||
"build-std=core", | ||
"--bins", | ||
"--features", "prog-test", | ||
"--message-format=json", | ||
"--release", | ||
"--target", | ||
&target, | ||
]); | ||
|
||
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch); | ||
|
||
// Workaround to make sure that the rust-toolchain.toml is respected. | ||
for key in ["RUSTUP_TOOLCHAIN", "RUSTC"] { | ||
cmd.env_remove(key); | ||
} | ||
cmd.current_dir(ebpf_dir); | ||
|
||
// Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself. | ||
let ebpf_target_dir = out_dir.join("backend/ebpf"); | ||
cmd.arg("--target-dir").arg(&ebpf_target_dir); | ||
|
||
let mut child = cmd | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn() | ||
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}")); | ||
let Child { stdout, stderr, .. } = &mut child; | ||
|
||
// Trampoline stdout to cargo warnings. | ||
let stderr = stderr.take().unwrap(); | ||
let stderr = BufReader::new(stderr); | ||
let stderr = std::thread::spawn(move || { | ||
for line in stderr.lines() { | ||
let line = line.unwrap(); | ||
println!("cargo:warning={line}"); | ||
} | ||
}); | ||
|
||
let stdout = stdout.take().unwrap(); | ||
let stdout = BufReader::new(stdout); | ||
let mut executables = Vec::new(); | ||
for message in Message::parse_stream(stdout) { | ||
#[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, .. }) => { | ||
for line in message.rendered.unwrap_or_default().split('\n') { | ||
println!("cargo:warning={line}"); | ||
} | ||
} | ||
Message::TextLine(line) => { | ||
println!("cargo:warning={line}"); | ||
} | ||
_ => {} | ||
} | ||
} | ||
|
||
let status = child | ||
.wait() | ||
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}")); | ||
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}"); | ||
|
||
stderr.join().map_err(std::panic::resume_unwind).unwrap(); | ||
|
||
for (name, binary) in executables { | ||
let dst = out_dir.join(name); | ||
let _: u64 = fs::copy(&binary, &dst) | ||
.unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// SPDX-FileCopyrightText: 2025 Franz Schlicht <[email protected]> | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
use std::{io, mem, os::fd::{AsFd, AsRawFd}}; | ||
|
||
use aya::{maps::{HashMap, RingBuf}, programs::RawTracePoint, EbpfLoader}; | ||
use aya_obj::generated::{bpf_attr, bpf_cmd}; | ||
use backend_common::{SysSigquitCall, TryFromRaw}; | ||
use libc::{getpid, gettid, syscall, SYS_bpf}; | ||
|
||
|
||
#[test] | ||
fn prog_test_run_example() { | ||
let mut ebpf = EbpfLoader::default() | ||
.load(aya::include_bytes_aligned!(concat!( | ||
env!("OUT_DIR"), | ||
"/backend-ebpf" | ||
))) | ||
.unwrap(); | ||
|
||
let p: &mut RawTracePoint = ebpf.program_mut("sys_sigquit").unwrap().try_into().unwrap(); | ||
p.load().unwrap(); | ||
p.attach("sys_enter").unwrap(); | ||
|
||
let fd = p.fd().unwrap().as_fd().as_raw_fd(); | ||
|
||
let mut pids: HashMap<_, u32, u64> = ebpf.take_map("SYS_SIGQUIT_PIDS").unwrap().try_into().unwrap(); | ||
let old = pids.iter().filter_map(Result::ok).map(|x| x.0).collect::<Vec<_>>(); | ||
for old in old { | ||
pids.remove(&old).unwrap(); | ||
} | ||
// Pid of the program seems to always be the next pid | ||
pids.insert(unsafe { gettid() as u32 }, 0, 0).unwrap(); | ||
let mut events: RingBuf<_> = ebpf.take_map("SYS_SIGQUIT_EVENTS").unwrap().try_into().unwrap(); | ||
|
||
let target_pid = 1111; | ||
let signal = 3; // sigquit | ||
let args = [0u64, 0u64, target_pid, signal]; | ||
|
||
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; | ||
|
||
attr.test.prog_fd = fd as u32; | ||
attr.test.ctx_in = args.as_ptr() as u64; | ||
attr.test.ctx_size_in = args.len() as u32 * 8; | ||
|
||
let _ = { | ||
let ret = unsafe { syscall(SYS_bpf, bpf_cmd::BPF_PROG_TEST_RUN, &mut attr, size_of::<bpf_attr>()) }; | ||
|
||
match ret { | ||
0.. => Ok(ret), | ||
ret => Err((ret, io::Error::last_os_error())), | ||
} | ||
}.unwrap(); | ||
|
||
println!("{:?}", unsafe { attr.test }); | ||
|
||
let first = events.next().unwrap().to_vec(); | ||
|
||
for next in [first] { | ||
println!("{next:?}"); | ||
println!("{:?}", SysSigquitCall::try_from_raw(&*next)); | ||
println!("{} {}", unsafe { gettid() }, unsafe { getpid() }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters