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 d7878ac commit c716234
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 93 deletions.
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,18 @@ zstd = ["dep:zstd"]
# Below here are dev-mostly features that should not be needed by
# regular users.

# Expose test-only helpers for convenient use in end-to-end tests from
# crate.
test = []
# Enable code paths requiring a nightly toolchain. This feature is only meant to
# be used for testing and benchmarking purposes, not for the core library, which
# is expected to work on stable.
nightly = []

[[test]]
name = "blazesym"
required-features = ["test"]

[[example]]
name = "addr2ln"

Expand All @@ -103,6 +110,7 @@ required-features = ["demangle", "blazesym-dev/generate-unit-test-files"]
[[bench]]
name = "main"
harness = false
required-features = ["test"]

[profile.release]
debug = true
Expand Down Expand Up @@ -135,7 +143,7 @@ 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", "breakpad", "gsym", "tracing"]}
blazesym = {path = ".", features = ["apk", "breakpad", "gsym", "tracing", "test"]}
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"]}
Expand Down
47 changes: 47 additions & 0 deletions benches/symbolize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use blazesym::symbolize::Input;
use blazesym::symbolize::Process;
use blazesym::symbolize::Source;
use blazesym::symbolize::Symbolizer;
#[cfg(target_os = "linux")]
use blazesym::test_helper::with_bpf_symbolization_target_addrs;
use blazesym::Addr;
use blazesym::Pid;

Expand Down Expand Up @@ -175,6 +177,47 @@ where
});
}

/// Benchmark the symbolization of BPF program kernel addresses.
#[cfg(target_os = "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(target_os = "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 +230,8 @@ where
bench_fn!(group, symbolize_dwarf);
bench_fn!(group, symbolize_gsym);
bench_sub_fn!(group, symbolize_gsym_multi_no_setup);
#[cfg(target_os = "linux")]
bench_sub_fn!(group, symbolize_kernel_bpf_uncached);
#[cfg(target_os = "linux")]
bench_sub_fn!(group, symbolize_kernel_bpf_cached);
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub mod normalize;
mod once;
mod pid;
pub mod symbolize;
#[cfg(test)]
#[cfg(any(test, feature = "test"))]
mod test_helper;
mod util;
#[cfg(feature = "apk")]
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(target_os = "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(target_os = "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(target_os = "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(target_os = "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(target_os = "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);
});
});
}
}
9 changes: 5 additions & 4 deletions src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ mod bpf {
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)
/// Retrieve the address of the `handle__getpid` and `subprogram`
/// functions in the `getpid.bpf.o` BPF program.
#[allow(dead_code)]
pub fn with_bpf_symbolization_target_addrs<F>(f: F)
where
F: FnOnce(Addr, Addr),
{
Expand All @@ -167,4 +168,4 @@ mod bpf {
}

#[cfg(target_os = "linux")]
pub(crate) use bpf::*;
pub use bpf::*;
39 changes: 39 additions & 0 deletions tests/blazesym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ use blazesym::symbolize::Reason;
use blazesym::symbolize::Resolve;
use blazesym::symbolize::Symbolized;
use blazesym::symbolize::Symbolizer;
#[cfg(target_os = "linux")]
use blazesym::test_helper::with_bpf_symbolization_target_addrs;
use blazesym::Addr;
use blazesym::ErrorKind;
use blazesym::Pid;
Expand Down Expand Up @@ -805,6 +807,43 @@ fn symbolize_process_with_custom_dispatch() {
test(process_no_dispatch);
}

/// Test symbolization of a kernel address inside a BPF program.
#[cfg(target_os = "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);
})
}

/// Check that we can normalize addresses in an ELF shared object.
#[cfg(target_os = "linux")]
#[test]
Expand Down

0 comments on commit c716234

Please sign in to comment.