Skip to main content

blazesym_c/
symbolize.rs

1use std::alloc::alloc;
2use std::alloc::dealloc;
3use std::alloc::Layout;
4use std::ffi::CStr;
5use std::ffi::CString;
6use std::ffi::OsStr;
7use std::fmt::Debug;
8use std::io;
9use std::mem;
10use std::ops::Deref as _;
11use std::os::raw::c_char;
12use std::os::raw::c_void;
13use std::os::unix::ffi::OsStrExt as _;
14use std::path::Path;
15use std::path::PathBuf;
16use std::ptr;
17
18use blazesym::helper::ElfResolver;
19use blazesym::symbolize::cache;
20use blazesym::symbolize::source::Elf;
21use blazesym::symbolize::source::GsymData;
22use blazesym::symbolize::source::GsymFile;
23use blazesym::symbolize::source::Kernel;
24use blazesym::symbolize::source::Process;
25use blazesym::symbolize::source::Source;
26use blazesym::symbolize::CodeInfo;
27use blazesym::symbolize::Input;
28use blazesym::symbolize::ProcessMemberType;
29use blazesym::symbolize::Reason;
30use blazesym::symbolize::Sym;
31use blazesym::symbolize::Symbolized;
32use blazesym::symbolize::Symbolizer;
33use blazesym::Addr;
34use blazesym::MaybeDefault;
35
36use crate::blaze_err;
37#[cfg(doc)]
38use crate::blaze_err_last;
39use crate::set_last_err;
40use crate::util::slice_from_aligned_user_array;
41use crate::util::slice_from_user_array;
42use crate::util::DynSize as _;
43
44
45/// Configuration for caching of ELF symbolization data.
46#[repr(C)]
47#[derive(Debug)]
48pub struct blaze_cache_src_elf {
49    /// The size of this object's type.
50    ///
51    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
52    /// ensure compatibility in the presence of member additions.
53    pub type_size: usize,
54    /// The path to the ELF file.
55    pub path: *const c_char,
56    /// Unused member available for future expansion. Must be initialized
57    /// to zero.
58    pub reserved: [u8; 16],
59}
60
61impl Default for blaze_cache_src_elf {
62    fn default() -> Self {
63        Self {
64            type_size: mem::size_of::<Self>(),
65            path: ptr::null(),
66            reserved: [0; 16],
67        }
68    }
69}
70
71impl From<blaze_cache_src_elf> for cache::Elf {
72    fn from(elf: blaze_cache_src_elf) -> Self {
73        let blaze_cache_src_elf {
74            type_size: _,
75            path,
76            reserved: _,
77        } = elf;
78        Self {
79            path: unsafe { from_cstr(path) },
80            _non_exhaustive: (),
81        }
82    }
83}
84
85
86/// Configuration for caching of process-level data.
87#[repr(C)]
88#[derive(Debug)]
89pub struct blaze_cache_src_process {
90    /// The size of this object's type.
91    ///
92    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
93    /// ensure compatibility in the presence of member additions.
94    pub type_size: usize,
95    /// The referenced process' ID.
96    pub pid: u32,
97    /// Whether to cache the process' VMAs for later use.
98    ///
99    /// Caching VMAs can be useful, because it conceptually enables the
100    /// library to serve a symbolization request targeting a process
101    /// even if said process has since exited the system.
102    ///
103    /// Note that once VMAs have been cached this way, the library will
104    /// refrain from re-reading updated VMAs unless instructed to.
105    /// Hence, if you have reason to believe that a process may have
106    /// changed its memory regions (by loading a new shared object, for
107    /// example), you would have to make another request to cache them
108    /// yourself.
109    ///
110    /// Note furthermore that if you cache VMAs to later symbolize
111    /// addresses after the original process has already exited, you
112    /// will have to opt-out of usage of `/proc/<pid>/map_files/` as
113    /// part of the symbolization request. Refer to
114    /// [`blaze_symbolize_src_process::no_map_files`].
115    pub cache_vmas: bool,
116    /// Unused member available for future expansion. Must be initialized
117    /// to zero.
118    pub reserved: [u8; 19],
119}
120
121impl Default for blaze_cache_src_process {
122    fn default() -> Self {
123        Self {
124            type_size: mem::size_of::<Self>(),
125            pid: 0,
126            cache_vmas: false,
127            reserved: [0; 19],
128        }
129    }
130}
131
132impl From<blaze_cache_src_process> for cache::Process {
133    fn from(process: blaze_cache_src_process) -> Self {
134        let blaze_cache_src_process {
135            type_size: _,
136            pid,
137            cache_vmas,
138            reserved: _,
139        } = process;
140        Self {
141            pid: pid.into(),
142            cache_vmas,
143            _non_exhaustive: (),
144        }
145    }
146}
147
148
149/// The parameters to load symbols and debug information from an ELF.
150///
151/// Describes the path and address of an ELF file loaded in a
152/// process.
153#[repr(C)]
154#[derive(Debug)]
155pub struct blaze_symbolize_src_elf {
156    /// The size of this object's type.
157    ///
158    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
159    /// ensure compatibility in the presence of member additions.
160    pub type_size: usize,
161    /// The path to the ELF file.
162    ///
163    /// The referenced file may be an executable or shared object. For example,
164    /// passing "/bin/sh" will load symbols and debug information from `sh` and
165    /// passing "/lib/libc.so.xxx" will load symbols and debug information from
166    /// libc.
167    pub path: *const c_char,
168    /// Whether or not to consult debug symbols to satisfy the request
169    /// (if present).
170    pub debug_syms: bool,
171    /// Unused member available for future expansion. Must be initialized
172    /// to zero.
173    pub reserved: [u8; 23],
174}
175
176impl Default for blaze_symbolize_src_elf {
177    fn default() -> Self {
178        Self {
179            type_size: mem::size_of::<Self>(),
180            path: ptr::null(),
181            debug_syms: false,
182            reserved: [0; 23],
183        }
184    }
185}
186
187impl From<blaze_symbolize_src_elf> for Elf {
188    fn from(elf: blaze_symbolize_src_elf) -> Self {
189        let blaze_symbolize_src_elf {
190            type_size: _,
191            path,
192            debug_syms,
193            reserved: _,
194        } = elf;
195        Self {
196            path: unsafe { from_cstr(path) },
197            debug_syms,
198            _non_exhaustive: (),
199        }
200    }
201}
202
203
204/// The parameters to load symbols and debug information from a kernel.
205#[repr(C)]
206#[derive(Debug)]
207pub struct blaze_symbolize_src_kernel {
208    /// The size of this object's type.
209    ///
210    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
211    /// ensure compatibility in the presence of member additions.
212    pub type_size: usize,
213    /// The path of a `kallsyms` file to use.
214    ///
215    /// When `NULL`, this will refer to `kallsyms` of the running kernel.
216    /// If set to `'\0'` (`""`) usage of `kallsyms` will be disabled.
217    /// Otherwise the copy at the given path will be used.
218    ///
219    /// If both a `vmlinux` as well as a `kallsyms` file are found,
220    /// `vmlinux` will generally be given preference and `kallsyms` acts
221    /// as a fallback.
222    pub kallsyms: *const c_char,
223    /// The path of the `vmlinux` file to use.
224    ///
225    /// `vmlinux` is generally an uncompressed and unstripped object
226    /// file that is typically used in debugging, profiling, and
227    /// similar use cases.
228    ///
229    /// When `NULL`, the library will search for `vmlinux` candidates in
230    /// various locations, taking into account the currently running
231    /// kernel version. If set to `'\0'` (`""`) discovery and usage of a
232    /// `vmlinux` will be disabled. Otherwise the copy at the given path
233    /// will be used.
234    ///
235    /// If both a `vmlinux` as well as a `kallsyms` file are found,
236    /// `vmlinux` will generally be given preference and `kallsyms` acts
237    /// as a fallback.
238    pub vmlinux: *const c_char,
239    /// Whether or not to consult debug symbols from `vmlinux` to
240    /// satisfy the request (if present).
241    pub debug_syms: bool,
242    /// Unused member available for future expansion. Must be initialized
243    /// to zero.
244    pub reserved: [u8; 23],
245}
246
247impl Default for blaze_symbolize_src_kernel {
248    fn default() -> Self {
249        Self {
250            type_size: mem::size_of::<Self>(),
251            kallsyms: ptr::null(),
252            vmlinux: ptr::null(),
253            debug_syms: false,
254            reserved: [0; 23],
255        }
256    }
257}
258
259impl From<blaze_symbolize_src_kernel> for Kernel {
260    fn from(kernel: blaze_symbolize_src_kernel) -> Self {
261        fn to_maybe_path(path: *const c_char) -> MaybeDefault<PathBuf> {
262            if !path.is_null() {
263                let path = unsafe { from_cstr(path) };
264                if path.as_os_str().is_empty() {
265                    MaybeDefault::None
266                } else {
267                    MaybeDefault::Some(path)
268                }
269            } else {
270                MaybeDefault::Default
271            }
272        }
273
274        let blaze_symbolize_src_kernel {
275            type_size: _,
276            kallsyms,
277            vmlinux,
278            debug_syms,
279            reserved: _,
280        } = kernel;
281        Self {
282            kallsyms: to_maybe_path(kallsyms),
283            vmlinux: to_maybe_path(vmlinux),
284            kaslr_offset: None,
285            debug_syms,
286            _non_exhaustive: (),
287        }
288    }
289}
290
291
292/// The parameters to load symbols and debug information from a process.
293///
294/// Load all ELF files in a process as the sources of symbols and debug
295/// information.
296#[repr(C)]
297#[derive(Debug)]
298pub struct blaze_symbolize_src_process {
299    /// The size of this object's type.
300    ///
301    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
302    /// ensure compatibility in the presence of member additions.
303    pub type_size: usize,
304    /// The referenced process' ID.
305    pub pid: u32,
306    /// Whether or not to consult debug symbols to satisfy the request
307    /// (if present).
308    pub debug_syms: bool,
309    /// Whether to incorporate a process' perf map file into the symbolization
310    /// procedure.
311    pub perf_map: bool,
312    /// Whether to work with `/proc/<pid>/map_files/` entries or with
313    /// symbolic paths mentioned in `/proc/<pid>/maps` instead.
314    ///
315    /// `no_map_files` usage is generally discouraged, as symbolic paths
316    /// are unlikely to work reliably in mount namespace contexts or
317    /// when files have been deleted from the file system. However, by
318    /// using symbolic paths (i.e., with `no_map_files` being `true`)
319    /// the need for requiring the `SYS_ADMIN` capability is eliminated.
320    pub no_map_files: bool,
321    /// Whether or not to symbolize addresses in a vDSO (virtual dynamic
322    /// shared object).
323    pub no_vdso: bool,
324    /// Unused member available for future expansion. Must be initialized
325    /// to zero.
326    pub reserved: [u8; 16],
327}
328
329impl Default for blaze_symbolize_src_process {
330    fn default() -> Self {
331        Self {
332            type_size: mem::size_of::<Self>(),
333            pid: 0,
334            debug_syms: false,
335            perf_map: false,
336            no_map_files: false,
337            no_vdso: false,
338            reserved: [0; 16],
339        }
340    }
341}
342
343impl From<blaze_symbolize_src_process> for Process {
344    fn from(process: blaze_symbolize_src_process) -> Self {
345        let blaze_symbolize_src_process {
346            type_size: _,
347            pid,
348            debug_syms,
349            perf_map,
350            no_map_files,
351            no_vdso,
352            reserved: _,
353        } = process;
354        Self {
355            pid: pid.into(),
356            debug_syms,
357            perf_map,
358            map_files: !no_map_files,
359            vdso: !no_vdso,
360            _non_exhaustive: (),
361        }
362    }
363}
364
365
366/// The parameters to load symbols and debug information from "raw" Gsym data.
367#[repr(C)]
368#[derive(Debug)]
369pub struct blaze_symbolize_src_gsym_data {
370    /// The size of this object's type.
371    ///
372    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
373    /// ensure compatibility in the presence of member additions.
374    pub type_size: usize,
375    /// The Gsym data.
376    pub data: *const u8,
377    /// The size of the Gsym data.
378    pub data_len: usize,
379    /// Unused member available for future expansion. Must be initialized
380    /// to zero.
381    pub reserved: [u8; 16],
382}
383
384impl Default for blaze_symbolize_src_gsym_data {
385    fn default() -> Self {
386        Self {
387            type_size: mem::size_of::<Self>(),
388            data: ptr::null(),
389            data_len: 0,
390            reserved: [0; 16],
391        }
392    }
393}
394
395impl From<blaze_symbolize_src_gsym_data> for GsymData<'_> {
396    fn from(gsym: blaze_symbolize_src_gsym_data) -> Self {
397        let blaze_symbolize_src_gsym_data {
398            type_size: _,
399            data,
400            data_len,
401            reserved: _,
402        } = gsym;
403        Self {
404            data: unsafe { slice_from_aligned_user_array(data, data_len) },
405            _non_exhaustive: (),
406        }
407    }
408}
409
410
411/// The parameters to load symbols and debug information from a Gsym file.
412#[repr(C)]
413#[derive(Debug)]
414pub struct blaze_symbolize_src_gsym_file {
415    /// The size of this object's type.
416    ///
417    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
418    /// ensure compatibility in the presence of member additions.
419    pub type_size: usize,
420    /// The path to a gsym file.
421    pub path: *const c_char,
422    /// Unused member available for future expansion. Must be initialized
423    /// to zero.
424    pub reserved: [u8; 16],
425}
426
427impl Default for blaze_symbolize_src_gsym_file {
428    fn default() -> Self {
429        Self {
430            type_size: mem::size_of::<Self>(),
431            path: ptr::null(),
432            reserved: [0; 16],
433        }
434    }
435}
436
437impl From<blaze_symbolize_src_gsym_file> for GsymFile {
438    fn from(gsym: blaze_symbolize_src_gsym_file) -> Self {
439        let blaze_symbolize_src_gsym_file {
440            type_size: _,
441            path,
442            reserved: _,
443        } = gsym;
444        Self {
445            path: unsafe { from_cstr(path) },
446            _non_exhaustive: (),
447        }
448    }
449}
450
451
452/// C ABI compatible version of [`blazesym::symbolize::Symbolizer`].
453///
454/// It is returned by [`blaze_symbolizer_new`] and should be free by
455/// [`blaze_symbolizer_free`].
456pub type blaze_symbolizer = Symbolizer;
457
458
459/// The reason why symbolization failed.
460///
461/// The reason is generally only meant as a hint. Reasons reported may
462/// change over time and, hence, should not be relied upon for the
463/// correctness of the application.
464#[repr(transparent)]
465#[derive(Copy, Clone, Debug, Eq, PartialEq)]
466pub struct blaze_symbolize_reason(u8);
467
468// Please adjust `blaze_normalize_reason` as well when adding a new
469// variant.
470impl blaze_symbolize_reason {
471    /// Symbolization was successful.
472    pub const SUCCESS: Self = Self(0);
473    /// The absolute address was not found in the corresponding process'
474    /// virtual memory map.
475    pub const UNMAPPED: Self = Self(1);
476    /// The file offset does not map to a valid piece of code/data.
477    pub const INVALID_FILE_OFFSET: Self = Self(2);
478    /// The `/proc/<pid>/maps` entry corresponding to the address does
479    /// not have a component (file system path, object, ...) associated
480    /// with it.
481    pub const MISSING_COMPONENT: Self = Self(3);
482    /// The symbolization source has no or no relevant symbols.
483    pub const MISSING_SYMS: Self = Self(4);
484    /// The address could not be found in the symbolization source.
485    pub const UNKNOWN_ADDR: Self = Self(5);
486    /// The address belonged to an entity that is currently unsupported.
487    pub const UNSUPPORTED: Self = Self(6);
488    /// An error prevented the symbolization of the address from succeeding.
489    pub const IGNORED_ERROR: Self = Self(7);
490}
491
492
493impl From<Reason> for blaze_symbolize_reason {
494    fn from(reason: Reason) -> Self {
495        match reason {
496            Reason::Unmapped => Self::UNMAPPED,
497            Reason::InvalidFileOffset => Self::INVALID_FILE_OFFSET,
498            Reason::MissingComponent => Self::MISSING_COMPONENT,
499            Reason::MissingSyms => Self::MISSING_SYMS,
500            Reason::Unsupported => Self::UNSUPPORTED,
501            Reason::UnknownAddr => Self::UNKNOWN_ADDR,
502            Reason::IgnoredError => Self::IGNORED_ERROR,
503            _ => unreachable!(),
504        }
505    }
506}
507
508
509/// Retrieve a textual representation of the reason of a symbolization
510/// failure.
511#[no_mangle]
512pub extern "C" fn blaze_symbolize_reason_str(reason: blaze_symbolize_reason) -> *const c_char {
513    match reason {
514        blaze_symbolize_reason::SUCCESS => b"success\0".as_ptr().cast(),
515        blaze_symbolize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
516        blaze_symbolize_reason::INVALID_FILE_OFFSET => {
517            Reason::InvalidFileOffset.as_bytes().as_ptr().cast()
518        }
519        blaze_symbolize_reason::MISSING_COMPONENT => {
520            Reason::MissingComponent.as_bytes().as_ptr().cast()
521        }
522        blaze_symbolize_reason::MISSING_SYMS => Reason::MissingSyms.as_bytes().as_ptr().cast(),
523        blaze_symbolize_reason::UNKNOWN_ADDR => Reason::UnknownAddr.as_bytes().as_ptr().cast(),
524        blaze_symbolize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
525        blaze_symbolize_reason::IGNORED_ERROR => Reason::IgnoredError.as_bytes().as_ptr().cast(),
526        _ => b"unknown reason\0".as_ptr().cast(),
527    }
528}
529
530
531/// Source code location information for a symbol or inlined function.
532#[repr(C)]
533#[derive(Debug)]
534pub struct blaze_symbolize_code_info {
535    /// The directory in which the source file resides.
536    ///
537    /// This attribute is optional and may be NULL.
538    pub dir: *const c_char,
539    /// The file that defines the symbol.
540    ///
541    /// This attribute is optional and may be NULL.
542    pub file: *const c_char,
543    /// The line number on which the symbol is located in the source
544    /// code.
545    pub line: u32,
546    /// The column number of the symbolized instruction in the source
547    /// code.
548    pub column: u16,
549    /// Unused member available for future expansion.
550    pub reserved: [u8; 10],
551}
552
553
554/// Data about an inlined function call.
555#[repr(C)]
556#[derive(Debug)]
557pub struct blaze_symbolize_inlined_fn {
558    /// The symbol name of the inlined function.
559    pub name: *const c_char,
560    /// Source code location information for the inlined function.
561    pub code_info: blaze_symbolize_code_info,
562    /// Unused member available for future expansion.
563    pub reserved: [u8; 8],
564}
565
566
567/// The result of symbolization of an address.
568///
569/// A `blaze_sym` is the information of a symbol found for an
570/// address.
571#[repr(C)]
572#[derive(Debug)]
573pub struct blaze_sym {
574    /// The symbol name is where the given address should belong to.
575    ///
576    /// If an address could not be symbolized, this member will be NULL.
577    /// Check the `reason` member for additional information pertaining
578    /// the failure.
579    pub name: *const c_char,
580    /// The path to or name of the module containing the symbol.
581    ///
582    /// Typically this would be the path to a executable or shared
583    /// object. Depending on the symbol source this member may not be
584    /// present or it could also just be a file name without path. In
585    /// case of an ELF file contained inside an APK, this will be an
586    /// Android style path of the form `<apk>!<elf-in-apk>`. E.g.,
587    /// `/root/test.apk!/lib/libc.so`.
588    pub module: *const c_char,
589    /// The address at which the symbol is located (i.e., its "start").
590    ///
591    /// This is the "normalized" address of the symbol, as present in
592    /// the file (and reported by tools such as `readelf(1)`,
593    /// `llvm-gsymutil`, or similar).
594    pub addr: Addr,
595    /// The byte offset of the address that got symbolized from the
596    /// start of the symbol (i.e., from `addr`).
597    ///
598    /// E.g., when symbolizing address 0x1337 of a function that starts at
599    /// 0x1330, the offset will be set to 0x07 (and `addr` will be 0x1330). This
600    /// member is especially useful in contexts when input addresses are not
601    /// already normalized, such as when symbolizing an address in a process
602    /// context (which may have been relocated and/or have layout randomizations
603    /// applied).
604    pub offset: usize,
605    /// The size of the symbol.
606    ///
607    /// If the symbol's size is not available, this member will be `-1`.
608    /// Note that some symbol sources may not distinguish between
609    /// "unknown" size and `0`. In that case the size will be reported
610    /// as `0` here as well.
611    pub size: isize,
612    /// Source code location information for the symbol.
613    pub code_info: blaze_symbolize_code_info,
614    /// The number of symbolized inlined function calls present.
615    pub inlined_cnt: usize,
616    /// An array of `inlined_cnt` symbolized inlined function calls.
617    pub inlined: *const blaze_symbolize_inlined_fn,
618    /// On error (i.e., if `name` is NULL), a reason trying to explain
619    /// why symbolization failed.
620    pub reason: blaze_symbolize_reason,
621    /// Unused member available for future expansion.
622    pub reserved: [u8; 15],
623}
624
625/// `blaze_syms` is the result of symbolization of a list of addresses.
626///
627/// Instances of [`blaze_syms`] are returned by any of the `blaze_symbolize_*`
628/// variants. They should be freed by calling [`blaze_syms_free`].
629#[repr(C)]
630#[derive(Debug)]
631pub struct blaze_syms {
632    /// The number of symbols being reported.
633    pub cnt: usize,
634    /// The symbols corresponding to input addresses.
635    ///
636    /// Symbolization happens based on the ordering of (input) addresses.
637    /// Therefore, every input address has an associated symbol.
638    pub syms: [blaze_sym; 0],
639}
640
641/// Create a `PathBuf` from a pointer of C string
642///
643/// # Safety
644/// The provided `cstr` should be terminated with a NUL byte.
645pub(crate) unsafe fn from_cstr(cstr: *const c_char) -> PathBuf {
646    Path::new(OsStr::from_bytes(
647        unsafe { CStr::from_ptr(cstr) }.to_bytes(),
648    ))
649    .to_path_buf()
650}
651
652
653/// Configuration for custom process member dispatch.
654///
655/// When provided to [`blaze_symbolizer_opts`] via
656/// [`process_dispatch`][blaze_symbolizer_opts::process_dispatch], the
657/// callback is invoked for each process member that has a file path during
658/// process symbolization. It allows the caller to provide an alternative
659/// ELF file path for symbolization. For example, the path may be fetched
660/// via debuginfod.
661///
662/// The callback receives the `/proc/<pid>/map_files/...` path and the
663/// symbolic path from `/proc/<pid>/maps`, along with the user-provided
664/// context pointer ([`ctx`][Self::ctx]).
665///
666/// The callback should return one of:
667/// - A `malloc`'d path string to an alternative ELF file to use for
668///   symbolization. The library will `free` this string after use.
669/// - `NULL` to use the default symbolization behavior for this member.
670#[repr(C)]
671#[derive(Debug)]
672pub struct blaze_symbolizer_dispatch {
673    /// The dispatch callback function. Must not be `NULL`.
674    pub dispatch_cb: unsafe extern "C" fn(
675        maps_file: *const c_char,
676        symbolic_path: *const c_char,
677        ctx: *mut c_void,
678    ) -> *mut c_char,
679    /// Opaque context pointer passed to [`dispatch_cb`][Self::dispatch_cb].
680    pub ctx: *mut c_void,
681}
682
683/// Options for configuring [`blaze_symbolizer`] objects.
684#[repr(C)]
685#[derive(Debug)]
686pub struct blaze_symbolizer_opts {
687    /// The size of this object's type.
688    ///
689    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
690    /// ensure compatibility in the presence of member additions.
691    pub type_size: usize,
692    /// Array of debug directories to search for split debug information.
693    ///
694    /// These directories will be consulted (in given order) when resolving
695    /// debug links in binaries. By default and when this member is NULL,
696    /// `/usr/lib/debug` and `/lib/debug/` will be searched. Setting an array
697    /// here will overwrite these defaults, so make sure to include these
698    /// directories as desired.
699    ///
700    /// Note that the directory containing a symbolization source is always an
701    /// implicit candidate target directory of the highest precedence.
702    pub debug_dirs: *const *const c_char,
703    /// The number of array elements in [`debug_dirs`][Self::debug_dirs].
704    pub debug_dirs_len: usize,
705    /// Whether or not to automatically reload file system based
706    /// symbolization sources that were updated since the last
707    /// symbolization operation.
708    pub auto_reload: bool,
709    /// Whether to attempt to gather source code location information.
710    ///
711    /// This option only has an effect if `debug_syms` of the particular
712    /// symbol source is set to `true`. Furthermore, it is a necessary
713    /// prerequisite for retrieving inlined function information (see
714    /// [`inlined_fns`][Self::inlined_fns]).
715    pub code_info: bool,
716    /// Whether to report inlined functions as part of symbolization.
717    ///
718    /// This option only has an effect if [`code_info`][Self::code_info]
719    /// is `true`.
720    pub inlined_fns: bool,
721    /// Whether or not to transparently demangle symbols.
722    ///
723    /// Demangling happens on a best-effort basis. Currently supported
724    /// languages are Rust and C++ and the flag will have no effect if
725    /// the underlying language does not mangle symbols (such as C).
726    pub demangle: bool,
727    /// Unused member available for future expansion. Must be initialized
728    /// to zero.
729    pub _reserved1: [u8; 4],
730    /// Optional pointer to a [`blaze_symbolizer_dispatch`] struct for custom
731    /// process member dispatch. Set to `NULL` to disable.
732    pub process_dispatch: *const blaze_symbolizer_dispatch,
733    /// Unused member available for future expansion. Must be initialized
734    /// to zero.
735    pub reserved: [u8; 8],
736}
737
738impl Default for blaze_symbolizer_opts {
739    fn default() -> Self {
740        Self {
741            type_size: mem::size_of::<Self>(),
742            debug_dirs: ptr::null(),
743            debug_dirs_len: 0,
744            auto_reload: false,
745            code_info: false,
746            inlined_fns: false,
747            demangle: false,
748            _reserved1: [0; 4],
749            process_dispatch: ptr::null(),
750            reserved: [0; 8],
751        }
752    }
753}
754
755
756/// Create an instance of a symbolizer.
757///
758/// C ABI compatible version of [`blazesym::symbolize::Symbolizer::new()`].
759/// Please refer to its documentation for the default configuration in use.
760///
761/// On success, the function creates a new [`blaze_symbolizer`] object
762/// and returns it. The resulting object should be released using
763/// [`blaze_symbolizer_free`] once it is no longer needed.
764///
765/// On error, the function returns `NULL` and sets the thread's last error to
766/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
767/// error.
768#[no_mangle]
769pub extern "C" fn blaze_symbolizer_new() -> *mut blaze_symbolizer {
770    let symbolizer = Symbolizer::new();
771    let symbolizer_box = Box::new(symbolizer);
772    let () = set_last_err(blaze_err::OK);
773    Box::into_raw(symbolizer_box)
774}
775
776/// Create an instance of a symbolizer with configurable options.
777///
778/// On success, the function creates a new [`blaze_symbolizer`] object
779/// and returns it. The resulting object should be released using
780/// [`blaze_symbolizer_free`] once it is no longer needed.
781///
782/// On error, the function returns `NULL` and sets the thread's last error to
783/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
784/// error.
785///
786/// # Safety
787/// - `opts` needs to point to a valid [`blaze_symbolizer_opts`] object
788#[no_mangle]
789pub unsafe extern "C" fn blaze_symbolizer_new_opts(
790    opts: *const blaze_symbolizer_opts,
791) -> *mut blaze_symbolizer {
792    if !input_zeroed!(opts, blaze_symbolizer_opts) {
793        let () = set_last_err(blaze_err::INVALID_INPUT);
794        return ptr::null_mut()
795    }
796    let opts = input_sanitize!(opts, blaze_symbolizer_opts);
797
798    let blaze_symbolizer_opts {
799        type_size: _,
800        debug_dirs,
801        debug_dirs_len,
802        auto_reload,
803        code_info,
804        inlined_fns,
805        demangle,
806        _reserved1: _,
807        process_dispatch,
808        reserved: _,
809    } = opts;
810
811    let builder = Symbolizer::builder()
812        .enable_auto_reload(auto_reload)
813        .enable_code_info(code_info)
814        .enable_inlined_fns(inlined_fns)
815        .enable_demangling(demangle);
816
817    let debug_dir_paths = if debug_dirs.is_null() {
818        None
819    } else {
820        // SAFETY: The caller ensures that the pointer is valid and the count
821        //         matches.
822        let slice = unsafe { slice_from_user_array(debug_dirs, debug_dirs_len) };
823        Some(
824            slice
825                .iter()
826                .map(|cstr| {
827                    PathBuf::from(OsStr::from_bytes(
828                        // SAFETY: The caller ensures that valid C strings are
829                        //         provided.
830                        unsafe { CStr::from_ptr(cstr.cast()) }.to_bytes(),
831                    ))
832                })
833                .collect::<Vec<_>>(),
834        )
835    };
836
837    #[cfg(feature = "dwarf")]
838    let builder = if let Some(ref dirs) = debug_dir_paths {
839        builder.set_debug_dirs(Some(dirs.iter().map(PathBuf::as_path)))
840    } else {
841        builder
842    };
843
844    let builder = if !process_dispatch.is_null() {
845        // SAFETY: The caller guarantees that the pointer is valid.
846        let dispatch = unsafe { &*process_dispatch };
847        let cb = dispatch.dispatch_cb;
848        let ctx = dispatch.ctx;
849        builder.set_process_dispatcher(move |info| {
850            match info.member_entry {
851                ProcessMemberType::Path(entry) => {
852                    let maps_file = CString::new(entry.maps_file.as_os_str().as_bytes())
853                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
854                    let sym_path = CString::new(entry.symbolic_path.as_os_str().as_bytes())
855                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
856                    // SAFETY: The caller guarantees that the callback is safe
857                    //         to call with valid C string pointers and the
858                    //         provided context.
859                    let result = unsafe { cb(maps_file.as_ptr(), sym_path.as_ptr(), ctx) };
860                    if result.is_null() {
861                        return Ok(None)
862                    }
863                    // SAFETY: The callback is required to return a valid,
864                    //         NUL-terminated, `malloc`'d C string.
865                    let path_cstr = unsafe { CStr::from_ptr(result) };
866                    let path = Path::new(OsStr::from_bytes(path_cstr.to_bytes()));
867                    let resolver = if let Some(ref dirs) = debug_dir_paths {
868                        ElfResolver::open_with_debug_dirs(path, dirs)
869                    } else {
870                        ElfResolver::open(path)
871                    };
872                    // SAFETY: The string was `malloc`'d by the callback.
873                    unsafe { libc::free(result.cast()) };
874                    Ok(Some(Box::new(resolver?)))
875                }
876                _ => Ok(None),
877            }
878        })
879    } else {
880        builder
881    };
882
883    let symbolizer = builder.build();
884    let symbolizer_box = Box::new(symbolizer);
885    let () = set_last_err(blaze_err::OK);
886    Box::into_raw(symbolizer_box)
887}
888
889/// Free an instance of blazesym a symbolizer for C API.
890///
891/// # Safety
892/// The pointer must have been returned by [`blaze_symbolizer_new`] or
893/// [`blaze_symbolizer_new_opts`].
894#[no_mangle]
895pub unsafe extern "C" fn blaze_symbolizer_free(symbolizer: *mut blaze_symbolizer) {
896    if !symbolizer.is_null() {
897        drop(unsafe { Box::from_raw(symbolizer) });
898    }
899}
900
901
902/// Cache an ELF symbolization source.
903///
904/// Cache symbolization data of an ELF file.
905///
906/// The function sets the thread's last error to either
907/// [`blaze_err::OK`] to indicate success or a different error code
908/// associated with the problem encountered. Use [`blaze_err_last`] to
909/// retrieve this error.
910///
911/// # Safety
912/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
913/// - `cache` needs to point to a valid [`blaze_cache_src_process`] object
914#[no_mangle]
915pub unsafe extern "C" fn blaze_symbolize_cache_elf(
916    symbolizer: *mut blaze_symbolizer,
917    cache: *const blaze_cache_src_elf,
918) {
919    if !input_zeroed!(cache, blaze_cache_src_elf) {
920        let () = set_last_err(blaze_err::INVALID_INPUT);
921        return
922    }
923    let cache = input_sanitize!(cache, blaze_cache_src_elf);
924    let cache = cache::Cache::from(cache::Elf::from(cache));
925
926    // SAFETY: The caller ensures that the pointer is valid.
927    let symbolizer = unsafe { &*symbolizer };
928    let result = symbolizer.cache(&cache);
929    let err = result
930        .map(|()| blaze_err::OK)
931        .unwrap_or_else(|err| err.kind().into());
932    let () = set_last_err(err);
933}
934
935
936/// Cache VMA meta data associated with a process.
937///
938/// Cache VMA meta data associated with a process. This will speed up
939/// subsequent symbolization requests while also enabling symbolization
940/// of addresses belonging to processes that exited after being cache
941/// this way.
942///
943/// If this method fails, any previously cached data is left untouched
944/// and will be used subsequently as if no failure occurred. Put
945/// differently, this method is only effectful on the happy path.
946///
947/// The function sets the thread's last error to either
948/// [`blaze_err::OK`] to indicate success or a different error code
949/// associated with the problem encountered. Use [`blaze_err_last`] to
950/// retrieve this error.
951///
952/// # Safety
953/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
954/// - `cache` needs to point to a valid [`blaze_cache_src_process`] object
955#[no_mangle]
956pub unsafe extern "C" fn blaze_symbolize_cache_process(
957    symbolizer: *mut blaze_symbolizer,
958    cache: *const blaze_cache_src_process,
959) {
960    if !input_zeroed!(cache, blaze_cache_src_process) {
961        let () = set_last_err(blaze_err::INVALID_INPUT);
962        return
963    }
964    let cache = input_sanitize!(cache, blaze_cache_src_process);
965    let cache = cache::Cache::from(cache::Process::from(cache));
966
967    // SAFETY: The caller ensures that the pointer is valid.
968    let symbolizer = unsafe { &*symbolizer };
969    let result = symbolizer.cache(&cache);
970    let err = result
971        .map(|()| blaze_err::OK)
972        .unwrap_or_else(|err| err.kind().into());
973    let () = set_last_err(err);
974}
975
976fn make_cstr(src: &OsStr, cstr_last: &mut *mut c_char) -> *mut c_char {
977    let cstr = *cstr_last;
978    unsafe { ptr::copy_nonoverlapping(src.as_bytes().as_ptr(), cstr as *mut u8, src.len()) };
979    unsafe { *cstr.add(src.len()) = 0 };
980    *cstr_last = unsafe { cstr_last.add(src.len() + 1) };
981
982    cstr
983}
984
985fn convert_code_info(
986    code_info_in: Option<&CodeInfo>,
987    code_info_out: &mut blaze_symbolize_code_info,
988    cstr_last: &mut *mut c_char,
989) {
990    code_info_out.dir = code_info_in
991        .as_ref()
992        .and_then(|info| {
993            info.dir
994                .as_ref()
995                .map(|d| make_cstr(d.as_os_str(), cstr_last))
996        })
997        .unwrap_or_else(ptr::null_mut);
998    code_info_out.file = code_info_in
999        .as_ref()
1000        .map(|info| make_cstr(&info.file, cstr_last))
1001        .unwrap_or_else(ptr::null_mut);
1002    code_info_out.line = code_info_in
1003        .as_ref()
1004        .and_then(|info| info.line)
1005        .unwrap_or(0);
1006    code_info_out.column = code_info_in
1007        .as_ref()
1008        .and_then(|info| info.column)
1009        .unwrap_or(0);
1010}
1011
1012
1013/// Convert a [`Sym`] into the `blaze_sym` C correspondent.
1014pub(crate) fn convert_sym(
1015    sym: &Sym,
1016    sym_ref: &mut blaze_sym,
1017    inlined_last: &mut *mut blaze_symbolize_inlined_fn,
1018    cstr_last: &mut *mut c_char,
1019) {
1020    let name_ptr = make_cstr(OsStr::new(sym.name.as_ref()), cstr_last);
1021    let module_ptr = sym
1022        .module
1023        .as_deref()
1024        .map(|module| make_cstr(module, cstr_last))
1025        .unwrap_or(ptr::null_mut());
1026
1027    sym_ref.name = name_ptr;
1028    sym_ref.module = module_ptr;
1029    sym_ref.addr = sym.addr;
1030    sym_ref.offset = sym.offset;
1031    sym_ref.size = sym
1032        .size
1033        .map(|size| isize::try_from(size).unwrap_or(isize::MAX))
1034        .unwrap_or(-1);
1035    convert_code_info(sym.code_info.as_deref(), &mut sym_ref.code_info, cstr_last);
1036    sym_ref.inlined_cnt = sym.inlined.len();
1037    sym_ref.inlined = *inlined_last;
1038    sym_ref.reason = blaze_symbolize_reason::SUCCESS;
1039
1040    for inlined in sym.inlined.iter() {
1041        let inlined_ref = unsafe { &mut **inlined_last };
1042
1043        let name_ptr = make_cstr(OsStr::new(inlined.name.as_ref()), cstr_last);
1044        inlined_ref.name = name_ptr;
1045        convert_code_info(
1046            inlined.code_info.as_ref(),
1047            &mut inlined_ref.code_info,
1048            cstr_last,
1049        );
1050
1051        *inlined_last = unsafe { inlined_last.add(1) };
1052    }
1053}
1054
1055
1056/// Convert [`Sym`] objects to [`blaze_syms`] ones.
1057///
1058/// The returned pointer should be released using [`blaze_syms_free`] once
1059/// usage concluded.
1060fn convert_symbolizedresults_to_c(results: Vec<Symbolized>) -> *const blaze_syms {
1061    // Allocate a buffer to contain a blaze_syms, all
1062    // blaze_sym, and C strings of symbol and path.
1063    let (strtab_size, inlined_fn_cnt) = results.iter().fold((0, 0), |acc, sym| match sym {
1064        Symbolized::Sym(sym) => (acc.0 + sym.c_str_size(), acc.1 + sym.inlined.len()),
1065        Symbolized::Unknown(..) => acc,
1066    });
1067
1068    let buf_size = mem::size_of::<u64>()
1069        + strtab_size
1070        + mem::size_of::<blaze_syms>()
1071        + mem::size_of::<blaze_sym>() * results.len()
1072        + mem::size_of::<blaze_symbolize_inlined_fn>() * inlined_fn_cnt;
1073    let buf = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
1074    if buf.is_null() {
1075        return ptr::null()
1076    }
1077
1078    // Prepend a `u64` to store the size of the buffer.
1079    unsafe { *buf.cast::<u64>() = buf_size as u64 };
1080
1081    let syms_buf = unsafe { buf.add(mem::size_of::<u64>()) };
1082
1083    let syms_ptr = syms_buf.cast::<blaze_syms>();
1084    let mut syms_last = unsafe { (*syms_ptr).syms.as_mut_ptr() };
1085    let mut inlined_last = unsafe {
1086        syms_buf.add(mem::size_of::<blaze_syms>() + mem::size_of::<blaze_sym>() * results.len())
1087    } as *mut blaze_symbolize_inlined_fn;
1088    let mut cstr_last = unsafe {
1089        syms_buf.add(
1090            mem::size_of::<blaze_syms>()
1091                + mem::size_of::<blaze_sym>() * results.len()
1092                + mem::size_of::<blaze_symbolize_inlined_fn>() * inlined_fn_cnt,
1093        )
1094    } as *mut c_char;
1095
1096    unsafe { (*syms_ptr).cnt = results.len() };
1097
1098    // Convert all `Sym`s to `blazesym_sym`s.
1099    for sym in results {
1100        match sym {
1101            Symbolized::Sym(sym) => {
1102                let sym_ref = unsafe { &mut *syms_last };
1103                let () = convert_sym(&sym, sym_ref, &mut inlined_last, &mut cstr_last);
1104            }
1105            Symbolized::Unknown(reason) => {
1106                // Unknown symbols/addresses are just represented with all
1107                // fields set to zero (except for reason).
1108                // SAFETY: `syms_last` is pointing to a writable and properly
1109                //         aligned `blaze_sym` object.
1110                let () = unsafe { syms_last.write_bytes(0, 1) };
1111                let sym_ref = unsafe { &mut *syms_last };
1112                sym_ref.reason = reason.into();
1113            }
1114        }
1115
1116        syms_last = unsafe { syms_last.add(1) };
1117    }
1118
1119    syms_ptr
1120}
1121
1122unsafe fn blaze_symbolize_impl(
1123    symbolizer: *mut blaze_symbolizer,
1124    src: Source<'_>,
1125    inputs: Input<*const u64>,
1126    input_cnt: usize,
1127) -> *const blaze_syms {
1128    // SAFETY: The caller ensures that the pointer is valid.
1129    let symbolizer = unsafe { &*symbolizer };
1130    // SAFETY: The caller ensures that the pointer is valid and the count
1131    //         matches.
1132    let addrs = unsafe { slice_from_user_array(*inputs.as_inner_ref(), input_cnt) };
1133
1134    let input = match inputs {
1135        Input::AbsAddr(..) => Input::AbsAddr(addrs.deref()),
1136        Input::VirtOffset(..) => Input::VirtOffset(addrs.deref()),
1137        Input::FileOffset(..) => Input::FileOffset(addrs.deref()),
1138    };
1139
1140    let result = symbolizer.symbolize(&src, input);
1141    match result {
1142        Ok(results) if results.is_empty() => {
1143            let () = set_last_err(blaze_err::OK);
1144            ptr::null()
1145        }
1146        Ok(results) => {
1147            let result = convert_symbolizedresults_to_c(results);
1148            if result.is_null() {
1149                let () = set_last_err(blaze_err::OUT_OF_MEMORY);
1150            } else {
1151                let () = set_last_err(blaze_err::OK);
1152            }
1153            result
1154        }
1155        Err(err) => {
1156            let () = set_last_err(err.kind().into());
1157            ptr::null_mut()
1158        }
1159    }
1160}
1161
1162
1163/// Symbolize a list of process absolute addresses.
1164///
1165/// On success, the function returns a [`blaze_syms`] containing an
1166/// array of `abs_addr_cnt` [`blaze_sym`] objects. The returned object
1167/// should be released using [`blaze_syms_free`] once it is no longer
1168/// needed.
1169///
1170/// On error, the function returns `NULL` and sets the thread's last error to
1171/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
1172/// error.
1173///
1174/// # Safety
1175/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
1176/// - `src` needs to point to a valid [`blaze_symbolize_src_process`] object
1177/// - `abs_addrs` needs to point to an array of `abs_addr_cnt` addresses
1178#[no_mangle]
1179pub unsafe extern "C" fn blaze_symbolize_process_abs_addrs(
1180    symbolizer: *mut blaze_symbolizer,
1181    src: *const blaze_symbolize_src_process,
1182    abs_addrs: *const Addr,
1183    abs_addr_cnt: usize,
1184) -> *const blaze_syms {
1185    if !input_zeroed!(src, blaze_symbolize_src_process) {
1186        let () = set_last_err(blaze_err::INVALID_INPUT);
1187        return ptr::null()
1188    }
1189    let src = input_sanitize!(src, blaze_symbolize_src_process);
1190    let src = Source::from(Process::from(src));
1191
1192    unsafe { blaze_symbolize_impl(symbolizer, src, Input::AbsAddr(abs_addrs), abs_addr_cnt) }
1193}
1194
1195
1196/// Symbolize a list of kernel absolute addresses.
1197///
1198/// On success, the function returns a [`blaze_syms`] containing an
1199/// array of `abs_addr_cnt` [`blaze_sym`] objects. The returned object
1200/// should be released using [`blaze_syms_free`] once it is no longer
1201/// needed.
1202///
1203/// On error, the function returns `NULL` and sets the thread's last error to
1204/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
1205/// error.
1206///
1207/// # Safety
1208/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
1209/// - `src` needs to point to a valid [`blaze_symbolize_src_kernel`] object
1210/// - `abs_addrs` needs to point to an array of `abs_addr_cnt` addresses
1211#[no_mangle]
1212pub unsafe extern "C" fn blaze_symbolize_kernel_abs_addrs(
1213    symbolizer: *mut blaze_symbolizer,
1214    src: *const blaze_symbolize_src_kernel,
1215    abs_addrs: *const Addr,
1216    abs_addr_cnt: usize,
1217) -> *const blaze_syms {
1218    if !input_zeroed!(src, blaze_symbolize_src_kernel) {
1219        let () = set_last_err(blaze_err::INVALID_INPUT);
1220        return ptr::null()
1221    }
1222    let src = input_sanitize!(src, blaze_symbolize_src_kernel);
1223    let src = Source::from(Kernel::from(src));
1224
1225    unsafe { blaze_symbolize_impl(symbolizer, src, Input::AbsAddr(abs_addrs), abs_addr_cnt) }
1226}
1227
1228
1229/// Symbolize virtual offsets in an ELF file.
1230///
1231/// On success, the function returns a [`blaze_syms`] containing an
1232/// array of `virt_offset_cnt` [`blaze_sym`] objects. The returned
1233/// object should be released using [`blaze_syms_free`] once it is no
1234/// longer needed.
1235///
1236/// On error, the function returns `NULL` and sets the thread's last error to
1237/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
1238/// error.
1239///
1240/// # Safety
1241/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
1242/// - `src` needs to point to a valid [`blaze_symbolize_src_elf`] object
1243/// - `virt_offsets` needs to point to an array of `virt_offset_cnt` addresses
1244#[no_mangle]
1245pub unsafe extern "C" fn blaze_symbolize_elf_virt_offsets(
1246    symbolizer: *mut blaze_symbolizer,
1247    src: *const blaze_symbolize_src_elf,
1248    virt_offsets: *const Addr,
1249    virt_offset_cnt: usize,
1250) -> *const blaze_syms {
1251    if !input_zeroed!(src, blaze_symbolize_src_elf) {
1252        let () = set_last_err(blaze_err::INVALID_INPUT);
1253        return ptr::null()
1254    }
1255    let src = input_sanitize!(src, blaze_symbolize_src_elf);
1256    let src = Source::from(Elf::from(src));
1257
1258    unsafe {
1259        blaze_symbolize_impl(
1260            symbolizer,
1261            src,
1262            Input::VirtOffset(virt_offsets),
1263            virt_offset_cnt,
1264        )
1265    }
1266}
1267
1268/// Symbolize file offsets in an ELF file.
1269///
1270/// On success, the function returns a [`blaze_syms`] containing an
1271/// array of `file_offset_cnt` [`blaze_sym`] objects. The returned
1272/// object should be released using [`blaze_syms_free`] once it is no
1273/// longer needed.
1274///
1275/// On error, the function returns `NULL` and sets the thread's last error to
1276/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
1277/// error.
1278///
1279/// # Safety
1280/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
1281/// - `src` needs to point to a valid [`blaze_symbolize_src_elf`] object
1282/// - `file_offsets` needs to point to an array of `file_offset_cnt` addresses
1283#[no_mangle]
1284pub unsafe extern "C" fn blaze_symbolize_elf_file_offsets(
1285    symbolizer: *mut blaze_symbolizer,
1286    src: *const blaze_symbolize_src_elf,
1287    file_offsets: *const Addr,
1288    file_offset_cnt: usize,
1289) -> *const blaze_syms {
1290    if !input_zeroed!(src, blaze_symbolize_src_elf) {
1291        let () = set_last_err(blaze_err::INVALID_INPUT);
1292        return ptr::null()
1293    }
1294    let src = input_sanitize!(src, blaze_symbolize_src_elf);
1295    let src = Source::from(Elf::from(src));
1296
1297    unsafe {
1298        blaze_symbolize_impl(
1299            symbolizer,
1300            src,
1301            Input::FileOffset(file_offsets),
1302            file_offset_cnt,
1303        )
1304    }
1305}
1306
1307
1308/// Symbolize virtual offsets using "raw" Gsym data.
1309///
1310/// On success, the function returns a [`blaze_syms`] containing an
1311/// array of `virt_offset_cnt` [`blaze_sym`] objects. The returned
1312/// object should be released using [`blaze_syms_free`] once it is no
1313/// longer needed.
1314///
1315/// On error, the function returns `NULL` and sets the thread's last error to
1316/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
1317/// error.
1318///
1319/// # Safety
1320/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
1321/// - `src` needs to point to a valid [`blaze_symbolize_src_gsym_data`] object
1322/// - `virt_offsets` needs to point to an array of `virt_offset_cnt` addresses
1323#[no_mangle]
1324pub unsafe extern "C" fn blaze_symbolize_gsym_data_virt_offsets(
1325    symbolizer: *mut blaze_symbolizer,
1326    src: *const blaze_symbolize_src_gsym_data,
1327    virt_offsets: *const Addr,
1328    virt_offset_cnt: usize,
1329) -> *const blaze_syms {
1330    if !input_zeroed!(src, blaze_symbolize_src_gsym_data) {
1331        let () = set_last_err(blaze_err::INVALID_INPUT);
1332        return ptr::null()
1333    }
1334    let src = input_sanitize!(src, blaze_symbolize_src_gsym_data);
1335    let src = Source::from(GsymData::from(src));
1336    unsafe {
1337        blaze_symbolize_impl(
1338            symbolizer,
1339            src,
1340            Input::VirtOffset(virt_offsets),
1341            virt_offset_cnt,
1342        )
1343    }
1344}
1345
1346
1347/// Symbolize virtual offsets in a Gsym file.
1348///
1349/// On success, the function returns a [`blaze_syms`] containing an
1350/// array of `virt_offset_cnt` [`blaze_sym`] objects. The returned
1351/// object should be released using [`blaze_syms_free`] once it is no
1352/// longer needed.
1353///
1354/// On error, the function returns `NULL` and sets the thread's last error to
1355/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
1356/// error.
1357///
1358/// # Safety
1359/// - `symbolizer` needs to point to a valid [`blaze_symbolizer`] object
1360/// - `src` needs to point to a valid [`blaze_symbolize_src_gsym_file`] object
1361/// - `virt_offsets` needs to point to an array of `virt_offset_cnt` addresses
1362#[no_mangle]
1363pub unsafe extern "C" fn blaze_symbolize_gsym_file_virt_offsets(
1364    symbolizer: *mut blaze_symbolizer,
1365    src: *const blaze_symbolize_src_gsym_file,
1366    virt_offsets: *const Addr,
1367    virt_offset_cnt: usize,
1368) -> *const blaze_syms {
1369    if !input_zeroed!(src, blaze_symbolize_src_gsym_file) {
1370        let () = set_last_err(blaze_err::INVALID_INPUT);
1371        return ptr::null()
1372    }
1373    let src = input_sanitize!(src, blaze_symbolize_src_gsym_file);
1374    let src = Source::from(GsymFile::from(src));
1375
1376    unsafe {
1377        blaze_symbolize_impl(
1378            symbolizer,
1379            src,
1380            Input::VirtOffset(virt_offsets),
1381            virt_offset_cnt,
1382        )
1383    }
1384}
1385
1386
1387/// Free an array returned by any of the `blaze_symbolize_*` variants.
1388///
1389/// # Safety
1390/// The pointer must have been returned by any of the `blaze_symbolize_*`
1391/// variants.
1392#[no_mangle]
1393pub unsafe extern "C" fn blaze_syms_free(syms: *const blaze_syms) {
1394    if syms.is_null() {
1395        return
1396    }
1397
1398    // Retrieve back the buffer with the `u64` size header.
1399    let buf = unsafe { syms.byte_sub(mem::size_of::<u64>()).cast::<u8>().cast_mut() };
1400    let size = unsafe { *buf.cast::<u64>() } as usize;
1401    unsafe { dealloc(buf, Layout::from_size_align(size, 8).unwrap()) };
1402}
1403
1404
1405#[cfg(test)]
1406mod tests {
1407    use super::*;
1408
1409    use std::borrow::Cow;
1410    use std::ffi::CString;
1411    use std::fs::copy;
1412    use std::fs::read as read_file;
1413    use std::fs::remove_file;
1414    use std::hint::black_box;
1415    use std::io::Error;
1416    use std::os::unix::ffi::OsStringExt as _;
1417    use std::slice;
1418
1419    use blazesym::inspect;
1420    use blazesym::normalize;
1421    use blazesym::symbolize::InlinedFn;
1422    use blazesym::symbolize::Reason;
1423    use blazesym::Pid;
1424
1425    use tempfile::tempdir;
1426
1427    use test_tag::tag;
1428
1429    use crate::blaze_err_last;
1430
1431
1432    /// Check that various types have expected sizes.
1433    #[tag(miri)]
1434    #[test]
1435    #[cfg(target_pointer_width = "64")]
1436    fn type_sizes() {
1437        assert_eq!(mem::size_of::<blaze_cache_src_elf>(), 32);
1438        assert_eq!(mem::size_of::<blaze_cache_src_process>(), 32);
1439        assert_eq!(mem::size_of::<blaze_symbolize_src_elf>(), 40);
1440        assert_eq!(mem::size_of::<blaze_symbolize_src_kernel>(), 48);
1441        assert_eq!(mem::size_of::<blaze_symbolize_src_process>(), 32);
1442        assert_eq!(mem::size_of::<blaze_symbolize_src_gsym_data>(), 40);
1443        assert_eq!(mem::size_of::<blaze_symbolize_src_gsym_file>(), 32);
1444        assert_eq!(mem::size_of::<blaze_symbolizer_opts>(), 48);
1445        assert_eq!(mem::size_of::<blaze_symbolize_code_info>(), 32);
1446        assert_eq!(mem::size_of::<blaze_symbolize_inlined_fn>(), 48);
1447        assert_eq!(mem::size_of::<blaze_sym>(), 104);
1448    }
1449
1450    /// Exercise the `Debug` representation of various types.
1451    #[tag(miri)]
1452    #[test]
1453    fn debug_repr() {
1454        let elf = blaze_symbolize_src_elf {
1455            type_size: 24,
1456            ..Default::default()
1457        };
1458        assert_eq!(
1459            format!("{elf:?}"),
1460            "blaze_symbolize_src_elf { type_size: 24, path: 0x0, debug_syms: false, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1461        );
1462
1463        let kernel = blaze_symbolize_src_kernel {
1464            type_size: 32,
1465            debug_syms: true,
1466            ..Default::default()
1467        };
1468        assert_eq!(
1469            format!("{kernel:?}"),
1470            "blaze_symbolize_src_kernel { type_size: 32, kallsyms: 0x0, vmlinux: 0x0, debug_syms: true, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1471        );
1472
1473        let process = blaze_symbolize_src_process {
1474            type_size: 16,
1475            pid: 1337,
1476            debug_syms: true,
1477            ..Default::default()
1478        };
1479        assert_eq!(
1480            format!("{process:?}"),
1481            "blaze_symbolize_src_process { type_size: 16, pid: 1337, debug_syms: true, perf_map: false, no_map_files: false, no_vdso: false, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1482        );
1483
1484        let gsym_data = blaze_symbolize_src_gsym_data {
1485            type_size: 24,
1486            data: ptr::null(),
1487            data_len: 0,
1488            reserved: [0; 16],
1489        };
1490        assert_eq!(
1491            format!("{gsym_data:?}"),
1492            "blaze_symbolize_src_gsym_data { type_size: 24, data: 0x0, data_len: 0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1493        );
1494
1495        let gsym_file = blaze_symbolize_src_gsym_file {
1496            type_size: 16,
1497            path: ptr::null(),
1498            reserved: [0; 16],
1499        };
1500        assert_eq!(
1501            format!("{gsym_file:?}"),
1502            "blaze_symbolize_src_gsym_file { type_size: 16, path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1503        );
1504
1505        let sym = blaze_sym {
1506            name: ptr::null(),
1507            module: ptr::null(),
1508            addr: 0x1337,
1509            offset: 24,
1510            size: 16,
1511            code_info: blaze_symbolize_code_info {
1512                dir: ptr::null(),
1513                file: ptr::null(),
1514                line: 42,
1515                column: 1,
1516                reserved: [0; 10],
1517            },
1518            inlined_cnt: 0,
1519            inlined: ptr::null(),
1520            reason: blaze_symbolize_reason::UNSUPPORTED,
1521            reserved: [0; 15],
1522        };
1523        assert_eq!(
1524            format!("{sym:?}"),
1525            "blaze_sym { name: 0x0, module: 0x0, addr: 4919, offset: 24, size: 16, code_info: blaze_symbolize_code_info { dir: 0x0, file: 0x0, line: 42, column: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, inlined_cnt: 0, inlined: 0x0, reason: blaze_symbolize_reason(6), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
1526        );
1527
1528        let inlined = blaze_symbolize_inlined_fn {
1529            name: ptr::null(),
1530            code_info: blaze_symbolize_code_info {
1531                dir: ptr::null(),
1532                file: ptr::null(),
1533                line: 42,
1534                column: 1,
1535                reserved: [0; 10],
1536            },
1537            reserved: [0; 8],
1538        };
1539        assert_eq!(
1540            format!("{inlined:?}"),
1541            "blaze_symbolize_inlined_fn { name: 0x0, code_info: blaze_symbolize_code_info { dir: 0x0, file: 0x0, line: 42, column: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reserved: [0, 0, 0, 0, 0, 0, 0, 0] }"
1542        );
1543
1544        let syms = blaze_syms { cnt: 0, syms: [] };
1545        assert_eq!(format!("{syms:?}"), "blaze_syms { cnt: 0, syms: [] }");
1546
1547        let opts = blaze_symbolizer_opts {
1548            type_size: 16,
1549            demangle: true,
1550            ..Default::default()
1551        };
1552        assert_eq!(
1553            format!("{opts:?}"),
1554            "blaze_symbolizer_opts { type_size: 16, debug_dirs: 0x0, debug_dirs_len: 0, auto_reload: false, code_info: false, inlined_fns: false, demangle: true, _reserved1: [0, 0, 0, 0], process_dispatch: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0] }"
1555        );
1556    }
1557
1558    /// Make sure that we can stringify symbolization reasons as expected.
1559    #[tag(miri)]
1560    #[test]
1561    fn reason_stringification() {
1562        let data = [
1563            (Reason::Unmapped, blaze_symbolize_reason::UNMAPPED),
1564            (
1565                Reason::InvalidFileOffset,
1566                blaze_symbolize_reason::INVALID_FILE_OFFSET,
1567            ),
1568            (
1569                Reason::MissingComponent,
1570                blaze_symbolize_reason::MISSING_COMPONENT,
1571            ),
1572            (Reason::MissingSyms, blaze_symbolize_reason::MISSING_SYMS),
1573            (Reason::Unsupported, blaze_symbolize_reason::UNSUPPORTED),
1574            (Reason::UnknownAddr, blaze_symbolize_reason::UNKNOWN_ADDR),
1575            (Reason::IgnoredError, blaze_symbolize_reason::IGNORED_ERROR),
1576        ];
1577
1578        for (reason, expected) in data {
1579            assert_eq!(blaze_symbolize_reason::from(reason), expected);
1580            let cstr = unsafe { CStr::from_ptr(blaze_symbolize_reason_str(expected)) };
1581            let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
1582            assert_eq!(cstr, expected);
1583        }
1584    }
1585
1586
1587    /// Check that we can convert a [`blaze_symbolize_src_kernel`]
1588    /// reference into a [`Kernel`].
1589    #[tag(miri)]
1590    #[test]
1591    fn kernel_conversion() {
1592        let kernel = blaze_symbolize_src_kernel::default();
1593        let kernel = Kernel::from(kernel);
1594        assert_eq!(kernel.kallsyms, MaybeDefault::Default);
1595        assert_eq!(kernel.vmlinux, MaybeDefault::Default);
1596
1597        let kernel = blaze_symbolize_src_kernel {
1598            kallsyms: b"\0" as *const _ as *const c_char,
1599            vmlinux: b"\0" as *const _ as *const c_char,
1600            ..Default::default()
1601        };
1602        let kernel = Kernel::from(kernel);
1603        assert_eq!(kernel.kallsyms, MaybeDefault::None);
1604        assert_eq!(kernel.vmlinux, MaybeDefault::None);
1605
1606        let kernel = blaze_symbolize_src_kernel {
1607            kallsyms: b"/proc/kallsyms\0" as *const _ as *const c_char,
1608            vmlinux: b"/boot/vmlinux\0" as *const _ as *const c_char,
1609            debug_syms: false,
1610            ..Default::default()
1611        };
1612
1613        let kernel = Kernel::from(kernel);
1614        assert_eq!(
1615            kernel.kallsyms,
1616            MaybeDefault::Some(PathBuf::from("/proc/kallsyms"))
1617        );
1618        assert_eq!(
1619            kernel.vmlinux,
1620            MaybeDefault::Some(PathBuf::from("/boot/vmlinux"))
1621        );
1622    }
1623
1624    /// Check that we can convert a [`blaze_cache_src_process`] into a
1625    /// [`cache::Process`].
1626    #[tag(miri)]
1627    #[test]
1628    fn cache_process_conversion() {
1629        let process = blaze_cache_src_process {
1630            pid: 42,
1631            cache_vmas: false,
1632            ..Default::default()
1633        };
1634        let process = cache::Process::from(process);
1635        assert_eq!(process.pid, Pid::from(42))
1636    }
1637
1638    /// Test the Rust to C symbol conversion.
1639    #[tag(miri)]
1640    #[test]
1641    fn symbol_conversion() {
1642        fn touch<X: Clone>(x: &X) {
1643            let x = x.clone();
1644            let _x = black_box(x);
1645        }
1646
1647        fn touch_cstr(s: *const c_char) {
1648            if !s.is_null() {
1649                let s = unsafe { CStr::from_ptr(s) }.to_bytes();
1650                let _x = black_box(s);
1651            }
1652        }
1653
1654        fn touch_code_info(code_info: &blaze_symbolize_code_info) {
1655            let blaze_symbolize_code_info {
1656                dir,
1657                file,
1658                line,
1659                column,
1660                reserved: _,
1661            } = code_info;
1662
1663            let _x = touch_cstr(*dir);
1664            let _x = touch_cstr(*file);
1665            let _x = touch(line);
1666            let _x = touch(column);
1667        }
1668
1669        /// Touch all "members" of a [`blaze_syms`].
1670        fn touch_syms(syms: *const blaze_syms) {
1671            let syms = unsafe { &*syms };
1672            for i in 0..syms.cnt {
1673                let sym = unsafe { &*syms.syms.as_slice().as_ptr().add(i) };
1674                let blaze_sym {
1675                    name,
1676                    module,
1677                    addr,
1678                    offset,
1679                    size,
1680                    code_info,
1681                    inlined_cnt,
1682                    inlined,
1683                    reason,
1684                    reserved: _,
1685                } = sym;
1686
1687                let () = touch_cstr(*name);
1688                let () = touch_cstr(*module);
1689                let _x = touch(addr);
1690                let _x = touch(offset);
1691                let _x = touch(size);
1692                let () = touch_code_info(code_info);
1693
1694                for j in 0..*inlined_cnt {
1695                    let inlined_fn = unsafe { &*inlined.add(j) };
1696                    let blaze_symbolize_inlined_fn {
1697                        name,
1698                        code_info,
1699                        reserved: _,
1700                    } = inlined_fn;
1701                    let () = touch_cstr(*name);
1702                    let () = touch_code_info(code_info);
1703                }
1704                let () = touch(reason);
1705            }
1706        }
1707
1708        // Empty list of symbols.
1709        let results = vec![];
1710        let syms = convert_symbolizedresults_to_c(results);
1711        assert!(!syms.is_null());
1712
1713        let () = touch_syms(syms);
1714        let () = unsafe { blaze_syms_free(syms) };
1715
1716        // A single symbol with inlined function information.
1717        let results = vec![Symbolized::Sym(Sym {
1718            name: "test".into(),
1719            module: Some(Cow::from(OsStr::new("module"))),
1720            addr: 0x1337,
1721            offset: 0x1338,
1722            size: Some(42),
1723            code_info: Some(Box::new(CodeInfo {
1724                dir: None,
1725                file: OsStr::new("a-file").into(),
1726                line: Some(42),
1727                column: Some(43),
1728                _non_exhaustive: (),
1729            })),
1730            inlined: vec![InlinedFn {
1731                name: "inlined_fn".into(),
1732                code_info: Some(CodeInfo {
1733                    dir: Some(Path::new("/some/dir").into()),
1734                    file: OsStr::new("another-file").into(),
1735                    line: Some(42),
1736                    column: Some(43),
1737                    _non_exhaustive: (),
1738                }),
1739                _non_exhaustive: (),
1740            }]
1741            .into_boxed_slice(),
1742            _non_exhaustive: (),
1743        })];
1744        let syms = convert_symbolizedresults_to_c(results);
1745        assert!(!syms.is_null());
1746        let () = touch_syms(syms);
1747        let () = unsafe { blaze_syms_free(syms) };
1748
1749        // One symbol and some unsymbolized values.
1750        let results = vec![
1751            Symbolized::Unknown(Reason::UnknownAddr),
1752            Symbolized::Sym(Sym {
1753                name: "test".into(),
1754                module: Some(Cow::from(OsStr::new("module"))),
1755                addr: 0x1337,
1756                offset: 0x1338,
1757                size: None,
1758                code_info: None,
1759                inlined: vec![InlinedFn {
1760                    name: "inlined_fn".into(),
1761                    code_info: None,
1762                    _non_exhaustive: (),
1763                }]
1764                .into_boxed_slice(),
1765                _non_exhaustive: (),
1766            }),
1767            Symbolized::Unknown(Reason::InvalidFileOffset),
1768        ];
1769        let syms = convert_symbolizedresults_to_c(results);
1770        assert!(!syms.is_null());
1771        let () = touch_syms(syms);
1772        let () = unsafe { blaze_syms_free(syms) };
1773    }
1774
1775    /// Make sure that we can create and free a symbolizer instance.
1776    #[tag(miri)]
1777    #[test]
1778    fn symbolizer_creation() {
1779        let symbolizer = blaze_symbolizer_new();
1780        let () = unsafe { blaze_symbolizer_free(symbolizer) };
1781    }
1782
1783    /// Make sure that we can create and free a symbolizer instance with the
1784    /// provided options.
1785    #[tag(miri)]
1786    #[test]
1787    fn symbolizer_creation_with_opts() {
1788        let opts = blaze_symbolizer_opts {
1789            demangle: true,
1790            ..Default::default()
1791        };
1792
1793        let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
1794        let () = unsafe { blaze_symbolizer_free(symbolizer) };
1795    }
1796
1797    /// Make sure that we can symbolize an address using ELF, DWARF, and
1798    /// GSYM.
1799    #[test]
1800    fn symbolize_elf_dwarf_gsym() {
1801        fn test<F>(symbolize: F, has_code_info: bool)
1802        where
1803            F: FnOnce(*mut blaze_symbolizer, *const Addr, usize) -> *const blaze_syms,
1804        {
1805            let symbolizer = blaze_symbolizer_new();
1806            let addrs = [0x2000200];
1807            let result = symbolize(symbolizer, addrs.as_ptr(), addrs.len());
1808
1809            assert!(!result.is_null());
1810
1811            let result = unsafe { &*result };
1812            assert_eq!(result.cnt, 1);
1813            let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
1814            let sym = &syms[0];
1815            assert_eq!(
1816                unsafe { CStr::from_ptr(sym.name) },
1817                CStr::from_bytes_with_nul(b"factorial\0").unwrap()
1818            );
1819            assert_eq!(sym.reason, blaze_symbolize_reason::SUCCESS);
1820            assert_eq!(sym.addr, 0x2000200);
1821            assert_eq!(sym.offset, 0);
1822            assert!(sym.size > 0);
1823
1824            if has_code_info {
1825                assert!(!sym.code_info.dir.is_null());
1826                assert!(!sym.code_info.file.is_null());
1827                assert_eq!(
1828                    unsafe { CStr::from_ptr(sym.code_info.file) },
1829                    CStr::from_bytes_with_nul(b"test-stable-addrs.c\0").unwrap()
1830                );
1831                assert_eq!(sym.code_info.line, 10);
1832            } else {
1833                assert!(sym.code_info.dir.is_null());
1834                assert!(sym.code_info.file.is_null());
1835                assert_eq!(sym.code_info.line, 0);
1836            }
1837
1838            let () = unsafe { blaze_syms_free(result) };
1839            let () = unsafe { blaze_symbolizer_free(symbolizer) };
1840        }
1841
1842        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
1843            .join("..")
1844            .join("data")
1845            .join("test-stable-addrs-no-dwarf.bin");
1846        let path_c = CString::new(path.to_str().unwrap()).unwrap();
1847        let elf_src = blaze_symbolize_src_elf {
1848            path: path_c.as_ptr(),
1849            debug_syms: true,
1850            ..Default::default()
1851        };
1852
1853        let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
1854            blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs, addr_cnt)
1855        };
1856        test(symbolize, false);
1857
1858        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
1859            .join("..")
1860            .join("data")
1861            .join("test-stable-addrs-stripped-elf-with-dwarf.bin");
1862        let path_c = CString::new(path.to_str().unwrap()).unwrap();
1863        let elf_src = blaze_symbolize_src_elf {
1864            path: path_c.as_ptr(),
1865            debug_syms: true,
1866            ..Default::default()
1867        };
1868
1869        let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
1870            blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs, addr_cnt)
1871        };
1872        test(symbolize, true);
1873
1874        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
1875            .join("..")
1876            .join("data")
1877            .join("test-stable-addrs.gsym");
1878        let path_c = CString::new(path.to_str().unwrap()).unwrap();
1879        let gsym_src = blaze_symbolize_src_gsym_file {
1880            path: path_c.as_ptr(),
1881            ..Default::default()
1882        };
1883
1884        let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
1885            blaze_symbolize_gsym_file_virt_offsets(symbolizer, &gsym_src, addrs, addr_cnt)
1886        };
1887        test(symbolize, true);
1888
1889        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
1890            .join("..")
1891            .join("data")
1892            .join("test-stable-addrs.gsym");
1893        let data = read_file(path).unwrap();
1894        let gsym_src = blaze_symbolize_src_gsym_data {
1895            data: data.as_ptr(),
1896            data_len: data.len(),
1897            ..Default::default()
1898        };
1899
1900        let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
1901            blaze_symbolize_gsym_data_virt_offsets(symbolizer, &gsym_src, addrs, addr_cnt)
1902        };
1903        test(symbolize, true);
1904    }
1905
1906
1907    /// Check that we can symbolize a file offset in an ELF file.
1908    #[test]
1909    #[cfg_attr(
1910        not(target_pointer_width = "64"),
1911        ignore = "loads 64 bit shared object"
1912    )]
1913    fn symbolize_elf_file_offset() {
1914        let test_so = Path::new(&env!("CARGO_MANIFEST_DIR"))
1915            .join("..")
1916            .join("data")
1917            .join("libtest-so.so")
1918            .canonicalize()
1919            .unwrap();
1920        let so_cstr = CString::new(test_so.clone().into_os_string().into_vec()).unwrap();
1921        let handle = unsafe { libc::dlopen(so_cstr.as_ptr(), libc::RTLD_NOW) };
1922        assert!(!handle.is_null());
1923
1924        let the_answer_addr = unsafe { libc::dlsym(handle, "the_answer\0".as_ptr().cast()) };
1925        assert!(!the_answer_addr.is_null());
1926
1927        let normalizer = normalize::Normalizer::new();
1928        let normalized = normalizer
1929            .normalize_user_addrs(Pid::Slf, [the_answer_addr as Addr].as_slice())
1930            .unwrap();
1931        assert_eq!(normalized.outputs.len(), 1);
1932        assert_eq!(normalized.meta.len(), 1);
1933
1934        let rc = unsafe { libc::dlclose(handle) };
1935        assert_eq!(rc, 0, "{}", Error::last_os_error());
1936
1937        let output = normalized.outputs[0];
1938        let meta = &normalized.meta[output.1];
1939        assert_eq!(meta.as_elf().unwrap().path, test_so);
1940
1941        let symbolizer = blaze_symbolizer_new();
1942        let elf_src = blaze_symbolize_src_elf {
1943            path: so_cstr.as_ptr(),
1944            ..Default::default()
1945        };
1946        let offsets = [output.0];
1947        let result = unsafe {
1948            blaze_symbolize_elf_file_offsets(
1949                symbolizer,
1950                &elf_src,
1951                offsets.as_slice().as_ptr(),
1952                offsets.len(),
1953            )
1954        };
1955        assert!(!result.is_null());
1956
1957        let result = unsafe { &*result };
1958        assert_eq!(result.cnt, 1);
1959
1960        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
1961        let sym = &syms[0];
1962        assert!(!sym.name.is_null());
1963        assert!(!sym.module.is_null());
1964        assert!(sym.size > 0);
1965        assert_eq!(
1966            unsafe { CStr::from_ptr(sym.name) },
1967            CStr::from_bytes_with_nul(b"the_answer\0").unwrap()
1968        );
1969
1970        let () = unsafe { blaze_syms_free(result) };
1971        let () = unsafe { blaze_symbolizer_free(symbolizer) };
1972    }
1973
1974    /// Check that we can symbolize data in a non-existent ELF binary after
1975    /// caching it.
1976    #[tag(other_os)]
1977    #[test]
1978    fn symbolize_elf_cached() {
1979        let dir = tempdir().unwrap();
1980        let path__ = Path::new(&env!("CARGO_MANIFEST_DIR"))
1981            .join("..")
1982            .join("data")
1983            .join("test-stable-addrs.bin");
1984        let path = dir.path().join("test-stable-addrs-temporary.bin");
1985        let _count = copy(&path__, &path).unwrap();
1986
1987        let symbolizer = blaze_symbolizer_new();
1988
1989        let path_c = CString::new(path.to_str().unwrap()).unwrap();
1990        let cache = blaze_cache_src_elf {
1991            path: path_c.as_ptr(),
1992            ..Default::default()
1993        };
1994        let () = unsafe { blaze_symbolize_cache_elf(symbolizer, &cache) };
1995        assert_eq!(blaze_err_last(), blaze_err::OK);
1996
1997        let () = remove_file(&path).unwrap();
1998
1999        let src = blaze_symbolize_src_elf {
2000            path: path_c.as_ptr(),
2001            debug_syms: false,
2002            ..Default::default()
2003        };
2004        let addrs = [0x2000200];
2005        let result = unsafe {
2006            blaze_symbolize_elf_virt_offsets(symbolizer, &src, addrs.as_ptr(), addrs.len())
2007        };
2008        assert!(!result.is_null());
2009
2010        let result = unsafe { &*result };
2011        assert_eq!(result.cnt, 1);
2012        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2013        let sym = &syms[0];
2014
2015        assert_eq!(
2016            unsafe { CStr::from_ptr(sym.name) },
2017            CStr::from_bytes_with_nul(b"factorial\0").unwrap()
2018        );
2019        assert_eq!(sym.reason, blaze_symbolize_reason::SUCCESS);
2020        assert_eq!(sym.addr, 0x2000200);
2021
2022        let () = unsafe { blaze_syms_free(result) };
2023        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2024    }
2025
2026    /// Symbolize an address inside a DWARF file, with and without
2027    /// auto-demangling enabled.
2028    #[test]
2029    fn symbolize_dwarf_demangle() {
2030        fn test(path: &Path, addr: Addr) -> Result<(), ()> {
2031            let opts = blaze_symbolizer_opts {
2032                code_info: true,
2033                inlined_fns: true,
2034                ..Default::default()
2035            };
2036
2037            let path_c = CString::new(path.to_str().unwrap()).unwrap();
2038            let elf_src = blaze_symbolize_src_elf {
2039                path: path_c.as_ptr(),
2040                debug_syms: true,
2041                ..Default::default()
2042            };
2043
2044            let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
2045            let addrs = [addr];
2046            let result = unsafe {
2047                blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
2048            };
2049            assert!(!result.is_null());
2050
2051            let result = unsafe { &*result };
2052            assert_eq!(result.cnt, 1);
2053            let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2054            let sym = &syms[0];
2055            let name = unsafe { CStr::from_ptr(sym.name) };
2056            assert!(
2057                name.to_str().unwrap().contains("test13test_function"),
2058                "{name:?}"
2059            );
2060
2061            if sym.inlined_cnt == 0 {
2062                let () = unsafe { blaze_syms_free(result) };
2063                let () = unsafe { blaze_symbolizer_free(symbolizer) };
2064                return Err(())
2065            }
2066
2067            assert_eq!(sym.inlined_cnt, 1);
2068            let name = unsafe { CStr::from_ptr((*sym.inlined).name) };
2069            assert!(
2070                name.to_str().unwrap().contains("test12inlined_call"),
2071                "{name:?}"
2072            );
2073
2074            let () = unsafe { blaze_syms_free(result) };
2075            let () = unsafe { blaze_symbolizer_free(symbolizer) };
2076
2077            // Do it again, this time with demangling enabled.
2078            let opts = blaze_symbolizer_opts {
2079                code_info: true,
2080                inlined_fns: true,
2081                demangle: true,
2082                ..Default::default()
2083            };
2084
2085            let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
2086            let addrs = [addr];
2087            let result = unsafe {
2088                blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
2089            };
2090            assert!(!result.is_null());
2091
2092            let result = unsafe { &*result };
2093            assert_eq!(result.cnt, 1);
2094            let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2095            let sym = &syms[0];
2096            assert_eq!(
2097                unsafe { CStr::from_ptr(sym.name) },
2098                CStr::from_bytes_with_nul(b"test::test_function\0").unwrap()
2099            );
2100            assert_eq!(unsafe { CStr::from_ptr(sym.module) }, &*path_c);
2101
2102            assert_eq!(sym.inlined_cnt, 1);
2103            assert_eq!(
2104                unsafe { CStr::from_ptr((*sym.inlined).name) },
2105                CStr::from_bytes_with_nul(b"test::inlined_call\0").unwrap()
2106            );
2107
2108            let () = unsafe { blaze_syms_free(result) };
2109            let () = unsafe { blaze_symbolizer_free(symbolizer) };
2110            Ok(())
2111        }
2112
2113        let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
2114            .join("..")
2115            .join("data")
2116            .join("test-rs.bin");
2117        let elf = inspect::source::Elf::new(&test_dwarf);
2118        let src = inspect::source::Source::Elf(elf);
2119
2120        let inspector = inspect::Inspector::new();
2121        let results = inspector
2122            .lookup(&src, &["_RNvCs69hjMPjVIJK_4test13test_function"])
2123            .unwrap()
2124            .into_iter()
2125            .flatten()
2126            .collect::<Vec<_>>();
2127        assert!(!results.is_empty());
2128
2129        let addr = results[0].addr;
2130        let src = Source::Elf(Elf::new(&test_dwarf));
2131        let symbolizer = Symbolizer::builder().enable_demangling(false).build();
2132        let result = symbolizer
2133            .symbolize_single(&src, Input::VirtOffset(addr))
2134            .unwrap()
2135            .into_sym()
2136            .unwrap();
2137
2138        let addr = result.addr;
2139        let size = result.size.unwrap() as u64;
2140        for inst_addr in addr..addr + size {
2141            if test(&test_dwarf, inst_addr).is_ok() {
2142                return
2143            }
2144        }
2145
2146        panic!("failed to find inlined function call");
2147    }
2148
2149    /// Make sure that we can symbolize an address in a process.
2150    #[test]
2151    fn symbolize_in_process() {
2152        let process_src = blaze_symbolize_src_process {
2153            pid: 0,
2154            debug_syms: true,
2155            perf_map: true,
2156            ..Default::default()
2157        };
2158
2159        let symbolizer = blaze_symbolizer_new();
2160        let addrs = [blaze_symbolizer_new as *const () as Addr];
2161        let result = unsafe {
2162            blaze_symbolize_process_abs_addrs(symbolizer, &process_src, addrs.as_ptr(), addrs.len())
2163        };
2164
2165        assert!(!result.is_null());
2166
2167        let result = unsafe { &*result };
2168        assert_eq!(result.cnt, 1);
2169        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2170        let sym = &syms[0];
2171        assert_eq!(
2172            unsafe { CStr::from_ptr(sym.name) },
2173            CStr::from_bytes_with_nul(b"blaze_symbolizer_new\0").unwrap()
2174        );
2175
2176        let () = unsafe { blaze_syms_free(result) };
2177        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2178    }
2179
2180    /// Make sure that we can symbolize addresses in a process after
2181    /// caching the corresponding metadata.
2182    #[test]
2183    fn symbolize_in_process_cached() {
2184        let symbolizer = blaze_symbolizer_new();
2185        let cache = blaze_cache_src_process {
2186            pid: 0,
2187            cache_vmas: true,
2188            ..Default::default()
2189        };
2190        let () = unsafe { blaze_symbolize_cache_process(symbolizer, &cache) };
2191        assert_eq!(blaze_err_last(), blaze_err::OK);
2192
2193        let src = blaze_symbolize_src_process {
2194            pid: 0,
2195            debug_syms: true,
2196            perf_map: true,
2197            ..Default::default()
2198        };
2199        let addrs = [blaze_symbolizer_new as *const () as Addr];
2200        let result = unsafe {
2201            blaze_symbolize_process_abs_addrs(symbolizer, &src, addrs.as_ptr(), addrs.len())
2202        };
2203
2204        assert!(!result.is_null());
2205
2206        let result = unsafe { &*result };
2207        assert_eq!(result.cnt, 1);
2208        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2209        let sym = &syms[0];
2210        assert_eq!(
2211            unsafe { CStr::from_ptr(sym.name) },
2212            CStr::from_bytes_with_nul(b"blaze_symbolizer_new\0").unwrap()
2213        );
2214
2215        let () = unsafe { blaze_syms_free(result) };
2216        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2217    }
2218
2219    /// Make sure that we can symbolize an address in the kernel via
2220    /// kallsyms.
2221    #[test]
2222    fn symbolize_in_kernel() {
2223        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
2224            .join("..")
2225            .join("data")
2226            .join("kallsyms");
2227        let path_c = CString::new(path.to_str().unwrap()).unwrap();
2228        let src = blaze_symbolize_src_kernel {
2229            kallsyms: path_c.as_ptr(),
2230            vmlinux: b"\0" as *const _ as *const c_char,
2231            ..Default::default()
2232        };
2233
2234        let symbolizer = blaze_symbolizer_new();
2235        let addrs = [0xc080a470];
2236        let result = unsafe {
2237            blaze_symbolize_kernel_abs_addrs(symbolizer, &src, addrs.as_ptr(), addrs.len())
2238        };
2239
2240        assert!(!result.is_null());
2241
2242        let result = unsafe { &*result };
2243        assert_eq!(result.cnt, 1);
2244        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2245        let sym = &syms[0];
2246        assert_eq!(
2247            unsafe { CStr::from_ptr(sym.name) },
2248            CStr::from_bytes_with_nul(b"init_task\0").unwrap()
2249        );
2250        assert_eq!(sym.module, ptr::null_mut());
2251
2252        let () = unsafe { blaze_syms_free(result) };
2253        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2254    }
2255
2256    /// Check that we report the expected error when attempting to
2257    /// symbolize data using a non-existent source.
2258    #[test]
2259    fn symbolize_elf_non_existent() {
2260        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
2261            .join("..")
2262            .join("data")
2263            .join("does-not-actually-exist.bin");
2264        let path_c = CString::new(path.to_str().unwrap()).unwrap();
2265        let elf_src = blaze_symbolize_src_elf {
2266            path: path_c.as_ptr(),
2267            debug_syms: true,
2268            ..Default::default()
2269        };
2270
2271        let symbolizer = blaze_symbolizer_new();
2272        let addrs = [blaze_symbolizer_new as *const () as Addr];
2273        let result = unsafe {
2274            blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
2275        };
2276        assert!(result.is_null());
2277        assert_eq!(blaze_err_last(), blaze_err::NOT_FOUND);
2278
2279        let () = unsafe { blaze_syms_free(result) };
2280        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2281    }
2282
2283    /// Check that we can handle no configured debug directories.
2284    #[test]
2285    fn symbolize_no_debug_dirs() {
2286        let dir = tempdir().unwrap();
2287        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
2288            .join("..")
2289            .join("data")
2290            .join("test-stable-addrs-stripped-with-link.bin");
2291        let dst = dir.path().join("test-stable-addrs-stripped-with-link.bin");
2292        let _count = copy(path, &dst).unwrap();
2293
2294        let debug_dirs = [];
2295        let opts = blaze_symbolizer_opts {
2296            debug_dirs: debug_dirs.as_ptr(),
2297            debug_dirs_len: debug_dirs.len(),
2298            code_info: true,
2299            inlined_fns: true,
2300            demangle: true,
2301            ..Default::default()
2302        };
2303        let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
2304
2305        let path_c = CString::new(dst.to_str().unwrap()).unwrap();
2306        let elf_src = blaze_symbolize_src_elf {
2307            path: path_c.as_ptr(),
2308            debug_syms: true,
2309            ..Default::default()
2310        };
2311        let addrs = [0x2000200];
2312        let result = unsafe {
2313            blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
2314        };
2315        assert!(!result.is_null());
2316
2317        let result = unsafe { &*result };
2318        assert_eq!(result.cnt, 1);
2319        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2320        let sym = &syms[0];
2321        // Shouldn't have symbolized because the debug link target cannot be
2322        // found.
2323        assert_eq!(sym.name, ptr::null());
2324        assert_eq!(sym.module, ptr::null());
2325        assert_eq!(sym.reason, blaze_symbolize_reason::MISSING_SYMS);
2326
2327        let () = unsafe { blaze_syms_free(result) };
2328        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2329    }
2330
2331    /// Make sure that debug directories are configurable.
2332    #[test]
2333    fn symbolize_configurable_debug_dirs() {
2334        let debug_dir1 = tempdir().unwrap();
2335        let debug_dir1_c = CString::new(debug_dir1.path().to_str().unwrap()).unwrap();
2336        let debug_dir2 = tempdir().unwrap();
2337        let debug_dir2_c = CString::new(debug_dir2.path().to_str().unwrap()).unwrap();
2338
2339        let src = Path::new(&env!("CARGO_MANIFEST_DIR"))
2340            .join("..")
2341            .join("data")
2342            .join("test-stable-addrs-dwarf-only.dbg");
2343        let dst = debug_dir2.path().join("test-stable-addrs-dwarf-only.dbg");
2344        let _count = copy(src, dst).unwrap();
2345
2346        let debug_dirs = [debug_dir1_c.as_ptr(), debug_dir2_c.as_ptr()];
2347        let opts = blaze_symbolizer_opts {
2348            debug_dirs: debug_dirs.as_ptr(),
2349            debug_dirs_len: debug_dirs.len(),
2350            code_info: true,
2351            inlined_fns: true,
2352            demangle: true,
2353            ..Default::default()
2354        };
2355        let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
2356
2357        let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
2358            .join("..")
2359            .join("data")
2360            .join("test-stable-addrs-stripped-with-link.bin");
2361        let path_c = CString::new(path.to_str().unwrap()).unwrap();
2362        let elf_src = blaze_symbolize_src_elf {
2363            path: path_c.as_ptr(),
2364            debug_syms: true,
2365            ..Default::default()
2366        };
2367        let addrs = [0x2000200];
2368        let result = unsafe {
2369            blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
2370        };
2371        assert!(!result.is_null());
2372
2373        let result = unsafe { &*result };
2374        assert_eq!(result.cnt, 1);
2375        let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2376        let sym = &syms[0];
2377        let name = unsafe { CStr::from_ptr(sym.name) };
2378        assert_eq!(name.to_str().unwrap(), "factorial");
2379        let module = unsafe { CStr::from_ptr(sym.module) };
2380        assert!(
2381            module
2382                .to_str()
2383                .unwrap()
2384                .ends_with("test-stable-addrs-stripped-with-link.bin"),
2385            "{module:?}"
2386        );
2387
2388        let () = unsafe { blaze_syms_free(result) };
2389        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2390    }
2391
2392    /// Symbolize `addr` in the current process using the given dispatch
2393    /// callback.
2394    fn symbolize_with_dispatch(
2395        cb: unsafe extern "C" fn(*const c_char, *const c_char, *mut c_void) -> *mut c_char,
2396        addr: Addr,
2397        expected_sym: Option<&str>,
2398    ) {
2399        let dispatch = blaze_symbolizer_dispatch {
2400            dispatch_cb: cb,
2401            ctx: 0xc0ffee as *mut c_void,
2402        };
2403        let opts = blaze_symbolizer_opts {
2404            process_dispatch: &dispatch,
2405            ..Default::default()
2406        };
2407        let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
2408        assert!(!symbolizer.is_null());
2409
2410        let process_src = blaze_symbolize_src_process {
2411            pid: 0,
2412            debug_syms: true,
2413            ..Default::default()
2414        };
2415
2416        let addrs = [addr];
2417        let result = unsafe {
2418            blaze_symbolize_process_abs_addrs(symbolizer, &process_src, addrs.as_ptr(), addrs.len())
2419        };
2420        let () = unsafe { blaze_symbolizer_free(symbolizer) };
2421
2422        if let Some(expected) = expected_sym {
2423            assert!(!result.is_null());
2424            let result = unsafe { &*result };
2425            assert_eq!(result.cnt, 1);
2426            let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
2427            let name = unsafe { CStr::from_ptr(syms[0].name) }.to_str().unwrap();
2428            assert!(name.contains(expected), "{name}");
2429            let () = unsafe { blaze_syms_free(result) };
2430        } else {
2431            assert!(result.is_null());
2432        }
2433    }
2434
2435    /// Make sure that we can symbolize an address in the current process
2436    /// using a custom process dispatch callback that returns the
2437    /// `maps_file` path as-is.
2438    #[test]
2439    fn symbolize_in_process_with_dispatch() {
2440        unsafe extern "C" fn cb(
2441            maps_file: *const c_char,
2442            _symbolic_path: *const c_char,
2443            ctx: *mut c_void,
2444        ) -> *mut c_char {
2445            assert_eq!(ctx as usize, 0xc0ffee);
2446            unsafe { libc::strdup(maps_file) }
2447        }
2448
2449        symbolize_with_dispatch(
2450            cb,
2451            symbolize_in_process_with_dispatch as *const () as Addr,
2452            Some("symbolize_in_process_with_dispatch"),
2453        )
2454    }
2455
2456    /// Make sure that a dispatch callback returning NULL falls back to
2457    /// the default symbolization behavior.
2458    #[test]
2459    fn symbolize_in_process_with_null_dispatch() {
2460        unsafe extern "C" fn cb(
2461            _maps_file: *const c_char,
2462            _symbolic_path: *const c_char,
2463            ctx: *mut c_void,
2464        ) -> *mut c_char {
2465            assert_eq!(ctx as usize, 0xc0ffee);
2466            ptr::null_mut()
2467        }
2468
2469        symbolize_with_dispatch(
2470            cb,
2471            symbolize_in_process_with_null_dispatch as *const () as Addr,
2472            Some("symbolize_in_process_with_null_dispatch"),
2473        )
2474    }
2475
2476    /// Make sure that a dispatch callback returning a non-existent path
2477    /// causes symbolization to fail for that address.
2478    #[test]
2479    fn symbolize_in_process_with_bad_dispatch() {
2480        unsafe extern "C" fn cb(
2481            _maps_file: *const c_char,
2482            _symbolic_path: *const c_char,
2483            ctx: *mut c_void,
2484        ) -> *mut c_char {
2485            assert_eq!(ctx as usize, 0xc0ffee);
2486            unsafe { libc::strdup(c"/no/such/file".as_ptr()) }
2487        }
2488
2489        symbolize_with_dispatch(
2490            cb,
2491            symbolize_in_process_with_bad_dispatch as *const () as Addr,
2492            None,
2493        )
2494    }
2495}