Skip to content

Commit

Permalink
Expose inlined functions through C API
Browse files Browse the repository at this point in the history
With this change we expose inlined function information through the C
API.

Signed-off-by: Daniel Müller <[email protected]>
  • Loading branch information
d-e-s-o authored and danielocfb committed Oct 3, 2023
1 parent d2c6f43 commit 2e05212
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 16 deletions.
26 changes: 26 additions & 0 deletions include/blazesym.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ typedef struct blaze_symbolizer_opts {
* This setting implies `debug_syms` (and forces it to `true`).
*/
bool code_info;
/**
* Whether to report inlined functions as part of symbolization.
*/
bool inlined_fns;
/**
* Whether or not to transparently demangle symbols.
*
Expand Down Expand Up @@ -300,6 +304,20 @@ typedef struct blaze_symbolize_code_info {
uint16_t column;
} blaze_symbolize_code_info;

/**
* Data about an inlined function call.
*/
typedef struct blaze_symbolize_inlined_fn {
/**
* The symbol name of the inlined function.
*/
const char *name;
/**
* Source code location information for the inlined function.
*/
struct blaze_symbolize_code_info code_info;
} blaze_symbolize_inlined_fn;

/**
* The result of symbolization of an address.
*
Expand Down Expand Up @@ -337,6 +355,14 @@ typedef struct blaze_sym {
* Source code location information for the symbol.
*/
struct blaze_symbolize_code_info code_info;
/**
* The number of symbolized inlined function calls present.
*/
size_t inlined_cnt;
/**
* An array of `inlined_cnt` symbolized inlined function calls.
*/
const struct blaze_symbolize_inlined_fn *inlined;
} blaze_sym;

/**
Expand Down
86 changes: 71 additions & 15 deletions src/c_api/symbolize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::symbolize::CodeInfo;
use crate::symbolize::Elf;
use crate::symbolize::GsymData;
use crate::symbolize::GsymFile;
use crate::symbolize::InlinedFn;
use crate::symbolize::Kernel;
use crate::symbolize::Process;
use crate::symbolize::Source;
Expand Down Expand Up @@ -185,6 +186,17 @@ pub struct blaze_symbolize_code_info {
}


/// Data about an inlined function call.
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_inlined_fn {
/// The symbol name of the inlined function.
pub name: *const c_char,
/// Source code location information for the inlined function.
pub code_info: blaze_symbolize_code_info,
}


/// The result of symbolization of an address.
///
/// A `blaze_sym` is the information of a symbol found for an
Expand Down Expand Up @@ -214,6 +226,10 @@ pub struct blaze_sym {
pub offset: usize,
/// Source code location information for the symbol.
pub code_info: blaze_symbolize_code_info,
/// The number of symbolized inlined function calls present.
pub inlined_cnt: usize,
/// An array of `inlined_cnt` symbolized inlined function calls.
pub inlined: *const blaze_symbolize_inlined_fn,
}

/// `blaze_result` is the result of symbolization for C API.
Expand Down Expand Up @@ -254,6 +270,8 @@ pub struct blaze_symbolizer_opts {
///
/// This setting implies `debug_syms` (and forces it to `true`).
pub code_info: bool,
/// Whether to report inlined functions as part of symbolization.
pub inlined_fns: bool,
/// Whether or not to transparently demangle symbols.
///
/// Demangling happens on a best-effort basis. Currently supported
Expand Down Expand Up @@ -284,12 +302,14 @@ pub unsafe extern "C" fn blaze_symbolizer_new_opts(
let blaze_symbolizer_opts {
debug_syms,
code_info,
inlined_fns,
demangle,
} = opts;

let symbolizer = Symbolizer::builder()
.enable_debug_syms(*debug_syms)
.enable_code_info(*code_info)
.enable_inlined_fns(*inlined_fns)
.enable_demangling(*demangle)
.build();
let symbolizer_box = Box::new(symbolizer);
Expand Down Expand Up @@ -320,8 +340,19 @@ fn code_info_strtab_size(code_info: &Option<CodeInfo>) -> usize {
.unwrap_or(0)
}

fn inlined_fn_strtab_size(inlined_fn: &InlinedFn) -> usize {
inlined_fn.name.len() + 1 + code_info_strtab_size(&inlined_fn.code_info)
}

fn sym_strtab_size(sym: &Sym) -> usize {
sym.name.len() + 1 + code_info_strtab_size(&sym.code_info)
sym.name.len()
+ 1
+ code_info_strtab_size(&sym.code_info)
+ sym
.inlined
.iter()
.map(inlined_fn_strtab_size)
.sum::<usize>()
}

fn convert_code_info(
Expand Down Expand Up @@ -354,16 +385,15 @@ fn convert_code_info(
fn convert_symbolizedresults_to_c(results: Vec<Symbolized>) -> *const blaze_result {
// Allocate a buffer to contain a blaze_result, all
// blaze_sym, and C strings of symbol and path.
let strtab_size = results
.iter()
.map(|sym| match sym {
Symbolized::Sym(sym) => sym_strtab_size(sym),
Symbolized::Unknown => 0,
})
.sum::<usize>();

let buf_size =
strtab_size + mem::size_of::<blaze_result>() + mem::size_of::<blaze_sym>() * results.len();
let (strtab_size, inlined_fn_cnt) = results.iter().fold((0, 0), |acc, sym| match sym {
Symbolized::Sym(sym) => (acc.0 + sym_strtab_size(sym), acc.1 + sym.inlined.len()),
Symbolized::Unknown => acc,
});

let buf_size = strtab_size
+ mem::size_of::<blaze_result>()
+ mem::size_of::<blaze_sym>() * results.len()
+ mem::size_of::<blaze_symbolize_inlined_fn>() * inlined_fn_cnt;
let raw_buf_with_sz =
unsafe { alloc(Layout::from_size_align(buf_size + mem::size_of::<u64>(), 8).unwrap()) };
if raw_buf_with_sz.is_null() {
Expand All @@ -377,13 +407,20 @@ fn convert_symbolizedresults_to_c(results: Vec<Symbolized>) -> *const blaze_resu

let result_ptr = raw_buf as *mut blaze_result;
let mut syms_last = unsafe { &mut (*result_ptr).syms as *mut blaze_sym };
let mut cstr_last = unsafe {
let mut inlined_last = unsafe {
raw_buf.add(mem::size_of::<blaze_result>() + mem::size_of::<blaze_sym>() * results.len())
} as *mut blaze_symbolize_inlined_fn;
let mut cstr_last = unsafe {
raw_buf.add(
mem::size_of::<blaze_result>()
+ mem::size_of::<blaze_sym>() * results.len()
+ mem::size_of::<blaze_symbolize_inlined_fn>() * inlined_fn_cnt,
)
} as *mut c_char;

let mut make_cstr = |src: &OsStr| {
let cstr = cstr_last;
unsafe { ptr::copy(src.as_bytes().as_ptr(), cstr as *mut u8, src.len()) };
unsafe { ptr::copy_nonoverlapping(src.as_bytes().as_ptr(), cstr as *mut u8, src.len()) };
unsafe { *cstr.add(src.len()) = 0 };
cstr_last = unsafe { cstr_last.add(src.len() + 1) };

Expand All @@ -403,6 +440,22 @@ fn convert_symbolizedresults_to_c(results: Vec<Symbolized>) -> *const blaze_resu
sym_ref.addr = sym.addr;
sym_ref.offset = sym.offset;
convert_code_info(&sym.code_info, &mut sym_ref.code_info, &mut make_cstr);
sym_ref.inlined_cnt = sym.inlined.len();
sym_ref.inlined = inlined_last;

for inlined in sym.inlined.iter() {
let inlined_ref = unsafe { &mut *inlined_last };

let name_ptr = make_cstr(OsStr::new(&inlined.name));
inlined_ref.name = name_ptr;
convert_code_info(
&inlined.code_info,
&mut inlined_ref.code_info,
&mut make_cstr,
);

inlined_last = unsafe { inlined_last.add(1) };
}
}
Symbolized::Unknown => {
// Unknown symbols/addresses are just represented with all
Expand Down Expand Up @@ -637,10 +690,12 @@ mod tests {
line: 42,
column: 1,
},
inlined_cnt: 0,
inlined: ptr::null(),
};
assert_eq!(
format!("{sym:?}"),
"blaze_sym { name: 0x0, addr: 4919, offset: 24, code_info: blaze_symbolize_code_info { dir: 0x0, file: 0x0, line: 42, column: 1 } }"
"blaze_sym { name: 0x0, addr: 4919, offset: 24, code_info: blaze_symbolize_code_info { dir: 0x0, file: 0x0, line: 42, column: 1 }, inlined_cnt: 0, inlined: 0x0 }"
);

let result = blaze_result { cnt: 0, syms: [] };
Expand All @@ -649,11 +704,12 @@ mod tests {
let opts = blaze_symbolizer_opts {
debug_syms: true,
code_info: false,
inlined_fns: false,
demangle: true,
};
assert_eq!(
format!("{opts:?}"),
"blaze_symbolizer_opts { debug_syms: true, code_info: false, demangle: true }"
"blaze_symbolizer_opts { debug_syms: true, code_info: false, inlined_fns: false, demangle: true }"
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/symbolize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ impl CodeInfo {
/// A type representing an inlined function.
#[derive(Clone, Debug, PartialEq)]
pub struct InlinedFn {
/// The symbol name of the function.
/// The symbol name of the inlined function.
pub name: String,
/// Source code location information for the call to the function.
pub code_info: Option<CodeInfo>,
Expand Down
134 changes: 134 additions & 0 deletions tests/c_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::ptr;
use std::slice;

use blazesym::inspect;
use blazesym::symbolize;

use blazesym::c_api::blaze_inspect_elf_src;
use blazesym::c_api::blaze_inspect_syms_elf;
Expand Down Expand Up @@ -52,6 +53,7 @@ fn symbolizer_creation_with_opts() {
let opts = blaze_symbolizer_opts {
debug_syms: true,
code_info: false,
inlined_fns: false,
demangle: true,
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
Expand Down Expand Up @@ -153,6 +155,138 @@ fn symbolize_elf_dwarf_gsym() {
}


/// Symbolize an address inside a DWARF file, with and without auto-demangling
/// enabled.
#[test]
fn symbolize_dwarf_demangle() {
fn test(path: &Path, addr: Addr) -> Result<(), ()> {
let opts = blaze_symbolizer_opts {
debug_syms: true,
code_info: true,
inlined_fns: true,
demangle: false,
};

let path_c = CString::new(path.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let addrs = [addr];
let result =
unsafe { blaze_symbolize_elf(symbolizer, &elf_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];
let name = unsafe { CStr::from_ptr(sym.name) };
assert!(
name.to_str().unwrap().contains("test13test_function"),
"{:?}",
name
);

if sym.inlined_cnt == 0 {
let () = unsafe { blaze_result_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
return Err(())
}

assert_eq!(sym.inlined_cnt, 1);
let name = unsafe { CStr::from_ptr((*sym.inlined).name) };
assert!(
name.to_str().unwrap().contains("test12inlined_call"),
"{:?}",
name
);

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

// Do it again, this time with demangling enabled.
let opts = blaze_symbolizer_opts {
debug_syms: true,
code_info: true,
inlined_fns: true,
demangle: true,
};

let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let addrs = [addr];
let result =
unsafe { blaze_symbolize_elf(symbolizer, &elf_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"test::test_function\0").unwrap()
);


assert_eq!(sym.inlined_cnt, 1);
assert_eq!(
unsafe { CStr::from_ptr((*sym.inlined).name) },
CStr::from_bytes_with_nul(b"test::inlined_call\0").unwrap()
);

let () = unsafe { blaze_result_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
Ok(())
}

let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-rs.bin");
let elf = inspect::Elf::new(&test_dwarf);
let src = inspect::Source::Elf(elf);

let inspector = inspect::Inspector::new();
let results = inspector
.lookup(
// Evidently we could still end up with different mangled symbols
// for the same clear text name. Ugh.
&[
"_RNvCs69hjMPjVIJK_4test13test_function",
"_RNvCseTrKHoaUPIf_4test13test_function",
"_RNvCsfpyvYpDUPq_4test13test_function",
],
&src,
)
.unwrap()
.into_iter()
.flatten()
.collect::<Vec<_>>();
assert!(!results.is_empty());

let addr = results[0].addr;
let src = symbolize::Source::Elf(symbolize::Elf::new(&test_dwarf));
let symbolizer = symbolize::Symbolizer::builder()
.enable_demangling(false)
.build();
let result = symbolizer
.symbolize_single(&src, addr)
.unwrap()
.into_sym()
.unwrap();

let addr = result.addr;
let size = result.size.unwrap();
for inst_addr in addr..addr + size {
if test(&test_dwarf, inst_addr).is_ok() {
return
}
}

panic!("failed to find inlined function call");
}


/// Make sure that we can symbolize an address in a process.
#[test]
fn symbolize_in_process() {
Expand Down

0 comments on commit 2e05212

Please sign in to comment.