Skip to content

Commit

Permalink
Test
Browse files Browse the repository at this point in the history
  • Loading branch information
d-e-s-o committed Oct 18, 2024
1 parent 5634fc6 commit f89fb18
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 105 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
rust: stable
profile: release
args: "--lib --no-default-features"
- runs-on: windows-latest
rust: stable
profile: dev
args: "--tests --features=blazesym-dev/dont-generate-unit-test-files"
- runs-on: macos-latest
rust: stable
profile: release
Expand Down
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,22 @@ anyhow = "1.0.71"
# TODO: Enable `zstd` feature once toolchain support for it is more
# widespread (enabled by default in `ld`). Remove conditionals in
# test code alongside.
blazesym = {path = ".", features = ["apk", "bpf", "breakpad", "gsym", "tracing"]}
blazesym = {path = ".", features = ["apk", "breakpad", "gsym", "tracing"]}
blazesym-dev = {path = "dev", features = ["generate-unit-test-files"]}
# TODO: Use 0.5.2 once released.
criterion = {git = "https://github.com/bheisler/criterion.rs.git", rev = "b913e232edd98780961ecfbae836ec77ede49259", default-features = false, features = ["rayon", "cargo_bench_support"]}
libbpf-rs = "0.24"
scopeguard = "1.2"
stats_alloc = {version = "0.1.1", features = ["nightly"]}
tempfile = "3.4"
test-log = {version = "0.2.14", default-features = false, features = ["trace"]}
test-tag = "0.1.3"

[target.'cfg(target_os = "linux")'.dev-dependencies]
# `bpf` shouldn't be enabled by default for non-Linux targets, because
# it simply can't work.
blazesym = {path = ".", features = ["bpf"]}
libbpf-rs = "0.24"

# A set of unused dependencies that we require to force correct minimum versions
# of transitive dependencies, for cases where our dependencies have incorrect
# dependency specifications themselves.
Expand Down
4 changes: 3 additions & 1 deletion dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ dont-generate-unit-test-files = []

[build-dependencies]
dump_syms = {version = "2.3", optional = true, default-features = false}
libbpf-sys = {version = "1.4.1", default-features = false, optional = true}
libc = "0.2.137"
reqwest = {version = "0.12.0", optional = true, features = ["blocking"]}
tempfile = {version = "3.3", optional = true}
vmlinux = {git = "https://github.com/libbpf/vmlinux.h.git", rev = "a9c092aa771310bf8b00b5018f7d40a1fdb6ec82"}
xz2 = {version = "0.1.7", optional = true}
zip = {version = "2.0.0", optional = true, default-features = false}
zstd = {version = "0.13.1", default-features = false, optional = true}

[target.'cfg(target_os = "linux")'.build-dependencies]
libbpf-sys = {version = "1.4.1", default-features = false, optional = true}
15 changes: 12 additions & 3 deletions dev/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,10 @@ fn prepare_test_files() {


/// Extract vendored libbpf header files into a directory.
#[cfg(feature = "generate-unit-test-files")]
#[cfg(all(
feature = "generate-unit-test-files",
not(feature = "dont-generate-unit-test-files")
))]
fn extract_libbpf_headers(target_dir: &Path) {
use std::fs;
use std::fs::OpenOptions;
Expand All @@ -616,7 +619,10 @@ fn extract_libbpf_headers(target_dir: &Path) {
}


#[cfg(feature = "generate-unit-test-files")]
#[cfg(all(
feature = "generate-unit-test-files",
not(feature = "dont-generate-unit-test-files")
))]
fn with_bpf_headers<F>(f: F)
where
F: FnOnce(&Path),
Expand All @@ -628,7 +634,10 @@ where
let () = f(header_parent_dir.path());
}

#[cfg(not(feature = "generate-unit-test-files"))]
#[cfg(any(
not(feature = "generate-unit-test-files"),
feature = "dont-generate-unit-test-files"
))]
fn with_bpf_headers<F>(_f: F)
where
F: FnOnce(&Path),
Expand Down
3 changes: 3 additions & 0 deletions src/kernel/bpf/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(not(target_os = "linux"))]
compile_error!("BPF support is only present on Linux, please disable `bpf` feature");

mod btf;
mod prog;
mod sys;
Expand Down
31 changes: 21 additions & 10 deletions src/kernel/ksym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,16 @@ impl Ksym {
fn as_kfunc(&self) -> Option<&Kfunc> {
match self {
Self::Kfunc(kfunc) => Some(kfunc),
#[cfg(feature = "bpf")]
_ => None,
}
}

#[cfg(test)]
#[cfg(all(test, feature = "bpf"))]
fn as_bpf_prog(&self) -> Option<&BpfProg> {
match self {
Self::BpfProg(bpf_prog) => Some(bpf_prog),
#[cfg(feature = "bpf")]
_ => None,
}
}
Expand Down Expand Up @@ -350,8 +352,6 @@ mod tests {

use crate::ErrorKind;

use super::super::bpf::BpfTag;


/// Check that our `Ksym` type has the expected size.
#[test]
Expand Down Expand Up @@ -400,13 +400,24 @@ ffffffffc212d000 t ftrace_trampoline [__builtin__ftrace]
assert_eq!(&*ksym.name, "fuse_dev_init");
assert_eq!(ksym.addr, 0xffffffffc0279010);

let prog = resolver.syms[1].as_bpf_prog().unwrap();
assert_eq!(prog.addr(), 0xffffffffc003e9c8);
assert_eq!(prog.name(), "kprobe__cap_capable");
assert_eq!(
prog.tag(),
BpfTag::from([0x30, 0x30, 0x4e, 0x82, 0xb4, 0x03, 0x3e, 0xa3])
);
#[cfg(feature = "bpf")]
{
use crate::kernel::bpf::BpfTag;

let prog = resolver.syms[1].as_bpf_prog().unwrap();
assert_eq!(prog.addr(), 0xffffffffc003e9c8);
assert_eq!(prog.name(), "kprobe__cap_capable");
assert_eq!(
prog.tag(),
BpfTag::from([0x30, 0x30, 0x4e, 0x82, 0xb4, 0x03, 0x3e, 0xa3])
);
}
#[cfg(not(feature = "bpf"))]
{
let ksym = resolver.syms[1].as_kfunc().unwrap();
assert_eq!(&*ksym.name, "bpf_prog_30304e82b4033ea3_kprobe__cap_capable");
assert_eq!(ksym.addr, 0xffffffffc003e9c8);
}
}

/// Check that we can use a `KSymResolver` to find symbols.
Expand Down
4 changes: 3 additions & 1 deletion src/symbolize/symbolizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,6 @@ mod tests {
use crate::symbolize;
use crate::symbolize::CodeInfo;
use crate::test_helper::find_the_answer_fn_in_zip;
use crate::test_helper::with_bpf_symbolization_target_addrs;


/// Exercise the `Debug` representation of various types.
Expand Down Expand Up @@ -1690,8 +1689,11 @@ mod tests {
}

/// Test symbolization of a kernel address inside a BPF program.
#[cfg(target_os = "linux")]
#[test]
fn symbolize_kernel_bpf_program() {
use crate::test_helper::with_bpf_symbolization_target_addrs;

with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let src = symbolize::Source::Kernel(symbolize::Kernel::default());
let symbolizer = Symbolizer::new();
Expand Down
188 changes: 100 additions & 88 deletions src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@ use std::mem::transmute;
use std::path::Path;
use std::path::PathBuf;

use libbpf_rs::Map;
use libbpf_rs::MapCore as _;
use libbpf_rs::MapMut;
use libbpf_rs::Object;
use libbpf_rs::ObjectBuilder;
use libbpf_rs::OpenObject;
use libbpf_rs::ProgramMut;
use libbpf_rs::RingBufferBuilder;

use crate::elf::ElfParser;
use crate::inspect;
use crate::util::ReadRaw as _;
use crate::zip;
use crate::Addr;
use crate::Mmap;
Expand Down Expand Up @@ -67,92 +57,114 @@ pub(crate) fn find_the_answer_fn_in_zip(mmap: &Mmap) -> (inspect::SymInfo<'stati
(sym, the_answer_addr)
}

fn test_object_path(object: &str) -> PathBuf {
Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("bpf")
.join(object)
}

#[track_caller]
fn open_test_object(object: &str) -> OpenObject {
let obj_path = test_object_path(object);
let obj = ObjectBuilder::default()
.open_file(obj_path)
.expect("failed to open object");
obj
}
#[cfg(target_os = "linux")]
mod bpf {
use super::*;

#[track_caller]
pub(crate) fn test_object(filename: &str) -> Object {
open_test_object(filename)
.load()
.expect("failed to load object")
}
use libbpf_rs::Map;
use libbpf_rs::MapCore as _;
use libbpf_rs::MapMut;
use libbpf_rs::Object;
use libbpf_rs::ObjectBuilder;
use libbpf_rs::OpenObject;
use libbpf_rs::ProgramMut;
use libbpf_rs::RingBufferBuilder;

/// Find the BPF map with the given name, panic if it does not exist.
#[track_caller]
fn map_mut<'obj>(object: &'obj mut Object, name: &str) -> MapMut<'obj> {
object
.maps_mut()
.find(|map| map.name() == name)
.unwrap_or_else(|| panic!("failed to find map `{name}`"))
}
use crate::util::ReadRaw as _;

/// Find the BPF program with the given name, panic if it does not exist.
#[track_caller]
pub(crate) fn prog_mut<'obj>(object: &'obj mut Object, name: &str) -> ProgramMut<'obj> {
object
.progs_mut()
.find(|map| map.name() == name)
.unwrap_or_else(|| panic!("failed to find program `{name}`"))
}

/// A helper function for instantiating a `RingBuffer` with a callback meant to
/// be invoked when `action` is executed and that is intended to trigger a write
/// to said `RingBuffer` from kernel space, which then reads a single `u32` from
/// this buffer from user space and returns it.
fn with_ringbuffer<F>(map: &Map, action: F) -> Vec<u8>
where
F: FnOnce(),
{
let mut value = None;
{
let callback = |data: &[u8]| {
value = Some(data.to_vec());
0
};
fn test_object_path(object: &str) -> PathBuf {
Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("bpf")
.join(object)
}

let mut builder = RingBufferBuilder::new();
builder.add(map, callback).expect("failed to add ringbuf");
let ringbuf = builder.build().expect("failed to build");
#[track_caller]
fn open_test_object(object: &str) -> OpenObject {
let obj_path = test_object_path(object);
let obj = ObjectBuilder::default()
.open_file(obj_path)
.expect("failed to open object");
obj
}

let () = action();
let () = ringbuf.consume().expect("failed to consume ringbuf");
#[track_caller]
pub(crate) fn test_object(filename: &str) -> Object {
open_test_object(filename)
.load()
.expect("failed to load object")
}

value.expect("did not receive RingBuffer callback")
}
/// Find the BPF map with the given name, panic if it does not exist.
#[track_caller]
fn map_mut<'obj>(object: &'obj mut Object, name: &str) -> MapMut<'obj> {
object
.maps_mut()
.find(|map| map.name() == name)
.unwrap_or_else(|| panic!("failed to find map `{name}`"))
}

/// Retrieve the address of the `symbolization_target` function in the
/// `getpid.bpf.o` BPF program.
pub(crate) fn with_bpf_symbolization_target_addrs<F>(f: F)
where
F: FnOnce(Addr, Addr),
{
let mut obj = test_object("getpid.bpf.o");
let prog = prog_mut(&mut obj, "handle__getpid");
let _link = prog
.attach_tracepoint("syscalls", "sys_enter_getpid")
.expect("failed to attach prog");

let map = map_mut(&mut obj, "ringbuf");
let action = || {
let _pid = unsafe { libc::getpid() };
};
let data = with_ringbuffer(&map, action);
let mut data = data.as_slice();
let handle_getpid = data.read_pod::<Addr>().unwrap();
let subprogram = data.read_pod::<Addr>().unwrap();
f(handle_getpid, subprogram)
/// Find the BPF program with the given name, panic if it does not exist.
#[track_caller]
pub(crate) fn prog_mut<'obj>(object: &'obj mut Object, name: &str) -> ProgramMut<'obj> {
object
.progs_mut()
.find(|map| map.name() == name)
.unwrap_or_else(|| panic!("failed to find program `{name}`"))
}

/// A helper function for instantiating a `RingBuffer` with a callback meant
/// to be invoked when `action` is executed and that is intended to
/// trigger a write to said `RingBuffer` from kernel space, which then
/// reads a single `u32` from this buffer from user space and returns
/// it.
fn with_ringbuffer<F>(map: &Map, action: F) -> Vec<u8>
where
F: FnOnce(),
{
let mut value = None;
{
let callback = |data: &[u8]| {
value = Some(data.to_vec());
0
};

let mut builder = RingBufferBuilder::new();
builder.add(map, callback).expect("failed to add ringbuf");
let ringbuf = builder.build().expect("failed to build");

let () = action();
let () = ringbuf.consume().expect("failed to consume ringbuf");
}

value.expect("did not receive RingBuffer callback")
}

/// Retrieve the address of the `symbolization_target` function in the
/// `getpid.bpf.o` BPF program.
pub(crate) fn with_bpf_symbolization_target_addrs<F>(f: F)
where
F: FnOnce(Addr, Addr),
{
let mut obj = test_object("getpid.bpf.o");
let prog = prog_mut(&mut obj, "handle__getpid");
let _link = prog
.attach_tracepoint("syscalls", "sys_enter_getpid")
.expect("failed to attach prog");

let map = map_mut(&mut obj, "ringbuf");
let action = || {
let _pid = unsafe { libc::getpid() };
};
let data = with_ringbuffer(&map, action);
let mut data = data.as_slice();
let handle_getpid = data.read_pod::<Addr>().unwrap();
let subprogram = data.read_pod::<Addr>().unwrap();
f(handle_getpid, subprogram)
}
}

#[cfg(target_os = "linux")]
pub(crate) use bpf::*;

0 comments on commit f89fb18

Please sign in to comment.