blazesym_c/
inspect.rs

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
36/// C ABI compatible version of [`blazesym::inspect::Inspector`].
37pub type blaze_inspector = Inspector;
38
39
40/// An object representing an ELF inspection source.
41///
42/// C ABI compatible version of [`inspect::source::Elf`].
43#[repr(C)]
44#[derive(Debug)]
45pub struct blaze_inspect_elf_src {
46    /// The size of this object's type.
47    ///
48    /// Make sure to initialize it to `sizeof(<type>)`. This member is used to
49    /// ensure compatibility in the presence of member additions.
50    pub type_size: usize,
51    /// The path to the ELF file. This member is always present.
52    pub path: *const c_char,
53    /// Whether or not to consult debug symbols to satisfy the request
54    /// (if present).
55    pub debug_syms: bool,
56    /// Unused member available for future expansion. Must be initialized
57    /// to zero.
58    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/// The type of a symbol.
128#[repr(transparent)]
129#[derive(Copy, Clone, Debug, PartialEq)]
130pub struct blaze_sym_type(u8);
131
132impl blaze_sym_type {
133    /// The symbol type is unspecified or unknown.
134    ///
135    /// In input contexts this variant can be used to encompass all
136    /// other variants (functions and variables), whereas in output
137    /// contexts it means that the type is not known.
138    pub const UNDEF: blaze_sym_type = blaze_sym_type(0);
139    /// The symbol is a function.
140    pub const FUNC: blaze_sym_type = blaze_sym_type(1);
141    /// The symbol is a variable.
142    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/// Information about a looked up symbol.
158#[repr(C)]
159#[derive(Debug)]
160pub struct blaze_sym_info {
161    /// See [`inspect::SymInfo::name`].
162    pub name: *const c_char,
163    /// See [`inspect::SymInfo::addr`].
164    pub addr: Addr,
165    /// See [`inspect::SymInfo::size`].
166    ///
167    /// If the symbol's size is not available, this member will be `-1`.
168    /// Note that some symbol sources may not distinguish between
169    /// "unknown" size and `0`. In that case the size will be reported
170    /// as `0` here as well.
171    pub size: isize,
172    /// See [`inspect::SymInfo::file_offset`].
173    pub file_offset: u64,
174    /// See [`inspect::SymInfo::module`].
175    pub module: *const c_char,
176    /// See [`inspect::SymInfo::sym_type`].
177    pub sym_type: blaze_sym_type,
178    /// Unused member available for future expansion.
179    pub reserved: [u8; 23],
180}
181
182
183/// Convert [`SymInfo`] objects as returned by
184/// [`Symbolizer::find_addrs`] to a C array.
185fn 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/// Lookup symbol information in an ELF file.
270///
271/// On success, returns an array with `name_cnt` elements. Each such element, in
272/// turn, is NULL terminated array comprised of each symbol found. The returned
273/// object should be released using [`blaze_inspect_syms_free`] once it is no
274/// longer needed.
275///
276/// On error, the function returns `NULL` and sets the thread's last error to
277/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
278/// error.
279///
280/// # Safety
281/// - `inspector` needs to point to an initialized [`blaze_inspector`] object
282/// - `src` needs to point to an initialized [`blaze_inspect_syms_elf`] object
283/// - `names` needs to be a valid pointer to `name_cnt` NUL terminated strings
284#[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    // SAFETY: The caller ensures that the pointer is valid.
298    let inspector = unsafe { &*inspector };
299    // SAFETY: The caller ensures that the pointer is valid.
300    let src = Source::Elf(Elf::from(src));
301    // SAFETY: The caller ensures that the pointer is valid and the count
302    //         matches.
303    let names = unsafe { slice_from_user_array(names, name_cnt) };
304    let names = names
305        .iter()
306        .map(|&p| {
307            // SAFETY: The caller ensures that the pointer is valid.
308            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/// Free an array returned by [`blaze_inspect_syms_elf`].
331///
332/// # Safety
333///
334/// The pointer must be returned by [`blaze_inspect_syms_elf`].
335#[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/// Create an instance of a blazesym inspector.
348///
349/// C ABI compatible version of [`blazesym::inspect::Inspector::new()`].
350/// Please refer to its documentation for the default configuration in
351/// use.
352///
353/// On success, the function creates a new [`blaze_inspector`] object
354/// and returns it. The resulting object should be released using
355/// [`blaze_inspector_free`] once it is no longer needed.
356///
357/// On error, the function returns `NULL` and sets the thread's last error to
358/// indicate the problem encountered. Use [`blaze_err_last`] to retrieve this
359/// error.
360#[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/// Free a blazesym inspector.
370///
371/// Release resources associated with a inspector as created by
372/// [`blaze_inspector_new`], for example.
373///
374/// # Safety
375/// The provided inspector should have been created by
376/// [`blaze_inspector_new`].
377#[no_mangle]
378pub unsafe extern "C" fn blaze_inspector_free(inspector: *mut blaze_inspector) {
379    if !inspector.is_null() {
380        // SAFETY: The caller needs to ensure that `inspector` is a
381        //         valid pointer.
382        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    /// Check that various types have expected sizes.
403    #[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    /// Exercise the `Debug` representation of various types.
412    #[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    /// Test that we can correctly validate zeroed "extensions" of a
442    /// struct.
443    #[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    /// Check that we can properly convert a "syms list" into the corresponding
490    /// C representation.
491    #[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        // Test conversion of no symbols.
528        let syms = vec![];
529        test(syms);
530
531        // Test conversion with a single symbol.
532        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        // Test conversion of two symbols in one result.
544        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        // Test conversion of two symbols spread over two results.
567        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        // Test conversion of a `SymInfo` vector with many elements.
590        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        // Test conversion of many `SymInfo` vectors.
603        let syms = (0..200).map(|_| vec![sym.clone()]).collect();
604        test(syms);
605    }
606
607    /// Make sure that we can create and free an inspector instance.
608    #[tag(miri)]
609    #[test]
610    fn inspector_creation() {
611        let inspector = blaze_inspector_new();
612        let () = unsafe { blaze_inspector_free(inspector) };
613    }
614
615    /// Check that `blaze_inspect_syms_elf` fails if the input source
616    /// does not have reserved fields set to zero.
617    #[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    /// Check that we see the expected error being reported when a source file
641    /// does not exist.
642    #[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    /// Make sure that we can lookup a function's address using DWARF
664    /// information.
665    #[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}