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
32pub type blaze_normalizer = Normalizer;
34
35
36#[repr(C)]
38#[derive(Debug)]
39pub struct blaze_normalizer_opts {
40 pub type_size: usize,
45 pub use_procmap_query: bool,
64 pub cache_vmas: bool,
71 pub build_ids: bool,
77 pub cache_build_ids: bool,
80 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#[repr(C)]
101#[derive(Debug)]
102pub struct blaze_normalize_opts {
103 pub type_size: usize,
108 pub sorted_addrs: bool,
114 pub map_files: bool,
124 pub apk_to_elf: bool,
132 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#[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#[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#[no_mangle]
242pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut blaze_normalizer) {
243 if !normalizer.is_null() {
244 drop(unsafe { Box::from_raw(normalizer) });
247 }
248}
249
250
251#[repr(C)]
255#[derive(Debug)]
256pub struct blaze_normalized_output {
257 pub output: u64,
259 pub meta_idx: usize,
261 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#[repr(transparent)]
279#[derive(Debug, PartialEq)]
280pub struct blaze_user_meta_kind(u8);
281
282impl blaze_user_meta_kind {
283 pub const BLAZE_USER_META_UNKNOWN: blaze_user_meta_kind = blaze_user_meta_kind(0);
285 pub const BLAZE_USER_META_APK: blaze_user_meta_kind = blaze_user_meta_kind(1);
287 pub const BLAZE_USER_META_ELF: blaze_user_meta_kind = blaze_user_meta_kind(2);
289}
290
291
292#[repr(C)]
294#[derive(Debug)]
295pub struct blaze_user_meta_apk {
296 pub path: *mut c_char,
299 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#[repr(C)]
334#[derive(Debug)]
335pub struct blaze_user_meta_elf {
336 pub path: *mut c_char,
344 pub build_id_len: usize,
346 pub build_id: *mut u8,
348 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 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#[repr(transparent)]
415#[derive(Copy, Clone, Debug, PartialEq)]
416pub struct blaze_normalize_reason(u8);
417
418impl blaze_normalize_reason {
419 pub const UNMAPPED: blaze_normalize_reason = blaze_normalize_reason(0);
422 pub const MISSING_COMPONENT: blaze_normalize_reason = blaze_normalize_reason(1);
425 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#[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#[repr(C)]
457#[derive(Debug)]
458pub struct blaze_user_meta_unknown {
459 pub reason: blaze_normalize_reason,
464 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#[repr(C)]
493pub union blaze_user_meta_variant {
494 pub apk: ManuallyDrop<blaze_user_meta_apk>,
496 pub elf: ManuallyDrop<blaze_user_meta_elf>,
498 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#[repr(C)]
511#[derive(Debug)]
512pub struct blaze_user_meta {
513 pub kind: blaze_user_meta_kind,
515 pub unused: [u8; 7],
517 pub variant: blaze_user_meta_variant,
519 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#[repr(C)]
579#[derive(Debug)]
580pub struct blaze_normalized_user_output {
581 pub meta_cnt: usize,
583 pub metas: *mut blaze_user_meta,
585 pub output_cnt: usize,
587 pub outputs: *mut blaze_normalized_output,
589 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 let normalizer = unsafe { &*normalizer };
642 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#[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#[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#[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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[tag(miri)]
950 #[test]
951 fn normalizer_creation() {
952 let normalizer = blaze_normalizer_new();
953 let () = unsafe { blaze_normalizer_free(normalizer) };
954 }
955
956 #[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 #[test]
1046 fn normalize_user_addrs_sorted_proc_maps() {
1047 test_normalize_user_addrs_sorted(false)
1048 }
1049
1050 #[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 #[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 #[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 #[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}