blazesym_c/
symbolize.rs

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