Skip to content

Commit

Permalink
Make BPF symbolization tests end-to-end tests
Browse files Browse the repository at this point in the history
TODO: Does not work because we are attempting to expose dev-only stuff
      (libbpbf-rs) via the main crate. This may be possible to resolve
      by moving this functionality into blazesym-dev (but that, in turn,
      requires more stuff to be exposed from the main crate)

The BPF symbolization tests were added as unit tests, because they
require a bunch of setup shenanigans that we only had internally to the
crate. Conceptually, however, they are very much end-to-end tests that
test the public API surface and not just some implementation detail. We
*really* want to preserve the ability to get a good overview of all
end-to-end tests in a single location and so with this change we take
necessary steps to expose necessary test helpers so that they can be
used from end-to-end tests. Furthermore, we move said BPF symbolization
tests into tests/blazesym.rs, co-located with most other end-to-end
tests.

Signed-off-by: Daniel Müller <deso@posteo.net>
  • Loading branch information
d-e-s-o committed Oct 25, 2024
1 parent 033a2e1 commit eddda35
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 209 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ test-tag = "0.1.3"
# `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
Expand Down
48 changes: 48 additions & 0 deletions benches/symbolize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ use blazesym::symbolize::Symbolizer;
use blazesym::Addr;
use blazesym::Pid;

#[cfg(linux)]
use blazesym_dev::with_bpf_symbolization_target_addrs;

use criterion::measurement::Measurement;
use criterion::Bencher;
use criterion::BenchmarkGroup;
Expand Down Expand Up @@ -175,6 +178,47 @@ where
});
}

/// Benchmark the symbolization of BPF program kernel addresses.
#[cfg(linux)]
fn symbolize_kernel_bpf_uncached<M>(b: &mut Bencher<'_, M>) {
with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let () = b.iter(|| {
let src = symbolize::Source::Kernel(symbolize::Kernel::default());
let symbolizer = Symbolizer::new();

let result = symbolizer
.symbolize(
&src,
symbolize::Input::AbsAddr(&[handle_getpid, subprogram]),
)
.unwrap();

assert_eq!(result.len(), 2);
});
});
}

/// Benchmark the symbolization of BPF program kernel addresses when
/// relevant data is readily cached.
#[cfg(linux)]
fn symbolize_kernel_bpf_cached<M>(b: &mut Bencher<'_, M>) {
with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let src = symbolize::Source::Kernel(symbolize::Kernel::default());
let symbolizer = Symbolizer::new();

let () = b.iter(|| {
let result = symbolizer
.symbolize(
&src,
symbolize::Input::AbsAddr(&[handle_getpid, subprogram]),
)
.unwrap();

assert_eq!(result.len(), 2);
});
});
}


pub fn benchmark<M>(group: &mut BenchmarkGroup<'_, M>)
where
Expand All @@ -187,4 +231,8 @@ where
bench_fn!(group, symbolize_dwarf);
bench_fn!(group, symbolize_gsym);
bench_sub_fn!(group, symbolize_gsym_multi_no_setup);
#[cfg(linux)]
bench_sub_fn!(group, symbolize_kernel_bpf_uncached);
#[cfg(linux)]
bench_sub_fn!(group, symbolize_kernel_bpf_cached);
}
7 changes: 7 additions & 0 deletions dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,12 @@ 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}

[dependencies]
blazesym = {version = "=0.2.0-rc.2", path = "../"}
libc = "0.2.137"

[target.'cfg(target_os = "linux")'.dependencies]
libbpf-rs = "0.24"

[target.'cfg(any(target_os = "linux", target_os = "android"))'.build-dependencies]
libbpf-sys = {version = "1.4.1", default-features = false, optional = true}
6 changes: 6 additions & 0 deletions dev/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,12 @@ fn prepare_bench_files() {
}

fn main() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "linux" || target_os == "android" {
println!("cargo:rustc-cfg=linux");
}
println!("cargo:rustc-check-cfg=cfg(linux)");

if cfg!(feature = "generate-unit-test-files")
&& !cfg!(feature = "dont-generate-unit-test-files")
{
Expand Down
123 changes: 123 additions & 0 deletions dev/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,124 @@
#![allow(
clippy::clone_on_ref_ptr,
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::fn_to_numeric_cast,
clippy::let_and_return,
clippy::let_unit_value
)]
#![deny(unsafe_op_in_unsafe_fn)]


#[cfg(linux)]
mod bpf {
use std::path::Path;
use std::path::PathBuf;

use blazesym::Addr;
use blazesym::__private::ReadRaw as _;

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;


fn test_object_path(object: &str) -> PathBuf {
Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.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
}

#[track_caller]
pub fn test_object(filename: &str) -> Object {
open_test_object(filename)
.load()
.expect("failed to load object")
}

/// 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}`"))
}

/// Find the BPF program with the given name, panic if it does not exist.
#[track_caller]
pub 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 `handle__getpid` and `subprogram`
/// functions in the `getpid.bpf.o` BPF program.
pub 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(linux)]
pub use bpf::*;
4 changes: 2 additions & 2 deletions src/kernel/bpf/btf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ impl Btf {
mod tests {
use super::*;

use crate::test_helper::prog_mut;
use crate::test_helper::test_object;
use blazesym_dev::prog_mut;
use blazesym_dev::test_object;

use test_tag::tag;

Expand Down
6 changes: 3 additions & 3 deletions src/kernel/bpf/prog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,12 +420,12 @@ impl<'prog> TryFrom<&'prog BpfProg> for SymInfo<'prog> {
mod tests {
use super::*;

use blazesym_dev::prog_mut;
use blazesym_dev::test_object;

use test_log::test;
use test_tag::tag;

use crate::test_helper::prog_mut;
use crate::test_helper::test_object;


/// Test that we can parse a BPF program string as it may appear in
/// `kallsyms` successfully.
Expand Down
4 changes: 2 additions & 2 deletions src/kernel/bpf/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ mod tests {
use std::ffi::CStr;
use std::os::fd::AsRawFd as _;

use crate::test_helper::prog_mut;
use crate::test_helper::test_object;
use blazesym_dev::prog_mut;
use blazesym_dev::test_object;

use tempfile::tempfile;

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub mod helper {
pub mod __private {
pub use crate::util::bytes_to_path;
pub use crate::util::stat;
pub use crate::util::ReadRaw;
}


Expand Down
87 changes: 0 additions & 87 deletions src/symbolize/symbolizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1375,17 +1375,12 @@ impl Default for Symbolizer {
mod tests {
use super::*;

#[cfg(all(linux, feature = "nightly"))]
use test::Bencher;

use test_log::test;

use crate::maps::Perm;
use crate::symbolize;
use crate::symbolize::CodeInfo;
use crate::test_helper::find_the_answer_fn_in_zip;
#[cfg(linux)]
use crate::test_helper::with_bpf_symbolization_target_addrs;


/// Exercise the `Debug` representation of various types.
Expand Down Expand Up @@ -1692,86 +1687,4 @@ mod tests {
let () = test(zip_error_dispatch);
let () = test(zip_delayed_error_dispatch);
}

/// Test symbolization of a kernel address inside a BPF program.
#[cfg(linux)]
#[test]
fn symbolize_kernel_bpf_program() {
with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let src = symbolize::Source::Kernel(symbolize::Kernel::default());
let symbolizer = Symbolizer::new();
let result = symbolizer
.symbolize(
&src,
symbolize::Input::AbsAddr(&[handle_getpid, subprogram]),
)
.unwrap();
let handle_getpid_sym = result[0].as_sym().unwrap();
assert_eq!(handle_getpid_sym.name, "handle__getpid");
let code_info = handle_getpid_sym.code_info.as_ref().unwrap();
assert_eq!(code_info.dir, None);
assert_eq!(
Path::new(&code_info.file).file_name(),
Some(OsStr::new("getpid.bpf.c"))
);
assert_eq!(code_info.line, Some(33));
assert_ne!(code_info.column, None);

let subprogram_sym = result[1].as_sym().unwrap();
assert_eq!(subprogram_sym.name, "subprogram");
let code_info = subprogram_sym.code_info.as_ref().unwrap();
assert_eq!(code_info.dir, None);
assert_eq!(
Path::new(&code_info.file).file_name(),
Some(OsStr::new("getpid.bpf.c"))
);
assert_eq!(code_info.line, Some(15));
assert_ne!(code_info.column, None);
})
}

/// Benchmark the symbolization of BPF program kernel addresses.
#[cfg(linux)]
#[cfg(feature = "nightly")]
#[bench]
fn bench_symbolize_kernel_bpf_uncached(b: &mut Bencher) {
with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let () = b.iter(|| {
let src = symbolize::Source::Kernel(symbolize::Kernel::default());
let symbolizer = Symbolizer::new();

let result = symbolizer
.symbolize(
&src,
symbolize::Input::AbsAddr(&[handle_getpid, subprogram]),
)
.unwrap();

assert_eq!(result.len(), 2);
});
});
}

/// Benchmark the symbolization of BPF program kernel addresses when
/// relevant data is readily cached.
#[cfg(linux)]
#[cfg(feature = "nightly")]
#[bench]
fn bench_symbolize_kernel_bpf_cached(b: &mut Bencher) {
with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let src = symbolize::Source::Kernel(symbolize::Kernel::default());
let symbolizer = Symbolizer::new();

let () = b.iter(|| {
let result = symbolizer
.symbolize(
&src,
symbolize::Input::AbsAddr(&[handle_getpid, subprogram]),
)
.unwrap();

assert_eq!(result.len(), 2);
});
});
}
}
Loading

0 comments on commit eddda35

Please sign in to comment.