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
41pub type blaze_normalizer = Normalizer;
43
44
45#[repr(C)]
47#[derive(Debug)]
48pub struct blaze_normalizer_opts {
49 pub type_size: usize,
54 pub use_procmap_query: bool,
73 pub cache_vmas: bool,
80 pub build_ids: bool,
86 pub cache_build_ids: bool,
89 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#[repr(C)]
110#[derive(Debug)]
111pub struct blaze_normalize_opts {
112 pub type_size: usize,
117 pub sorted_addrs: bool,
123 pub map_files: bool,
133 pub apk_to_elf: bool,
139 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#[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#[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#[no_mangle]
249pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut blaze_normalizer) {
250 if !normalizer.is_null() {
251 drop(unsafe { Box::from_raw(normalizer) });
254 }
255}
256
257
258#[repr(C)]
262#[derive(Debug)]
263pub struct blaze_normalized_output {
264 pub output: u64,
266 pub meta_idx: usize,
268 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#[repr(transparent)]
286#[derive(Copy, Clone, Debug, Eq, PartialEq)]
287pub struct blaze_user_meta_kind(u8);
288
289impl blaze_user_meta_kind {
290 pub const UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
292 pub const APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
294 pub const ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
296 pub const SYM: blaze_user_meta_kind = blaze_user_meta_kind(3);
298
299 #[deprecated]
302 pub const BLAZE_USER_META_UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
303 #[deprecated]
305 pub const BLAZE_USER_META_APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
306 #[deprecated]
308 pub const BLAZE_USER_META_ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
309}
310
311
312#[repr(C)]
314#[derive(Debug)]
315pub struct blaze_user_meta_apk {
316 pub path: *mut c_char,
319 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#[repr(C)]
354#[derive(Debug)]
355pub struct blaze_user_meta_elf {
356 pub path: *mut c_char,
364 pub build_id_len: usize,
366 pub build_id: *mut u8,
368 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 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#[repr(C)]
431#[derive(Debug)]
432pub struct blaze_user_meta_sym {
433 pub sym: *const blaze_sym,
435 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 assert!(!buf.is_null());
451
452 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#[repr(transparent)]
484#[derive(Copy, Clone, Debug, Eq, PartialEq)]
485pub struct blaze_normalize_reason(u8);
486
487impl blaze_normalize_reason {
488 pub const UNMAPPED: blaze_normalize_reason = blaze_normalize_reason(0);
491 pub const MISSING_COMPONENT: blaze_normalize_reason = blaze_normalize_reason(1);
494 pub const UNSUPPORTED: blaze_normalize_reason = blaze_normalize_reason(2);
496 pub const INVALID_FILE_OFFSET: blaze_normalize_reason = blaze_normalize_reason(3);
498 pub const MISSING_SYMS: blaze_normalize_reason = blaze_normalize_reason(4);
500 pub const UNKNOWN_ADDR: blaze_normalize_reason = blaze_normalize_reason(5);
502}
503
504impl From<Reason> for blaze_normalize_reason {
505 fn from(reason: Reason) -> Self {
506 match reason {
507 Reason::Unmapped => blaze_normalize_reason::UNMAPPED,
508 Reason::MissingComponent => blaze_normalize_reason::MISSING_COMPONENT,
509 Reason::Unsupported => blaze_normalize_reason::UNSUPPORTED,
510 Reason::InvalidFileOffset => blaze_normalize_reason::INVALID_FILE_OFFSET,
511 Reason::MissingSyms => blaze_normalize_reason::MISSING_SYMS,
512 Reason::UnknownAddr => blaze_normalize_reason::UNKNOWN_ADDR,
513 _ => unreachable!(),
514 }
515 }
516}
517
518
519#[no_mangle]
521pub extern "C" fn blaze_normalize_reason_str(reason: blaze_normalize_reason) -> *const c_char {
522 match reason {
523 blaze_normalize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
524 blaze_normalize_reason::MISSING_COMPONENT => {
525 Reason::MissingComponent.as_bytes().as_ptr().cast()
526 }
527 blaze_normalize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
528 blaze_normalize_reason::INVALID_FILE_OFFSET => {
529 Reason::InvalidFileOffset.as_bytes().as_ptr().cast()
530 }
531 blaze_normalize_reason::MISSING_SYMS => Reason::MissingSyms.as_bytes().as_ptr().cast(),
532 blaze_normalize_reason::UNKNOWN_ADDR => Reason::UnknownAddr.as_bytes().as_ptr().cast(),
533 _ => b"unknown reason\0".as_ptr().cast(),
534 }
535}
536
537
538#[repr(C)]
540#[derive(Debug)]
541pub struct blaze_user_meta_unknown {
542 pub reason: blaze_normalize_reason,
547 pub reserved: [u8; 15],
549}
550
551impl blaze_user_meta_unknown {
552 fn from(other: Unknown) -> ManuallyDrop<Self> {
553 let Unknown {
554 reason,
555 _non_exhaustive: (),
556 } = other;
557
558 let slf = Self {
559 reason: reason.into(),
560 reserved: [0; 15],
561 };
562 ManuallyDrop::new(slf)
563 }
564
565 fn free(self) {
566 let blaze_user_meta_unknown {
567 reason: _,
568 reserved: _,
569 } = self;
570 }
571}
572
573
574#[repr(C)]
576pub union blaze_user_meta_variant {
577 pub apk: ManuallyDrop<blaze_user_meta_apk>,
579 pub elf: ManuallyDrop<blaze_user_meta_elf>,
581 pub sym: ManuallyDrop<blaze_user_meta_sym>,
583 pub unknown: ManuallyDrop<blaze_user_meta_unknown>,
585}
586
587impl Debug for blaze_user_meta_variant {
588 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
589 f.debug_struct(stringify!(blaze_user_meta_variant)).finish()
590 }
591}
592
593
594#[repr(C)]
596#[derive(Debug)]
597pub struct blaze_user_meta {
598 pub kind: blaze_user_meta_kind,
600 pub unused: [u8; 7],
602 pub variant: blaze_user_meta_variant,
604 pub reserved: [u8; 16],
607}
608
609impl blaze_user_meta {
610 fn from(other: UserMeta) -> ManuallyDrop<Self> {
611 let slf = match other {
612 UserMeta::Apk(apk) => Self {
613 kind: blaze_user_meta_kind::APK,
614 unused: [0; 7],
615 variant: blaze_user_meta_variant {
616 apk: blaze_user_meta_apk::from(apk),
617 },
618 reserved: [0; 16],
619 },
620 UserMeta::Elf(elf) => Self {
621 kind: blaze_user_meta_kind::ELF,
622 unused: [0; 7],
623 variant: blaze_user_meta_variant {
624 elf: blaze_user_meta_elf::from(elf),
625 },
626 reserved: [0; 16],
627 },
628 UserMeta::Sym(sym) => Self {
629 kind: blaze_user_meta_kind::SYM,
630 unused: [0; 7],
631 variant: blaze_user_meta_variant {
632 sym: blaze_user_meta_sym::from(sym),
633 },
634 reserved: [0; 16],
635 },
636 UserMeta::Unknown(unknown) => Self {
637 kind: blaze_user_meta_kind::UNKNOWN,
638 unused: [0; 7],
639 variant: blaze_user_meta_variant {
640 unknown: blaze_user_meta_unknown::from(unknown),
641 },
642 reserved: [0; 16],
643 },
644 _ => unreachable!(),
645 };
646 ManuallyDrop::new(slf)
647 }
648
649 unsafe fn free(self) {
650 match self.kind {
651 blaze_user_meta_kind::APK => unsafe {
652 ManuallyDrop::into_inner(self.variant.apk).free()
653 },
654 blaze_user_meta_kind::ELF => unsafe {
655 ManuallyDrop::into_inner(self.variant.elf).free()
656 },
657 blaze_user_meta_kind::SYM => unsafe {
658 ManuallyDrop::into_inner(self.variant.sym).free()
659 },
660 blaze_user_meta_kind::UNKNOWN => {
661 ManuallyDrop::into_inner(unsafe { self.variant.unknown }).free()
662 }
663 _ => {
664 debug_assert!(false)
665 }
666 }
667 }
668}
669
670
671#[repr(C)]
675#[derive(Debug)]
676pub struct blaze_normalized_user_output {
677 pub meta_cnt: usize,
679 pub metas: *mut blaze_user_meta,
681 pub output_cnt: usize,
683 pub outputs: *mut blaze_normalized_output,
685 pub reserved: [u8; 16],
687}
688
689impl blaze_normalized_user_output {
690 fn from(other: UserOutput) -> ManuallyDrop<Self> {
691 let slf = Self {
692 meta_cnt: other.meta.len(),
693 metas: unsafe {
694 Box::into_raw(
695 other
696 .meta
697 .into_iter()
698 .map(blaze_user_meta::from)
699 .map(ManuallyDrop::into_inner)
700 .collect::<Vec<_>>()
701 .into_boxed_slice(),
702 )
703 .as_mut()
704 .unwrap()
705 .as_mut_ptr()
706 },
707 output_cnt: other.outputs.len(),
708 outputs: unsafe {
709 Box::into_raw(
710 other
711 .outputs
712 .into_iter()
713 .map(blaze_normalized_output::from)
714 .collect::<Vec<_>>()
715 .into_boxed_slice(),
716 )
717 .as_mut()
718 .unwrap()
719 .as_mut_ptr()
720 },
721 reserved: [0; 16],
722 };
723 ManuallyDrop::new(slf)
724 }
725}
726
727
728unsafe fn blaze_normalize_user_addrs_impl(
729 normalizer: *const blaze_normalizer,
730 pid: u32,
731 addrs: *const Addr,
732 addr_cnt: usize,
733 opts: &NormalizeOpts,
734) -> *mut blaze_normalized_user_output {
735 let normalizer = unsafe { &*normalizer };
738 let addrs = unsafe { slice_from_user_array(addrs, addr_cnt) };
741 let result = normalizer.normalize_user_addrs_opts(pid.into(), &addrs, opts);
742 match result {
743 Ok(output) => {
744 let output_box = Box::new(ManuallyDrop::into_inner(
745 blaze_normalized_user_output::from(output),
746 ));
747 let () = set_last_err(blaze_err::OK);
748 Box::into_raw(output_box)
749 }
750 Err(err) => {
751 let () = set_last_err(err.kind().into());
752 ptr::null_mut()
753 }
754 }
755}
756
757
758#[no_mangle]
776pub unsafe extern "C" fn blaze_normalize_user_addrs(
777 normalizer: *const blaze_normalizer,
778 pid: u32,
779 addrs: *const Addr,
780 addr_cnt: usize,
781) -> *mut blaze_normalized_user_output {
782 let opts = NormalizeOpts::default();
783
784 unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
785}
786
787
788#[no_mangle]
808pub unsafe extern "C" fn blaze_normalize_user_addrs_opts(
809 normalizer: *const blaze_normalizer,
810 pid: u32,
811 addrs: *const Addr,
812 addr_cnt: usize,
813 opts: *const blaze_normalize_opts,
814) -> *mut blaze_normalized_user_output {
815 if !input_zeroed!(opts, blaze_normalize_opts) {
816 let () = set_last_err(blaze_err::INVALID_INPUT);
817 return ptr::null_mut()
818 }
819 let opts = input_sanitize!(opts, blaze_normalize_opts);
820 let opts = NormalizeOpts::from(opts);
821
822 unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
823}
824
825
826#[no_mangle]
834pub unsafe extern "C" fn blaze_user_output_free(output: *mut blaze_normalized_user_output) {
835 if output.is_null() {
836 return
837 }
838
839 let user_output = unsafe { Box::from_raw(output) };
842 let addr_metas = unsafe {
843 Box::<[blaze_user_meta]>::from_raw(slice::from_raw_parts_mut(
844 user_output.metas,
845 user_output.meta_cnt,
846 ))
847 }
848 .into_vec();
849 let _norm_addrs = unsafe {
850 Box::<[blaze_normalized_output]>::from_raw(slice::from_raw_parts_mut(
851 user_output.outputs,
852 user_output.output_cnt,
853 ))
854 }
855 .into_vec();
856
857 for addr_meta in addr_metas {
858 let () = unsafe { addr_meta.free() };
859 }
860}
861
862
863#[cfg(test)]
864mod tests {
865 use super::*;
866
867 use std::ffi::CStr;
868 use std::io;
869 use std::path::Path;
870
871 use blazesym::helper::read_elf_build_id;
872 use blazesym::Mmap;
873 use blazesym::__private::find_the_answer_fn;
874 use blazesym::__private::zip;
875
876 use test_tag::tag;
877
878 use crate::blaze_err_last;
879
880
881 #[tag(miri)]
883 #[test]
884 #[cfg(target_pointer_width = "64")]
885 fn type_sizes() {
886 assert_eq!(size_of::<blaze_normalizer_opts>(), 32);
887 assert_eq!(size_of::<blaze_normalize_opts>(), 32);
888 assert_eq!(size_of::<blaze_user_meta_apk>(), 24);
889 assert_eq!(size_of::<blaze_user_meta_elf>(), 40);
890 assert_eq!(size_of::<blaze_user_meta_sym>(), 24);
891 assert_eq!(size_of::<blaze_user_meta_unknown>(), 16);
892 }
893
894 #[tag(miri)]
896 #[test]
897 fn debug_repr() {
898 let output = blaze_normalized_output {
899 output: 0x1337,
900 meta_idx: 1,
901 reserved: [0; 16],
902 };
903 assert_eq!(
904 format!("{output:?}"),
905 "blaze_normalized_output { output: 4919, meta_idx: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
906 );
907
908 let meta_kind = blaze_user_meta_kind::APK;
909 assert_eq!(format!("{meta_kind:?}"), "blaze_user_meta_kind(1)");
910
911 let apk = blaze_user_meta_apk {
912 path: ptr::null_mut(),
913 reserved: [0; 16],
914 };
915 assert_eq!(
916 format!("{apk:?}"),
917 "blaze_user_meta_apk { path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
918 );
919
920 let elf = blaze_user_meta_elf {
921 path: ptr::null_mut(),
922 build_id_len: 0,
923 build_id: ptr::null_mut(),
924 reserved: [0; 16],
925 };
926 assert_eq!(
927 format!("{elf:?}"),
928 "blaze_user_meta_elf { path: 0x0, build_id_len: 0, build_id: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
929 );
930
931 let unknown = blaze_user_meta_unknown {
932 reason: blaze_normalize_reason::UNMAPPED,
933 reserved: [0; 15],
934 };
935 assert_eq!(
936 format!("{unknown:?}"),
937 "blaze_user_meta_unknown { reason: blaze_normalize_reason(0), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
938 );
939
940 let meta = blaze_user_meta {
941 kind: blaze_user_meta_kind::UNKNOWN,
942 unused: [0; 7],
943 variant: blaze_user_meta_variant {
944 unknown: ManuallyDrop::new(blaze_user_meta_unknown {
945 reason: blaze_normalize_reason::UNMAPPED,
946 reserved: [0; 15],
947 }),
948 },
949 reserved: [0; 16],
950 };
951 assert_eq!(
952 format!("{meta:?}"),
953 "blaze_user_meta { kind: blaze_user_meta_kind(0), unused: [0, 0, 0, 0, 0, 0, 0], variant: blaze_user_meta_variant, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
954 );
955
956 let normalized = blaze_normalized_user_output {
957 meta_cnt: 0,
958 metas: ptr::null_mut(),
959 output_cnt: 0,
960 outputs: ptr::null_mut(),
961 reserved: [0; 16],
962 };
963 assert_eq!(
964 format!("{normalized:?}"),
965 "blaze_normalized_user_output { meta_cnt: 0, metas: 0x0, output_cnt: 0, outputs: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
966 );
967 }
968
969 #[tag(miri)]
971 #[test]
972 fn reason_stringification() {
973 let data = [
974 (Reason::Unmapped, blaze_normalize_reason::UNMAPPED),
975 (
976 Reason::MissingComponent,
977 blaze_normalize_reason::MISSING_COMPONENT,
978 ),
979 (Reason::Unsupported, blaze_normalize_reason::UNSUPPORTED),
980 (
981 Reason::InvalidFileOffset,
982 blaze_normalize_reason::INVALID_FILE_OFFSET,
983 ),
984 (Reason::MissingSyms, blaze_normalize_reason::MISSING_SYMS),
985 (Reason::UnknownAddr, blaze_normalize_reason::UNKNOWN_ADDR),
986 ];
987
988 for (reason, expected) in data {
989 assert_eq!(blaze_normalize_reason::from(reason), expected);
990 let cstr = unsafe { CStr::from_ptr(blaze_normalize_reason_str(expected)) };
991 let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
992 assert_eq!(cstr, expected);
993 }
994 }
995
996 #[tag(miri)]
999 #[test]
1000 fn unknown_conversion() {
1001 let unknown = Unknown {
1002 reason: Reason::Unsupported,
1003 _non_exhaustive: (),
1004 };
1005
1006 let unknown_c = blaze_user_meta_unknown::from(unknown.clone());
1007 let () = ManuallyDrop::into_inner(unknown_c).free();
1008
1009 let meta = UserMeta::Unknown(unknown);
1010 let meta_c = blaze_user_meta::from(meta);
1011 let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1012 }
1013
1014 #[tag(miri)]
1017 #[test]
1018 fn apk_conversion() {
1019 let apk = Apk {
1020 path: PathBuf::from("/tmp/archive.apk"),
1021 _non_exhaustive: (),
1022 };
1023
1024 let apk_c = blaze_user_meta_apk::from(apk.clone());
1025 let () = unsafe { ManuallyDrop::into_inner(apk_c).free() };
1026
1027 let meta = UserMeta::Apk(apk);
1028 let meta_c = blaze_user_meta::from(meta);
1029 let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1030 }
1031
1032 #[tag(miri)]
1035 #[test]
1036 fn elf_conversion() {
1037 let elf = Elf {
1038 path: PathBuf::from("/tmp/file.so"),
1039 build_id: Some(Cow::Borrowed(&[0x01, 0x02, 0x03, 0x04])),
1040 _non_exhaustive: (),
1041 };
1042
1043 let elf_c = blaze_user_meta_elf::from(elf.clone());
1044 let () = unsafe { ManuallyDrop::into_inner(elf_c).free() };
1045
1046 let meta = UserMeta::Elf(elf);
1047 let meta_c = blaze_user_meta::from(meta);
1048 let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
1049 }
1050
1051 #[tag(miri)]
1053 #[test]
1054 fn normalizer_creation() {
1055 let normalizer = blaze_normalizer_new();
1056 let () = unsafe { blaze_normalizer_free(normalizer) };
1057 }
1058
1059 #[test]
1061 fn normalize_user_addrs() {
1062 fn test(normalizer: *const blaze_normalizer) {
1063 let addrs = [
1064 0x0,
1065 libc::atexit as Addr,
1066 libc::chdir as Addr,
1067 libc::fopen as Addr,
1068 elf_conversion as Addr,
1069 normalize_user_addrs as Addr,
1070 ];
1071
1072 let result = unsafe {
1073 blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1074 };
1075 assert_ne!(result, ptr::null_mut());
1076
1077 let normalized = unsafe { &*result };
1078 assert_eq!(normalized.meta_cnt, 3);
1079 assert_eq!(normalized.output_cnt, 6);
1080
1081 let meta = unsafe { normalized.metas.read() };
1082 assert_eq!(meta.kind, blaze_user_meta_kind::UNKNOWN);
1083 assert_eq!(
1084 unsafe { meta.variant.unknown.reason },
1085 blaze_normalize_reason::UNMAPPED
1086 );
1087
1088 let () = unsafe { blaze_user_output_free(result) };
1089 }
1090
1091 let normalizer = blaze_normalizer_new();
1092 assert_ne!(normalizer, ptr::null_mut());
1093 test(normalizer);
1094 let () = unsafe { blaze_normalizer_free(normalizer) };
1095
1096 let opts = blaze_normalizer_opts {
1097 cache_vmas: true,
1098 ..Default::default()
1099 };
1100 let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1101 assert_ne!(normalizer, ptr::null_mut());
1102 test(normalizer);
1103 test(normalizer);
1104 let () = unsafe { blaze_normalizer_free(normalizer) };
1105 }
1106
1107 fn test_normalize_user_addrs_sorted(use_procmap_query: bool) {
1108 let mut addrs = [
1109 libc::atexit as Addr,
1110 libc::chdir as Addr,
1111 libc::fopen as Addr,
1112 elf_conversion as Addr,
1113 normalize_user_addrs as Addr,
1114 ];
1115 let () = addrs.sort();
1116
1117 let opts = blaze_normalizer_opts {
1118 use_procmap_query,
1119 ..Default::default()
1120 };
1121 let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1122 assert_ne!(normalizer, ptr::null_mut());
1123
1124 let opts = blaze_normalize_opts {
1125 sorted_addrs: true,
1126 ..Default::default()
1127 };
1128 let result = unsafe {
1129 blaze_normalize_user_addrs_opts(
1130 normalizer,
1131 0,
1132 addrs.as_slice().as_ptr(),
1133 addrs.len(),
1134 &opts,
1135 )
1136 };
1137 assert_ne!(result, ptr::null_mut());
1138
1139 let normalized = unsafe { &*result };
1140 assert_eq!(normalized.meta_cnt, 2);
1141 assert_eq!(normalized.output_cnt, 5);
1142
1143 let () = unsafe { blaze_user_output_free(result) };
1144 let () = unsafe { blaze_normalizer_free(normalizer) };
1145 }
1146
1147 #[test]
1149 fn normalize_user_addrs_sorted_proc_maps() {
1150 test_normalize_user_addrs_sorted(false)
1151 }
1152
1153 #[test]
1156 #[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
1157 fn normalize_user_addrs_sorted_ioctl() {
1158 test_normalize_user_addrs_sorted(true)
1159 }
1160
1161 #[test]
1164 fn normalize_user_addrs_unsorted_failure() {
1165 let mut addrs = [
1166 libc::atexit as Addr,
1167 libc::chdir as Addr,
1168 libc::fopen as Addr,
1169 elf_conversion as Addr,
1170 normalize_user_addrs as Addr,
1171 ];
1172 let () = addrs.sort_by(|addr1, addr2| addr1.cmp(addr2).reverse());
1173
1174 let normalizer = blaze_normalizer_new();
1175 assert_ne!(normalizer, ptr::null_mut());
1176
1177 let opts = blaze_normalize_opts {
1178 sorted_addrs: true,
1179 ..Default::default()
1180 };
1181 let result = unsafe {
1182 blaze_normalize_user_addrs_opts(
1183 normalizer,
1184 0,
1185 addrs.as_slice().as_ptr(),
1186 addrs.len(),
1187 &opts,
1188 )
1189 };
1190 assert_eq!(result, ptr::null_mut());
1191 assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
1192
1193 let () = unsafe { blaze_normalizer_free(normalizer) };
1194 }
1195
1196 #[test]
1198 #[cfg_attr(
1199 not(target_pointer_width = "64"),
1200 ignore = "loads 64 bit shared object"
1201 )]
1202 fn normalize_build_id_reading() {
1203 fn test(read_build_ids: bool) {
1204 let test_so = Path::new(&env!("CARGO_MANIFEST_DIR"))
1205 .join("..")
1206 .join("data")
1207 .join("libtest-so.so")
1208 .canonicalize()
1209 .unwrap();
1210 let so_cstr = CString::new(test_so.clone().into_os_string().into_vec()).unwrap();
1211 let handle = unsafe { libc::dlopen(so_cstr.as_ptr(), libc::RTLD_NOW) };
1212 assert!(!handle.is_null());
1213
1214 let the_answer_addr = unsafe { libc::dlsym(handle, "the_answer\0".as_ptr().cast()) };
1215 assert!(!the_answer_addr.is_null());
1216
1217 let opts = blaze_normalizer_opts {
1218 build_ids: read_build_ids,
1219 ..Default::default()
1220 };
1221
1222 let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
1223 assert!(!normalizer.is_null());
1224
1225 let opts = blaze_normalize_opts {
1226 sorted_addrs: true,
1227 ..Default::default()
1228 };
1229 let addrs = [the_answer_addr as Addr];
1230 let result = unsafe {
1231 blaze_normalize_user_addrs_opts(
1232 normalizer,
1233 0,
1234 addrs.as_slice().as_ptr(),
1235 addrs.len(),
1236 &opts,
1237 )
1238 };
1239 assert!(!result.is_null());
1240
1241 let normalized = unsafe { &*result };
1242 assert_eq!(normalized.meta_cnt, 1);
1243 assert_eq!(normalized.output_cnt, 1);
1244
1245 let rc = unsafe { libc::dlclose(handle) };
1246 assert_eq!(rc, 0, "{}", io::Error::last_os_error());
1247
1248 let output = unsafe { &*normalized.outputs.add(0) };
1249 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1250 assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1251
1252 let elf = unsafe { &meta.variant.elf };
1253
1254 assert!(!elf.path.is_null());
1255 let path = unsafe { CStr::from_ptr(elf.path) };
1256 assert_eq!(path, so_cstr.as_ref());
1257
1258 if read_build_ids {
1259 let expected = read_elf_build_id(&test_so).unwrap().unwrap();
1260 let build_id = unsafe { slice_from_user_array(elf.build_id, elf.build_id_len) };
1261 assert_eq!(build_id, expected.as_ref());
1262 } else {
1263 assert!(elf.build_id.is_null());
1264 }
1265
1266 let () = unsafe { blaze_user_output_free(result) };
1267 let () = unsafe { blaze_normalizer_free(normalizer) };
1268 }
1269
1270 test(true);
1271 test(false);
1272 }
1273
1274 #[test]
1277 fn normalize_custom_so_in_zip() {
1278 let test_zip = Path::new(&env!("CARGO_MANIFEST_DIR"))
1279 .join("..")
1280 .join("data")
1281 .join("test.zip");
1282 let so_name = "libtest-so.so";
1283
1284 let mmap = Mmap::builder().exec().open(&test_zip).unwrap();
1285 let archive = zip::Archive::with_mmap(mmap.clone()).unwrap();
1286 let so = archive
1287 .entries()
1288 .find_map(|entry| {
1289 let entry = entry.unwrap();
1290 (entry.path == Path::new(so_name)).then_some(entry)
1291 })
1292 .unwrap();
1293
1294 let elf_mmap = mmap
1295 .constrain(so.data_offset..so.data_offset + so.data.len() as u64)
1296 .unwrap();
1297 let (_sym, the_answer_addr) = find_the_answer_fn(&elf_mmap);
1298
1299 let normalizer = blaze_normalizer_new();
1300 assert!(!normalizer.is_null());
1301
1302 let addrs = [the_answer_addr];
1303 let opts = blaze_normalize_opts {
1304 apk_to_elf: true,
1305 ..Default::default()
1306 };
1307 let result = unsafe {
1308 blaze_normalize_user_addrs_opts(
1309 normalizer,
1310 0,
1311 addrs.as_slice().as_ptr(),
1312 addrs.len(),
1313 &opts,
1314 )
1315 };
1316 assert_ne!(result, ptr::null_mut());
1317
1318 let normalized = unsafe { &*result };
1319 assert_eq!(normalized.meta_cnt, 1);
1320 assert_eq!(normalized.output_cnt, 1);
1321
1322 let output = unsafe { &*normalized.outputs.add(0) };
1323 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1324 assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
1325
1326 let elf = unsafe { &meta.variant.elf };
1327 let path = unsafe { CStr::from_ptr(elf.path) };
1328 assert!(path.to_str().unwrap().ends_with(so_name), "{path:?}");
1329
1330 let () = unsafe { blaze_user_output_free(result) };
1331 let () = unsafe { blaze_normalizer_free(normalizer) };
1332 }
1333
1334 #[cfg(linux)]
1337 #[cfg(target_pointer_width = "64")]
1339 #[test]
1340 fn normalize_local_vdso_address() {
1341 use libc::gettimeofday;
1342
1343 let addrs = [normalize_local_vdso_address as Addr, gettimeofday as Addr];
1344 let normalizer = blaze_normalizer_new();
1345 assert!(!normalizer.is_null());
1346
1347 let result = unsafe {
1348 blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1349 };
1350 assert_ne!(result, ptr::null_mut());
1351
1352 let normalized = unsafe { &*result };
1353 assert_eq!(normalized.meta_cnt, 2);
1354 assert_eq!(normalized.output_cnt, 2);
1355
1356 let output = unsafe { &*normalized.outputs.add(1) };
1357 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1358 assert_eq!(meta.kind, blaze_user_meta_kind::SYM);
1359
1360 let sym = unsafe { &*meta.variant.sym.sym };
1361 let name = unsafe { CStr::from_ptr(sym.name) };
1362 assert!(name.to_str().unwrap().ends_with("gettimeofday"), "{name:?}");
1363
1364 let () = unsafe { blaze_user_output_free(result) };
1365 let () = unsafe { blaze_normalizer_free(normalizer) };
1366 }
1367
1368 #[cfg(linux)]
1371 #[cfg(target_pointer_width = "64")]
1372 #[test]
1373 fn normalize_invalid_vdso_address() {
1374 use blazesym::__private::find_vdso_range;
1375
1376 let vdso = find_vdso_range();
1377 let addrs = [vdso.start];
1381 let normalizer = blaze_normalizer_new();
1382 assert!(!normalizer.is_null());
1383
1384 let result = unsafe {
1385 blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
1386 };
1387 assert_ne!(result, ptr::null_mut());
1388
1389 let normalized = unsafe { &*result };
1390 assert_eq!(normalized.meta_cnt, 1);
1391 assert_eq!(normalized.output_cnt, 1);
1392
1393 let output = unsafe { &*normalized.outputs };
1394 let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
1395 match meta.kind {
1396 blaze_user_meta_kind::UNKNOWN | blaze_user_meta_kind::SYM => (),
1402 _ => panic!("encountered unexpected meta kind: {:?}", meta.kind),
1403 }
1404
1405 let () = unsafe { blaze_user_output_free(result) };
1406 let () = unsafe { blaze_normalizer_free(normalizer) };
1407 }
1408}