diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b76aa609..116df6de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,13 +134,16 @@ jobs: with: tool: cargo-codspeed + - name: Install libbpf build deps + run: sudo apt-get update && sudo apt-get install -y build-essential pkgconf zlib1g-dev libbpf-dev autopoint bison flex + - name: Build benchmarks - run: cargo codspeed build -p runner-shared + run: cargo codspeed build -p runner-shared -p memtrack - name: Run benchmarks uses: CodSpeedHQ/action@3194d9a39c4d46684cb44bf7207fc56626aad8fd # v4 with: mode: simulation - run: cargo codspeed run -p runner-shared + run: cargo codspeed run -p runner-shared -p memtrack check: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index daf2000e..151bb010 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2378,6 +2378,7 @@ dependencies = [ "anyhow", "bindgen", "clap", + "codspeed-divan-compat", "env_logger", "glob", "insta", @@ -2389,6 +2390,7 @@ dependencies = [ "log", "object", "paste", + "rayon", "rstest", "runner-shared", "serde", diff --git a/crates/memtrack/Cargo.toml b/crates/memtrack/Cargo.toml index ae481941..6757c07e 100644 --- a/crates/memtrack/Cargo.toml +++ b/crates/memtrack/Cargo.toml @@ -34,6 +34,7 @@ paste = "1.0.15" libbpf-rs = { version = "0.26", features = ["vendored"], optional = true } glob = "0.3.3" object = { workspace = true } +rayon = "1.12" [build-dependencies] libbpf-cargo = { version = "0.26", optional = true } @@ -46,6 +47,11 @@ rstest = { workspace = true } test-log = { workspace = true } insta = { workspace = true } test-with = { workspace = true } +divan = { package = "codspeed-divan-compat", version = "4" } + +[[bench]] +name = "attach" +harness = false [package.metadata.dist] targets = ["aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"] diff --git a/crates/memtrack/benches/attach.rs b/crates/memtrack/benches/attach.rs new file mode 100644 index 00000000..c4cde0ce --- /dev/null +++ b/crates/memtrack/benches/attach.rs @@ -0,0 +1,36 @@ +use memtrack::{AllocatorLib, resolve_symbol_offsets}; +use rayon::prelude::*; + +fn main() { + divan::main(); +} + +fn discovered_allocators() -> Vec { + let libs = AllocatorLib::find_all().expect("failed to discover allocators"); + assert!(!libs.is_empty(), "no allocators discovered"); + libs +} + +/// Resolve symbol offsets for every discovered allocator one library at a time. +#[divan::bench] +fn resolve_offsets_serial(bencher: divan::Bencher) { + let libs = discovered_allocators(); + + bencher.bench_local(|| { + libs.iter() + .map(|lib| resolve_symbol_offsets(&lib.path).expect("failed to resolve offsets")) + .collect::>() + }); +} + +/// Resolve symbol offsets for every discovered allocator in parallel. +#[divan::bench] +fn resolve_offsets_parallel(bencher: divan::Bencher) { + let libs = discovered_allocators(); + + bencher.bench_local(|| { + libs.par_iter() + .map(|lib| resolve_symbol_offsets(&lib.path).expect("failed to resolve offsets")) + .collect::>() + }); +} diff --git a/crates/memtrack/src/ebpf/memtrack.rs b/crates/memtrack/src/ebpf/memtrack.rs index 3a1bfa26..d7787b99 100644 --- a/crates/memtrack/src/ebpf/memtrack.rs +++ b/crates/memtrack/src/ebpf/memtrack.rs @@ -4,10 +4,12 @@ use libbpf_rs::skel::OpenSkel; use libbpf_rs::skel::SkelBuilder; use libbpf_rs::{MapCore, UprobeOpts}; use paste::paste; +use std::collections::HashMap; +use std::collections::HashSet; use std::mem::MaybeUninit; use std::path::Path; -use crate::allocators::AllocatorKind; +use crate::allocators::{AllocatorKind, AllocatorLib}; use crate::ebpf::poller::RingBufferPoller; pub mod memtrack_skel { @@ -15,15 +17,15 @@ pub mod memtrack_skel { } pub use memtrack_skel::*; -/// Resolve symbol offset from .symtab to ensure that libbpf can find it. Otherwise -/// it will print a warning at runtime. -fn ensure_symbol_exists(lib_path: &Path, symbol_name: &str) -> Result<()> { +/// Resolve libbpf attach targets for every defined symbol in `lib_path`. +pub fn resolve_symbol_offsets(lib_path: &Path) -> Result { use object::{Object, ObjectSymbol}; let data = std::fs::read(lib_path)?; let file = object::File::parse(&*data)?; + let mut offsets = HashMap::new(); + let mut unresolved = HashSet::new(); - // Check both regular and dynamic symbols for symbol in file.symbols().chain(file.dynamic_symbols()) { if !symbol.is_definition() { continue; @@ -33,28 +35,65 @@ fn ensure_symbol_exists(lib_path: &Path, symbol_name: &str) -> Result<()> { continue; }; - if name == symbol_name { - let addr = symbol.address(); - if addr != 0 { - return Ok(()); + match symbol_file_offset(&file, &symbol) { + Some(file_offset) => { + offsets.insert(name.to_owned(), file_offset); + } + None => { + unresolved.insert(name.to_owned()); } } } - bail!("Symbol {symbol_name} not found in {}", lib_path.display()) + unresolved.retain(|name| !offsets.contains_key(name)); + + Ok(ResolvedSymbols { + offsets, + unresolved, + }) } -/// Macro to attach a function with both entry and return probes. -/// Also generates a `try_attach_*` variant that logs errors instead of returning them. -/// -/// Uses offset-based attachment by resolving symbols from .symtab. -/// Fails if the symbol is not found. +/// The libbpf file offset for `symbol`, or `None` when it has no address in a +/// file-backed section (absolute, `SHT_NOBITS`, ...). +fn symbol_file_offset<'a>( + file: &object::File, + symbol: &impl object::ObjectSymbol<'a>, +) -> Option { + use object::{Object, ObjectSection}; + + let address = symbol.address(); + if address == 0 { + return None; + } + + let section = file.section_by_index(symbol.section_index()?).ok()?; + let (sh_offset, _) = section.file_range()?; + Some((address - section.address() + sh_offset) as usize) +} + +/// Attach targets resolved from a library's symbol tables. +pub struct ResolvedSymbols { + offsets: HashMap, + unresolved: HashSet, +} + +impl ResolvedSymbols { + fn offset(&self, symbol: &str) -> Option { + self.offsets.get(symbol).copied() + } + + fn is_defined_without_offset(&self, symbol: &str) -> bool { + self.unresolved.contains(symbol) + } +} + +/// Macro to attach a function with both entry and return probes at a resolved +/// file offset. Also generates a `try_attach_*` variant that looks the symbol up +/// in the resolved offset table, skips it if absent, and logs errors instead of +/// returning them. macro_rules! attach_uprobe_uretprobe { ($name:ident, $prog_entry:ident, $prog_return:ident) => { - fn $name(&mut self, lib_path: &Path, symbol: &str) -> Result<()> { - ensure_symbol_exists(lib_path, symbol)?; - - // Attach entry probe at function entry via func_name + fn $name(&mut self, lib_path: &Path, offset: usize) -> Result<()> { let link = self .skel .progs @@ -62,21 +101,19 @@ macro_rules! attach_uprobe_uretprobe { .attach_uprobe_with_opts( -1, lib_path, - 0, + offset, UprobeOpts { retprobe: false, - func_name: Some(symbol.to_owned()), ..Default::default() }, ) .context(format!( - "Failed to attach {} uprobe in {}", - symbol, + "Failed to attach uprobe at offset {:#x} in {}", + offset, lib_path.display() ))?; self.probes.push(link); - // Attach return probe at function entry via func_name let link = self .skel .progs @@ -84,16 +121,15 @@ macro_rules! attach_uprobe_uretprobe { .attach_uprobe_with_opts( -1, lib_path, - 0, + offset, UprobeOpts { retprobe: true, - func_name: Some(symbol.to_owned()), ..Default::default() }, ) .context(format!( - "Failed to attach {} uretprobe in {}", - symbol, + "Failed to attach uretprobe at offset {:#x} in {}", + offset, lib_path.display() ))?; self.probes.push(link); @@ -102,24 +138,78 @@ macro_rules! attach_uprobe_uretprobe { } paste! { - fn [](&mut self, lib_path: &Path, symbol: &str) { - let result = self.$name(lib_path, symbol); + fn [<$name _by_name>](&mut self, lib_path: &Path, symbol: &str) -> Result<()> { + let link = self + .skel + .progs + .$prog_entry + .attach_uprobe_with_opts( + -1, + lib_path, + 0, + UprobeOpts { + retprobe: false, + func_name: Some(symbol.to_owned()), + ..Default::default() + }, + ) + .context(format!( + "Failed to attach uprobe to {} in {}", + symbol, + lib_path.display() + ))?; + self.probes.push(link); + + let link = self + .skel + .progs + .$prog_return + .attach_uprobe_with_opts( + -1, + lib_path, + 0, + UprobeOpts { + retprobe: true, + func_name: Some(symbol.to_owned()), + ..Default::default() + }, + ) + .context(format!( + "Failed to attach uretprobe to {} in {}", + symbol, + lib_path.display() + ))?; + self.probes.push(link); + + Ok(()) + } + + fn []( + &mut self, + lib_path: &Path, + symbol: &str, + symbols: &ResolvedSymbols, + ) { + let result = if let Some(offset) = symbols.offset(symbol) { + self.$name(lib_path, offset) + } else if symbols.is_defined_without_offset(symbol) { + self.[<$name _by_name>](lib_path, symbol) + } else { + return; + }; log::trace!("{} uprobe attach result: {:?}", symbol, result); } } }; } -/// Macro to attach a function with only an entry probe (no return probe). -/// Also generates a `try_attach_*` variant that logs errors instead of returning them. -/// -/// Uses offset-based attachment by resolving symbols from .symtab. -/// Fails if the symbol is not found. +/// Macro to attach a function with only an entry probe (no return probe) at a +/// resolved file offset. Also generates a `try_attach_*` variant that looks the +/// symbol up in the resolved offset table, skips it if absent, and logs errors +/// instead of returning them. macro_rules! attach_uprobe { ($name:ident, $prog:ident) => { - fn $name(&mut self, lib_path: &Path, symbol: &str) -> Result<()> { - ensure_symbol_exists(lib_path, symbol)?; - + fn $name(&mut self, lib_path: &Path, offset: usize) -> Result<()> { let link = self .skel .progs @@ -127,16 +217,15 @@ macro_rules! attach_uprobe { .attach_uprobe_with_opts( -1, lib_path, - 0, + offset, UprobeOpts { retprobe: false, - func_name: Some(symbol.to_owned()), ..Default::default() }, ) .context(format!( - "Failed to attach {} uprobe in {}", - symbol, + "Failed to attach uprobe at offset {:#x} in {}", + offset, lib_path.display() ))?; self.probes.push(link); @@ -144,8 +233,43 @@ macro_rules! attach_uprobe { } paste! { - fn [](&mut self, lib_path: &Path, symbol: &str) { - let result = self.$name(lib_path, symbol); + fn [<$name _by_name>](&mut self, lib_path: &Path, symbol: &str) -> Result<()> { + let link = self + .skel + .progs + .$prog + .attach_uprobe_with_opts( + -1, + lib_path, + 0, + UprobeOpts { + retprobe: false, + func_name: Some(symbol.to_owned()), + ..Default::default() + }, + ) + .context(format!( + "Failed to attach uprobe to {} in {}", + symbol, + lib_path.display() + ))?; + self.probes.push(link); + Ok(()) + } + + fn []( + &mut self, + lib_path: &Path, + symbol: &str, + symbols: &ResolvedSymbols, + ) { + let result = if let Some(offset) = symbols.offset(symbol) { + self.$name(lib_path, offset) + } else if symbols.is_defined_without_offset(symbol) { + self.[<$name _by_name>](lib_path, symbol) + } else { + return; + }; log::trace!("{} uprobe attach result: {:?}", symbol, result); } } @@ -280,10 +404,36 @@ impl MemtrackBpf { // Attach methods grouped by allocator // ========================================================================= + /// Attach probes for every discovered allocator library. + pub fn attach_allocators(&mut self, libs: &[AllocatorLib]) -> Result<()> { + use rayon::prelude::*; + + let resolved = libs + .par_iter() + .map(|lib| resolve_symbol_offsets(&lib.path).map(|offsets| (lib, offsets))) + .collect::>>()?; + + for (lib, offsets) in resolved { + self.attach_allocator_probes_with_offsets(lib.kind, &lib.path, &offsets)?; + } + + Ok(()) + } + /// Attach probes for a specific allocator kind. /// This attaches both standard probes (if the allocator exports them) and /// allocator-specific prefixed probes. pub fn attach_allocator_probes(&mut self, kind: AllocatorKind, lib_path: &Path) -> Result<()> { + let offsets = resolve_symbol_offsets(lib_path)?; + self.attach_allocator_probes_with_offsets(kind, lib_path, &offsets) + } + + fn attach_allocator_probes_with_offsets( + &mut self, + kind: AllocatorKind, + lib_path: &Path, + offsets: &ResolvedSymbols, + ) -> Result<()> { debug!( "Attaching {} probes to: {}", kind.name(), @@ -293,29 +443,29 @@ impl MemtrackBpf { match kind { AllocatorKind::Libc => { // Libc only has standard probes, and they must succeed - self.attach_libc_probes(lib_path) + self.attach_libc_probes(lib_path, offsets) } AllocatorKind::LibCpp => { // libc++ exports C++ operator new/delete symbols - self.attach_libcpp_probes(lib_path) + self.attach_libcpp_probes(lib_path, offsets) } AllocatorKind::Jemalloc => { // Jemalloc exposes libc/libcpp compatible allocator functions: - let _ = self.attach_libc_probes(lib_path); - let _ = self.attach_libcpp_probes(lib_path); - self.attach_jemalloc_probes(lib_path) + let _ = self.attach_libc_probes(lib_path, offsets); + let _ = self.attach_libcpp_probes(lib_path, offsets); + self.attach_jemalloc_probes(lib_path, offsets) } AllocatorKind::Mimalloc => { // Mimalloc exposes libc/libcpp compatible allocator functions: - let _ = self.attach_libc_probes(lib_path); - let _ = self.attach_libcpp_probes(lib_path); - self.attach_mimalloc_probes(lib_path) + let _ = self.attach_libc_probes(lib_path, offsets); + let _ = self.attach_libcpp_probes(lib_path, offsets); + self.attach_mimalloc_probes(lib_path, offsets) } AllocatorKind::Tcmalloc => { // Tcmalloc exposes libc/libcpp compatible allocator functions: - let _ = self.attach_libc_probes(lib_path); - let _ = self.attach_libcpp_probes(lib_path); - self.attach_tcmalloc_probes(lib_path) + let _ = self.attach_libc_probes(lib_path, offsets); + let _ = self.attach_libcpp_probes(lib_path, offsets); + self.attach_tcmalloc_probes(lib_path, offsets) } } } @@ -325,6 +475,7 @@ impl MemtrackBpf { lib_path: &Path, prefixes: &[&str], suffixes: &[&str], + offsets: &ResolvedSymbols, ) -> Result<()> { // Always include "" to capture the basic case let prefixes_with_base: Vec<&str> = std::iter::once("") @@ -339,18 +490,30 @@ impl MemtrackBpf { for prefix in &prefixes_with_base { for suffix in &suffixes_with_base { - self.try_attach_malloc(lib_path, &format!("{prefix}malloc{suffix}")); - self.try_attach_malloc(lib_path, &format!("{prefix}valloc{suffix}")); - self.try_attach_malloc(lib_path, &format!("{prefix}pvalloc{suffix}")); - self.try_attach_calloc(lib_path, &format!("{prefix}calloc{suffix}")); - self.try_attach_realloc(lib_path, &format!("{prefix}realloc{suffix}")); - self.try_attach_aligned_alloc(lib_path, &format!("{prefix}aligned_alloc{suffix}")); - self.try_attach_memalign(lib_path, &format!("{prefix}memalign{suffix}")); - self.try_attach_memalign(lib_path, &format!("{prefix}posix_memalign{suffix}")); - self.try_attach_free(lib_path, &format!("{prefix}free{suffix}")); - self.try_attach_free(lib_path, &format!("{prefix}free_sized{suffix}")); - self.try_attach_free(lib_path, &format!("{prefix}free_aligned_sized{suffix}")); - self.try_attach_free(lib_path, &format!("{prefix}cfree{suffix}")); + self.try_attach_malloc(lib_path, &format!("{prefix}malloc{suffix}"), offsets); + self.try_attach_malloc(lib_path, &format!("{prefix}valloc{suffix}"), offsets); + self.try_attach_malloc(lib_path, &format!("{prefix}pvalloc{suffix}"), offsets); + self.try_attach_calloc(lib_path, &format!("{prefix}calloc{suffix}"), offsets); + self.try_attach_realloc(lib_path, &format!("{prefix}realloc{suffix}"), offsets); + self.try_attach_aligned_alloc( + lib_path, + &format!("{prefix}aligned_alloc{suffix}"), + offsets, + ); + self.try_attach_memalign(lib_path, &format!("{prefix}memalign{suffix}"), offsets); + self.try_attach_memalign( + lib_path, + &format!("{prefix}posix_memalign{suffix}"), + offsets, + ); + self.try_attach_free(lib_path, &format!("{prefix}free{suffix}"), offsets); + self.try_attach_free(lib_path, &format!("{prefix}free_sized{suffix}"), offsets); + self.try_attach_free( + lib_path, + &format!("{prefix}free_aligned_sized{suffix}"), + offsets, + ); + self.try_attach_free(lib_path, &format!("{prefix}cfree{suffix}"), offsets); } } @@ -360,32 +523,32 @@ impl MemtrackBpf { /// Attach standard library allocation probes (libc-style: malloc, free, calloc, etc.) /// This works for libc and allocators that export standard symbol names. /// For non-libc allocators, standard names are optional - just try them silently. - fn attach_libc_probes(&mut self, lib_path: &Path) -> Result<()> { - self.attach_standard_probes(lib_path, &[], &[]) + fn attach_libc_probes(&mut self, lib_path: &Path, offsets: &ResolvedSymbols) -> Result<()> { + self.attach_standard_probes(lib_path, &[], &[], offsets) } /// Attach C++ operator new/delete probes. /// These are mangled C++ symbols that wrap the underlying allocator. /// C++ operators have identical signatures to malloc/free, so we reuse those handlers. - fn attach_libcpp_probes(&mut self, lib_path: &Path) -> Result<()> { - self.try_attach_malloc(lib_path, "_Znwm"); // operator new(size_t) - self.try_attach_malloc(lib_path, "_Znam"); // operator new[](size_t) - self.try_attach_malloc(lib_path, "_ZnwmSt11align_val_t"); // operator new(size_t, std::align_val_t) - self.try_attach_malloc(lib_path, "_ZnamSt11align_val_t"); // operator new[](size_t, std::align_val_t) - self.try_attach_free(lib_path, "_ZdlPv"); // operator delete(void*) - self.try_attach_free(lib_path, "_ZdaPv"); // operator delete[](void*) - self.try_attach_free(lib_path, "_ZdlPvm"); // operator delete(void*, size_t) - C++14 sized delete - self.try_attach_free(lib_path, "_ZdaPvm"); // operator delete[](void*, size_t) - C++14 sized delete - self.try_attach_free(lib_path, "_ZdlPvSt11align_val_t"); // operator delete(void*, std::align_val_t) - self.try_attach_free(lib_path, "_ZdaPvSt11align_val_t"); // operator delete[](void*, std::align_val_t) - self.try_attach_free(lib_path, "_ZdlPvmSt11align_val_t"); // operator delete(void*, size_t, std::align_val_t) - self.try_attach_free(lib_path, "_ZdaPvmSt11align_val_t"); // operator delete[](void*, size_t, std::align_val_t) + fn attach_libcpp_probes(&mut self, lib_path: &Path, offsets: &ResolvedSymbols) -> Result<()> { + self.try_attach_malloc(lib_path, "_Znwm", offsets); // operator new(size_t) + self.try_attach_malloc(lib_path, "_Znam", offsets); // operator new[](size_t) + self.try_attach_malloc(lib_path, "_ZnwmSt11align_val_t", offsets); // operator new(size_t, std::align_val_t) + self.try_attach_malloc(lib_path, "_ZnamSt11align_val_t", offsets); // operator new[](size_t, std::align_val_t) + self.try_attach_free(lib_path, "_ZdlPv", offsets); // operator delete(void*) + self.try_attach_free(lib_path, "_ZdaPv", offsets); // operator delete[](void*) + self.try_attach_free(lib_path, "_ZdlPvm", offsets); // operator delete(void*, size_t) - C++14 sized delete + self.try_attach_free(lib_path, "_ZdaPvm", offsets); // operator delete[](void*, size_t) - C++14 sized delete + self.try_attach_free(lib_path, "_ZdlPvSt11align_val_t", offsets); // operator delete(void*, std::align_val_t) + self.try_attach_free(lib_path, "_ZdaPvSt11align_val_t", offsets); // operator delete[](void*, std::align_val_t) + self.try_attach_free(lib_path, "_ZdlPvmSt11align_val_t", offsets); // operator delete(void*, size_t, std::align_val_t) + self.try_attach_free(lib_path, "_ZdaPvmSt11align_val_t", offsets); // operator delete[](void*, size_t, std::align_val_t) Ok(()) } /// Attach jemalloc-specific probes (prefixed and extended API). - fn attach_jemalloc_probes(&mut self, lib_path: &Path) -> Result<()> { + fn attach_jemalloc_probes(&mut self, lib_path: &Path, offsets: &ResolvedSymbols) -> Result<()> { // The following functions are used in Rust when setting a global allocator: // - rust_alloc: _rjem_malloc and _rjem_mallocx // - rust_alloc_zeroed: _rjem_mallocx / _rjem_calloc @@ -397,16 +560,16 @@ impl MemtrackBpf { let prefixes = ["je_", "_rjem_"]; let suffixes = ["", "_default"]; - self.attach_standard_probes(lib_path, &prefixes, &suffixes)?; + self.attach_standard_probes(lib_path, &prefixes, &suffixes, offsets)?; // Non-standard API that has an additional flag parameter // See: https://jemalloc.net/jemalloc.3.html for prefix in prefixes { for suffix in suffixes { - self.try_attach_malloc(lib_path, &format!("{prefix}mallocx{suffix}")); - self.try_attach_realloc(lib_path, &format!("{prefix}rallocx{suffix}")); - self.try_attach_free(lib_path, &format!("{prefix}dallocx{suffix}")); - self.try_attach_free(lib_path, &format!("{prefix}sdallocx{suffix}")); + self.try_attach_malloc(lib_path, &format!("{prefix}mallocx{suffix}"), offsets); + self.try_attach_realloc(lib_path, &format!("{prefix}rallocx{suffix}"), offsets); + self.try_attach_free(lib_path, &format!("{prefix}dallocx{suffix}"), offsets); + self.try_attach_free(lib_path, &format!("{prefix}sdallocx{suffix}"), offsets); } } @@ -414,20 +577,20 @@ impl MemtrackBpf { } /// Attach mimalloc-specific probes (mi_* API). - fn attach_mimalloc_probes(&mut self, lib_path: &Path) -> Result<()> { + fn attach_mimalloc_probes(&mut self, lib_path: &Path, offsets: &ResolvedSymbols) -> Result<()> { // The following functions are used in Rust when setting a global allocator: // - mi_malloc_aligned // - mi_free // - mi_realloc_aligned // - mi_zalloc_aligned - self.attach_standard_probes(lib_path, &["mi_"], &[])?; + self.attach_standard_probes(lib_path, &["mi_"], &[], offsets)?; // Zero-initialized and aligned variants - self.try_attach_malloc(lib_path, "mi_malloc_aligned"); - self.try_attach_calloc(lib_path, "mi_zalloc"); - self.try_attach_calloc(lib_path, "mi_zalloc_aligned"); - self.try_attach_realloc(lib_path, "mi_realloc_aligned"); + self.try_attach_malloc(lib_path, "mi_malloc_aligned", offsets); + self.try_attach_calloc(lib_path, "mi_zalloc", offsets); + self.try_attach_calloc(lib_path, "mi_zalloc_aligned", offsets); + self.try_attach_realloc(lib_path, "mi_realloc_aligned", offsets); Ok(()) } @@ -437,12 +600,12 @@ impl MemtrackBpf { /// See: /// - https://github.com/google/tcmalloc/blob/master/docs/reference.md /// - https://github.com/gperftools/gperftools/blob/a47243150ec41097602730ff8779fafcc172d1fb/src/tcmalloc.cc#L178-L190 - fn attach_tcmalloc_probes(&mut self, lib_path: &Path) -> Result<()> { - self.attach_standard_probes(lib_path, &["tc_"], &[])?; + fn attach_tcmalloc_probes(&mut self, lib_path: &Path, offsets: &ResolvedSymbols) -> Result<()> { + self.attach_standard_probes(lib_path, &["tc_"], &[], offsets)?; - self.try_attach_free(lib_path, "free_sized"); - self.try_attach_free(lib_path, "free_aligned_sized"); - self.try_attach_free(lib_path, "sdallocx"); + self.try_attach_free(lib_path, "free_sized", offsets); + self.try_attach_free(lib_path, "free_aligned_sized", offsets); + self.try_attach_free(lib_path, "sdallocx", offsets); Ok(()) } diff --git a/crates/memtrack/src/ebpf/mod.rs b/crates/memtrack/src/ebpf/mod.rs index 0badf542..cb4716be 100644 --- a/crates/memtrack/src/ebpf/mod.rs +++ b/crates/memtrack/src/ebpf/mod.rs @@ -3,5 +3,5 @@ mod memtrack; mod poller; mod tracker; -pub use memtrack::MemtrackBpf; +pub use memtrack::{MemtrackBpf, ResolvedSymbols, resolve_symbol_offsets}; pub use tracker::Tracker; diff --git a/crates/memtrack/src/ebpf/tracker.rs b/crates/memtrack/src/ebpf/tracker.rs index 9f1af587..8205b561 100644 --- a/crates/memtrack/src/ebpf/tracker.rs +++ b/crates/memtrack/src/ebpf/tracker.rs @@ -36,12 +36,7 @@ impl Tracker { } pub fn attach_allocators(&mut self, libs: &[AllocatorLib]) -> Result<()> { - for allocator in libs { - self.bpf - .attach_allocator_probes(allocator.kind, &allocator.path)?; - } - - Ok(()) + self.bpf.attach_allocators(libs) } pub fn attach_allocator(&mut self, lib: &AllocatorLib) -> Result<()> {