1use std::alloc::alloc;
2use std::alloc::dealloc;
3use std::alloc::Layout;
4use std::ffi::CStr;
5use std::ffi::CString;
6use std::ffi::OsStr;
7use std::ffi::OsString;
8use std::fmt::Debug;
9use std::mem;
10use std::mem::ManuallyDrop;
11use std::ops::Deref as _;
12use std::os::raw::c_char;
13use std::os::unix::ffi::OsStrExt as _;
14use std::os::unix::ffi::OsStringExt as _;
15use std::path::PathBuf;
16use std::ptr;
17
18#[cfg(doc)]
19use blazesym::inspect;
20use blazesym::inspect::source::Elf;
21use blazesym::inspect::source::Source;
22use blazesym::inspect::Inspector;
23use blazesym::inspect::SymInfo;
24use blazesym::Addr;
25use blazesym::SymType;
26
27use crate::blaze_err;
28#[cfg(doc)]
29use crate::blaze_err_last;
30use crate::from_cstr;
31use crate::set_last_err;
32use crate::util::slice_from_user_array;
33
34
35pub type blaze_inspector = Inspector;
37
38
39#[repr(C)]
43#[derive(Debug)]
44pub struct blaze_inspect_elf_src {
45 pub type_size: usize,
50 pub path: *const c_char,
52 pub debug_syms: bool,
55 pub reserved: [u8; 23],
58}
59
60impl Default for blaze_inspect_elf_src {
61 fn default() -> Self {
62 Self {
63 type_size: mem::size_of::<Self>(),
64 path: ptr::null(),
65 debug_syms: false,
66 reserved: [0; 23],
67 }
68 }
69}
70
71#[cfg_attr(not(test), allow(unused))]
72impl blaze_inspect_elf_src {
73 fn from(other: Elf) -> ManuallyDrop<Self> {
74 let Elf {
75 path,
76 debug_syms,
77 _non_exhaustive: (),
78 } = other;
79
80 let slf = Self {
81 path: CString::new(path.into_os_string().into_vec())
82 .expect("encountered path with NUL bytes")
83 .into_raw(),
84 debug_syms,
85 ..Default::default()
86 };
87 ManuallyDrop::new(slf)
88 }
89
90 unsafe fn free(self) {
91 let Self {
92 type_size: _,
93 path,
94 debug_syms,
95 reserved: _,
96 } = self;
97
98 let _elf = Elf {
99 path: PathBuf::from(OsString::from_vec(
100 unsafe { CString::from_raw(path as *mut _) }.into_bytes(),
101 )),
102 debug_syms,
103 _non_exhaustive: (),
104 };
105 }
106}
107
108impl From<blaze_inspect_elf_src> for Elf {
109 fn from(other: blaze_inspect_elf_src) -> Self {
110 let blaze_inspect_elf_src {
111 type_size: _,
112 path,
113 debug_syms,
114 reserved: _,
115 } = other;
116
117 Self {
118 path: unsafe { from_cstr(path) },
119 debug_syms,
120 _non_exhaustive: (),
121 }
122 }
123}
124
125
126#[repr(transparent)]
128#[derive(Copy, Clone, Debug, PartialEq)]
129pub struct blaze_sym_type(u8);
130
131impl blaze_sym_type {
132 pub const UNDEF: blaze_sym_type = blaze_sym_type(0);
138 pub const FUNC: blaze_sym_type = blaze_sym_type(1);
140 pub const VAR: blaze_sym_type = blaze_sym_type(2);
142}
143
144impl From<SymType> for blaze_sym_type {
145 fn from(other: SymType) -> Self {
146 match other {
147 SymType::Undefined => blaze_sym_type::UNDEF,
148 SymType::Function => blaze_sym_type::FUNC,
149 SymType::Variable => blaze_sym_type::VAR,
150 _ => unreachable!(),
151 }
152 }
153}
154
155
156#[repr(C)]
158#[derive(Debug)]
159pub struct blaze_sym_info {
160 pub name: *const c_char,
162 pub addr: Addr,
164 pub size: isize,
171 pub file_offset: u64,
173 pub module: *const c_char,
175 pub sym_type: blaze_sym_type,
177 pub reserved: [u8; 23],
179}
180
181
182fn convert_syms_list_to_c(syms_list: Vec<Vec<SymInfo>>) -> *const *const blaze_sym_info {
185 let mut sym_cnt = 0;
186 let mut str_buf_sz = 0;
187
188 for syms in &syms_list {
189 sym_cnt += syms.len() + 1;
190 for sym in syms {
191 str_buf_sz += sym.name.len() + 1;
192 if let Some(fname) = sym.module.as_ref() {
193 str_buf_sz += AsRef::<OsStr>::as_ref(fname.deref()).as_bytes().len() + 1;
194 }
195 }
196 }
197
198 let array_sz = (mem::size_of::<*const u64>() * syms_list.len() + mem::size_of::<u64>() - 1)
199 / mem::size_of::<u64>()
200 * mem::size_of::<u64>();
201 let sym_buf_sz = mem::size_of::<blaze_sym_info>() * sym_cnt;
202 let buf_size = mem::size_of::<u64>() + array_sz + sym_buf_sz + str_buf_sz;
203 let raw_buf_with_sz = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
204 if raw_buf_with_sz.is_null() {
205 return ptr::null()
206 }
207
208 unsafe { *(raw_buf_with_sz as *mut u64) = buf_size as u64 };
209
210 let raw_buf = unsafe { raw_buf_with_sz.add(mem::size_of::<u64>()) };
211 let mut syms_ptr = raw_buf as *mut *mut blaze_sym_info;
212 let mut sym_ptr = unsafe { raw_buf.add(array_sz) } as *mut blaze_sym_info;
213 let mut str_ptr = unsafe { raw_buf.add(array_sz + sym_buf_sz) } as *mut c_char;
214
215 for syms in syms_list {
216 unsafe { *syms_ptr = sym_ptr };
217 for SymInfo {
218 name,
219 addr,
220 size,
221 sym_type,
222 file_offset,
223 module,
224 } in syms
225 {
226 let name_ptr = str_ptr.cast();
227 unsafe { ptr::copy_nonoverlapping(name.as_ptr().cast(), str_ptr, name.len()) };
228 str_ptr = unsafe { str_ptr.add(name.len()) };
229 unsafe { *str_ptr = 0 };
230 str_ptr = unsafe { str_ptr.add(1) };
231 let module = if let Some(fname) = module.as_ref() {
232 let fname = AsRef::<OsStr>::as_ref(fname.deref()).as_bytes();
233 let obj_fname_ptr = str_ptr;
234 unsafe { ptr::copy_nonoverlapping(fname.as_ptr().cast(), str_ptr, fname.len()) };
235 str_ptr = unsafe { str_ptr.add(fname.len()) };
236 unsafe { *str_ptr = 0 };
237 str_ptr = unsafe { str_ptr.add(1) };
238 obj_fname_ptr
239 } else {
240 ptr::null()
241 };
242
243 unsafe {
244 (*sym_ptr) = blaze_sym_info {
245 name: name_ptr,
246 addr,
247 size: size
248 .map(|size| isize::try_from(size).unwrap_or(isize::MAX))
249 .unwrap_or(-1),
250 sym_type: sym_type.into(),
251 file_offset: file_offset.unwrap_or(0),
252 module,
253 reserved: [0; 23],
254 }
255 };
256 sym_ptr = unsafe { sym_ptr.add(1) };
257 }
258 unsafe {
259 (*sym_ptr) = blaze_sym_info {
260 name: ptr::null(),
261 addr: 0,
262 size: 0,
263 sym_type: blaze_sym_type::UNDEF,
264 file_offset: 0,
265 module: ptr::null(),
266 reserved: [0; 23],
267 }
268 };
269 sym_ptr = unsafe { sym_ptr.add(1) };
270
271 syms_ptr = unsafe { syms_ptr.add(1) };
272 }
273
274 raw_buf as *const *const blaze_sym_info
275}
276
277
278#[no_mangle]
294pub unsafe extern "C" fn blaze_inspect_syms_elf(
295 inspector: *const blaze_inspector,
296 src: *const blaze_inspect_elf_src,
297 names: *const *const c_char,
298 name_cnt: usize,
299) -> *const *const blaze_sym_info {
300 if !input_zeroed!(src, blaze_inspect_elf_src) {
301 let () = set_last_err(blaze_err::INVALID_INPUT);
302 return ptr::null()
303 }
304 let src = input_sanitize!(src, blaze_inspect_elf_src);
305
306 let inspector = unsafe { &*inspector };
308 let src = Source::Elf(Elf::from(src));
310 let names = unsafe { slice_from_user_array(names, name_cnt) };
313 let names = names
314 .iter()
315 .map(|&p| {
316 unsafe { CStr::from_ptr(p) }.to_str().unwrap()
318 })
319 .collect::<Vec<_>>();
320 let result = inspector.lookup(&src, &names);
321 match result {
322 Ok(syms) => {
323 let result = convert_syms_list_to_c(syms);
324 if result.is_null() {
325 let () = set_last_err(blaze_err::OUT_OF_MEMORY);
326 } else {
327 let () = set_last_err(blaze_err::OK);
328 }
329 result
330 }
331 Err(err) => {
332 let () = set_last_err(err.kind().into());
333 ptr::null()
334 }
335 }
336}
337
338
339#[no_mangle]
345pub unsafe extern "C" fn blaze_inspect_syms_free(syms: *const *const blaze_sym_info) {
346 if syms.is_null() {
347 return
348 }
349
350 let raw_buf_with_sz = unsafe { (syms as *mut u8).offset(-(mem::size_of::<u64>() as isize)) };
351 let sz = unsafe { *(raw_buf_with_sz as *mut u64) } as usize;
352 unsafe { dealloc(raw_buf_with_sz, Layout::from_size_align(sz, 8).unwrap()) };
353}
354
355
356#[no_mangle]
370pub extern "C" fn blaze_inspector_new() -> *mut blaze_inspector {
371 let inspector = Inspector::new();
372 let inspector_box = Box::new(inspector);
373 let () = set_last_err(blaze_err::OK);
374 Box::into_raw(inspector_box)
375}
376
377
378#[no_mangle]
387pub unsafe extern "C" fn blaze_inspector_free(inspector: *mut blaze_inspector) {
388 if !inspector.is_null() {
389 drop(unsafe { Box::from_raw(inspector) });
392 }
393}
394
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 use std::mem::MaybeUninit;
401 use std::path::Path;
402 use std::ptr::addr_of;
403 use std::slice;
404
405 use test_log::test;
406 use test_tag::tag;
407
408 use crate::blaze_err_last;
409
410
411 #[tag(miri)]
413 #[test]
414 #[cfg(target_pointer_width = "64")]
415 fn type_sizes() {
416 assert_eq!(mem::size_of::<blaze_inspect_elf_src>(), 40);
417 assert_eq!(mem::size_of::<blaze_sym_info>(), 64);
418 }
419
420 #[tag(miri)]
422 #[test]
423 fn debug_repr() {
424 let elf = blaze_inspect_elf_src {
425 type_size: 24,
426 path: ptr::null(),
427 debug_syms: true,
428 reserved: [0; 23],
429 };
430 assert_eq!(
431 format!("{elf:?}"),
432 "blaze_inspect_elf_src { type_size: 24, path: 0x0, debug_syms: true, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
433 );
434
435 let info = blaze_sym_info {
436 name: ptr::null(),
437 addr: 42,
438 size: 1337,
439 file_offset: 31,
440 module: ptr::null(),
441 sym_type: blaze_sym_type::VAR,
442 reserved: [0; 23],
443 };
444 assert_eq!(
445 format!("{info:?}"),
446 "blaze_sym_info { name: 0x0, addr: 42, size: 1337, file_offset: 31, module: 0x0, sym_type: blaze_sym_type(2), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
447 );
448 }
449
450 #[tag(miri)]
453 #[test]
454 fn elf_src_validity() {
455 #[repr(C)]
456 struct elf_src_with_ext {
457 type_size: usize,
458 _path: *const c_char,
459 debug_syms: bool,
460 reserved: [u8; 23],
461 foobar: bool,
462 reserved2: [u8; 7],
463 }
464
465 assert!(mem::size_of::<blaze_inspect_elf_src>() < mem::size_of::<elf_src_with_ext>());
466
467 let mut src = MaybeUninit::<elf_src_with_ext>::uninit();
468 let () = unsafe {
469 ptr::write_bytes(
470 src.as_mut_ptr().cast::<u8>(),
471 0,
472 mem::size_of::<elf_src_with_ext>(),
473 )
474 };
475
476 let mut src = unsafe { src.assume_init() };
477 src.type_size = mem::size_of::<elf_src_with_ext>();
478 src.debug_syms = true;
479
480 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
481 assert!(input_zeroed!(src_ptr, blaze_inspect_elf_src));
482
483 src.reserved[0] = 1;
484 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
485 assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
486 src.reserved[0] = 0;
487
488 src.type_size = mem::size_of::<usize>() - 1;
489 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
490 assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
491 src.type_size = mem::size_of::<elf_src_with_ext>();
492
493 src.foobar = true;
494 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
495 assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
496 }
497
498 #[tag(miri)]
501 #[test]
502 fn syms_list_conversion() {
503 fn test(syms: Vec<Vec<SymInfo>>) {
504 let copy = syms.clone();
505 let ptr = convert_syms_list_to_c(syms);
506 assert!(!ptr.is_null());
507
508 for (i, list) in copy.into_iter().enumerate() {
509 for (j, sym) in list.into_iter().enumerate() {
510 let c_sym = unsafe { &(*(*ptr.add(i)).add(j)) };
511 assert_eq!(
512 unsafe { CStr::from_ptr(c_sym.name) }.to_bytes(),
513 CString::new(sym.name.deref()).unwrap().to_bytes()
514 );
515 assert_eq!(c_sym.addr, sym.addr);
516 assert_eq!(
517 c_sym.size,
518 sym.size
519 .map(|size| isize::try_from(size).unwrap_or(isize::MAX))
520 .unwrap_or(-1)
521 );
522 assert_eq!(c_sym.sym_type, blaze_sym_type::from(sym.sym_type));
523 assert_eq!(Some(c_sym.file_offset), sym.file_offset);
524 assert_eq!(
525 unsafe { CStr::from_ptr(c_sym.module) }.to_bytes(),
526 CString::new(sym.module.as_deref().unwrap().to_os_string().into_vec())
527 .unwrap()
528 .to_bytes()
529 );
530 }
531 }
532
533 let () = unsafe { blaze_inspect_syms_free(ptr) };
534 }
535
536 let syms = vec![];
538 test(syms);
539
540 let syms = vec![vec![SymInfo {
542 name: "sym1".into(),
543 addr: 0xdeadbeef,
544 size: Some(42),
545 sym_type: SymType::Function,
546 file_offset: Some(1337),
547 module: Some(OsStr::new("/tmp/foobar.so").into()),
548 }]];
549 test(syms);
550
551 let syms = vec![vec![
553 SymInfo {
554 name: "sym1".into(),
555 addr: 0xdeadbeef,
556 size: Some(42),
557 sym_type: SymType::Function,
558 file_offset: Some(1337),
559 module: Some(OsStr::new("/tmp/foobar.so").into()),
560 },
561 SymInfo {
562 name: "sym2".into(),
563 addr: 0xdeadbeef + 52,
564 size: Some(45),
565 sym_type: SymType::Undefined,
566 file_offset: Some(1338),
567 module: Some(OsStr::new("other.so").into()),
568 },
569 ]];
570 test(syms);
571
572 let syms = vec![
574 vec![SymInfo {
575 name: "sym1".into(),
576 addr: 0xdeadbeef,
577 size: Some(42),
578 sym_type: SymType::Function,
579 file_offset: Some(1337),
580 module: Some(OsStr::new("/tmp/foobar.so").into()),
581 }],
582 vec![SymInfo {
583 name: "sym2".into(),
584 addr: 0xdeadbeef + 52,
585 size: Some(45),
586 sym_type: SymType::Undefined,
587 file_offset: Some(1338),
588 module: Some(OsStr::new("other.so").into()),
589 }],
590 ];
591 test(syms);
592
593 let sym = SymInfo {
595 name: "sym1".into(),
596 addr: 0xdeadbeef,
597 size: Some(42),
598 sym_type: SymType::Function,
599 file_offset: Some(1337),
600 module: Some(OsStr::new("/tmp/foobar.so").into()),
601 };
602 let syms = vec![(0..200).map(|_| sym.clone()).collect()];
603 test(syms);
604
605 let syms = (0..200).map(|_| vec![sym.clone()]).collect();
607 test(syms);
608 }
609
610 #[tag(miri)]
612 #[test]
613 fn inspector_creation() {
614 let inspector = blaze_inspector_new();
615 let () = unsafe { blaze_inspector_free(inspector) };
616 }
617
618 #[test]
621 fn non_zero_reserved() {
622 let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
623 .join("..")
624 .join("data")
625 .join("test-stable-addrs.bin");
626
627 let mut src = blaze_inspect_elf_src::from(Elf::new(path));
628 src.reserved[1] = 1;
629
630 let factorial = CString::new("factorial").unwrap();
631 let names = [factorial.as_ptr()];
632 let inspector = blaze_inspector_new();
633
634 let result =
635 unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
636 let () = unsafe { ManuallyDrop::into_inner(src).free() };
637 assert_eq!(result, ptr::null());
638 assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
639
640 let () = unsafe { blaze_inspector_free(inspector) };
641 }
642
643 #[test]
646 fn non_present_file() {
647 let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
648 .join("..")
649 .join("data")
650 .join("does-not-exist");
651
652 let src = blaze_inspect_elf_src::from(Elf::new(path));
653 let factorial = CString::new("factorial").unwrap();
654 let names = [factorial.as_ptr()];
655 let inspector = blaze_inspector_new();
656
657 let result =
658 unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
659 let () = unsafe { ManuallyDrop::into_inner(src).free() };
660 assert_eq!(result, ptr::null());
661 assert_eq!(blaze_err_last(), blaze_err::NOT_FOUND);
662
663 let () = unsafe { blaze_inspector_free(inspector) };
664 }
665
666 #[test]
669 fn lookup_dwarf() {
670 let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
671 .join("..")
672 .join("data")
673 .join("test-stable-addrs-stripped-elf-with-dwarf.bin");
674
675 let src = blaze_inspect_elf_src::from(Elf::new(test_dwarf));
676 let factorial = CString::new("factorial").unwrap();
677 let names = [factorial.as_ptr()];
678
679 let inspector = blaze_inspector_new();
680 let result =
681 unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
682 let () = unsafe { ManuallyDrop::into_inner(src).free() };
683 assert!(!result.is_null());
684
685 let sym_infos = unsafe { slice::from_raw_parts(result, names.len()) };
686 let sym_info = unsafe { &*sym_infos[0] };
687 assert_eq!(
688 unsafe { CStr::from_ptr(sym_info.name) },
689 CStr::from_bytes_with_nul(b"factorial\0").unwrap()
690 );
691 assert_eq!(sym_info.addr, 0x2000200);
692
693 let () = unsafe { blaze_inspect_syms_free(result) };
694 let () = unsafe { blaze_inspector_free(inspector) };
695 }
696}