blazesym_c/
normalize.rs

1use std::alloc::alloc;
2use std::alloc::dealloc;
3use std::alloc::Layout;
4use std::borrow::Cow;
5use std::ffi::CString;
6use std::ffi::OsString;
7use std::fmt::Debug;
8use std::fmt::Formatter;
9use std::fmt::Result as FmtResult;
10use std::mem;
11use std::mem::size_of;
12use std::mem::ManuallyDrop;
13use std::os::raw::c_char;
14use std::os::unix::ffi::OsStringExt as _;
15use std::path::PathBuf;
16use std::ptr;
17
18use blazesym::normalize::Apk;
19use blazesym::normalize::Elf;
20use blazesym::normalize::NormalizeOpts;
21use blazesym::normalize::Normalizer;
22use blazesym::normalize::Reason;
23use blazesym::normalize::Unknown;
24use blazesym::normalize::UserMeta;
25use blazesym::normalize::UserOutput;
26use blazesym::symbolize::Sym;
27use blazesym::Addr;
28
29use crate::blaze_err;
30#[cfg(doc)]
31use crate::blaze_err_last;
32use crate::blaze_sym;
33use crate::blaze_symbolize_inlined_fn;
34use crate::convert_sym;
35use crate::set_last_err;
36use crate::util::slice_from_user_array;
37use crate::util::DynSize as _;
38
39
40/// C ABI compatible version of [`blazesym::normalize::Normalizer`].
41pub type blaze_normalizer = Normalizer;
42
43
44/// Options for configuring [`blaze_normalizer`] objects.
45#[repr(C)]
46#[derive(Debug)]
47pub struct blaze_normalizer_opts {
48    /// The size of this object's type.
49    ///
50    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
51    /// ensure compatibility in the presence of member additions.
52    pub type_size: usize,
53    /// Whether or not to use the `PROCMAP_QUERY` ioctl instead of
54    /// parsing `/proc/<pid>/maps` for getting available VMA ranges.
55    ///
56    /// Refer to
57    /// [`blaze_supports_procmap_query`][crate::helper::blaze_supports_procmap_query]
58    /// as a way to check whether your system supports this
59    /// functionality.
60    ///
61    /// # Notes
62    ///
63    /// Support for this ioctl is only present in very recent kernels
64    /// (likely: 6.11+). See <https://lwn.net/Articles/979931/> for
65    /// details.
66    ///
67    /// Furthermore, the ioctl will also be used for retrieving build
68    /// IDs (if enabled). Build ID reading logic in the kernel is known
69    /// to be incomplete, with a fix slated to be included only with
70    /// 6.12.
71    pub use_procmap_query: bool,
72    /// Whether or not to cache `/proc/<pid>/maps` contents.
73    ///
74    /// Setting this flag to `true` is not generally recommended, because it
75    /// could result in addresses corresponding to mappings added after caching
76    /// may not be normalized successfully, as there is no reasonable way of
77    /// detecting staleness.
78    pub cache_vmas: bool,
79    /// Whether to read and report build IDs as part of the normalization
80    /// process.
81    ///
82    /// Note that build ID read failures will be swallowed without
83    /// failing the normalization operation.
84    pub build_ids: bool,
85    /// Whether or not to cache build IDs. This flag only has an effect
86    /// if build ID reading is enabled in the first place.
87    pub cache_build_ids: bool,
88    /// Unused member available for future expansion. Must be initialized
89    /// to zero.
90    pub reserved: [u8; 20],
91}
92
93impl Default for blaze_normalizer_opts {
94    fn default() -> Self {
95        Self {
96            type_size: size_of::<Self>(),
97            use_procmap_query: false,
98            cache_vmas: false,
99            build_ids: false,
100            cache_build_ids: false,
101            reserved: [0; 20],
102        }
103    }
104}
105
106
107/// Options influencing the address normalization process.
108#[repr(C)]
109#[derive(Debug)]
110pub struct blaze_normalize_opts {
111    /// The size of this object's type.
112    ///
113    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
114    /// ensure compatibility in the presence of member additions.
115    pub type_size: usize,
116    /// Whether or not addresses are sorted (in ascending order) already.
117    ///
118    /// Normalization always happens on sorted addresses and if the addresses
119    /// are sorted already, the library does not need to sort and later restore
120    /// original ordering, speeding up the normalization process.
121    pub sorted_addrs: bool,
122    /// Whether to report `/proc/<pid>/map_files/` entry paths or work
123    /// with symbolic paths mentioned in `/proc/<pid>/maps` instead.
124    ///
125    /// Relying on `map_files` may make sense in cases where
126    /// symbolization happens on the local system and the reported paths
127    /// can be worked with directly. In most other cases where one wants
128    /// to attach meaning to symbolic paths on a remote system (e.g., by
129    /// using them for file look up) symbolic paths are probably the
130    /// better choice.
131    pub map_files: bool,
132    /// Normalize addresses inside APKs to the contained ELF file and
133    /// report a regular [`blaze_user_meta_kind::ELF`] meta data entry
134    /// instead of an [`blaze_user_meta_kind::APK`] one. As a result,
135    /// the reported file offset will also be relative to the contained
136    /// ELF file and not to the APK itself.
137    pub apk_to_elf: bool,
138    /// Unused member available for future expansion. Must be initialized
139    /// to zero.
140    pub reserved: [u8; 21],
141}
142
143impl Default for blaze_normalize_opts {
144    fn default() -> Self {
145        Self {
146            type_size: size_of::<Self>(),
147            sorted_addrs: false,
148            map_files: false,
149            apk_to_elf: false,
150            reserved: [0; 21],
151        }
152    }
153}
154
155impl From<blaze_normalize_opts> for NormalizeOpts {
156    fn from(opts: blaze_normalize_opts) -> Self {
157        let blaze_normalize_opts {
158            type_size: _,
159            sorted_addrs,
160            map_files,
161            apk_to_elf,
162            reserved: _,
163        } = opts;
164        Self {
165            sorted_addrs,
166            map_files,
167            apk_to_elf,
168            _non_exhaustive: (),
169        }
170    }
171}
172
173
174/// Create an instance of a blazesym normalizer in the default
175/// configuration.
176///
177/// C ABI compatible version of [`blazesym::normalize::Normalizer::new()`].
178/// Please refer to its documentation for the default configuration in use.
179///
180/// On success, the function creates a new [`blaze_normalizer`] object and
181/// returns it. The resulting object should be released using
182/// [`blaze_normalizer_free`] once it is no longer needed.
183///
184/// On error, the function returns `NULL` and sets the thread's last error to
185/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
186/// error.
187#[no_mangle]
188pub extern "C" fn blaze_normalizer_new() -> *mut blaze_normalizer {
189    let normalizer = Normalizer::new();
190    let normalizer_box = Box::new(normalizer);
191    let () = set_last_err(blaze_err::OK);
192    Box::into_raw(normalizer_box)
193}
194
195
196/// Create an instance of a blazesym normalizer.
197///
198/// On success, the function creates a new [`blaze_normalizer`] object and
199/// returns it. The resulting object should be released using
200/// [`blaze_normalizer_free`] once it is no longer needed.
201///
202/// On error, the function returns `NULL` and sets the thread's last error to
203/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
204/// error.
205///
206/// # Safety
207/// - `opts` needs to point to a valid [`blaze_normalizer_opts`] object
208#[no_mangle]
209pub unsafe extern "C" fn blaze_normalizer_new_opts(
210    opts: *const blaze_normalizer_opts,
211) -> *mut blaze_normalizer {
212    if !input_zeroed!(opts, blaze_normalizer_opts) {
213        let () = set_last_err(blaze_err::INVALID_INPUT);
214        return ptr::null_mut()
215    }
216    let opts = input_sanitize!(opts, blaze_normalizer_opts);
217
218    let blaze_normalizer_opts {
219        type_size: _,
220        use_procmap_query,
221        cache_vmas,
222        build_ids,
223        cache_build_ids,
224        reserved: _,
225    } = opts;
226
227    let normalizer = Normalizer::builder()
228        .enable_procmap_query(use_procmap_query)
229        .enable_vma_caching(cache_vmas)
230        .enable_build_ids(build_ids)
231        .enable_build_id_caching(cache_build_ids)
232        .build();
233    let normalizer_box = Box::new(normalizer);
234    let () = set_last_err(blaze_err::OK);
235    Box::into_raw(normalizer_box)
236}
237
238
239/// Free a blazesym normalizer.
240///
241/// Release resources associated with a normalizer as created by
242/// [`blaze_normalizer_new`], for example.
243///
244/// # Safety
245/// The provided normalizer should have been created by
246/// [`blaze_normalizer_new`].
247#[no_mangle]
248pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut blaze_normalizer) {
249    if !normalizer.is_null() {
250        // SAFETY: The caller needs to ensure that `normalizer` is a
251        //         valid pointer.
252        drop(unsafe { Box::from_raw(normalizer) });
253    }
254}
255
256
257/// A file offset or non-normalized address along with an index into the
258/// associated [`blaze_user_meta`] array (such as
259/// [`blaze_normalized_user_output::metas`]).
260#[repr(C)]
261#[derive(Debug)]
262pub struct blaze_normalized_output {
263    /// The file offset or non-normalized address.
264    pub output: u64,
265    /// The index into the associated [`blaze_user_meta`] array.
266    pub meta_idx: usize,
267    /// Unused member available for future expansion. Must be initialized
268    /// to zero.
269    pub reserved: [u8; 16],
270}
271
272impl From<(u64, usize)> for blaze_normalized_output {
273    fn from((output, meta_idx): (u64, usize)) -> Self {
274        Self {
275            output,
276            meta_idx,
277            reserved: [0; 16],
278        }
279    }
280}
281
282
283/// The valid variant kind in [`blaze_user_meta`].
284#[repr(transparent)]
285#[derive(Copy, Clone, Debug, Eq, PartialEq)]
286pub struct blaze_user_meta_kind(u8);
287
288impl blaze_user_meta_kind {
289    /// [`blaze_user_meta_variant::unknown`] is valid.
290    pub const UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
291    /// [`blaze_user_meta_variant::apk`] is valid.
292    pub const APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
293    /// [`blaze_user_meta_variant::elf`] is valid.
294    pub const ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
295    /// [`blaze_user_meta_variant::sym`] is valid.
296    pub const SYM: blaze_user_meta_kind = blaze_user_meta_kind(3);
297
298    // TODO: Remove the following constants with the 0.2 release
299    /// Deprecated; use `BLAZE_USER_META_KIND_UNKNOWN`.
300    #[deprecated]
301    pub const BLAZE_USER_META_UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
302    /// Deprecated; use `BLAZE_USER_META_KIND_APK`.
303    #[deprecated]
304    pub const BLAZE_USER_META_APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
305    /// Deprecated; use `BLAZE_USER_META_KIND_ELF`.
306    #[deprecated]
307    pub const BLAZE_USER_META_ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
308}
309
310
311/// C compatible version of [`Apk`].
312#[repr(C)]
313#[derive(Debug)]
314pub struct blaze_user_meta_apk {
315    /// The canonical absolute path to the APK, including its name.
316    /// This member is always present.
317    pub path: *mut c_char,
318    /// Unused member available for future expansion.
319    pub reserved: [u8; 16],
320}
321
322impl blaze_user_meta_apk {
323    fn from(other: Apk) -> ManuallyDrop<Self> {
324        let Apk {
325            path,
326            _non_exhaustive: (),
327        } = other;
328
329        let slf = Self {
330            path: CString::new(path.into_os_string().into_vec())
331                .expect("encountered path with NUL bytes")
332                .into_raw(),
333            reserved: [0; 16],
334        };
335        ManuallyDrop::new(slf)
336    }
337
338    unsafe fn free(self) {
339        let Self { path, reserved: _ } = self;
340
341        let _apk = Apk {
342            path: PathBuf::from(OsString::from_vec(
343                unsafe { CString::from_raw(path) }.into_bytes(),
344            )),
345            _non_exhaustive: (),
346        };
347    }
348}
349
350
351/// C compatible version of [`Elf`].
352#[repr(C)]
353#[derive(Debug)]
354pub struct blaze_user_meta_elf {
355    /// Ordinarily, the canonical absolute path to the ELF file,
356    /// including its name. In case of an ELF file contained inside an
357    /// APK (see [`blaze_normalize_opts::apk_to_elf`]) this will be an
358    /// Android style path of the form `<apk>!<elf-in-apk>`. E.g.,
359    /// `/root/test.apk!/lib/libc.so`.
360    ///
361    /// This member is always present.
362    pub path: *mut c_char,
363    /// The length of the build ID, in bytes.
364    pub build_id_len: usize,
365    /// The optional build ID of the ELF file, if found and readable.
366    pub build_id: *mut u8,
367    /// Unused member available for future expansion.
368    pub reserved: [u8; 16],
369}
370
371impl blaze_user_meta_elf {
372    fn from(other: Elf) -> ManuallyDrop<Self> {
373        let Elf {
374            path,
375            build_id,
376            _non_exhaustive: (),
377        } = other;
378
379        let slf = Self {
380            path: CString::new(path.into_os_string().into_vec())
381                .expect("encountered path with NUL bytes")
382                .into_raw(),
383            build_id_len: build_id
384                .as_ref()
385                .map(|build_id| build_id.len())
386                .unwrap_or(0),
387            build_id: build_id
388                .map(|build_id| {
389                    // SAFETY: We know the pointer is valid because it
390                    //         came from a `Box`.
391                    unsafe {
392                        Box::into_raw(build_id.to_vec().into_boxed_slice())
393                            .as_mut()
394                            .unwrap()
395                            .as_mut_ptr()
396                    }
397                })
398                .unwrap_or_else(ptr::null_mut),
399            reserved: [0; 16],
400        };
401        ManuallyDrop::new(slf)
402    }
403
404    unsafe fn free(self) {
405        let blaze_user_meta_elf {
406            path,
407            build_id_len,
408            build_id,
409            reserved: _,
410        } = self;
411
412        let _elf = Elf {
413            path: PathBuf::from(OsString::from_vec(
414                unsafe { CString::from_raw(path) }.into_bytes(),
415            )),
416            build_id: (!build_id.is_null()).then(|| unsafe {
417                Cow::Owned(
418                    Box::<[u8]>::from_raw(ptr::slice_from_raw_parts_mut(build_id, build_id_len))
419                        .into_vec(),
420                )
421            }),
422            _non_exhaustive: (),
423        };
424    }
425}
426
427
428/// Readily symbolized information for an address.
429#[repr(C)]
430#[derive(Debug)]
431pub struct blaze_user_meta_sym {
432    /// The symbol data.
433    pub sym: *const blaze_sym,
434    /// Unused member available for future expansion.
435    pub reserved: [u8; 16],
436}
437
438impl blaze_user_meta_sym {
439    fn from(sym: Sym) -> ManuallyDrop<Self> {
440        let strtab_size = sym.c_str_size();
441        let buf_size = mem::size_of::<u64>()
442            + mem::size_of::<blaze_sym>()
443            + sym.inlined.len() * mem::size_of::<blaze_symbolize_inlined_fn>()
444            + strtab_size;
445        let buf = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
446        // TODO: Should ideally report runtime error, but we already
447        //       use panic-on-OOM functions elsewhere, so they'd all
448        //       need to be adjusted.
449        assert!(!buf.is_null());
450
451        // Prepend a `u64` to store the size of the buffer.
452        unsafe { *(buf as *mut u64) = buf_size as u64 };
453
454        let sym_buf = unsafe { buf.add(mem::size_of::<u64>()) }.cast::<blaze_sym>();
455        let mut inlined_last = unsafe { sym_buf.add(1) }.cast::<blaze_symbolize_inlined_fn>();
456        let mut cstr_last = unsafe { inlined_last.add(sym.inlined.len()) }.cast::<c_char>();
457        let sym_ref = unsafe { &mut *sym_buf };
458        let () = convert_sym(&sym, sym_ref, &mut inlined_last, &mut cstr_last);
459
460        let slf = Self {
461            sym: sym_buf,
462            reserved: [0; 16],
463        };
464        ManuallyDrop::new(slf)
465    }
466
467    unsafe fn free(self) {
468        let blaze_user_meta_sym { sym, reserved: _ } = self;
469
470        let buf = unsafe { sym.byte_sub(mem::size_of::<u64>()).cast::<u8>().cast_mut() };
471        let size = unsafe { *(buf as *mut u64) } as usize;
472        let () = unsafe { dealloc(buf, Layout::from_size_align(size, 8).unwrap()) };
473    }
474}
475
476
477/// The reason why normalization failed.
478///
479/// The reason is generally only meant as a hint. Reasons reported may change
480/// over time and, hence, should not be relied upon for the correctness of the
481/// application.
482#[repr(transparent)]
483#[derive(Copy, Clone, Debug, Eq, PartialEq)]
484pub struct blaze_normalize_reason(u8);
485
486impl blaze_normalize_reason {
487    /// The absolute address was not found in the corresponding process' virtual
488    /// memory map.
489    pub const UNMAPPED: blaze_normalize_reason = blaze_normalize_reason(0);
490    /// The `/proc/<pid>/maps` entry corresponding to the address does not have
491    /// a component (file system path, object, ...) associated with it.
492    pub const MISSING_COMPONENT: blaze_normalize_reason = blaze_normalize_reason(1);
493    /// The address belonged to an entity that is currently unsupported.
494    pub const UNSUPPORTED: blaze_normalize_reason = blaze_normalize_reason(2);
495    /// The file offset does not map to a valid piece of code/data.
496    pub const INVALID_FILE_OFFSET: blaze_normalize_reason = blaze_normalize_reason(3);
497    /// The symbolization source has no or no relevant symbols.
498    pub const MISSING_SYMS: blaze_normalize_reason = blaze_normalize_reason(4);
499    /// The address could not be found in the symbolization source.
500    pub const UNKNOWN_ADDR: blaze_normalize_reason = blaze_normalize_reason(5);
501    /// An error prevented the symbolization of the address from succeeding.
502    pub const IGNORED_ERROR: blaze_normalize_reason = blaze_normalize_reason(7);
503}
504
505impl From<Reason> for blaze_normalize_reason {
506    fn from(reason: Reason) -> Self {
507        match reason {
508            Reason::Unmapped => blaze_normalize_reason::UNMAPPED,
509            Reason::MissingComponent => blaze_normalize_reason::MISSING_COMPONENT,
510            Reason::Unsupported => blaze_normalize_reason::UNSUPPORTED,
511            Reason::InvalidFileOffset => blaze_normalize_reason::INVALID_FILE_OFFSET,
512            Reason::MissingSyms => blaze_normalize_reason::MISSING_SYMS,
513            Reason::UnknownAddr => blaze_normalize_reason::UNKNOWN_ADDR,
514            Reason::IgnoredError => blaze_normalize_reason::IGNORED_ERROR,
515            _ => unreachable!(),
516        }
517    }
518}
519
520
521/// Retrieve a textual representation of the reason of a normalization failure.
522#[no_mangle]
523pub extern "C" fn blaze_normalize_reason_str(reason: blaze_normalize_reason) -> *const c_char {
524    match reason {
525        blaze_normalize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
526        blaze_normalize_reason::MISSING_COMPONENT => {
527            Reason::MissingComponent.as_bytes().as_ptr().cast()
528        }
529        blaze_normalize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
530        blaze_normalize_reason::INVALID_FILE_OFFSET => {
531            Reason::InvalidFileOffset.as_bytes().as_ptr().cast()
532        }
533        blaze_normalize_reason::MISSING_SYMS => Reason::MissingSyms.as_bytes().as_ptr().cast(),
534        blaze_normalize_reason::UNKNOWN_ADDR => Reason::UnknownAddr.as_bytes().as_ptr().cast(),
535        blaze_normalize_reason::IGNORED_ERROR => Reason::IgnoredError.as_bytes().as_ptr().cast(),
536        _ => b"unknown reason\0".as_ptr().cast(),
537    }
538}
539
540
541/// C compatible version of [`Unknown`].
542#[repr(C)]
543#[derive(Debug)]
544pub struct blaze_user_meta_unknown {
545    /// The reason why normalization failed.
546    ///
547    /// The provided reason is a best guess, hinting at what ultimately
548    /// prevented the normalization from being successful.
549    pub reason: blaze_normalize_reason,
550    /// Unused member available for future expansion.
551    pub reserved: [u8; 15],
552}
553
554impl blaze_user_meta_unknown {
555    fn from(other: Unknown) -> ManuallyDrop<Self> {
556        let Unknown {
557            reason,
558            _non_exhaustive: (),
559        } = other;
560
561        let slf = Self {
562            reason: reason.into(),
563            reserved: [0; 15],
564        };
565        ManuallyDrop::new(slf)
566    }
567
568    fn free(self) {
569        let blaze_user_meta_unknown {
570            reason: _,
571            reserved: _,
572        } = self;
573    }
574}
575
576
577/// The actual variant data in [`blaze_user_meta`].
578#[repr(C)]
579pub union blaze_user_meta_variant {
580    /// Valid on [`blaze_user_meta_kind::APK`].
581    pub apk: ManuallyDrop<blaze_user_meta_apk>,
582    /// Valid on [`blaze_user_meta_kind::ELF`].
583    pub elf: ManuallyDrop<blaze_user_meta_elf>,
584    /// Valid on [`blaze_user_meta_kind::SYM`].
585    pub sym: ManuallyDrop<blaze_user_meta_sym>,
586    /// Valid on [`blaze_user_meta_kind::UNKNOWN`].
587    pub unknown: ManuallyDrop<blaze_user_meta_unknown>,
588}
589
590impl Debug for blaze_user_meta_variant {
591    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
592        f.debug_struct(stringify!(blaze_user_meta_variant)).finish()
593    }
594}
595
596
597/// C ABI compatible version of [`UserMeta`].
598#[repr(C)]
599#[derive(Debug)]
600pub struct blaze_user_meta {
601    /// The variant kind that is present.
602    pub kind: blaze_user_meta_kind,
603    /// Currently unused bytes.
604    pub unused: [u8; 7],
605    /// The actual variant with its data.
606    pub variant: blaze_user_meta_variant,
607    /// Unused member available for future expansion. Must be initialized
608    /// to zero.
609    pub reserved: [u8; 16],
610}
611
612impl blaze_user_meta {
613    fn from(other: UserMeta) -> ManuallyDrop<Self> {
614        let slf = match other {
615            UserMeta::Apk(apk) => Self {
616                kind: blaze_user_meta_kind::APK,
617                unused: [0; 7],
618                variant: blaze_user_meta_variant {
619                    apk: blaze_user_meta_apk::from(apk),
620                },
621                reserved: [0; 16],
622            },
623            UserMeta::Elf(elf) => Self {
624                kind: blaze_user_meta_kind::ELF,
625                unused: [0; 7],
626                variant: blaze_user_meta_variant {
627                    elf: blaze_user_meta_elf::from(elf),
628                },
629                reserved: [0; 16],
630            },
631            UserMeta::Sym(sym) => Self {
632                kind: blaze_user_meta_kind::SYM,
633                unused: [0; 7],
634                variant: blaze_user_meta_variant {
635                    sym: blaze_user_meta_sym::from(sym),
636                },
637                reserved: [0; 16],
638            },
639            UserMeta::Unknown(unknown) => Self {
640                kind: blaze_user_meta_kind::UNKNOWN,
641                unused: [0; 7],
642                variant: blaze_user_meta_variant {
643                    unknown: blaze_user_meta_unknown::from(unknown),
644                },
645                reserved: [0; 16],
646            },
647            _ => unreachable!(),
648        };
649        ManuallyDrop::new(slf)
650    }
651
652    unsafe fn free(self) {
653        match self.kind {
654            blaze_user_meta_kind::APK => unsafe {
655                ManuallyDrop::into_inner(self.variant.apk).free()
656            },
657            blaze_user_meta_kind::ELF => unsafe {
658                ManuallyDrop::into_inner(self.variant.elf).free()
659            },
660            blaze_user_meta_kind::SYM => unsafe {
661                ManuallyDrop::into_inner(self.variant.sym).free()
662            },
663            blaze_user_meta_kind::UNKNOWN => {
664                ManuallyDrop::into_inner(unsafe { self.variant.unknown }).free()
665            }
666            _ => {
667                debug_assert!(false)
668            }
669        }
670    }
671}
672
673
674/// An object representing normalized user addresses.
675///
676/// C ABI compatible version of [`UserOutput`].
677#[repr(C)]
678#[derive(Debug)]
679pub struct blaze_normalized_user_output {
680    /// The number of [`blaze_user_meta`] objects present in `metas`.
681    pub meta_cnt: usize,
682    /// An array of `meta_cnt` objects.
683    pub metas: *mut blaze_user_meta,
684    /// The number of [`blaze_normalized_output`] objects present in `outputs`.
685    pub output_cnt: usize,
686    /// An array of `output_cnt` objects.
687    pub outputs: *mut blaze_normalized_output,
688    /// Unused member available for future expansion.
689    pub reserved: [u8; 16],
690}
691
692impl blaze_normalized_user_output {
693    fn from(other: UserOutput) -> ManuallyDrop<Self> {
694        let slf = Self {
695            meta_cnt: other.meta.len(),
696            metas: unsafe {
697                Box::into_raw(
698                    other
699                        .meta
700                        .into_iter()
701                        .map(blaze_user_meta::from)
702                        .map(ManuallyDrop::into_inner)
703                        .collect::<Vec<_>>()
704                        .into_boxed_slice(),
705                )
706                .as_mut()
707                .unwrap()
708                .as_mut_ptr()
709            },
710            output_cnt: other.outputs.len(),
711            outputs: unsafe {
712                Box::into_raw(
713                    other
714                        .outputs
715                        .into_iter()
716                        .map(blaze_normalized_output::from)
717                        .collect::<Vec<_>>()
718                        .into_boxed_slice(),
719                )
720                .as_mut()
721                .unwrap()
722                .as_mut_ptr()
723            },
724            reserved: [0; 16],
725        };
726        ManuallyDrop::new(slf)
727    }
728}
729
730
731unsafe fn blaze_normalize_user_addrs_impl(
732    normalizer: *const blaze_normalizer,
733    pid: u32,
734    addrs: *const Addr,
735    addr_cnt: usize,
736    opts: &NormalizeOpts,
737) -> *mut blaze_normalized_user_output {
738    // SAFETY: The caller needs to ensure that `normalizer` is a valid
739    //         pointer.
740    let normalizer = unsafe { &*normalizer };
741    // SAFETY: The caller needs to ensure that `addrs` is a valid pointer and
742    //         that it points to `addr_cnt` elements.
743    let addrs = unsafe { slice_from_user_array(addrs, addr_cnt) };
744    let result = normalizer.normalize_user_addrs_opts(pid.into(), &addrs, opts);
745    match result {
746        Ok(output) => {
747            let output_box = Box::new(ManuallyDrop::into_inner(
748                blaze_normalized_user_output::from(output),
749            ));
750            let () = set_last_err(blaze_err::OK);
751            Box::into_raw(output_box)
752        }
753        Err(err) => {
754            let () = set_last_err(err.kind().into());
755            ptr::null_mut()
756        }
757    }
758}
759
760
761/// Normalize a list of user space addresses.
762///
763/// C ABI compatible version of [`Normalizer::normalize_user_addrs`].
764///
765/// `pid` should describe the PID of the process to which the addresses
766/// belongs. It may be `0` if they belong to the calling process.
767///
768/// On success, the function creates a new [`blaze_normalized_user_output`]
769/// object and returns it. The resulting object should be released using
770/// [`blaze_user_output_free`] once it is no longer needed.
771///
772/// On error, the function returns `NULL` and sets the thread's last error to
773/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
774/// error.
775///
776/// # Safety
777/// - `addrs` needs to be a valid pointer to `addr_cnt` addresses
778#[no_mangle]
779pub unsafe extern "C" fn blaze_normalize_user_addrs(
780    normalizer: *const blaze_normalizer,
781    pid: u32,
782    addrs: *const Addr,
783    addr_cnt: usize,
784) -> *mut blaze_normalized_user_output {
785    let opts = NormalizeOpts::default();
786
787    unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
788}
789
790
791/// Normalize a list of user space addresses.
792///
793/// C ABI compatible version of [`Normalizer::normalize_user_addrs_opts`].
794///
795/// `pid` should describe the PID of the process to which the addresses
796/// belongs. It may be `0` if they belong to the calling process.
797///
798/// `opts` should point to a valid [`blaze_normalize_opts`] object.
799///
800/// On success, the function creates a new [`blaze_normalized_user_output`]
801/// object and returns it. The resulting object should be released using
802/// [`blaze_user_output_free`] once it is no longer needed.
803///
804/// On error, the function returns `NULL` and sets the thread's last error to
805/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
806/// error.
807///
808/// # Safety
809/// - `addrs` needs to be a valid pointer to `addr_cnt` addresses
810#[no_mangle]
811pub unsafe extern "C" fn blaze_normalize_user_addrs_opts(
812    normalizer: *const blaze_normalizer,
813    pid: u32,
814    addrs: *const Addr,
815    addr_cnt: usize,
816    opts: *const blaze_normalize_opts,
817) -> *mut blaze_normalized_user_output {
818    if !input_zeroed!(opts, blaze_normalize_opts) {
819        let () = set_last_err(blaze_err::INVALID_INPUT);
820        return ptr::null_mut()
821    }
822    let opts = input_sanitize!(opts, blaze_normalize_opts);
823    let opts = NormalizeOpts::from(opts);
824
825    unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
826}
827
828
829/// Free an object as returned by [`blaze_normalize_user_addrs`] or
830/// [`blaze_normalize_user_addrs_opts`].
831///
832/// # Safety
833/// The provided object should have been created by
834/// [`blaze_normalize_user_addrs`] or
835/// [`blaze_normalize_user_addrs_opts`].
836#[no_mangle]
837pub unsafe extern "C" fn blaze_user_output_free(output: *mut blaze_normalized_user_output) {
838    if output.is_null() {
839        return
840    }
841
842    // SAFETY: The caller should make sure that `output` was created by one of
843    //         our blessed functions.
844    let user_output = unsafe { Box::from_raw(output) };
845    let addr_metas = unsafe {
846        Box::<[blaze_user_meta]>::from_raw(ptr::slice_from_raw_parts_mut(
847            user_output.metas,
848            user_output.meta_cnt,
849        ))
850    }
851    .into_vec();
852    let _norm_addrs = unsafe {
853        Box::<[blaze_normalized_output]>::from_raw(ptr::slice_from_raw_parts_mut(
854            user_output.outputs,
855            user_output.output_cnt,
856        ))
857    }
858    .into_vec();
859
860    for addr_meta in addr_metas {
861        let () = unsafe { addr_meta.free() };
862    }
863}
864
865
866#[cfg(test)]
867mod tests {
868    use super::*;
869
870    use std::ffi::CStr;
871    use std::io;
872    use std::path::Path;
873
874    use blazesym::helper::read_elf_build_id;
875    use blazesym::Mmap;
876    use blazesym::__private::find_the_answer_fn;
877    use blazesym::__private::zip;
878
879    use test_tag::tag;
880
881    use crate::blaze_err_last;
882
883
884    /// Check that various types have expected sizes.
885    #[tag(miri)]
886    #[test]
887    #[cfg(target_pointer_width = "64")]
888    fn type_sizes() {
889        assert_eq!(size_of::<blaze_normalizer_opts>(), 32);
890        assert_eq!(size_of::<blaze_normalize_opts>(), 32);
891        assert_eq!(size_of::<blaze_user_meta_apk>(), 24);
892        assert_eq!(size_of::<blaze_user_meta_elf>(), 40);
893        assert_eq!(size_of::<blaze_user_meta_sym>(), 24);
894        assert_eq!(size_of::<blaze_user_meta_unknown>(), 16);
895    }
896
897    /// Exercise the `Debug` representation of various types.
898    #[tag(miri)]
899    #[test]
900    fn debug_repr() {
901        let output = blaze_normalized_output {
902            output: 0x1337,
903            meta_idx: 1,
904            reserved: [0; 16],
905        };
906        assert_eq!(
907            format!("{output:?}"),
908            "blaze_normalized_output { output: 4919, meta_idx: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
909        );
910
911        let meta_kind = blaze_user_meta_kind::APK;
912        assert_eq!(format!("{meta_kind:?}"), "blaze_user_meta_kind(1)");
913
914        let apk = blaze_user_meta_apk {
915            path: ptr::null_mut(),
916            reserved: [0; 16],
917        };
918        assert_eq!(
919            format!("{apk:?}"),
920            "blaze_user_meta_apk { path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
921        );
922
923        let elf = blaze_user_meta_elf {
924            path: ptr::null_mut(),
925            build_id_len: 0,
926            build_id: ptr::null_mut(),
927            reserved: [0; 16],
928        };
929        assert_eq!(
930            format!("{elf:?}"),
931            "blaze_user_meta_elf { path: 0x0, build_id_len: 0, build_id: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
932        );
933
934        let unknown = blaze_user_meta_unknown {
935            reason: blaze_normalize_reason::UNMAPPED,
936            reserved: [0; 15],
937        };
938        assert_eq!(
939            format!("{unknown:?}"),
940            "blaze_user_meta_unknown { reason: blaze_normalize_reason(0), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
941        );
942
943        let meta = blaze_user_meta {
944            kind: blaze_user_meta_kind::UNKNOWN,
945            unused: [0; 7],
946            variant: blaze_user_meta_variant {
947                unknown: ManuallyDrop::new(blaze_user_meta_unknown {
948                    reason: blaze_normalize_reason::UNMAPPED,
949                    reserved: [0; 15],
950                }),
951            },
952            reserved: [0; 16],
953        };
954        assert_eq!(
955            format!("{meta:?}"),
956            "blaze_user_meta { kind: blaze_user_meta_kind(0), unused: [0, 0, 0, 0, 0, 0, 0], variant: blaze_user_meta_variant, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
957        );
958
959        let normalized = blaze_normalized_user_output {
960            meta_cnt: 0,
961            metas: ptr::null_mut(),
962            output_cnt: 0,
963            outputs: ptr::null_mut(),
964            reserved: [0; 16],
965        };
966        assert_eq!(
967            format!("{normalized:?}"),
968            "blaze_normalized_user_output { meta_cnt: 0, metas: 0x0, output_cnt: 0, outputs: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
969        );
970    }
971
972    /// Make sure that we can stringify normalization reasons as expected.
973    #[tag(miri)]
974    #[test]
975    fn reason_stringification() {
976        let data = [
977            (Reason::Unmapped, blaze_normalize_reason::UNMAPPED),
978            (
979                Reason::MissingComponent,
980                blaze_normalize_reason::MISSING_COMPONENT,
981            ),
982            (Reason::Unsupported, blaze_normalize_reason::UNSUPPORTED),
983            (
984                Reason::InvalidFileOffset,
985                blaze_normalize_reason::INVALID_FILE_OFFSET,
986            ),
987            (Reason::MissingSyms, blaze_normalize_reason::MISSING_SYMS),
988            (Reason::UnknownAddr, blaze_normalize_reason::UNKNOWN_ADDR),
989            (Reason::IgnoredError, blaze_normalize_reason::IGNORED_ERROR),
990        ];
991
992        for (reason, expected) in data {
993            assert_eq!(blaze_normalize_reason::from(reason), expected);
994            let cstr = unsafe { CStr::from_ptr(blaze_normalize_reason_str(expected)) };
995            let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
996            assert_eq!(cstr, expected);
997        }
998    }
999
1000    /// Check that we can convert an [`Unknown`] into a
1001    /// [`blaze_user_meta_unknown`] and back.
1002    #[tag(miri)]
1003    #[test]
1004    fn unknown_conversion() {
1005        let unknown = Unknown {
1006            reason: Reason::Unsupported,
1007            _non_exhaustive: (),
1008        };
1009
1010        let unknown_c = blaze_user_meta_unknown::from(unknown.clone());
1011        let () = ManuallyDrop::into_inner(unknown_c).free();
1012
1013        let meta = UserMeta::Unknown(unknown);
1014        let meta_c = blaze_user_meta::from(meta);
1015        let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1016    }
1017
1018    /// Check that we can convert an [`Apk`] into a [`blaze_user_meta_apk`] and
1019    /// back.
1020    #[tag(miri)]
1021    #[test]
1022    fn apk_conversion() {
1023        let apk = Apk {
1024            path: PathBuf::from("/tmp/archive.apk"),
1025            _non_exhaustive: (),
1026        };
1027
1028        let apk_c = blaze_user_meta_apk::from(apk.clone());
1029        let () = unsafe { ManuallyDrop::into_inner(apk_c).free() };
1030
1031        let meta = UserMeta::Apk(apk);
1032        let meta_c = blaze_user_meta::from(meta);
1033        let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1034    }
1035
1036    /// Check that we can convert an [`Elf`] into a [`blaze_user_meta_elf`]
1037    /// and back.
1038    #[tag(miri)]
1039    #[test]
1040    fn elf_conversion() {
1041        let elf = Elf {
1042            path: PathBuf::from("/tmp/file.so"),
1043            build_id: Some(Cow::Borrowed(&[0x01, 0x02, 0x03, 0x04])),
1044            _non_exhaustive: (),
1045        };
1046
1047        let elf_c = blaze_user_meta_elf::from(elf.clone());
1048        let () = unsafe { ManuallyDrop::into_inner(elf_c).free() };
1049
1050        let meta = UserMeta::Elf(elf);
1051        let meta_c = blaze_user_meta::from(meta);
1052        let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1053    }
1054
1055    /// Make sure that we can create and free a normalizer instance.
1056    #[tag(miri)]
1057    #[test]
1058    fn normalizer_creation() {
1059        let normalizer = blaze_normalizer_new();
1060        let () = unsafe { blaze_normalizer_free(normalizer) };
1061    }
1062
1063    /// Check that we can normalize user space addresses.
1064    #[test]
1065    fn normalize_user_addrs() {
1066        fn test(normalizer: *const blaze_normalizer) {
1067            let addrs = [
1068                0x0,
1069                libc::atexit as Addr,
1070                libc::chdir as Addr,
1071                libc::fopen as Addr,
1072                elf_conversion as Addr,
1073                normalize_user_addrs as Addr,
1074            ];
1075
1076            let result = unsafe {
1077                blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1078            };
1079            assert_ne!(result, ptr::null_mut());
1080
1081            let normalized = unsafe { &*result };
1082            assert_eq!(normalized.meta_cnt, 3);
1083            assert_eq!(normalized.output_cnt, 6);
1084
1085            let meta = unsafe { normalized.metas.read() };
1086            assert_eq!(meta.kind, blaze_user_meta_kind::UNKNOWN);
1087            assert_eq!(
1088                unsafe { meta.variant.unknown.reason },
1089                blaze_normalize_reason::UNMAPPED
1090            );
1091
1092            let () = unsafe { blaze_user_output_free(result) };
1093        }
1094
1095        let normalizer = blaze_normalizer_new();
1096        assert_ne!(normalizer, ptr::null_mut());
1097        test(normalizer);
1098        let () = unsafe { blaze_normalizer_free(normalizer) };
1099
1100        let opts = blaze_normalizer_opts {
1101            cache_vmas: true,
1102            ..Default::default()
1103        };
1104        let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1105        assert_ne!(normalizer, ptr::null_mut());
1106        test(normalizer);
1107        test(normalizer);
1108        let () = unsafe { blaze_normalizer_free(normalizer) };
1109    }
1110
1111    fn test_normalize_user_addrs_sorted(use_procmap_query: bool) {
1112        let mut addrs = [
1113            libc::atexit as Addr,
1114            libc::chdir as Addr,
1115            libc::fopen as Addr,
1116            elf_conversion as Addr,
1117            normalize_user_addrs as Addr,
1118        ];
1119        let () = addrs.sort();
1120
1121        let opts = blaze_normalizer_opts {
1122            use_procmap_query,
1123            ..Default::default()
1124        };
1125        let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1126        assert_ne!(normalizer, ptr::null_mut());
1127
1128        let opts = blaze_normalize_opts {
1129            sorted_addrs: true,
1130            ..Default::default()
1131        };
1132        let result = unsafe {
1133            blaze_normalize_user_addrs_opts(
1134                normalizer,
1135                0,
1136                addrs.as_slice().as_ptr(),
1137                addrs.len(),
1138                &opts,
1139            )
1140        };
1141        assert_ne!(result, ptr::null_mut());
1142
1143        let normalized = unsafe { &*result };
1144        assert_eq!(normalized.meta_cnt, 2);
1145        assert_eq!(normalized.output_cnt, 5);
1146
1147        let () = unsafe { blaze_user_output_free(result) };
1148        let () = unsafe { blaze_normalizer_free(normalizer) };
1149    }
1150
1151    /// Check that we can normalize sorted user space addresses.
1152    #[test]
1153    fn normalize_user_addrs_sorted_proc_maps() {
1154        test_normalize_user_addrs_sorted(false)
1155    }
1156
1157    /// Check that we can normalize sorted user space addresses using
1158    /// the `PROCMAP_QUERY` ioctl.
1159    #[test]
1160    #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
1161    fn normalize_user_addrs_sorted_ioctl() {
1162        test_normalize_user_addrs_sorted(true)
1163    }
1164
1165    /// Check that we fail normalizing unsorted addresses with a function that
1166    /// requires them to be sorted.
1167    #[test]
1168    fn normalize_user_addrs_unsorted_failure() {
1169        let mut addrs = [
1170            libc::atexit as Addr,
1171            libc::chdir as Addr,
1172            libc::fopen as Addr,
1173            elf_conversion as Addr,
1174            normalize_user_addrs as Addr,
1175        ];
1176        let () = addrs.sort_by(|addr1, addr2| addr1.cmp(addr2).reverse());
1177
1178        let normalizer = blaze_normalizer_new();
1179        assert_ne!(normalizer, ptr::null_mut());
1180
1181        let opts = blaze_normalize_opts {
1182            sorted_addrs: true,
1183            ..Default::default()
1184        };
1185        let result = unsafe {
1186            blaze_normalize_user_addrs_opts(
1187                normalizer,
1188                0,
1189                addrs.as_slice().as_ptr(),
1190                addrs.len(),
1191                &opts,
1192            )
1193        };
1194        assert_eq!(result, ptr::null_mut());
1195        assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
1196
1197        let () = unsafe { blaze_normalizer_free(normalizer) };
1198    }
1199
1200    /// Check that we can enable/disable the reading of build IDs.
1201    #[test]
1202    #[cfg_attr(
1203        not(target_pointer_width = "64"),
1204        ignore = "loads 64 bit shared object"
1205    )]
1206    fn normalize_build_id_reading() {
1207        fn test(read_build_ids: bool) {
1208            let test_so = Path::new(&env!("CARGO_MANIFEST_DIR"))
1209                .join("..")
1210                .join("data")
1211                .join("libtest-so.so")
1212                .canonicalize()
1213                .unwrap();
1214            let so_cstr = CString::new(test_so.clone().into_os_string().into_vec()).unwrap();
1215            let handle = unsafe { libc::dlopen(so_cstr.as_ptr(), libc::RTLD_NOW) };
1216            assert!(!handle.is_null());
1217
1218            let the_answer_addr = unsafe { libc::dlsym(handle, "the_answer\0".as_ptr().cast()) };
1219            assert!(!the_answer_addr.is_null());
1220
1221            let opts = blaze_normalizer_opts {
1222                build_ids: read_build_ids,
1223                ..Default::default()
1224            };
1225
1226            let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1227            assert!(!normalizer.is_null());
1228
1229            let opts = blaze_normalize_opts {
1230                sorted_addrs: true,
1231                ..Default::default()
1232            };
1233            let addrs = [the_answer_addr as Addr];
1234            let result = unsafe {
1235                blaze_normalize_user_addrs_opts(
1236                    normalizer,
1237                    0,
1238                    addrs.as_slice().as_ptr(),
1239                    addrs.len(),
1240                    &opts,
1241                )
1242            };
1243            assert!(!result.is_null());
1244
1245            let normalized = unsafe { &*result };
1246            assert_eq!(normalized.meta_cnt, 1);
1247            assert_eq!(normalized.output_cnt, 1);
1248
1249            let rc = unsafe { libc::dlclose(handle) };
1250            assert_eq!(rc, 0, "{}", io::Error::last_os_error());
1251
1252            let output = unsafe { &*normalized.outputs.add(0) };
1253            let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1254            assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1255
1256            let elf = unsafe { &meta.variant.elf };
1257
1258            assert!(!elf.path.is_null());
1259            let path = unsafe { CStr::from_ptr(elf.path) };
1260            assert_eq!(path, so_cstr.as_ref());
1261
1262            if read_build_ids {
1263                let expected = read_elf_build_id(&test_so).unwrap().unwrap();
1264                let build_id = unsafe { slice_from_user_array(elf.build_id, elf.build_id_len) };
1265                assert_eq!(build_id, expected.as_ref());
1266            } else {
1267                assert!(elf.build_id.is_null());
1268            }
1269
1270            let () = unsafe { blaze_user_output_free(result) };
1271            let () = unsafe { blaze_normalizer_free(normalizer) };
1272        }
1273
1274        test(true);
1275        test(false);
1276    }
1277
1278    /// Check that we can normalize addresses in our own shared object inside a
1279    /// zip archive.
1280    #[test]
1281    fn normalize_custom_so_in_zip() {
1282        let test_zip = Path::new(&env!("CARGO_MANIFEST_DIR"))
1283            .join("..")
1284            .join("data")
1285            .join("test.zip");
1286        let so_name = "libtest-so.so";
1287
1288        let mmap = Mmap::builder().exec().open(&test_zip).unwrap();
1289        let archive = zip::Archive::with_mmap(mmap.clone()).unwrap();
1290        let so = archive
1291            .entries()
1292            .find_map(|entry| {
1293                let entry = entry.unwrap();
1294                (entry.path == Path::new(so_name)).then_some(entry)
1295            })
1296            .unwrap();
1297
1298        let elf_mmap = mmap
1299            .constrain(so.data_offset..so.data_offset + so.data.len() as u64)
1300            .unwrap();
1301        let (_sym, the_answer_addr) = find_the_answer_fn(&elf_mmap);
1302
1303        let normalizer = blaze_normalizer_new();
1304        assert!(!normalizer.is_null());
1305
1306        let addrs = [the_answer_addr];
1307        let opts = blaze_normalize_opts {
1308            apk_to_elf: true,
1309            ..Default::default()
1310        };
1311        let result = unsafe {
1312            blaze_normalize_user_addrs_opts(
1313                normalizer,
1314                0,
1315                addrs.as_slice().as_ptr(),
1316                addrs.len(),
1317                &opts,
1318            )
1319        };
1320        assert_ne!(result, ptr::null_mut());
1321
1322        let normalized = unsafe { &*result };
1323        assert_eq!(normalized.meta_cnt, 1);
1324        assert_eq!(normalized.output_cnt, 1);
1325
1326        let output = unsafe { &*normalized.outputs.add(0) };
1327        let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1328        assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1329
1330        let elf = unsafe { &meta.variant.elf };
1331        let path = unsafe { CStr::from_ptr(elf.path) };
1332        assert!(path.to_str().unwrap().ends_with(so_name), "{path:?}");
1333
1334        let () = unsafe { blaze_user_output_free(result) };
1335        let () = unsafe { blaze_normalizer_free(normalizer) };
1336    }
1337
1338    /// Make sure that we can normalize addresses in a vDSO in the current
1339    /// process.
1340    #[cfg(linux)]
1341    // 32 bit system may not have vDSO.
1342    #[cfg(target_pointer_width = "64")]
1343    #[test]
1344    fn normalize_local_vdso_address() {
1345        use libc::gettimeofday;
1346
1347        let addrs = [normalize_local_vdso_address as Addr, gettimeofday as Addr];
1348        let normalizer = blaze_normalizer_new();
1349        assert!(!normalizer.is_null());
1350
1351        let result = unsafe {
1352            blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1353        };
1354        assert_ne!(result, ptr::null_mut());
1355
1356        let normalized = unsafe { &*result };
1357        assert_eq!(normalized.meta_cnt, 2);
1358        assert_eq!(normalized.output_cnt, 2);
1359
1360        let output = unsafe { &*normalized.outputs.add(1) };
1361        let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1362        assert_eq!(meta.kind, blaze_user_meta_kind::SYM);
1363
1364        let sym = unsafe { &*meta.variant.sym.sym };
1365        let name = unsafe { CStr::from_ptr(sym.name) };
1366        assert!(name.to_str().unwrap().ends_with("gettimeofday"), "{name:?}");
1367
1368        let () = unsafe { blaze_user_output_free(result) };
1369        let () = unsafe { blaze_normalizer_free(normalizer) };
1370    }
1371
1372    /// Make sure that we can handle an invalid address inside the
1373    /// system vDSO as expected.
1374    #[cfg(linux)]
1375    #[cfg(target_pointer_width = "64")]
1376    #[test]
1377    fn normalize_invalid_vdso_address() {
1378        use blazesym::__private::find_vdso_range;
1379
1380        let vdso = find_vdso_range();
1381        // This address should map to the vDSO's ELF header, meaning it
1382        // does not belong to a symbol and normalization/symbolization
1383        // should fail.
1384        let addrs = [vdso.start];
1385        let normalizer = blaze_normalizer_new();
1386        assert!(!normalizer.is_null());
1387
1388        let result = unsafe {
1389            blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1390        };
1391        assert_ne!(result, ptr::null_mut());
1392
1393        let normalized = unsafe { &*result };
1394        assert_eq!(normalized.meta_cnt, 1);
1395        assert_eq!(normalized.output_cnt, 1);
1396
1397        let output = unsafe { &*normalized.outputs };
1398        let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1399        match meta.kind {
1400            // Perhaps counter-intuitively, we may actually see a
1401            // symbolization here. The reason being that some vDSOs
1402            // contains an absolute 0-byte LINUX_2.6 tag in there, which,
1403            // well, because of all the heuristics we need to employ, we
1404            // end up symbolizing.
1405            blaze_user_meta_kind::UNKNOWN | blaze_user_meta_kind::SYM => (),
1406            _ => panic!("encountered unexpected meta kind: {:?}", meta.kind),
1407        }
1408
1409        let () = unsafe { blaze_user_output_free(result) };
1410        let () = unsafe { blaze_normalizer_free(normalizer) };
1411    }
1412}