@@ -2,16 +2,20 @@ use std::alloc::alloc;
22use std:: alloc:: dealloc;
33use std:: alloc:: Layout ;
44use std:: ffi:: CStr ;
5+ use std:: ffi:: CString ;
56use std:: ffi:: OsStr ;
67use std:: fmt:: Debug ;
8+ use std:: io;
79use std:: mem;
810use std:: ops:: Deref as _;
911use std:: os:: raw:: c_char;
12+ use std:: os:: raw:: c_void;
1013use std:: os:: unix:: ffi:: OsStrExt as _;
1114use std:: path:: Path ;
1215use std:: path:: PathBuf ;
1316use std:: ptr;
1417
18+ use blazesym:: helper:: ElfResolver ;
1519use blazesym:: symbolize:: cache;
1620use blazesym:: symbolize:: source:: Elf ;
1721use blazesym:: symbolize:: source:: GsymData ;
@@ -21,6 +25,7 @@ use blazesym::symbolize::source::Process;
2125use blazesym:: symbolize:: source:: Source ;
2226use blazesym:: symbolize:: CodeInfo ;
2327use blazesym:: symbolize:: Input ;
28+ use blazesym:: symbolize:: ProcessMemberType ;
2429use blazesym:: symbolize:: Reason ;
2530use blazesym:: symbolize:: Sym ;
2631use blazesym:: symbolize:: Symbolized ;
@@ -689,9 +694,39 @@ pub struct blaze_symbolizer_opts {
689694 /// languages are Rust and C++ and the flag will have no effect if
690695 /// the underlying language does not mangle symbols (such as C).
691696 pub demangle : bool ,
697+ /// Explicit pad to avoid implicit gaps.
698+ pub _pad_a : [ u8 ; 4 ] ,
699+ /// Callback for custom process member path dispatch.
700+ ///
701+ /// When set, this callback is invoked for each process member that
702+ /// has a file path during process symbolization. It allows the
703+ /// caller to provide an alternative ELF file path for
704+ /// symbolization (e.g., fetched via debuginfod).
705+ ///
706+ /// The callback receives the `/proc/<pid>/map_files/...` path and
707+ /// the symbolic path from `/proc/<pid>/maps`, along with the
708+ /// user-provided context pointer
709+ /// ([`process_dispatch_ctx`][Self::process_dispatch_ctx]).
710+ ///
711+ /// The callback should return one of:
712+ /// - A `malloc`'d path string to an alternative ELF file to use for
713+ /// symbolization. The library will `free` this string after use.
714+ /// - `NULL` to use the default symbolization behavior for this member.
715+ ///
716+ /// Set to `NULL` to disable custom dispatch.
717+ pub process_dispatch_cb : Option <
718+ unsafe extern "C" fn (
719+ maps_file : * const c_char ,
720+ symbolic_path : * const c_char ,
721+ ctx : * mut c_void ,
722+ ) -> * mut c_char ,
723+ > ,
724+ /// Opaque context pointer passed to
725+ /// [`process_dispatch_cb`][Self::process_dispatch_cb].
726+ pub process_dispatch_ctx : * mut c_void ,
692727 /// Unused member available for future expansion. Must be initialized
693728 /// to zero.
694- pub reserved : [ u8 ; 20 ] ,
729+ pub reserved : [ u8 ; 24 ] ,
695730}
696731
697732impl Default for blaze_symbolizer_opts {
@@ -704,7 +739,10 @@ impl Default for blaze_symbolizer_opts {
704739 code_info : false ,
705740 inlined_fns : false ,
706741 demangle : false ,
707- reserved : [ 0 ; 20 ] ,
742+ _pad_a : [ 0 ; 4 ] ,
743+ process_dispatch_cb : None ,
744+ process_dispatch_ctx : ptr:: null_mut ( ) ,
745+ reserved : [ 0 ; 24 ] ,
708746 }
709747 }
710748}
@@ -760,6 +798,9 @@ pub unsafe extern "C" fn blaze_symbolizer_new_opts(
760798 code_info,
761799 inlined_fns,
762800 demangle,
801+ _pad_a : _,
802+ process_dispatch_cb,
803+ process_dispatch_ctx,
763804 reserved : _,
764805 } = opts;
765806
@@ -794,6 +835,41 @@ pub unsafe extern "C" fn blaze_symbolizer_new_opts(
794835 }
795836 } ;
796837
838+ let builder = if let Some ( cb) = process_dispatch_cb {
839+ // Cast the context pointer to usize so the closure is Send.
840+ // The caller is responsible for thread safety of the context.
841+ let ctx = process_dispatch_ctx as usize ;
842+ builder. set_process_dispatcher ( move |info| {
843+ let ctx = ctx as * mut c_void ;
844+ match info. member_entry {
845+ ProcessMemberType :: Path ( entry) => {
846+ let maps_file = CString :: new ( entry. maps_file . as_os_str ( ) . as_bytes ( ) )
847+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: InvalidData , e) ) ?;
848+ let sym_path = CString :: new ( entry. symbolic_path . as_os_str ( ) . as_bytes ( ) )
849+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: InvalidData , e) ) ?;
850+ // SAFETY: The caller guarantees that the callback is safe
851+ // to call with valid C string pointers and the
852+ // provided context.
853+ let result = unsafe { cb ( maps_file. as_ptr ( ) , sym_path. as_ptr ( ) , ctx) } ;
854+ if result. is_null ( ) {
855+ return Ok ( None )
856+ }
857+ // SAFETY: The callback is required to return a valid,
858+ // NUL-terminated, `malloc`'d C string.
859+ let path_cstr = unsafe { CStr :: from_ptr ( result) } ;
860+ let path = Path :: new ( OsStr :: from_bytes ( path_cstr. to_bytes ( ) ) ) ;
861+ let resolver = ElfResolver :: open ( path) ;
862+ // SAFETY: The string was `malloc`'d by the callback.
863+ unsafe { libc:: free ( result. cast ( ) ) } ;
864+ Ok ( Some ( Box :: new ( resolver?) ) )
865+ }
866+ _ => Ok ( None ) ,
867+ }
868+ } )
869+ } else {
870+ builder
871+ } ;
872+
797873 let symbolizer = builder. build ( ) ;
798874 let symbolizer_box = Box :: new ( symbolizer) ;
799875 let ( ) = set_last_err ( blaze_err:: OK ) ;
@@ -1355,7 +1431,7 @@ mod tests {
13551431 assert_eq ! ( mem:: size_of:: <blaze_symbolize_src_process>( ) , 32 ) ;
13561432 assert_eq ! ( mem:: size_of:: <blaze_symbolize_src_gsym_data>( ) , 40 ) ;
13571433 assert_eq ! ( mem:: size_of:: <blaze_symbolize_src_gsym_file>( ) , 32 ) ;
1358- assert_eq ! ( mem:: size_of:: <blaze_symbolizer_opts>( ) , 48 ) ;
1434+ assert_eq ! ( mem:: size_of:: <blaze_symbolizer_opts>( ) , 72 ) ;
13591435 assert_eq ! ( mem:: size_of:: <blaze_symbolize_code_info>( ) , 32 ) ;
13601436 assert_eq ! ( mem:: size_of:: <blaze_symbolize_inlined_fn>( ) , 48 ) ;
13611437 assert_eq ! ( mem:: size_of:: <blaze_sym>( ) , 104 ) ;
@@ -1465,7 +1541,7 @@ mod tests {
14651541 } ;
14661542 assert_eq ! (
14671543 format!( "{opts:?}" ) ,
1468- "blaze_symbolizer_opts { type_size: 16, debug_dirs: 0x0, debug_dirs_len: 0, auto_reload: false, code_info: false, inlined_fns: false, demangle: true, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1544+ "blaze_symbolizer_opts { type_size: 16, debug_dirs: 0x0, debug_dirs_len: 0, auto_reload: false, code_info: false, inlined_fns: false, demangle: true, _pad_a: [0, 0, 0, 0], process_dispatch_cb: None, process_dispatch_ctx: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
14691545 ) ;
14701546 }
14711547
@@ -2302,4 +2378,114 @@ mod tests {
23022378 let ( ) = unsafe { blaze_syms_free ( result) } ;
23032379 let ( ) = unsafe { blaze_symbolizer_free ( symbolizer) } ;
23042380 }
2381+
2382+ /// Helper to test process dispatch callbacks. Creates a symbolizer
2383+ /// with the given callback (ctx set to `0xc0ffee`), symbolizes
2384+ /// the given address, and checks that the symbol name contains
2385+ /// `expected_sym`. If `expected_sym` is `None`, asserts that
2386+ /// symbolization failed.
2387+ unsafe fn symbolize_with_dispatch (
2388+ cb : unsafe extern "C" fn ( * const c_char , * const c_char , * mut c_void ) -> * mut c_char ,
2389+ addr : Addr ,
2390+ expected_sym : Option < & str > ,
2391+ ) {
2392+ let opts = blaze_symbolizer_opts {
2393+ process_dispatch_cb : Some ( cb) ,
2394+ process_dispatch_ctx : 0xc0ffee as * mut c_void ,
2395+ ..Default :: default ( )
2396+ } ;
2397+ let symbolizer = unsafe { blaze_symbolizer_new_opts ( & opts) } ;
2398+ assert ! ( !symbolizer. is_null( ) ) ;
2399+
2400+ let process_src = blaze_symbolize_src_process {
2401+ pid : 0 ,
2402+ debug_syms : true ,
2403+ ..Default :: default ( )
2404+ } ;
2405+
2406+ let addrs = [ addr] ;
2407+ let result = unsafe {
2408+ blaze_symbolize_process_abs_addrs ( symbolizer, & process_src, addrs. as_ptr ( ) , addrs. len ( ) )
2409+ } ;
2410+ let ( ) = unsafe { blaze_symbolizer_free ( symbolizer) } ;
2411+
2412+ if let Some ( expected) = expected_sym {
2413+ assert ! ( !result. is_null( ) ) ;
2414+ let result = unsafe { & * result } ;
2415+ assert_eq ! ( result. cnt, 1 ) ;
2416+ let syms = unsafe { slice:: from_raw_parts ( result. syms . as_ptr ( ) , result. cnt ) } ;
2417+ let name = unsafe { CStr :: from_ptr ( syms[ 0 ] . name ) } . to_str ( ) . unwrap ( ) ;
2418+ assert ! ( name. contains( expected) , "{name}" ) ;
2419+ let ( ) = unsafe { blaze_syms_free ( result) } ;
2420+ } else {
2421+ assert ! ( result. is_null( ) ) ;
2422+ }
2423+ }
2424+
2425+ /// Make sure that we can symbolize an address in the current process
2426+ /// using a custom process dispatch callback that returns the
2427+ /// `maps_file` path as-is.
2428+ #[ test]
2429+ fn symbolize_in_process_with_dispatch ( ) {
2430+ unsafe extern "C" fn cb (
2431+ maps_file : * const c_char ,
2432+ _symbolic_path : * const c_char ,
2433+ ctx : * mut c_void ,
2434+ ) -> * mut c_char {
2435+ assert_eq ! ( ctx as usize , 0xc0ffee ) ;
2436+ unsafe { libc:: strdup ( maps_file) }
2437+ }
2438+
2439+ unsafe {
2440+ symbolize_with_dispatch (
2441+ cb,
2442+ symbolize_in_process_with_dispatch as * const ( ) as Addr ,
2443+ Some ( "symbolize_in_process_with_dispatch" ) ,
2444+ )
2445+ } ;
2446+ }
2447+
2448+ /// Make sure that a dispatch callback returning NULL falls back to
2449+ /// the default symbolization behavior.
2450+ #[ test]
2451+ fn symbolize_in_process_with_null_dispatch ( ) {
2452+ unsafe extern "C" fn cb (
2453+ _maps_file : * const c_char ,
2454+ _symbolic_path : * const c_char ,
2455+ ctx : * mut c_void ,
2456+ ) -> * mut c_char {
2457+ assert_eq ! ( ctx as usize , 0xc0ffee ) ;
2458+ ptr:: null_mut ( )
2459+ }
2460+
2461+ unsafe {
2462+ symbolize_with_dispatch (
2463+ cb,
2464+ symbolize_in_process_with_null_dispatch as * const ( ) as Addr ,
2465+ Some ( "symbolize_in_process_with_null_dispatch" ) ,
2466+ )
2467+ } ;
2468+ }
2469+
2470+ /// Make sure that a dispatch callback returning a non-existent path
2471+ /// causes symbolization to fail for that address.
2472+ #[ test]
2473+ fn symbolize_in_process_with_bad_dispatch ( ) {
2474+ unsafe extern "C" fn cb (
2475+ _maps_file : * const c_char ,
2476+ _symbolic_path : * const c_char ,
2477+ ctx : * mut c_void ,
2478+ ) -> * mut c_char {
2479+ assert_eq ! ( ctx as usize , 0xc0ffee ) ;
2480+ unsafe { libc:: strdup ( c"/no/such/file" . as_ptr ( ) ) }
2481+ }
2482+
2483+ unsafe {
2484+ symbolize_with_dispatch (
2485+ cb,
2486+ symbolize_in_process_with_bad_dispatch as * const ( ) as Addr ,
2487+ None ,
2488+ )
2489+ } ;
2490+ }
23052491}
0 commit comments