Skip to content

Commit

Permalink
capi: Add support for pre-caching process VMA metadata
Browse files Browse the repository at this point in the history
Hook up the support for pre-caching process VMA metadata to the C API
bindings.

Signed-off-by: Daniel Müller <deso@posteo.net>
  • Loading branch information
d-e-s-o committed Feb 10, 2025
1 parent 68951d2 commit 264d39c
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 0 deletions.
1 change: 1 addition & 0 deletions capi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Unreleased
`vmlinux`
- Added support for disabling `kallsyms` and `vmlinux` to
`blaze_symbolize_src_kernel`
- Added `blaze_symbolize_cache_process` for caching process VMA metadata


0.1.0-rc.2
Expand Down
63 changes: 63 additions & 0 deletions capi/include/blazesym.h
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,49 @@ typedef struct blaze_symbolizer_opts {
uint8_t reserved[4];
} blaze_symbolizer_opts;

/**
* Configuration for caching of process-level data.
*/
typedef struct blaze_cache_src_process {
/**
* The size of this object's type.
*
* Make sure to initialize it to `sizeof(<type>)`. This member is used to
* ensure compatibility in the presence of member additions.
*/
size_t type_size;
/**
* The referenced process' ID.
*/
uint32_t pid;
/**
* Whether to cache the process' VMAs for later use.
*
* Caching VMAs can be useful, because it conceptually enables the
* library to serve a symbolization request targeting a process
* even if said process has since exited the system.
*
* Note that once VMAs have been cached this way, the library will
* refrain from re-reading updated VMAs unless instructed to.
* Hence, if you have reason to believe that a process may have
* changed its memory regions (by loading a new shared object, for
* example), you would have to make another request to cache them
* yourself.
*
* Note furthermore that if you cache VMAs to later symbolize
* addresses after the original process has already exited, you
* will have to opt-out of usage of `/proc/<pid>/map_files/` as
* part of the symbolization request. Refer to
* [`blaze_symbolize_src_process::map_files`].
*/
bool cache_vmas;
/**
* Unused member available for future expansion. Must be initialized
* to zero.
*/
uint8_t reserved[15];
} blaze_cache_src_process;

/**
* Source code location information for a symbol or inlined function.
*/
Expand Down Expand Up @@ -1193,6 +1236,26 @@ blaze_symbolizer *blaze_symbolizer_new_opts(const struct blaze_symbolizer_opts *
*/
void blaze_symbolizer_free(blaze_symbolizer *symbolizer);

/**
* Symbolize a list of process absolute addresses.
*
* On success, the function returns a [`blaze_syms`] containing an
* array of `abs_addr_cnt` [`blaze_sym`] objects. The returned object
* should be released using [`blaze_syms_free`] once it is no longer
* needed.
*
* On error, the function returns `NULL` and sets the thread's last error to
* indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
* error.
*
* # Safety
* - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
* - `src` needs to point to a valid [`blaze_symbolize_src_process`] object
* - `abs_addrs` point to an array of `abs_addr_cnt` addresses
*/
void blaze_symbolize_cache_process(blaze_symbolizer *symbolizer,
const struct blaze_cache_src_process *cache);

/**
* Symbolize a list of process absolute addresses.
*
Expand Down
1 change: 1 addition & 0 deletions capi/src/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ mod tests {


/// Check that various types have expected sizes.
#[tag(miri)]
#[test]
#[cfg(target_pointer_width = "64")]
fn type_sizes() {
Expand Down
1 change: 1 addition & 0 deletions capi/src/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ mod tests {


/// Check that various types have expected sizes.
#[tag(miri)]
#[test]
#[cfg(target_pointer_width = "64")]
fn type_sizes() {
Expand Down
141 changes: 141 additions & 0 deletions capi/src/symbolize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::path::Path;
use std::path::PathBuf;
use std::ptr;

use blazesym::symbolize::cache;
use blazesym::symbolize::source::Elf;
use blazesym::symbolize::source::GsymData;
use blazesym::symbolize::source::GsymFile;
Expand All @@ -36,6 +37,69 @@ use crate::util::slice_from_aligned_user_array;
use crate::util::slice_from_user_array;


/// Configuration for caching of process-level data.
#[repr(C)]
#[derive(Debug)]
pub struct blaze_cache_src_process {
/// The size of this object's type.
///
/// Make sure to initialize it to `sizeof(<type>)`. This member is used to
/// ensure compatibility in the presence of member additions.
pub type_size: usize,
/// The referenced process' ID.
pub pid: u32,
/// Whether to cache the process' VMAs for later use.
///
/// Caching VMAs can be useful, because it conceptually enables the
/// library to serve a symbolization request targeting a process
/// even if said process has since exited the system.
///
/// Note that once VMAs have been cached this way, the library will
/// refrain from re-reading updated VMAs unless instructed to.
/// Hence, if you have reason to believe that a process may have
/// changed its memory regions (by loading a new shared object, for
/// example), you would have to make another request to cache them
/// yourself.
///
/// Note furthermore that if you cache VMAs to later symbolize
/// addresses after the original process has already exited, you
/// will have to opt-out of usage of `/proc/<pid>/map_files/` as
/// part of the symbolization request. Refer to
/// [`blaze_symbolize_src_process::map_files`].
pub cache_vmas: bool,
/// Unused member available for future expansion. Must be initialized
/// to zero.
pub reserved: [u8; 15],
}

impl Default for blaze_cache_src_process {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
pid: 0,
cache_vmas: false,
reserved: [0; 15],
}
}
}

impl From<blaze_cache_src_process> for cache::Process {
fn from(process: blaze_cache_src_process) -> Self {
let blaze_cache_src_process {
type_size: _,
pid,
cache_vmas,
reserved: _,
} = process;
Self {
pid: pid.into(),
cache_vmas,
_non_exhaustive: (),
}
}
}


/// The parameters to load symbols and debug information from an ELF.
///
/// Describes the path and address of an ELF file loaded in a
Expand Down Expand Up @@ -689,6 +753,43 @@ pub unsafe extern "C" fn blaze_symbolizer_free(symbolizer: *mut blaze_symbolizer
}
}


/// Symbolize a list of process absolute addresses.
///
/// On success, the function returns a [`blaze_syms`] containing an
/// array of `abs_addr_cnt` [`blaze_sym`] objects. The returned object
/// should be released using [`blaze_syms_free`] once it is no longer
/// needed.
///
/// On error, the function returns `NULL` and sets the thread's last error to
/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
/// error.
///
/// # Safety
/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
/// - `src` needs to point to a valid [`blaze_symbolize_src_process`] object
/// - `abs_addrs` point to an array of `abs_addr_cnt` addresses
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_cache_process(
symbolizer: *mut blaze_symbolizer,
cache: *const blaze_cache_src_process,
) {
if !input_zeroed!(cache, blaze_cache_src_process) {
let () = set_last_err(blaze_err::BLAZE_ERR_INVALID_INPUT);
return
}
let cache = input_sanitize!(cache, blaze_cache_src_process);
let cache = cache::Cache::from(cache::Process::from(cache));

// SAFETY: The caller ensures that the pointer is valid.
let symbolizer = unsafe { &*symbolizer };
let result = symbolizer.cache(&cache);
let err = result
.map(|()| blaze_err::BLAZE_ERR_OK)
.unwrap_or_else(|err| err.kind().into());
let () = set_last_err(err);
}

fn code_info_strtab_size(code_info: &Option<CodeInfo>) -> usize {
code_info
.as_ref()
Expand Down Expand Up @@ -1150,6 +1251,7 @@ mod tests {
#[test]
#[cfg(target_pointer_width = "64")]
fn type_sizes() {
assert_eq!(mem::size_of::<blaze_cache_src_process>(), 32);
assert_eq!(mem::size_of::<blaze_symbolize_src_elf>(), 24);
assert_eq!(mem::size_of::<blaze_symbolize_src_kernel>(), 32);
assert_eq!(mem::size_of::<blaze_symbolize_src_process>(), 16);
Expand Down Expand Up @@ -1820,6 +1922,45 @@ mod tests {
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}

/// Make sure that we can symbolize addresses in a process after
/// caching the corresponding metadata.
#[test]
fn symbolize_in_process_cached() {
let symbolizer = blaze_symbolizer_new();
let cache = blaze_cache_src_process {
pid: 0,
cache_vmas: true,
..Default::default()
};
let () = unsafe { blaze_symbolize_cache_process(symbolizer, &cache) };

let src = blaze_symbolize_src_process {
pid: 0,
debug_syms: true,
perf_map: true,
map_files: false,
..Default::default()
};
let addrs = [blaze_symbolizer_new as Addr];
let result = unsafe {
blaze_symbolize_process_abs_addrs(symbolizer, &src, addrs.as_ptr(), addrs.len())
};

assert!(!result.is_null());

let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"blaze_symbolizer_new\0").unwrap()
);

let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}

/// Make sure that we can symbolize an address in the kernel via
/// kallsyms.
#[test]
Expand Down

0 comments on commit 264d39c

Please sign in to comment.