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