1use std::alloc::alloc;
2use std::alloc::dealloc;
3use std::alloc::Layout;
4use std::borrow::Cow;
5use std::ffi::CString;
6use std::ffi::OsString;
7use std::fmt::Debug;
8use std::fmt::Formatter;
9use std::fmt::Result as FmtResult;
10use std::mem;
11use std::mem::size_of;
12use std::mem::ManuallyDrop;
13use std::os::raw::c_char;
14use std::os::unix::ffi::OsStringExt as _;
15use std::path::PathBuf;
16use std::ptr;
17
18use blazesym::normalize::Apk;
19use blazesym::normalize::Elf;
20use blazesym::normalize::NormalizeOpts;
21use blazesym::normalize::Normalizer;
22use blazesym::normalize::Reason;
23use blazesym::normalize::Unknown;
24use blazesym::normalize::UserMeta;
25use blazesym::normalize::UserOutput;
26use blazesym::symbolize::Sym;
27use blazesym::Addr;
28
29use crate::blaze_err;
30#[cfg(doc)]
31use crate::blaze_err_last;
32use crate::blaze_sym;
33use crate::blaze_symbolize_inlined_fn;
34use crate::convert_sym;
35use crate::set_last_err;
36use crate::util::slice_from_user_array;
37use crate::util::DynSize as _;
38
39
40pub type blaze_normalizer = Normalizer;
42
43
44#[repr(C)]
46#[derive(Debug)]
47pub struct blaze_normalizer_opts {
48 pub type_size: usize,
53 pub use_procmap_query: bool,
72 pub cache_vmas: bool,
79 pub build_ids: bool,
85 pub cache_build_ids: bool,
88 pub reserved: [u8; 20],
91}
92
93impl Default for blaze_normalizer_opts {
94 fn default() -> Self {
95 Self {
96 type_size: size_of::<Self>(),
97 use_procmap_query: false,
98 cache_vmas: false,
99 build_ids: false,
100 cache_build_ids: false,
101 reserved: [0; 20],
102 }
103 }
104}
105
106
107#[repr(C)]
109#[derive(Debug)]
110pub struct blaze_normalize_opts {
111 pub type_size: usize,
116 pub sorted_addrs: bool,
122 pub map_files: bool,
132 pub apk_to_elf: bool,
138 pub reserved: [u8; 21],
141}
142
143impl Default for blaze_normalize_opts {
144 fn default() -> Self {
145 Self {
146 type_size: size_of::<Self>(),
147 sorted_addrs: false,
148 map_files: false,
149 apk_to_elf: false,
150 reserved: [0; 21],
151 }
152 }
153}
154
155impl From<blaze_normalize_opts> for NormalizeOpts {
156 fn from(opts: blaze_normalize_opts) -> Self {
157 let blaze_normalize_opts {
158 type_size: _,
159 sorted_addrs,
160 map_files,
161 apk_to_elf,
162 reserved: _,
163 } = opts;
164 Self {
165 sorted_addrs,
166 map_files,
167 apk_to_elf,
168 _non_exhaustive: (),
169 }
170 }
171}
172
173
174#[no_mangle]
188pub extern "C" fn blaze_normalizer_new() -> *mut blaze_normalizer {
189 let normalizer = Normalizer::new();
190 let normalizer_box = Box::new(normalizer);
191 let () = set_last_err(blaze_err::OK);
192 Box::into_raw(normalizer_box)
193}
194
195
196#[no_mangle]
209pub unsafe extern "C" fn blaze_normalizer_new_opts(
210 opts: *const blaze_normalizer_opts,
211) -> *mut blaze_normalizer {
212 if !input_zeroed!(opts, blaze_normalizer_opts) {
213 let () = set_last_err(blaze_err::INVALID_INPUT);
214 return ptr::null_mut()
215 }
216 let opts = input_sanitize!(opts, blaze_normalizer_opts);
217
218 let blaze_normalizer_opts {
219 type_size: _,
220 use_procmap_query,
221 cache_vmas,
222 build_ids,
223 cache_build_ids,
224 reserved: _,
225 } = opts;
226
227 let normalizer = Normalizer::builder()
228 .enable_procmap_query(use_procmap_query)
229 .enable_vma_caching(cache_vmas)
230 .enable_build_ids(build_ids)
231 .enable_build_id_caching(cache_build_ids)
232 .build();
233 let normalizer_box = Box::new(normalizer);
234 let () = set_last_err(blaze_err::OK);
235 Box::into_raw(normalizer_box)
236}
237
238
239#[no_mangle]
248pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut blaze_normalizer) {
249 if !normalizer.is_null() {
250 drop(unsafe { Box::from_raw(normalizer) });
253 }
254}
255
256
257#[repr(C)]
261#[derive(Debug)]
262pub struct blaze_normalized_output {
263 pub output: u64,
265 pub meta_idx: usize,
267 pub reserved: [u8; 16],
270}
271
272impl From<(u64, usize)> for blaze_normalized_output {
273 fn from((output, meta_idx): (u64, usize)) -> Self {
274 Self {
275 output,
276 meta_idx,
277 reserved: [0; 16],
278 }
279 }
280}
281
282
283#[repr(transparent)]
285#[derive(Copy, Clone, Debug, Eq, PartialEq)]
286pub struct blaze_user_meta_kind(u8);
287
288impl blaze_user_meta_kind {
289 pub const UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
291 pub const APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
293 pub const ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
295 pub const SYM: blaze_user_meta_kind = blaze_user_meta_kind(3);
297
298 #[deprecated]
301 pub const BLAZE_USER_META_UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
302 #[deprecated]
304 pub const BLAZE_USER_META_APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
305 #[deprecated]
307 pub const BLAZE_USER_META_ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
308}
309
310
311#[repr(C)]
313#[derive(Debug)]
314pub struct blaze_user_meta_apk {
315 pub path: *mut c_char,
318 pub reserved: [u8; 16],
320}
321
322impl blaze_user_meta_apk {
323 fn from(other: Apk) -> ManuallyDrop<Self> {
324 let Apk {
325 path,
326 _non_exhaustive: (),
327 } = other;
328
329 let slf = Self {
330 path: CString::new(path.into_os_string().into_vec())
331 .expect("encountered path with NUL bytes")
332 .into_raw(),
333 reserved: [0; 16],
334 };
335 ManuallyDrop::new(slf)
336 }
337
338 unsafe fn free(self) {
339 let Self { path, reserved: _ } = self;
340
341 let _apk = Apk {
342 path: PathBuf::from(OsString::from_vec(
343 unsafe { CString::from_raw(path) }.into_bytes(),
344 )),
345 _non_exhaustive: (),
346 };
347 }
348}
349
350
351#[repr(C)]
353#[derive(Debug)]
354pub struct blaze_user_meta_elf {
355 pub path: *mut c_char,
363 pub build_id_len: usize,
365 pub build_id: *mut u8,
367 pub reserved: [u8; 16],
369}
370
371impl blaze_user_meta_elf {
372 fn from(other: Elf) -> ManuallyDrop<Self> {
373 let Elf {
374 path,
375 build_id,
376 _non_exhaustive: (),
377 } = other;
378
379 let slf = Self {
380 path: CString::new(path.into_os_string().into_vec())
381 .expect("encountered path with NUL bytes")
382 .into_raw(),
383 build_id_len: build_id
384 .as_ref()
385 .map(|build_id| build_id.len())
386 .unwrap_or(0),
387 build_id: build_id
388 .map(|build_id| {
389 unsafe {
392 Box::into_raw(build_id.to_vec().into_boxed_slice())
393 .as_mut()
394 .unwrap()
395 .as_mut_ptr()
396 }
397 })
398 .unwrap_or_else(ptr::null_mut),
399 reserved: [0; 16],
400 };
401 ManuallyDrop::new(slf)
402 }
403
404 unsafe fn free(self) {
405 let blaze_user_meta_elf {
406 path,
407 build_id_len,
408 build_id,
409 reserved: _,
410 } = self;
411
412 let _elf = Elf {
413 path: PathBuf::from(OsString::from_vec(
414 unsafe { CString::from_raw(path) }.into_bytes(),
415 )),
416 build_id: (!build_id.is_null()).then(|| unsafe {
417 Cow::Owned(
418 Box::<[u8]>::from_raw(ptr::slice_from_raw_parts_mut(build_id, build_id_len))
419 .into_vec(),
420 )
421 }),
422 _non_exhaustive: (),
423 };
424 }
425}
426
427
428#[repr(C)]
430#[derive(Debug)]
431pub struct blaze_user_meta_sym {
432 pub sym: *const blaze_sym,
434 pub reserved: [u8; 16],
436}
437
438impl blaze_user_meta_sym {
439 fn from(sym: Sym) -> ManuallyDrop<Self> {
440 let strtab_size = sym.c_str_size();
441 let buf_size = mem::size_of::<u64>()
442 + mem::size_of::<blaze_sym>()
443 + sym.inlined.len() * mem::size_of::<blaze_symbolize_inlined_fn>()
444 + strtab_size;
445 let buf = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
446 assert!(!buf.is_null());
450
451 unsafe { *(buf as *mut u64) = buf_size as u64 };
453
454 let sym_buf = unsafe { buf.add(mem::size_of::<u64>()) }.cast::<blaze_sym>();
455 let mut inlined_last = unsafe { sym_buf.add(1) }.cast::<blaze_symbolize_inlined_fn>();
456 let mut cstr_last = unsafe { inlined_last.add(sym.inlined.len()) }.cast::<c_char>();
457 let sym_ref = unsafe { &mut *sym_buf };
458 let () = convert_sym(&sym, sym_ref, &mut inlined_last, &mut cstr_last);
459
460 let slf = Self {
461 sym: sym_buf,
462 reserved: [0; 16],
463 };
464 ManuallyDrop::new(slf)
465 }
466
467 unsafe fn free(self) {
468 let blaze_user_meta_sym { sym, reserved: _ } = self;
469
470 let buf = unsafe { sym.byte_sub(mem::size_of::<u64>()).cast::<u8>().cast_mut() };
471 let size = unsafe { *(buf as *mut u64) } as usize;
472 let () = unsafe { dealloc(buf, Layout::from_size_align(size, 8).unwrap()) };
473 }
474}
475
476
477#[repr(transparent)]
483#[derive(Copy, Clone, Debug, Eq, PartialEq)]
484pub struct blaze_normalize_reason(u8);
485
486impl blaze_normalize_reason {
487 pub const UNMAPPED: blaze_normalize_reason = blaze_normalize_reason(0);
490 pub const MISSING_COMPONENT: blaze_normalize_reason = blaze_normalize_reason(1);
493 pub const UNSUPPORTED: blaze_normalize_reason = blaze_normalize_reason(2);
495 pub const INVALID_FILE_OFFSET: blaze_normalize_reason = blaze_normalize_reason(3);
497 pub const MISSING_SYMS: blaze_normalize_reason = blaze_normalize_reason(4);
499 pub const UNKNOWN_ADDR: blaze_normalize_reason = blaze_normalize_reason(5);
501 pub const IGNORED_ERROR: blaze_normalize_reason = blaze_normalize_reason(7);
503}
504
505impl From<Reason> for blaze_normalize_reason {
506 fn from(reason: Reason) -> Self {
507 match reason {
508 Reason::Unmapped => blaze_normalize_reason::UNMAPPED,
509 Reason::MissingComponent => blaze_normalize_reason::MISSING_COMPONENT,
510 Reason::Unsupported => blaze_normalize_reason::UNSUPPORTED,
511 Reason::InvalidFileOffset => blaze_normalize_reason::INVALID_FILE_OFFSET,
512 Reason::MissingSyms => blaze_normalize_reason::MISSING_SYMS,
513 Reason::UnknownAddr => blaze_normalize_reason::UNKNOWN_ADDR,
514 Reason::IgnoredError => blaze_normalize_reason::IGNORED_ERROR,
515 _ => unreachable!(),
516 }
517 }
518}
519
520
521#[no_mangle]
523pub extern "C" fn blaze_normalize_reason_str(reason: blaze_normalize_reason) -> *const c_char {
524 match reason {
525 blaze_normalize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
526 blaze_normalize_reason::MISSING_COMPONENT => {
527 Reason::MissingComponent.as_bytes().as_ptr().cast()
528 }
529 blaze_normalize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
530 blaze_normalize_reason::INVALID_FILE_OFFSET => {
531 Reason::InvalidFileOffset.as_bytes().as_ptr().cast()
532 }
533 blaze_normalize_reason::MISSING_SYMS => Reason::MissingSyms.as_bytes().as_ptr().cast(),
534 blaze_normalize_reason::UNKNOWN_ADDR => Reason::UnknownAddr.as_bytes().as_ptr().cast(),
535 blaze_normalize_reason::IGNORED_ERROR => Reason::IgnoredError.as_bytes().as_ptr().cast(),
536 _ => b"unknown reason\0".as_ptr().cast(),
537 }
538}
539
540
541#[repr(C)]
543#[derive(Debug)]
544pub struct blaze_user_meta_unknown {
545 pub reason: blaze_normalize_reason,
550 pub reserved: [u8; 15],
552}
553
554impl blaze_user_meta_unknown {
555 fn from(other: Unknown) -> ManuallyDrop<Self> {
556 let Unknown {
557 reason,
558 _non_exhaustive: (),
559 } = other;
560
561 let slf = Self {
562 reason: reason.into(),
563 reserved: [0; 15],
564 };
565 ManuallyDrop::new(slf)
566 }
567
568 fn free(self) {
569 let blaze_user_meta_unknown {
570 reason: _,
571 reserved: _,
572 } = self;
573 }
574}
575
576
577#[repr(C)]
579pub union blaze_user_meta_variant {
580 pub apk: ManuallyDrop<blaze_user_meta_apk>,
582 pub elf: ManuallyDrop<blaze_user_meta_elf>,
584 pub sym: ManuallyDrop<blaze_user_meta_sym>,
586 pub unknown: ManuallyDrop<blaze_user_meta_unknown>,
588}
589
590impl Debug for blaze_user_meta_variant {
591 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
592 f.debug_struct(stringify!(blaze_user_meta_variant)).finish()
593 }
594}
595
596
597#[repr(C)]
599#[derive(Debug)]
600pub struct blaze_user_meta {
601 pub kind: blaze_user_meta_kind,
603 pub unused: [u8; 7],
605 pub variant: blaze_user_meta_variant,
607 pub reserved: [u8; 16],
610}
611
612impl blaze_user_meta {
613 fn from(other: UserMeta) -> ManuallyDrop<Self> {
614 let slf = match other {
615 UserMeta::Apk(apk) => Self {
616 kind: blaze_user_meta_kind::APK,
617 unused: [0; 7],
618 variant: blaze_user_meta_variant {
619 apk: blaze_user_meta_apk::from(apk),
620 },
621 reserved: [0; 16],
622 },
623 UserMeta::Elf(elf) => Self {
624 kind: blaze_user_meta_kind::ELF,
625 unused: [0; 7],
626 variant: blaze_user_meta_variant {
627 elf: blaze_user_meta_elf::from(elf),
628 },
629 reserved: [0; 16],
630 },
631 UserMeta::Sym(sym) => Self {
632 kind: blaze_user_meta_kind::SYM,
633 unused: [0; 7],
634 variant: blaze_user_meta_variant {
635 sym: blaze_user_meta_sym::from(sym),
636 },
637 reserved: [0; 16],
638 },
639 UserMeta::Unknown(unknown) => Self {
640 kind: blaze_user_meta_kind::UNKNOWN,
641 unused: [0; 7],
642 variant: blaze_user_meta_variant {
643 unknown: blaze_user_meta_unknown::from(unknown),
644 },
645 reserved: [0; 16],
646 },
647 _ => unreachable!(),
648 };
649 ManuallyDrop::new(slf)
650 }
651
652 unsafe fn free(self) {
653 match self.kind {
654 blaze_user_meta_kind::APK => unsafe {
655 ManuallyDrop::into_inner(self.variant.apk).free()
656 },
657 blaze_user_meta_kind::ELF => unsafe {
658 ManuallyDrop::into_inner(self.variant.elf).free()
659 },
660 blaze_user_meta_kind::SYM => unsafe {
661 ManuallyDrop::into_inner(self.variant.sym).free()
662 },
663 blaze_user_meta_kind::UNKNOWN => {
664 ManuallyDrop::into_inner(unsafe { self.variant.unknown }).free()
665 }
666 _ => {
667 debug_assert!(false)
668 }
669 }
670 }
671}
672
673
674#[repr(C)]
678#[derive(Debug)]
679pub struct blaze_normalized_user_output {
680 pub meta_cnt: usize,
682 pub metas: *mut blaze_user_meta,
684 pub output_cnt: usize,
686 pub outputs: *mut blaze_normalized_output,
688 pub reserved: [u8; 16],
690}
691
692impl blaze_normalized_user_output {
693 fn from(other: UserOutput) -> ManuallyDrop<Self> {
694 let slf = Self {
695 meta_cnt: other.meta.len(),
696 metas: unsafe {
697 Box::into_raw(
698 other
699 .meta
700 .into_iter()
701 .map(blaze_user_meta::from)
702 .map(ManuallyDrop::into_inner)
703 .collect::<Vec<_>>()
704 .into_boxed_slice(),
705 )
706 .as_mut()
707 .unwrap()
708 .as_mut_ptr()
709 },
710 output_cnt: other.outputs.len(),
711 outputs: unsafe {
712 Box::into_raw(
713 other
714 .outputs
715 .into_iter()
716 .map(blaze_normalized_output::from)
717 .collect::<Vec<_>>()
718 .into_boxed_slice(),
719 )
720 .as_mut()
721 .unwrap()
722 .as_mut_ptr()
723 },
724 reserved: [0; 16],
725 };
726 ManuallyDrop::new(slf)
727 }
728}
729
730
731unsafe fn blaze_normalize_user_addrs_impl(
732 normalizer: *const blaze_normalizer,
733 pid: u32,
734 addrs: *const Addr,
735 addr_cnt: usize,
736 opts: &NormalizeOpts,
737) -> *mut blaze_normalized_user_output {
738 let normalizer = unsafe { &*normalizer };
741 let addrs = unsafe { slice_from_user_array(addrs, addr_cnt) };
744 let result = normalizer.normalize_user_addrs_opts(pid.into(), &addrs, opts);
745 match result {
746 Ok(output) => {
747 let output_box = Box::new(ManuallyDrop::into_inner(
748 blaze_normalized_user_output::from(output),
749 ));
750 let () = set_last_err(blaze_err::OK);
751 Box::into_raw(output_box)
752 }
753 Err(err) => {
754 let () = set_last_err(err.kind().into());
755 ptr::null_mut()
756 }
757 }
758}
759
760
761#[no_mangle]
779pub unsafe extern "C" fn blaze_normalize_user_addrs(
780 normalizer: *const blaze_normalizer,
781 pid: u32,
782 addrs: *const Addr,
783 addr_cnt: usize,
784) -> *mut blaze_normalized_user_output {
785 let opts = NormalizeOpts::default();
786
787 unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
788}
789
790
791#[no_mangle]
811pub unsafe extern "C" fn blaze_normalize_user_addrs_opts(
812 normalizer: *const blaze_normalizer,
813 pid: u32,
814 addrs: *const Addr,
815 addr_cnt: usize,
816 opts: *const blaze_normalize_opts,
817) -> *mut blaze_normalized_user_output {
818 if !input_zeroed!(opts, blaze_normalize_opts) {
819 let () = set_last_err(blaze_err::INVALID_INPUT);
820 return ptr::null_mut()
821 }
822 let opts = input_sanitize!(opts, blaze_normalize_opts);
823 let opts = NormalizeOpts::from(opts);
824
825 unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
826}
827
828
829#[no_mangle]
837pub unsafe extern "C" fn blaze_user_output_free(output: *mut blaze_normalized_user_output) {
838 if output.is_null() {
839 return
840 }
841
842 let user_output = unsafe { Box::from_raw(output) };
845 let addr_metas = unsafe {
846 Box::<[blaze_user_meta]>::from_raw(ptr::slice_from_raw_parts_mut(
847 user_output.metas,
848 user_output.meta_cnt,
849 ))
850 }
851 .into_vec();
852 let _norm_addrs = unsafe {
853 Box::<[blaze_normalized_output]>::from_raw(ptr::slice_from_raw_parts_mut(
854 user_output.outputs,
855 user_output.output_cnt,
856 ))
857 }
858 .into_vec();
859
860 for addr_meta in addr_metas {
861 let () = unsafe { addr_meta.free() };
862 }
863}
864
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869
870 use std::ffi::CStr;
871 use std::io;
872 use std::path::Path;
873
874 use blazesym::helper::read_elf_build_id;
875 use blazesym::Mmap;
876 use blazesym::__private::find_the_answer_fn;
877 use blazesym::__private::zip;
878
879 use test_tag::tag;
880
881 use crate::blaze_err_last;
882
883
884 #[tag(miri)]
886 #[test]
887 #[cfg(target_pointer_width = "64")]
888 fn type_sizes() {
889 assert_eq!(size_of::<blaze_normalizer_opts>(), 32);
890 assert_eq!(size_of::<blaze_normalize_opts>(), 32);
891 assert_eq!(size_of::<blaze_user_meta_apk>(), 24);
892 assert_eq!(size_of::<blaze_user_meta_elf>(), 40);
893 assert_eq!(size_of::<blaze_user_meta_sym>(), 24);
894 assert_eq!(size_of::<blaze_user_meta_unknown>(), 16);
895 }
896
897 #[tag(miri)]
899 #[test]
900 fn debug_repr() {
901 let output = blaze_normalized_output {
902 output: 0x1337,
903 meta_idx: 1,
904 reserved: [0; 16],
905 };
906 assert_eq!(
907 format!("{output:?}"),
908 "blaze_normalized_output { output: 4919, meta_idx: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
909 );
910
911 let meta_kind = blaze_user_meta_kind::APK;
912 assert_eq!(format!("{meta_kind:?}"), "blaze_user_meta_kind(1)");
913
914 let apk = blaze_user_meta_apk {
915 path: ptr::null_mut(),
916 reserved: [0; 16],
917 };
918 assert_eq!(
919 format!("{apk:?}"),
920 "blaze_user_meta_apk { path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
921 );
922
923 let elf = blaze_user_meta_elf {
924 path: ptr::null_mut(),
925 build_id_len: 0,
926 build_id: ptr::null_mut(),
927 reserved: [0; 16],
928 };
929 assert_eq!(
930 format!("{elf:?}"),
931 "blaze_user_meta_elf { path: 0x0, build_id_len: 0, build_id: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
932 );
933
934 let unknown = blaze_user_meta_unknown {
935 reason: blaze_normalize_reason::UNMAPPED,
936 reserved: [0; 15],
937 };
938 assert_eq!(
939 format!("{unknown:?}"),
940 "blaze_user_meta_unknown { reason: blaze_normalize_reason(0), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
941 );
942
943 let meta = blaze_user_meta {
944 kind: blaze_user_meta_kind::UNKNOWN,
945 unused: [0; 7],
946 variant: blaze_user_meta_variant {
947 unknown: ManuallyDrop::new(blaze_user_meta_unknown {
948 reason: blaze_normalize_reason::UNMAPPED,
949 reserved: [0; 15],
950 }),
951 },
952 reserved: [0; 16],
953 };
954 assert_eq!(
955 format!("{meta:?}"),
956 "blaze_user_meta { kind: blaze_user_meta_kind(0), unused: [0, 0, 0, 0, 0, 0, 0], variant: blaze_user_meta_variant, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
957 );
958
959 let normalized = blaze_normalized_user_output {
960 meta_cnt: 0,
961 metas: ptr::null_mut(),
962 output_cnt: 0,
963 outputs: ptr::null_mut(),
964 reserved: [0; 16],
965 };
966 assert_eq!(
967 format!("{normalized:?}"),
968 "blaze_normalized_user_output { meta_cnt: 0, metas: 0x0, output_cnt: 0, outputs: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
969 );
970 }
971
972 #[tag(miri)]
974 #[test]
975 fn reason_stringification() {
976 let data = [
977 (Reason::Unmapped, blaze_normalize_reason::UNMAPPED),
978 (
979 Reason::MissingComponent,
980 blaze_normalize_reason::MISSING_COMPONENT,
981 ),
982 (Reason::Unsupported, blaze_normalize_reason::UNSUPPORTED),
983 (
984 Reason::InvalidFileOffset,
985 blaze_normalize_reason::INVALID_FILE_OFFSET,
986 ),
987 (Reason::MissingSyms, blaze_normalize_reason::MISSING_SYMS),
988 (Reason::UnknownAddr, blaze_normalize_reason::UNKNOWN_ADDR),
989 (Reason::IgnoredError, blaze_normalize_reason::IGNORED_ERROR),
990 ];
991
992 for (reason, expected) in data {
993 assert_eq!(blaze_normalize_reason::from(reason), expected);
994 let cstr = unsafe { CStr::from_ptr(blaze_normalize_reason_str(expected)) };
995 let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
996 assert_eq!(cstr, expected);
997 }
998 }
999
1000 #[tag(miri)]
1003 #[test]
1004 fn unknown_conversion() {
1005 let unknown = Unknown {
1006 reason: Reason::Unsupported,
1007 _non_exhaustive: (),
1008 };
1009
1010 let unknown_c = blaze_user_meta_unknown::from(unknown.clone());
1011 let () = ManuallyDrop::into_inner(unknown_c).free();
1012
1013 let meta = UserMeta::Unknown(unknown);
1014 let meta_c = blaze_user_meta::from(meta);
1015 let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1016 }
1017
1018 #[tag(miri)]
1021 #[test]
1022 fn apk_conversion() {
1023 let apk = Apk {
1024 path: PathBuf::from("/tmp/archive.apk"),
1025 _non_exhaustive: (),
1026 };
1027
1028 let apk_c = blaze_user_meta_apk::from(apk.clone());
1029 let () = unsafe { ManuallyDrop::into_inner(apk_c).free() };
1030
1031 let meta = UserMeta::Apk(apk);
1032 let meta_c = blaze_user_meta::from(meta);
1033 let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1034 }
1035
1036 #[tag(miri)]
1039 #[test]
1040 fn elf_conversion() {
1041 let elf = Elf {
1042 path: PathBuf::from("/tmp/file.so"),
1043 build_id: Some(Cow::Borrowed(&[0x01, 0x02, 0x03, 0x04])),
1044 _non_exhaustive: (),
1045 };
1046
1047 let elf_c = blaze_user_meta_elf::from(elf.clone());
1048 let () = unsafe { ManuallyDrop::into_inner(elf_c).free() };
1049
1050 let meta = UserMeta::Elf(elf);
1051 let meta_c = blaze_user_meta::from(meta);
1052 let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1053 }
1054
1055 #[tag(miri)]
1057 #[test]
1058 fn normalizer_creation() {
1059 let normalizer = blaze_normalizer_new();
1060 let () = unsafe { blaze_normalizer_free(normalizer) };
1061 }
1062
1063 #[test]
1065 fn normalize_user_addrs() {
1066 fn test(normalizer: *const blaze_normalizer) {
1067 let addrs = [
1068 0x0,
1069 libc::atexit as Addr,
1070 libc::chdir as Addr,
1071 libc::fopen as Addr,
1072 elf_conversion as Addr,
1073 normalize_user_addrs as Addr,
1074 ];
1075
1076 let result = unsafe {
1077 blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1078 };
1079 assert_ne!(result, ptr::null_mut());
1080
1081 let normalized = unsafe { &*result };
1082 assert_eq!(normalized.meta_cnt, 3);
1083 assert_eq!(normalized.output_cnt, 6);
1084
1085 let meta = unsafe { normalized.metas.read() };
1086 assert_eq!(meta.kind, blaze_user_meta_kind::UNKNOWN);
1087 assert_eq!(
1088 unsafe { meta.variant.unknown.reason },
1089 blaze_normalize_reason::UNMAPPED
1090 );
1091
1092 let () = unsafe { blaze_user_output_free(result) };
1093 }
1094
1095 let normalizer = blaze_normalizer_new();
1096 assert_ne!(normalizer, ptr::null_mut());
1097 test(normalizer);
1098 let () = unsafe { blaze_normalizer_free(normalizer) };
1099
1100 let opts = blaze_normalizer_opts {
1101 cache_vmas: true,
1102 ..Default::default()
1103 };
1104 let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1105 assert_ne!(normalizer, ptr::null_mut());
1106 test(normalizer);
1107 test(normalizer);
1108 let () = unsafe { blaze_normalizer_free(normalizer) };
1109 }
1110
1111 fn test_normalize_user_addrs_sorted(use_procmap_query: bool) {
1112 let mut addrs = [
1113 libc::atexit as Addr,
1114 libc::chdir as Addr,
1115 libc::fopen as Addr,
1116 elf_conversion as Addr,
1117 normalize_user_addrs as Addr,
1118 ];
1119 let () = addrs.sort();
1120
1121 let opts = blaze_normalizer_opts {
1122 use_procmap_query,
1123 ..Default::default()
1124 };
1125 let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1126 assert_ne!(normalizer, ptr::null_mut());
1127
1128 let opts = blaze_normalize_opts {
1129 sorted_addrs: true,
1130 ..Default::default()
1131 };
1132 let result = unsafe {
1133 blaze_normalize_user_addrs_opts(
1134 normalizer,
1135 0,
1136 addrs.as_slice().as_ptr(),
1137 addrs.len(),
1138 &opts,
1139 )
1140 };
1141 assert_ne!(result, ptr::null_mut());
1142
1143 let normalized = unsafe { &*result };
1144 assert_eq!(normalized.meta_cnt, 2);
1145 assert_eq!(normalized.output_cnt, 5);
1146
1147 let () = unsafe { blaze_user_output_free(result) };
1148 let () = unsafe { blaze_normalizer_free(normalizer) };
1149 }
1150
1151 #[test]
1153 fn normalize_user_addrs_sorted_proc_maps() {
1154 test_normalize_user_addrs_sorted(false)
1155 }
1156
1157 #[test]
1160 #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
1161 fn normalize_user_addrs_sorted_ioctl() {
1162 test_normalize_user_addrs_sorted(true)
1163 }
1164
1165 #[test]
1168 fn normalize_user_addrs_unsorted_failure() {
1169 let mut addrs = [
1170 libc::atexit as Addr,
1171 libc::chdir as Addr,
1172 libc::fopen as Addr,
1173 elf_conversion as Addr,
1174 normalize_user_addrs as Addr,
1175 ];
1176 let () = addrs.sort_by(|addr1, addr2| addr1.cmp(addr2).reverse());
1177
1178 let normalizer = blaze_normalizer_new();
1179 assert_ne!(normalizer, ptr::null_mut());
1180
1181 let opts = blaze_normalize_opts {
1182 sorted_addrs: true,
1183 ..Default::default()
1184 };
1185 let result = unsafe {
1186 blaze_normalize_user_addrs_opts(
1187 normalizer,
1188 0,
1189 addrs.as_slice().as_ptr(),
1190 addrs.len(),
1191 &opts,
1192 )
1193 };
1194 assert_eq!(result, ptr::null_mut());
1195 assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
1196
1197 let () = unsafe { blaze_normalizer_free(normalizer) };
1198 }
1199
1200 #[test]
1202 #[cfg_attr(
1203 not(target_pointer_width = "64"),
1204 ignore = "loads 64 bit shared object"
1205 )]
1206 fn normalize_build_id_reading() {
1207 fn test(read_build_ids: bool) {
1208 let test_so = Path::new(&env!("CARGO_MANIFEST_DIR"))
1209 .join("..")
1210 .join("data")
1211 .join("libtest-so.so")
1212 .canonicalize()
1213 .unwrap();
1214 let so_cstr = CString::new(test_so.clone().into_os_string().into_vec()).unwrap();
1215 let handle = unsafe { libc::dlopen(so_cstr.as_ptr(), libc::RTLD_NOW) };
1216 assert!(!handle.is_null());
1217
1218 let the_answer_addr = unsafe { libc::dlsym(handle, "the_answer\0".as_ptr().cast()) };
1219 assert!(!the_answer_addr.is_null());
1220
1221 let opts = blaze_normalizer_opts {
1222 build_ids: read_build_ids,
1223 ..Default::default()
1224 };
1225
1226 let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1227 assert!(!normalizer.is_null());
1228
1229 let opts = blaze_normalize_opts {
1230 sorted_addrs: true,
1231 ..Default::default()
1232 };
1233 let addrs = [the_answer_addr as Addr];
1234 let result = unsafe {
1235 blaze_normalize_user_addrs_opts(
1236 normalizer,
1237 0,
1238 addrs.as_slice().as_ptr(),
1239 addrs.len(),
1240 &opts,
1241 )
1242 };
1243 assert!(!result.is_null());
1244
1245 let normalized = unsafe { &*result };
1246 assert_eq!(normalized.meta_cnt, 1);
1247 assert_eq!(normalized.output_cnt, 1);
1248
1249 let rc = unsafe { libc::dlclose(handle) };
1250 assert_eq!(rc, 0, "{}", io::Error::last_os_error());
1251
1252 let output = unsafe { &*normalized.outputs.add(0) };
1253 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1254 assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1255
1256 let elf = unsafe { &meta.variant.elf };
1257
1258 assert!(!elf.path.is_null());
1259 let path = unsafe { CStr::from_ptr(elf.path) };
1260 assert_eq!(path, so_cstr.as_ref());
1261
1262 if read_build_ids {
1263 let expected = read_elf_build_id(&test_so).unwrap().unwrap();
1264 let build_id = unsafe { slice_from_user_array(elf.build_id, elf.build_id_len) };
1265 assert_eq!(build_id, expected.as_ref());
1266 } else {
1267 assert!(elf.build_id.is_null());
1268 }
1269
1270 let () = unsafe { blaze_user_output_free(result) };
1271 let () = unsafe { blaze_normalizer_free(normalizer) };
1272 }
1273
1274 test(true);
1275 test(false);
1276 }
1277
1278 #[test]
1281 fn normalize_custom_so_in_zip() {
1282 let test_zip = Path::new(&env!("CARGO_MANIFEST_DIR"))
1283 .join("..")
1284 .join("data")
1285 .join("test.zip");
1286 let so_name = "libtest-so.so";
1287
1288 let mmap = Mmap::builder().exec().open(&test_zip).unwrap();
1289 let archive = zip::Archive::with_mmap(mmap.clone()).unwrap();
1290 let so = archive
1291 .entries()
1292 .find_map(|entry| {
1293 let entry = entry.unwrap();
1294 (entry.path == Path::new(so_name)).then_some(entry)
1295 })
1296 .unwrap();
1297
1298 let elf_mmap = mmap
1299 .constrain(so.data_offset..so.data_offset + so.data.len() as u64)
1300 .unwrap();
1301 let (_sym, the_answer_addr) = find_the_answer_fn(&elf_mmap);
1302
1303 let normalizer = blaze_normalizer_new();
1304 assert!(!normalizer.is_null());
1305
1306 let addrs = [the_answer_addr];
1307 let opts = blaze_normalize_opts {
1308 apk_to_elf: true,
1309 ..Default::default()
1310 };
1311 let result = unsafe {
1312 blaze_normalize_user_addrs_opts(
1313 normalizer,
1314 0,
1315 addrs.as_slice().as_ptr(),
1316 addrs.len(),
1317 &opts,
1318 )
1319 };
1320 assert_ne!(result, ptr::null_mut());
1321
1322 let normalized = unsafe { &*result };
1323 assert_eq!(normalized.meta_cnt, 1);
1324 assert_eq!(normalized.output_cnt, 1);
1325
1326 let output = unsafe { &*normalized.outputs.add(0) };
1327 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1328 assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1329
1330 let elf = unsafe { &meta.variant.elf };
1331 let path = unsafe { CStr::from_ptr(elf.path) };
1332 assert!(path.to_str().unwrap().ends_with(so_name), "{path:?}");
1333
1334 let () = unsafe { blaze_user_output_free(result) };
1335 let () = unsafe { blaze_normalizer_free(normalizer) };
1336 }
1337
1338 #[cfg(linux)]
1341 #[cfg(target_pointer_width = "64")]
1343 #[test]
1344 fn normalize_local_vdso_address() {
1345 use libc::gettimeofday;
1346
1347 let addrs = [normalize_local_vdso_address as Addr, gettimeofday as Addr];
1348 let normalizer = blaze_normalizer_new();
1349 assert!(!normalizer.is_null());
1350
1351 let result = unsafe {
1352 blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1353 };
1354 assert_ne!(result, ptr::null_mut());
1355
1356 let normalized = unsafe { &*result };
1357 assert_eq!(normalized.meta_cnt, 2);
1358 assert_eq!(normalized.output_cnt, 2);
1359
1360 let output = unsafe { &*normalized.outputs.add(1) };
1361 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1362 assert_eq!(meta.kind, blaze_user_meta_kind::SYM);
1363
1364 let sym = unsafe { &*meta.variant.sym.sym };
1365 let name = unsafe { CStr::from_ptr(sym.name) };
1366 assert!(name.to_str().unwrap().ends_with("gettimeofday"), "{name:?}");
1367
1368 let () = unsafe { blaze_user_output_free(result) };
1369 let () = unsafe { blaze_normalizer_free(normalizer) };
1370 }
1371
1372 #[cfg(linux)]
1375 #[cfg(target_pointer_width = "64")]
1376 #[test]
1377 fn normalize_invalid_vdso_address() {
1378 use blazesym::__private::find_vdso_range;
1379
1380 let vdso = find_vdso_range();
1381 let addrs = [vdso.start];
1385 let normalizer = blaze_normalizer_new();
1386 assert!(!normalizer.is_null());
1387
1388 let result = unsafe {
1389 blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1390 };
1391 assert_ne!(result, ptr::null_mut());
1392
1393 let normalized = unsafe { &*result };
1394 assert_eq!(normalized.meta_cnt, 1);
1395 assert_eq!(normalized.output_cnt, 1);
1396
1397 let output = unsafe { &*normalized.outputs };
1398 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1399 match meta.kind {
1400 blaze_user_meta_kind::UNKNOWN | blaze_user_meta_kind::SYM => (),
1406 _ => panic!("encountered unexpected meta kind: {:?}", meta.kind),
1407 }
1408
1409 let () = unsafe { blaze_user_output_free(result) };
1410 let () = unsafe { blaze_normalizer_free(normalizer) };
1411 }
1412}