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}
497
498impl From<Reason> for blaze_normalize_reason {
499    fn from(reason: Reason) -> Self {
500        match reason {
501            Reason::Unmapped => blaze_normalize_reason::UNMAPPED,
502            Reason::MissingComponent => blaze_normalize_reason::MISSING_COMPONENT,
503            Reason::Unsupported => blaze_normalize_reason::UNSUPPORTED,
504            _ => unreachable!(),
505        }
506    }
507}
508
509
510/// Retrieve a textual representation of the reason of a normalization failure.
511#[no_mangle]
512pub extern "C" fn blaze_normalize_reason_str(err: blaze_normalize_reason) -> *const c_char {
513    match err {
514        blaze_normalize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
515        blaze_normalize_reason::MISSING_COMPONENT => {
516            Reason::MissingComponent.as_bytes().as_ptr().cast()
517        }
518        blaze_normalize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
519        _ => b"unknown reason\0".as_ptr().cast(),
520    }
521}
522
523
524/// C compatible version of [`Unknown`].
525#[repr(C)]
526#[derive(Debug)]
527pub struct blaze_user_meta_unknown {
528    /// The reason why normalization failed.
529    ///
530    /// The provided reason is a best guess, hinting at what ultimately
531    /// prevented the normalization from being successful.
532    pub reason: blaze_normalize_reason,
533    /// Unused member available for future expansion.
534    pub reserved: [u8; 15],
535}
536
537impl blaze_user_meta_unknown {
538    fn from(other: Unknown) -> ManuallyDrop<Self> {
539        let Unknown {
540            reason,
541            _non_exhaustive: (),
542        } = other;
543
544        let slf = Self {
545            reason: reason.into(),
546            reserved: [0; 15],
547        };
548        ManuallyDrop::new(slf)
549    }
550
551    fn free(self) {
552        let blaze_user_meta_unknown {
553            reason: _,
554            reserved: _,
555        } = self;
556    }
557}
558
559
560/// The actual variant data in [`blaze_user_meta`].
561#[repr(C)]
562pub union blaze_user_meta_variant {
563    /// Valid on [`blaze_user_meta_kind::APK`].
564    pub apk: ManuallyDrop<blaze_user_meta_apk>,
565    /// Valid on [`blaze_user_meta_kind::ELF`].
566    pub elf: ManuallyDrop<blaze_user_meta_elf>,
567    /// Valid on [`blaze_user_meta_kind::SYM`].
568    pub sym: ManuallyDrop<blaze_user_meta_sym>,
569    /// Valid on [`blaze_user_meta_kind::UNKNOWN`].
570    pub unknown: ManuallyDrop<blaze_user_meta_unknown>,
571}
572
573impl Debug for blaze_user_meta_variant {
574    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
575        f.debug_struct(stringify!(blaze_user_meta_variant)).finish()
576    }
577}
578
579
580/// C ABI compatible version of [`UserMeta`].
581#[repr(C)]
582#[derive(Debug)]
583pub struct blaze_user_meta {
584    /// The variant kind that is present.
585    pub kind: blaze_user_meta_kind,
586    /// Currently unused bytes.
587    pub unused: [u8; 7],
588    /// The actual variant with its data.
589    pub variant: blaze_user_meta_variant,
590    /// Unused member available for future expansion. Must be initialized
591    /// to zero.
592    pub reserved: [u8; 16],
593}
594
595impl blaze_user_meta {
596    fn from(other: UserMeta) -> ManuallyDrop<Self> {
597        let slf = match other {
598            UserMeta::Apk(apk) => Self {
599                kind: blaze_user_meta_kind::APK,
600                unused: [0; 7],
601                variant: blaze_user_meta_variant {
602                    apk: blaze_user_meta_apk::from(apk),
603                },
604                reserved: [0; 16],
605            },
606            UserMeta::Elf(elf) => Self {
607                kind: blaze_user_meta_kind::ELF,
608                unused: [0; 7],
609                variant: blaze_user_meta_variant {
610                    elf: blaze_user_meta_elf::from(elf),
611                },
612                reserved: [0; 16],
613            },
614            UserMeta::Sym(sym) => Self {
615                kind: blaze_user_meta_kind::SYM,
616                unused: [0; 7],
617                variant: blaze_user_meta_variant {
618                    sym: blaze_user_meta_sym::from(sym),
619                },
620                reserved: [0; 16],
621            },
622            UserMeta::Unknown(unknown) => Self {
623                kind: blaze_user_meta_kind::UNKNOWN,
624                unused: [0; 7],
625                variant: blaze_user_meta_variant {
626                    unknown: blaze_user_meta_unknown::from(unknown),
627                },
628                reserved: [0; 16],
629            },
630            _ => unreachable!(),
631        };
632        ManuallyDrop::new(slf)
633    }
634
635    unsafe fn free(self) {
636        match self.kind {
637            blaze_user_meta_kind::APK => unsafe {
638                ManuallyDrop::into_inner(self.variant.apk).free()
639            },
640            blaze_user_meta_kind::ELF => unsafe {
641                ManuallyDrop::into_inner(self.variant.elf).free()
642            },
643            blaze_user_meta_kind::SYM => unsafe {
644                ManuallyDrop::into_inner(self.variant.sym).free()
645            },
646            blaze_user_meta_kind::UNKNOWN => {
647                ManuallyDrop::into_inner(unsafe { self.variant.unknown }).free()
648            }
649            _ => {
650                debug_assert!(false)
651            }
652        }
653    }
654}
655
656
657/// An object representing normalized user addresses.
658///
659/// C ABI compatible version of [`UserOutput`].
660#[repr(C)]
661#[derive(Debug)]
662pub struct blaze_normalized_user_output {
663    /// The number of [`blaze_user_meta`] objects present in `metas`.
664    pub meta_cnt: usize,
665    /// An array of `meta_cnt` objects.
666    pub metas: *mut blaze_user_meta,
667    /// The number of [`blaze_normalized_output`] objects present in `outputs`.
668    pub output_cnt: usize,
669    /// An array of `output_cnt` objects.
670    pub outputs: *mut blaze_normalized_output,
671    /// Unused member available for future expansion.
672    pub reserved: [u8; 16],
673}
674
675impl blaze_normalized_user_output {
676    fn from(other: UserOutput) -> ManuallyDrop<Self> {
677        let slf = Self {
678            meta_cnt: other.meta.len(),
679            metas: unsafe {
680                Box::into_raw(
681                    other
682                        .meta
683                        .into_iter()
684                        .map(blaze_user_meta::from)
685                        .map(ManuallyDrop::into_inner)
686                        .collect::<Vec<_>>()
687                        .into_boxed_slice(),
688                )
689                .as_mut()
690                .unwrap()
691                .as_mut_ptr()
692            },
693            output_cnt: other.outputs.len(),
694            outputs: unsafe {
695                Box::into_raw(
696                    other
697                        .outputs
698                        .into_iter()
699                        .map(blaze_normalized_output::from)
700                        .collect::<Vec<_>>()
701                        .into_boxed_slice(),
702                )
703                .as_mut()
704                .unwrap()
705                .as_mut_ptr()
706            },
707            reserved: [0; 16],
708        };
709        ManuallyDrop::new(slf)
710    }
711}
712
713
714unsafe fn blaze_normalize_user_addrs_impl(
715    normalizer: *const blaze_normalizer,
716    pid: u32,
717    addrs: *const Addr,
718    addr_cnt: usize,
719    opts: &NormalizeOpts,
720) -> *mut blaze_normalized_user_output {
721    // SAFETY: The caller needs to ensure that `normalizer` is a valid
722    //         pointer.
723    let normalizer = unsafe { &*normalizer };
724    // SAFETY: The caller needs to ensure that `addrs` is a valid pointer and
725    //         that it points to `addr_cnt` elements.
726    let addrs = unsafe { slice_from_user_array(addrs, addr_cnt) };
727    let result = normalizer.normalize_user_addrs_opts(pid.into(), &addrs, opts);
728    match result {
729        Ok(output) => {
730            let output_box = Box::new(ManuallyDrop::into_inner(
731                blaze_normalized_user_output::from(output),
732            ));
733            let () = set_last_err(blaze_err::OK);
734            Box::into_raw(output_box)
735        }
736        Err(err) => {
737            let () = set_last_err(err.kind().into());
738            ptr::null_mut()
739        }
740    }
741}
742
743
744/// Normalize a list of user space addresses.
745///
746/// C ABI compatible version of [`Normalizer::normalize_user_addrs`].
747///
748/// `pid` should describe the PID of the process to which the addresses
749/// belongs. It may be `0` if they belong to the calling process.
750///
751/// On success, the function creates a new [`blaze_normalized_user_output`]
752/// object and returns it. The resulting object should be released using
753/// [`blaze_user_output_free`] once it is no longer needed.
754///
755/// On error, the function returns `NULL` and sets the thread's last error to
756/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
757/// error.
758///
759/// # Safety
760/// - `addrs` needs to be a valid pointer to `addr_cnt` addresses
761#[no_mangle]
762pub unsafe extern "C" fn blaze_normalize_user_addrs(
763    normalizer: *const blaze_normalizer,
764    pid: u32,
765    addrs: *const Addr,
766    addr_cnt: usize,
767) -> *mut blaze_normalized_user_output {
768    let opts = NormalizeOpts::default();
769
770    unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
771}
772
773
774/// Normalize a list of user space addresses.
775///
776/// C ABI compatible version of [`Normalizer::normalize_user_addrs_opts`].
777///
778/// `pid` should describe the PID of the process to which the addresses
779/// belongs. It may be `0` if they belong to the calling process.
780///
781/// `opts` should point to a valid [`blaze_normalize_opts`] object.
782///
783/// On success, the function creates a new [`blaze_normalized_user_output`]
784/// object and returns it. The resulting object should be released using
785/// [`blaze_user_output_free`] once it is no longer needed.
786///
787/// On error, the function returns `NULL` and sets the thread's last error to
788/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
789/// error.
790///
791/// # Safety
792/// - `addrs` needs to be a valid pointer to `addr_cnt` addresses
793#[no_mangle]
794pub unsafe extern "C" fn blaze_normalize_user_addrs_opts(
795    normalizer: *const blaze_normalizer,
796    pid: u32,
797    addrs: *const Addr,
798    addr_cnt: usize,
799    opts: *const blaze_normalize_opts,
800) -> *mut blaze_normalized_user_output {
801    if !input_zeroed!(opts, blaze_normalize_opts) {
802        let () = set_last_err(blaze_err::INVALID_INPUT);
803        return ptr::null_mut()
804    }
805    let opts = input_sanitize!(opts, blaze_normalize_opts);
806    let opts = NormalizeOpts::from(opts);
807
808    unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
809}
810
811
812/// Free an object as returned by [`blaze_normalize_user_addrs`] or
813/// [`blaze_normalize_user_addrs_opts`].
814///
815/// # Safety
816/// The provided object should have been created by
817/// [`blaze_normalize_user_addrs`] or
818/// [`blaze_normalize_user_addrs_opts`].
819#[no_mangle]
820pub unsafe extern "C" fn blaze_user_output_free(output: *mut blaze_normalized_user_output) {
821    if output.is_null() {
822        return
823    }
824
825    // SAFETY: The caller should make sure that `output` was created by one of
826    //         our blessed functions.
827    let user_output = unsafe { Box::from_raw(output) };
828    let addr_metas = unsafe {
829        Box::<[blaze_user_meta]>::from_raw(slice::from_raw_parts_mut(
830            user_output.metas,
831            user_output.meta_cnt,
832        ))
833    }
834    .into_vec();
835    let _norm_addrs = unsafe {
836        Box::<[blaze_normalized_output]>::from_raw(slice::from_raw_parts_mut(
837            user_output.outputs,
838            user_output.output_cnt,
839        ))
840    }
841    .into_vec();
842
843    for addr_meta in addr_metas {
844        let () = unsafe { addr_meta.free() };
845    }
846}
847
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852
853    use std::ffi::CStr;
854    use std::io;
855    use std::path::Path;
856
857    use blazesym::helper::read_elf_build_id;
858    use blazesym::Mmap;
859    use blazesym::__private::find_the_answer_fn;
860    use blazesym::__private::zip;
861
862    use test_tag::tag;
863
864    use crate::blaze_err_last;
865
866
867    /// Check that various types have expected sizes.
868    #[tag(miri)]
869    #[test]
870    #[cfg(target_pointer_width = "64")]
871    fn type_sizes() {
872        assert_eq!(size_of::<blaze_normalizer_opts>(), 32);
873        assert_eq!(size_of::<blaze_normalize_opts>(), 32);
874        assert_eq!(size_of::<blaze_user_meta_apk>(), 24);
875        assert_eq!(size_of::<blaze_user_meta_elf>(), 40);
876        assert_eq!(size_of::<blaze_user_meta_sym>(), 24);
877        assert_eq!(size_of::<blaze_user_meta_unknown>(), 16);
878    }
879
880    /// Exercise the `Debug` representation of various types.
881    #[tag(miri)]
882    #[test]
883    fn debug_repr() {
884        let output = blaze_normalized_output {
885            output: 0x1337,
886            meta_idx: 1,
887            reserved: [0; 16],
888        };
889        assert_eq!(
890            format!("{output:?}"),
891            "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] }"
892        );
893
894        let meta_kind = blaze_user_meta_kind::APK;
895        assert_eq!(format!("{meta_kind:?}"), "blaze_user_meta_kind(1)");
896
897        let apk = blaze_user_meta_apk {
898            path: ptr::null_mut(),
899            reserved: [0; 16],
900        };
901        assert_eq!(
902            format!("{apk:?}"),
903            "blaze_user_meta_apk { path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
904        );
905
906        let elf = blaze_user_meta_elf {
907            path: ptr::null_mut(),
908            build_id_len: 0,
909            build_id: ptr::null_mut(),
910            reserved: [0; 16],
911        };
912        assert_eq!(
913            format!("{elf:?}"),
914            "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] }",
915        );
916
917        let unknown = blaze_user_meta_unknown {
918            reason: blaze_normalize_reason::UNMAPPED,
919            reserved: [0; 15],
920        };
921        assert_eq!(
922            format!("{unknown:?}"),
923            "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] }",
924        );
925
926        let meta = blaze_user_meta {
927            kind: blaze_user_meta_kind::UNKNOWN,
928            unused: [0; 7],
929            variant: blaze_user_meta_variant {
930                unknown: ManuallyDrop::new(blaze_user_meta_unknown {
931                    reason: blaze_normalize_reason::UNMAPPED,
932                    reserved: [0; 15],
933                }),
934            },
935            reserved: [0; 16],
936        };
937        assert_eq!(
938            format!("{meta:?}"),
939            "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] }",
940        );
941
942        let normalized = blaze_normalized_user_output {
943            meta_cnt: 0,
944            metas: ptr::null_mut(),
945            output_cnt: 0,
946            outputs: ptr::null_mut(),
947            reserved: [0; 16],
948        };
949        assert_eq!(
950            format!("{normalized:?}"),
951            "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] }",
952        );
953    }
954
955    /// Make sure that we can stringify normalization reasons as expected.
956    #[tag(miri)]
957    #[test]
958    fn reason_stringification() {
959        let data = [
960            (Reason::Unmapped, blaze_normalize_reason::UNMAPPED),
961            (
962                Reason::MissingComponent,
963                blaze_normalize_reason::MISSING_COMPONENT,
964            ),
965            (Reason::Unsupported, blaze_normalize_reason::UNSUPPORTED),
966        ];
967
968        for (reason, expected) in data {
969            assert_eq!(blaze_normalize_reason::from(reason), expected);
970            let cstr = unsafe { CStr::from_ptr(blaze_normalize_reason_str(expected)) };
971            let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
972            assert_eq!(cstr, expected);
973        }
974    }
975
976    /// Check that we can convert an [`Unknown`] into a
977    /// [`blaze_user_meta_unknown`] and back.
978    #[tag(miri)]
979    #[test]
980    fn unknown_conversion() {
981        let unknown = Unknown {
982            reason: Reason::Unsupported,
983            _non_exhaustive: (),
984        };
985
986        let unknown_c = blaze_user_meta_unknown::from(unknown.clone());
987        let () = ManuallyDrop::into_inner(unknown_c).free();
988
989        let meta = UserMeta::Unknown(unknown);
990        let meta_c = blaze_user_meta::from(meta);
991        let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
992    }
993
994    /// Check that we can convert an [`Apk`] into a [`blaze_user_meta_apk`] and
995    /// back.
996    #[tag(miri)]
997    #[test]
998    fn apk_conversion() {
999        let apk = Apk {
1000            path: PathBuf::from("/tmp/archive.apk"),
1001            _non_exhaustive: (),
1002        };
1003
1004        let apk_c = blaze_user_meta_apk::from(apk.clone());
1005        let () = unsafe { ManuallyDrop::into_inner(apk_c).free() };
1006
1007        let meta = UserMeta::Apk(apk);
1008        let meta_c = blaze_user_meta::from(meta);
1009        let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1010    }
1011
1012    /// Check that we can convert an [`Elf`] into a [`blaze_user_meta_elf`]
1013    /// and back.
1014    #[tag(miri)]
1015    #[test]
1016    fn elf_conversion() {
1017        let elf = Elf {
1018            path: PathBuf::from("/tmp/file.so"),
1019            build_id: Some(Cow::Borrowed(&[0x01, 0x02, 0x03, 0x04])),
1020            _non_exhaustive: (),
1021        };
1022
1023        let elf_c = blaze_user_meta_elf::from(elf.clone());
1024        let () = unsafe { ManuallyDrop::into_inner(elf_c).free() };
1025
1026        let meta = UserMeta::Elf(elf);
1027        let meta_c = blaze_user_meta::from(meta);
1028        let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1029    }
1030
1031    /// Make sure that we can create and free a normalizer instance.
1032    #[tag(miri)]
1033    #[test]
1034    fn normalizer_creation() {
1035        let normalizer = blaze_normalizer_new();
1036        let () = unsafe { blaze_normalizer_free(normalizer) };
1037    }
1038
1039    /// Check that we can normalize user space addresses.
1040    #[test]
1041    fn normalize_user_addrs() {
1042        fn test(normalizer: *const blaze_normalizer) {
1043            let addrs = [
1044                0x0,
1045                libc::atexit as Addr,
1046                libc::chdir as Addr,
1047                libc::fopen as Addr,
1048                elf_conversion as Addr,
1049                normalize_user_addrs as Addr,
1050            ];
1051
1052            let result = unsafe {
1053                blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1054            };
1055            assert_ne!(result, ptr::null_mut());
1056
1057            let normalized = unsafe { &*result };
1058            assert_eq!(normalized.meta_cnt, 3);
1059            assert_eq!(normalized.output_cnt, 6);
1060
1061            let meta = unsafe { normalized.metas.read() };
1062            assert_eq!(meta.kind, blaze_user_meta_kind::UNKNOWN);
1063            assert_eq!(
1064                unsafe { meta.variant.unknown.reason },
1065                blaze_normalize_reason::UNMAPPED
1066            );
1067
1068            let () = unsafe { blaze_user_output_free(result) };
1069        }
1070
1071        let normalizer = blaze_normalizer_new();
1072        assert_ne!(normalizer, ptr::null_mut());
1073        test(normalizer);
1074        let () = unsafe { blaze_normalizer_free(normalizer) };
1075
1076        let opts = blaze_normalizer_opts {
1077            cache_vmas: true,
1078            ..Default::default()
1079        };
1080        let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1081        assert_ne!(normalizer, ptr::null_mut());
1082        test(normalizer);
1083        test(normalizer);
1084        let () = unsafe { blaze_normalizer_free(normalizer) };
1085    }
1086
1087    fn test_normalize_user_addrs_sorted(use_procmap_query: bool) {
1088        let mut addrs = [
1089            libc::atexit as Addr,
1090            libc::chdir as Addr,
1091            libc::fopen as Addr,
1092            elf_conversion as Addr,
1093            normalize_user_addrs as Addr,
1094        ];
1095        let () = addrs.sort();
1096
1097        let opts = blaze_normalizer_opts {
1098            use_procmap_query,
1099            ..Default::default()
1100        };
1101        let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1102        assert_ne!(normalizer, ptr::null_mut());
1103
1104        let opts = blaze_normalize_opts {
1105            sorted_addrs: true,
1106            ..Default::default()
1107        };
1108        let result = unsafe {
1109            blaze_normalize_user_addrs_opts(
1110                normalizer,
1111                0,
1112                addrs.as_slice().as_ptr(),
1113                addrs.len(),
1114                &opts,
1115            )
1116        };
1117        assert_ne!(result, ptr::null_mut());
1118
1119        let normalized = unsafe { &*result };
1120        assert_eq!(normalized.meta_cnt, 2);
1121        assert_eq!(normalized.output_cnt, 5);
1122
1123        let () = unsafe { blaze_user_output_free(result) };
1124        let () = unsafe { blaze_normalizer_free(normalizer) };
1125    }
1126
1127    /// Check that we can normalize sorted user space addresses.
1128    #[test]
1129    fn normalize_user_addrs_sorted_proc_maps() {
1130        test_normalize_user_addrs_sorted(false)
1131    }
1132
1133    /// Check that we can normalize sorted user space addresses using
1134    /// the `PROCMAP_QUERY` ioctl.
1135    #[test]
1136    #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
1137    fn normalize_user_addrs_sorted_ioctl() {
1138        test_normalize_user_addrs_sorted(true)
1139    }
1140
1141    /// Check that we fail normalizing unsorted addresses with a function that
1142    /// requires them to be sorted.
1143    #[test]
1144    fn normalize_user_addrs_unsorted_failure() {
1145        let mut addrs = [
1146            libc::atexit as Addr,
1147            libc::chdir as Addr,
1148            libc::fopen as Addr,
1149            elf_conversion as Addr,
1150            normalize_user_addrs as Addr,
1151        ];
1152        let () = addrs.sort_by(|addr1, addr2| addr1.cmp(addr2).reverse());
1153
1154        let normalizer = blaze_normalizer_new();
1155        assert_ne!(normalizer, ptr::null_mut());
1156
1157        let opts = blaze_normalize_opts {
1158            sorted_addrs: true,
1159            ..Default::default()
1160        };
1161        let result = unsafe {
1162            blaze_normalize_user_addrs_opts(
1163                normalizer,
1164                0,
1165                addrs.as_slice().as_ptr(),
1166                addrs.len(),
1167                &opts,
1168            )
1169        };
1170        assert_eq!(result, ptr::null_mut());
1171        assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
1172
1173        let () = unsafe { blaze_normalizer_free(normalizer) };
1174    }
1175
1176    /// Check that we can enable/disable the reading of build IDs.
1177    #[test]
1178    #[cfg_attr(
1179        not(target_pointer_width = "64"),
1180        ignore = "loads 64 bit shared object"
1181    )]
1182    fn normalize_build_id_reading() {
1183        fn test(read_build_ids: bool) {
1184            let test_so = Path::new(&env!("CARGO_MANIFEST_DIR"))
1185                .join("..")
1186                .join("data")
1187                .join("libtest-so.so")
1188                .canonicalize()
1189                .unwrap();
1190            let so_cstr = CString::new(test_so.clone().into_os_string().into_vec()).unwrap();
1191            let handle = unsafe { libc::dlopen(so_cstr.as_ptr(), libc::RTLD_NOW) };
1192            assert!(!handle.is_null());
1193
1194            let the_answer_addr = unsafe { libc::dlsym(handle, "the_answer\0".as_ptr().cast()) };
1195            assert!(!the_answer_addr.is_null());
1196
1197            let opts = blaze_normalizer_opts {
1198                build_ids: read_build_ids,
1199                ..Default::default()
1200            };
1201
1202            let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1203            assert!(!normalizer.is_null());
1204
1205            let opts = blaze_normalize_opts {
1206                sorted_addrs: true,
1207                ..Default::default()
1208            };
1209            let addrs = [the_answer_addr as Addr];
1210            let result = unsafe {
1211                blaze_normalize_user_addrs_opts(
1212                    normalizer,
1213                    0,
1214                    addrs.as_slice().as_ptr(),
1215                    addrs.len(),
1216                    &opts,
1217                )
1218            };
1219            assert!(!result.is_null());
1220
1221            let normalized = unsafe { &*result };
1222            assert_eq!(normalized.meta_cnt, 1);
1223            assert_eq!(normalized.output_cnt, 1);
1224
1225            let rc = unsafe { libc::dlclose(handle) };
1226            assert_eq!(rc, 0, "{}", io::Error::last_os_error());
1227
1228            let output = unsafe { &*normalized.outputs.add(0) };
1229            let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1230            assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1231
1232            let elf = unsafe { &meta.variant.elf };
1233
1234            assert!(!elf.path.is_null());
1235            let path = unsafe { CStr::from_ptr(elf.path) };
1236            assert_eq!(path, so_cstr.as_ref());
1237
1238            if read_build_ids {
1239                let expected = read_elf_build_id(&test_so).unwrap().unwrap();
1240                let build_id = unsafe { slice_from_user_array(elf.build_id, elf.build_id_len) };
1241                assert_eq!(build_id, expected.as_ref());
1242            } else {
1243                assert!(elf.build_id.is_null());
1244            }
1245
1246            let () = unsafe { blaze_user_output_free(result) };
1247            let () = unsafe { blaze_normalizer_free(normalizer) };
1248        }
1249
1250        test(true);
1251        test(false);
1252    }
1253
1254    /// Check that we can normalize addresses in our own shared object inside a
1255    /// zip archive.
1256    #[test]
1257    fn normalize_custom_so_in_zip() {
1258        let test_zip = Path::new(&env!("CARGO_MANIFEST_DIR"))
1259            .join("..")
1260            .join("data")
1261            .join("test.zip");
1262        let so_name = "libtest-so.so";
1263
1264        let mmap = Mmap::builder().exec().open(&test_zip).unwrap();
1265        let archive = zip::Archive::with_mmap(mmap.clone()).unwrap();
1266        let so = archive
1267            .entries()
1268            .find_map(|entry| {
1269                let entry = entry.unwrap();
1270                (entry.path == Path::new(so_name)).then_some(entry)
1271            })
1272            .unwrap();
1273
1274        let elf_mmap = mmap
1275            .constrain(so.data_offset..so.data_offset + so.data.len() as u64)
1276            .unwrap();
1277        let (_sym, the_answer_addr) = find_the_answer_fn(&elf_mmap);
1278
1279        let normalizer = blaze_normalizer_new();
1280        assert!(!normalizer.is_null());
1281
1282        let addrs = [the_answer_addr];
1283        let opts = blaze_normalize_opts {
1284            apk_to_elf: true,
1285            ..Default::default()
1286        };
1287        let result = unsafe {
1288            blaze_normalize_user_addrs_opts(
1289                normalizer,
1290                0,
1291                addrs.as_slice().as_ptr(),
1292                addrs.len(),
1293                &opts,
1294            )
1295        };
1296        assert_ne!(result, ptr::null_mut());
1297
1298        let normalized = unsafe { &*result };
1299        assert_eq!(normalized.meta_cnt, 1);
1300        assert_eq!(normalized.output_cnt, 1);
1301
1302        let output = unsafe { &*normalized.outputs.add(0) };
1303        let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1304        assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1305
1306        let elf = unsafe { &meta.variant.elf };
1307        let path = unsafe { CStr::from_ptr(elf.path) };
1308        assert!(path.to_str().unwrap().ends_with(so_name), "{path:?}");
1309
1310        let () = unsafe { blaze_user_output_free(result) };
1311        let () = unsafe { blaze_normalizer_free(normalizer) };
1312    }
1313
1314    /// Make sure that we can normalize addresses in a vDSO in the current
1315    /// process.
1316    #[cfg(linux)]
1317    // 32 bit system may not have vDSO.
1318    #[cfg(target_pointer_width = "64")]
1319    #[test]
1320    fn normalize_local_vdso_address() {
1321        use libc::gettimeofday;
1322
1323        let addrs = [normalize_local_vdso_address as Addr, gettimeofday as Addr];
1324        let normalizer = blaze_normalizer_new();
1325        assert!(!normalizer.is_null());
1326
1327        let result = unsafe {
1328            blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1329        };
1330        assert_ne!(result, ptr::null_mut());
1331
1332        let normalized = unsafe { &*result };
1333        assert_eq!(normalized.meta_cnt, 2);
1334        assert_eq!(normalized.output_cnt, 2);
1335
1336        let output = unsafe { &*normalized.outputs.add(1) };
1337        let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1338        assert_eq!(meta.kind, blaze_user_meta_kind::SYM);
1339
1340        let sym = unsafe { &*meta.variant.sym.sym };
1341        let name = unsafe { CStr::from_ptr(sym.name) };
1342        assert!(name.to_str().unwrap().ends_with("gettimeofday"), "{name:?}");
1343
1344        let () = unsafe { blaze_user_output_free(result) };
1345        let () = unsafe { blaze_normalizer_free(normalizer) };
1346    }
1347}