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;
33use crate::util::DynSize as _;
34
35
36pub type blaze_inspector = Inspector;
38
39
40#[repr(C)]
44#[derive(Debug)]
45pub struct blaze_inspect_elf_src {
46 pub type_size: usize,
51 pub path: *const c_char,
53 pub debug_syms: bool,
56 pub reserved: [u8; 23],
59}
60
61impl Default for blaze_inspect_elf_src {
62 fn default() -> Self {
63 Self {
64 type_size: mem::size_of::<Self>(),
65 path: ptr::null(),
66 debug_syms: false,
67 reserved: [0; 23],
68 }
69 }
70}
71
72#[cfg_attr(not(test), allow(unused))]
73impl blaze_inspect_elf_src {
74 fn from(other: Elf) -> ManuallyDrop<Self> {
75 let Elf {
76 path,
77 debug_syms,
78 _non_exhaustive: (),
79 } = other;
80
81 let slf = Self {
82 path: CString::new(path.into_os_string().into_vec())
83 .expect("encountered path with NUL bytes")
84 .into_raw(),
85 debug_syms,
86 ..Default::default()
87 };
88 ManuallyDrop::new(slf)
89 }
90
91 unsafe fn free(self) {
92 let Self {
93 type_size: _,
94 path,
95 debug_syms,
96 reserved: _,
97 } = self;
98
99 let _elf = Elf {
100 path: PathBuf::from(OsString::from_vec(
101 unsafe { CString::from_raw(path as *mut _) }.into_bytes(),
102 )),
103 debug_syms,
104 _non_exhaustive: (),
105 };
106 }
107}
108
109impl From<blaze_inspect_elf_src> for Elf {
110 fn from(other: blaze_inspect_elf_src) -> Self {
111 let blaze_inspect_elf_src {
112 type_size: _,
113 path,
114 debug_syms,
115 reserved: _,
116 } = other;
117
118 Self {
119 path: unsafe { from_cstr(path) },
120 debug_syms,
121 _non_exhaustive: (),
122 }
123 }
124}
125
126
127#[repr(transparent)]
129#[derive(Copy, Clone, Debug, PartialEq)]
130pub struct blaze_sym_type(u8);
131
132impl blaze_sym_type {
133 pub const UNDEF: blaze_sym_type = blaze_sym_type(0);
139 pub const FUNC: blaze_sym_type = blaze_sym_type(1);
141 pub const VAR: blaze_sym_type = blaze_sym_type(2);
143}
144
145impl From<SymType> for blaze_sym_type {
146 fn from(other: SymType) -> Self {
147 match other {
148 SymType::Undefined => blaze_sym_type::UNDEF,
149 SymType::Function => blaze_sym_type::FUNC,
150 SymType::Variable => blaze_sym_type::VAR,
151 _ => unreachable!(),
152 }
153 }
154}
155
156
157#[repr(C)]
159#[derive(Debug)]
160pub struct blaze_sym_info {
161 pub name: *const c_char,
163 pub addr: Addr,
165 pub size: isize,
172 pub file_offset: u64,
174 pub module: *const c_char,
176 pub sym_type: blaze_sym_type,
178 pub reserved: [u8; 23],
180}
181
182
183fn convert_syms_list_to_c(syms_list: Vec<Vec<SymInfo>>) -> *const *const blaze_sym_info {
186 let sym_cnt = syms_list.iter().map(|syms| syms.len() + 1).sum::<usize>();
187 let str_buf_sz = syms_list.c_str_size();
188
189 let array_sz = (mem::size_of::<*const u64>() * syms_list.len()).div_ceil(mem::size_of::<u64>())
190 * mem::size_of::<u64>();
191 let sym_buf_sz = mem::size_of::<blaze_sym_info>() * sym_cnt;
192 let buf_size = mem::size_of::<u64>() + array_sz + sym_buf_sz + str_buf_sz;
193 let buf = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
194 if buf.is_null() {
195 return ptr::null()
196 }
197
198 unsafe { *buf.cast::<u64>() = buf_size as u64 };
199
200 let raw_buf = unsafe { buf.add(mem::size_of::<u64>()) };
201 let mut syms_ptr = raw_buf as *mut *mut blaze_sym_info;
202 let mut sym_ptr = unsafe { raw_buf.add(array_sz) } as *mut blaze_sym_info;
203 let mut str_ptr = unsafe { raw_buf.add(array_sz + sym_buf_sz) } as *mut c_char;
204
205 for syms in syms_list {
206 unsafe { *syms_ptr = sym_ptr };
207 for SymInfo {
208 name,
209 addr,
210 size,
211 sym_type,
212 file_offset,
213 module,
214 _non_exhaustive: (),
215 } in syms
216 {
217 let name_ptr = str_ptr.cast();
218 unsafe { ptr::copy_nonoverlapping(name.as_ptr().cast(), str_ptr, name.len()) };
219 str_ptr = unsafe { str_ptr.add(name.len()) };
220 unsafe { *str_ptr = 0 };
221 str_ptr = unsafe { str_ptr.add(1) };
222 let module = if let Some(fname) = module.as_ref() {
223 let fname = AsRef::<OsStr>::as_ref(fname.deref()).as_bytes();
224 let obj_fname_ptr = str_ptr;
225 unsafe { ptr::copy_nonoverlapping(fname.as_ptr().cast(), str_ptr, fname.len()) };
226 str_ptr = unsafe { str_ptr.add(fname.len()) };
227 unsafe { *str_ptr = 0 };
228 str_ptr = unsafe { str_ptr.add(1) };
229 obj_fname_ptr
230 } else {
231 ptr::null()
232 };
233
234 unsafe {
235 (*sym_ptr) = blaze_sym_info {
236 name: name_ptr,
237 addr,
238 size: size
239 .map(|size| isize::try_from(size).unwrap_or(isize::MAX))
240 .unwrap_or(-1),
241 sym_type: sym_type.into(),
242 file_offset: file_offset.unwrap_or(0),
243 module,
244 reserved: [0; 23],
245 }
246 };
247 sym_ptr = unsafe { sym_ptr.add(1) };
248 }
249 unsafe {
250 (*sym_ptr) = blaze_sym_info {
251 name: ptr::null(),
252 addr: 0,
253 size: 0,
254 sym_type: blaze_sym_type::UNDEF,
255 file_offset: 0,
256 module: ptr::null(),
257 reserved: [0; 23],
258 }
259 };
260 sym_ptr = unsafe { sym_ptr.add(1) };
261
262 syms_ptr = unsafe { syms_ptr.add(1) };
263 }
264
265 raw_buf as *const *const blaze_sym_info
266}
267
268
269#[no_mangle]
285pub unsafe extern "C" fn blaze_inspect_syms_elf(
286 inspector: *const blaze_inspector,
287 src: *const blaze_inspect_elf_src,
288 names: *const *const c_char,
289 name_cnt: usize,
290) -> *const *const blaze_sym_info {
291 if !input_zeroed!(src, blaze_inspect_elf_src) {
292 let () = set_last_err(blaze_err::INVALID_INPUT);
293 return ptr::null()
294 }
295 let src = input_sanitize!(src, blaze_inspect_elf_src);
296
297 let inspector = unsafe { &*inspector };
299 let src = Source::Elf(Elf::from(src));
301 let names = unsafe { slice_from_user_array(names, name_cnt) };
304 let names = names
305 .iter()
306 .map(|&p| {
307 unsafe { CStr::from_ptr(p) }.to_str().unwrap()
309 })
310 .collect::<Vec<_>>();
311 let result = inspector.lookup(&src, &names);
312 match result {
313 Ok(syms) => {
314 let result = convert_syms_list_to_c(syms);
315 if result.is_null() {
316 let () = set_last_err(blaze_err::OUT_OF_MEMORY);
317 } else {
318 let () = set_last_err(blaze_err::OK);
319 }
320 result
321 }
322 Err(err) => {
323 let () = set_last_err(err.kind().into());
324 ptr::null()
325 }
326 }
327}
328
329
330#[no_mangle]
336pub unsafe extern "C" fn blaze_inspect_syms_free(syms: *const *const blaze_sym_info) {
337 if syms.is_null() {
338 return
339 }
340
341 let buf = unsafe { syms.byte_sub(mem::size_of::<u64>()).cast::<u8>().cast_mut() };
342 let size = unsafe { *buf.cast::<u64>() } as usize;
343 unsafe { dealloc(buf, Layout::from_size_align(size, 8).unwrap()) };
344}
345
346
347#[no_mangle]
361pub extern "C" fn blaze_inspector_new() -> *mut blaze_inspector {
362 let inspector = Inspector::new();
363 let inspector_box = Box::new(inspector);
364 let () = set_last_err(blaze_err::OK);
365 Box::into_raw(inspector_box)
366}
367
368
369#[no_mangle]
378pub unsafe extern "C" fn blaze_inspector_free(inspector: *mut blaze_inspector) {
379 if !inspector.is_null() {
380 drop(unsafe { Box::from_raw(inspector) });
383 }
384}
385
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 use std::mem::MaybeUninit;
392 use std::path::Path;
393 use std::ptr::addr_of;
394 use std::slice;
395
396 use test_log::test;
397 use test_tag::tag;
398
399 use crate::blaze_err_last;
400
401
402 #[tag(miri)]
404 #[test]
405 #[cfg(target_pointer_width = "64")]
406 fn type_sizes() {
407 assert_eq!(mem::size_of::<blaze_inspect_elf_src>(), 40);
408 assert_eq!(mem::size_of::<blaze_sym_info>(), 64);
409 }
410
411 #[tag(miri)]
413 #[test]
414 fn debug_repr() {
415 let elf = blaze_inspect_elf_src {
416 type_size: 24,
417 path: ptr::null(),
418 debug_syms: true,
419 reserved: [0; 23],
420 };
421 assert_eq!(
422 format!("{elf:?}"),
423 "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] }"
424 );
425
426 let info = blaze_sym_info {
427 name: ptr::null(),
428 addr: 42,
429 size: 1337,
430 file_offset: 31,
431 module: ptr::null(),
432 sym_type: blaze_sym_type::VAR,
433 reserved: [0; 23],
434 };
435 assert_eq!(
436 format!("{info:?}"),
437 "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] }"
438 );
439 }
440
441 #[tag(miri)]
444 #[test]
445 fn elf_src_validity() {
446 #[repr(C)]
447 struct elf_src_with_ext {
448 type_size: usize,
449 _path: *const c_char,
450 debug_syms: bool,
451 reserved: [u8; 23],
452 foobar: bool,
453 reserved2: [u8; 7],
454 }
455
456 assert!(mem::size_of::<blaze_inspect_elf_src>() < mem::size_of::<elf_src_with_ext>());
457
458 let mut src = MaybeUninit::<elf_src_with_ext>::uninit();
459 let () = unsafe {
460 ptr::write_bytes(
461 src.as_mut_ptr().cast::<u8>(),
462 0,
463 mem::size_of::<elf_src_with_ext>(),
464 )
465 };
466
467 let mut src = unsafe { src.assume_init() };
468 src.type_size = mem::size_of::<elf_src_with_ext>();
469 src.debug_syms = true;
470
471 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
472 assert!(input_zeroed!(src_ptr, blaze_inspect_elf_src));
473
474 src.reserved[0] = 1;
475 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
476 assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
477 src.reserved[0] = 0;
478
479 src.type_size = mem::size_of::<usize>() - 1;
480 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
481 assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
482 src.type_size = mem::size_of::<elf_src_with_ext>();
483
484 src.foobar = true;
485 let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
486 assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
487 }
488
489 #[tag(miri)]
492 #[test]
493 fn syms_list_conversion() {
494 fn test(syms: Vec<Vec<SymInfo>>) {
495 let copy = syms.clone();
496 let ptr = convert_syms_list_to_c(syms);
497 assert!(!ptr.is_null());
498
499 for (i, list) in copy.into_iter().enumerate() {
500 for (j, sym) in list.into_iter().enumerate() {
501 let c_sym = unsafe { &(*(*ptr.add(i)).add(j)) };
502 assert_eq!(
503 unsafe { CStr::from_ptr(c_sym.name) }.to_bytes(),
504 CString::new(sym.name.deref()).unwrap().to_bytes()
505 );
506 assert_eq!(c_sym.addr, sym.addr);
507 assert_eq!(
508 c_sym.size,
509 sym.size
510 .map(|size| isize::try_from(size).unwrap_or(isize::MAX))
511 .unwrap_or(-1)
512 );
513 assert_eq!(c_sym.sym_type, blaze_sym_type::from(sym.sym_type));
514 assert_eq!(Some(c_sym.file_offset), sym.file_offset);
515 assert_eq!(
516 unsafe { CStr::from_ptr(c_sym.module) }.to_bytes(),
517 CString::new(sym.module.as_deref().unwrap().to_os_string().into_vec())
518 .unwrap()
519 .to_bytes()
520 );
521 }
522 }
523
524 let () = unsafe { blaze_inspect_syms_free(ptr) };
525 }
526
527 let syms = vec![];
529 test(syms);
530
531 let syms = vec![vec![SymInfo {
533 name: "sym1".into(),
534 addr: 0xdeadbeef,
535 size: Some(42),
536 sym_type: SymType::Function,
537 file_offset: Some(1337),
538 module: Some(OsStr::new("/tmp/foobar.so").into()),
539 _non_exhaustive: (),
540 }]];
541 test(syms);
542
543 let syms = vec![vec![
545 SymInfo {
546 name: "sym1".into(),
547 addr: 0xdeadbeef,
548 size: Some(42),
549 sym_type: SymType::Function,
550 file_offset: Some(1337),
551 module: Some(OsStr::new("/tmp/foobar.so").into()),
552 _non_exhaustive: (),
553 },
554 SymInfo {
555 name: "sym2".into(),
556 addr: 0xdeadbeef + 52,
557 size: Some(45),
558 sym_type: SymType::Undefined,
559 file_offset: Some(1338),
560 module: Some(OsStr::new("other.so").into()),
561 _non_exhaustive: (),
562 },
563 ]];
564 test(syms);
565
566 let syms = vec![
568 vec![SymInfo {
569 name: "sym1".into(),
570 addr: 0xdeadbeef,
571 size: Some(42),
572 sym_type: SymType::Function,
573 file_offset: Some(1337),
574 module: Some(OsStr::new("/tmp/foobar.so").into()),
575 _non_exhaustive: (),
576 }],
577 vec![SymInfo {
578 name: "sym2".into(),
579 addr: 0xdeadbeef + 52,
580 size: Some(45),
581 sym_type: SymType::Undefined,
582 file_offset: Some(1338),
583 module: Some(OsStr::new("other.so").into()),
584 _non_exhaustive: (),
585 }],
586 ];
587 test(syms);
588
589 let sym = SymInfo {
591 name: "sym1".into(),
592 addr: 0xdeadbeef,
593 size: Some(42),
594 sym_type: SymType::Function,
595 file_offset: Some(1337),
596 module: Some(OsStr::new("/tmp/foobar.so").into()),
597 _non_exhaustive: (),
598 };
599 let syms = vec![(0..200).map(|_| sym.clone()).collect()];
600 test(syms);
601
602 let syms = (0..200).map(|_| vec![sym.clone()]).collect();
604 test(syms);
605 }
606
607 #[tag(miri)]
609 #[test]
610 fn inspector_creation() {
611 let inspector = blaze_inspector_new();
612 let () = unsafe { blaze_inspector_free(inspector) };
613 }
614
615 #[test]
618 fn non_zero_reserved() {
619 let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
620 .join("..")
621 .join("data")
622 .join("test-stable-addrs.bin");
623
624 let mut src = blaze_inspect_elf_src::from(Elf::new(path));
625 src.reserved[1] = 1;
626
627 let factorial = CString::new("factorial").unwrap();
628 let names = [factorial.as_ptr()];
629 let inspector = blaze_inspector_new();
630
631 let result =
632 unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
633 let () = unsafe { ManuallyDrop::into_inner(src).free() };
634 assert_eq!(result, ptr::null());
635 assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
636
637 let () = unsafe { blaze_inspector_free(inspector) };
638 }
639
640 #[test]
643 fn non_present_file() {
644 let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
645 .join("..")
646 .join("data")
647 .join("does-not-exist");
648
649 let src = blaze_inspect_elf_src::from(Elf::new(path));
650 let factorial = CString::new("factorial").unwrap();
651 let names = [factorial.as_ptr()];
652 let inspector = blaze_inspector_new();
653
654 let result =
655 unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
656 let () = unsafe { ManuallyDrop::into_inner(src).free() };
657 assert_eq!(result, ptr::null());
658 assert_eq!(blaze_err_last(), blaze_err::NOT_FOUND);
659
660 let () = unsafe { blaze_inspector_free(inspector) };
661 }
662
663 #[test]
666 fn lookup_dwarf() {
667 let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
668 .join("..")
669 .join("data")
670 .join("test-stable-addrs-stripped-elf-with-dwarf.bin");
671
672 let src = blaze_inspect_elf_src::from(Elf::new(test_dwarf));
673 let factorial = CString::new("factorial").unwrap();
674 let names = [factorial.as_ptr()];
675
676 let inspector = blaze_inspector_new();
677 let result =
678 unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
679 let () = unsafe { ManuallyDrop::into_inner(src).free() };
680 assert!(!result.is_null());
681
682 let sym_infos = unsafe { slice::from_raw_parts(result, names.len()) };
683 let sym_info = unsafe { &*sym_infos[0] };
684 assert_eq!(
685 unsafe { CStr::from_ptr(sym_info.name) },
686 CStr::from_bytes_with_nul(b"factorial\0").unwrap()
687 );
688 assert_eq!(sym_info.addr, 0x2000200);
689
690 let () = unsafe { blaze_inspect_syms_free(result) };
691 let () = unsafe { blaze_inspector_free(inspector) };
692 }
693}