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