blazesym_c/
normalize.rs

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