blazesym/symbolize/
mod.rs

1//! Functionality for symbolizing addresses.
2//!
3//! This module contains functionality for symbolizing addresses, i.e., finding
4//! symbol names and other information based on "raw" addresses.
5//!
6//! For example, here we symbolize the address of `libc`'s `fopen` and `fseek`
7//! functions, given their addresses in the current process:
8//! ```no_run
9//! use blazesym::symbolize::source::Process;
10//! use blazesym::symbolize::source::Source;
11//! use blazesym::symbolize::Input;
12//! use blazesym::symbolize::Symbolizer;
13//! use blazesym::Addr;
14//! use blazesym::Pid;
15//!
16//! let addrs = [libc::fopen as Addr, libc::fseek as Addr];
17//!
18//! // Symbolize the addresses for the current process, as that's what they
19//! // belong to. The library also supports other symbolization sources, such as
20//! // arbitrary ELF files.
21//! let src = Source::Process(Process::new(Pid::Slf));
22//! let symbolizer = Symbolizer::new();
23//! let syms = symbolizer.symbolize(&src, Input::AbsAddr(&addrs)).unwrap();
24//!
25//! assert_eq!(syms.len(), 2);
26//!
27//! let fopen = syms[0].as_sym().unwrap();
28//! assert_eq!(fopen.name, "fopen");
29//!
30//! let fseek = syms[1].as_sym().unwrap();
31//! assert_eq!(fseek.name, "fseek");
32//! ```
33//!
34//! The example is contrived, of course, because we already know the names
35//! corresponding to the addresses, but it gets the basic workings across. Also,
36//! the library not only reports the name but additional metadata such as the
37//! symbol's start address and size, and even inlined callees if asked for and
38//! available. See the [`Sym`] type for details.
39//!
40//! In more realistic setting you can envision a backtrace being captured and
41//! symbolized instead. Refer to the runnable
42//! [`backtrace`](https://github.com/libbpf/blazesym/blob/main/examples/backtrace.rs)
43//! example.
44//!
45//! # Advanced use cases
46//! In many cases symbolization is straight forward: the user provides a
47//! symbolization source -- typically a file of a certain data format -- and the
48//! library knows how to parse it and look up a symbol.
49//!
50//! ### Processes
51//! However, in the case of process symbolization as briefly shown above, a
52//! process is really a form of container. That is to say, it contains a set of
53//! entities (e.g., loaded shared objects, otherwise mapped files etc.) that
54//! addresses can fall into and that each may require different ways of
55//! symbolization. **blazesym** comes with a default way of dealing with the
56//! entities inside such a container, where it honors embedded symbols and debug
57//! information (implicitly used in the above example), but advanced users may
58//! desire more flexibility. For example, envision a case where, instead of
59//! using embedded symbols in an executable, all binaries are stripped and
60//! symbol information is co-located somewhere on the file system. Said symbol
61//! data could also be not in the ELF or DWARF formats, but in the Gsym, which
62//! is optimized for fast lookup and also typically requires much less disk
63//! space.
64//!
65//! In such a setup, a user can install a custom process "dispatcher". This
66//! dispatcher is a callback function that **blazesym** invokes and that it
67//! provides certain information about the "member" that an address falls into
68//! to (in the form of a [`ProcessMemberInfo`] object). It is then the
69//! dispatcher's responsibility to use this information to instantiate and
70//! return a "resolver" that the library will use as part of address
71//! symbolization for addresses mapping to a single entity in inside the process
72//! (e.g., a shared object).
73//!
74//! **blazesym** provides a set of resolvers, that can act as building blocks
75//! for implementing a custom dispatch function. These resolver are all
76//! available in the [`helper`][crate::helper] module.
77//!
78//! A complete example using a custom dispatch function to symbolize addresses
79//! in a process after fetching their debug symbols via a
80//! [`debuginfod`](https://sourceware.org/elfutils/Debuginfod.html) client is
81//! available in the
82//! [`sym-debuginfod`](https://github.com/libbpf/blazesym/blob/main/examples/sym-debuginfod)
83//! example.
84//!
85//! ### APKs
86//! APKs are another container format (common on Android systems) that can be
87//! customized with a dispatcher. Installation of a custom dispatcher works
88//! similar to the process symbolization case, the only difference is that
89//! different data is provided to the dispatch function (refer to
90//! [`ApkMemberInfo`]). Please refer to the
91//! [`gsym-in-apk`](https://github.com/libbpf/blazesym/blob/main/examples/gsym-in-apk)
92//! example, which illustrates the basic workflow.
93
94pub mod cache;
95pub mod source;
96mod symbolizer;
97
98use std::borrow::Cow;
99use std::ffi::OsStr;
100use std::fmt::Debug;
101use std::fmt::Display;
102use std::fmt::Formatter;
103use std::fmt::Result as FmtResult;
104use std::path::Path;
105use std::str;
106
107cfg_apk! {
108    pub use symbolizer::ApkDispatch;
109    pub use symbolizer::ApkMemberInfo;
110}
111pub use symbolizer::Builder;
112pub use symbolizer::ProcessDispatch;
113pub use symbolizer::ProcessMemberInfo;
114pub use symbolizer::Symbolizer;
115
116pub(crate) use symbolizer::symbolize_with_resolver;
117pub(crate) use symbolizer::Resolver;
118
119// Strictly speaking these types are applicable to the entire crate, but right
120// now they are only used as part of the symbolization APIs, so we re-export
121// them through this module only.
122pub use crate::maps::EntryPath as ProcessMemberPath;
123pub use crate::maps::PathName as ProcessMemberType;
124
125use crate::Addr;
126use crate::Result;
127
128
129/// Options determining what data about a symbol to look up.
130#[derive(Debug)]
131#[non_exhaustive]
132pub enum FindSymOpts {
133    /// Only look up the "basic" symbol data (name, address, size, ...), without
134    /// source code location and inlined function information.
135    Basic,
136    /// Look up symbol data and source code location information.
137    CodeInfo,
138    /// Look up symbol data, source code location information, and inlined
139    /// function information.
140    CodeInfoAndInlined,
141}
142
143impl FindSymOpts {
144    #[inline]
145    pub(crate) fn code_info(&self) -> bool {
146        match self {
147            Self::Basic => false,
148            Self::CodeInfo | Self::CodeInfoAndInlined => true,
149        }
150    }
151
152    #[inline]
153    pub(crate) fn inlined_fns(&self) -> bool {
154        match self {
155            Self::Basic | Self::CodeInfo => false,
156            Self::CodeInfoAndInlined => true,
157        }
158    }
159}
160
161
162/// A enumeration of the different input types the symbolization APIs
163/// support.
164#[derive(Clone, Copy, Debug, PartialEq)]
165pub enum Input<T> {
166    /// An absolute address.
167    ///
168    /// A absolute address is an address as a process would see it, for example.
169    /// It may include relocation or address space randomization artifacts.
170    AbsAddr(T),
171    /// A virtual offset.
172    ///
173    /// A virtual offset is an address as it would appear in a binary or debug
174    /// symbol file.
175    VirtOffset(T),
176    /// A file offset.
177    ///
178    /// A file offset is the linear offset of a symbol in a file.
179    FileOffset(T),
180}
181
182impl<T> Input<T> {
183    /// Apply a function to the inner address while preserving the
184    /// active variant.
185    #[inline]
186    pub fn map<F, U>(&self, f: F) -> Input<U>
187    where
188        T: Copy,
189        F: FnOnce(T) -> U,
190    {
191        match self {
192            Self::AbsAddr(x) => Input::AbsAddr(f(*x)),
193            Self::VirtOffset(x) => Input::VirtOffset(f(*x)),
194            Self::FileOffset(x) => Input::FileOffset(f(*x)),
195        }
196    }
197
198    /// Retrieve a reference to the inner payload.
199    #[inline]
200    pub fn as_inner_ref(&self) -> &T {
201        match self {
202            Self::AbsAddr(x) | Self::VirtOffset(x) | Self::FileOffset(x) => x,
203        }
204    }
205
206    /// Extract the inner payload.
207    ///
208    /// ```rust
209    /// # use blazesym::symbolize;
210    /// let addrs = [1, 2, 3, 4];
211    /// let input = symbolize::Input::FileOffset(addrs.as_slice());
212    /// assert_eq!(input.into_inner(), &[1, 2, 3, 4]);
213    /// ```
214    #[inline]
215    pub fn into_inner(self) -> T {
216        match self {
217            Self::AbsAddr(x) | Self::VirtOffset(x) | Self::FileOffset(x) => x,
218        }
219    }
220}
221
222#[cfg(test)]
223impl<T> Input<&[T]>
224where
225    T: Copy,
226{
227    fn try_to_single(&self) -> Option<Input<T>> {
228        match self {
229            Self::AbsAddr([addr]) => Some(Input::AbsAddr(*addr)),
230            Self::VirtOffset([addr]) => Some(Input::VirtOffset(*addr)),
231            Self::FileOffset([offset]) => Some(Input::FileOffset(*offset)),
232            _ => None,
233        }
234    }
235}
236
237
238/// Source code location information for a symbol or inlined function.
239#[derive(Clone, Debug, PartialEq)]
240pub struct CodeInfo<'src> {
241    /// The directory in which the source file resides.
242    pub dir: Option<Cow<'src, Path>>,
243    /// The file that defines the symbol.
244    pub file: Cow<'src, OsStr>,
245    /// The line number of the symbolized instruction in the source
246    /// code.
247    ///
248    /// This is the line number of the instruction of the address being
249    /// symbolized, not the line number that defines the symbol
250    /// (function).
251    pub line: Option<u32>,
252    /// The column number of the symbolized instruction in the source
253    /// code.
254    pub column: Option<u16>,
255    /// The struct is non-exhaustive and open to extension.
256    #[doc(hidden)]
257    pub _non_exhaustive: (),
258}
259
260impl CodeInfo<'_> {
261    /// Helper method to retrieve the path to the represented source file,
262    /// on a best-effort basis. It depends on the symbolization source data
263    /// whether this path is absolute or relative and, if its the latter, what
264    /// directory it is relative to. In general this path is mostly intended for
265    /// displaying purposes.
266    #[inline]
267    pub fn to_path(&self) -> Cow<'_, Path> {
268        self.dir.as_ref().map_or_else(
269            || Cow::Borrowed(Path::new(&self.file)),
270            |dir| Cow::Owned(dir.join(&self.file)),
271        )
272    }
273
274    /// Convert this object into one with all references converted into
275    /// guaranteed owned (i.e., heap allocated) members.
276    pub fn into_owned(self) -> CodeInfo<'static> {
277        let Self {
278            dir,
279            file,
280            line,
281            column,
282            _non_exhaustive: (),
283        } = self;
284
285        CodeInfo {
286            dir: dir.map(|dir| Cow::Owned(dir.into_owned())),
287            file: Cow::Owned(file.into_owned()),
288            line,
289            column,
290            _non_exhaustive: (),
291        }
292    }
293}
294
295
296/// A type representing an inlined function.
297#[derive(Clone, Debug, PartialEq)]
298pub struct InlinedFn<'src> {
299    /// The symbol name of the inlined function.
300    pub name: Cow<'src, str>,
301    /// Source code location information for the call to the function.
302    pub code_info: Option<CodeInfo<'src>>,
303    /// The struct is non-exhaustive and open to extension.
304    #[doc(hidden)]
305    pub _non_exhaustive: (),
306}
307
308impl InlinedFn<'_> {
309    /// Convert this object into one with all references converted into
310    /// guaranteed owned (i.e., heap allocated) members.
311    pub fn into_owned(self) -> InlinedFn<'static> {
312        let Self {
313            name,
314            code_info,
315            _non_exhaustive: (),
316        } = self;
317
318        InlinedFn {
319            name: Cow::Owned(name.into_owned()),
320            code_info: code_info.map(CodeInfo::into_owned),
321            _non_exhaustive: (),
322        }
323    }
324}
325
326
327/// The source code language from which a symbol originates.
328#[derive(Clone, Copy, Default, Debug, PartialEq)]
329#[non_exhaustive]
330pub enum SrcLang {
331    /// The language is unknown.
332    #[default]
333    Unknown,
334    /// The language is C++.
335    Cpp,
336    /// The language is Rust.
337    Rust,
338}
339
340
341/// A type representing a symbol as produced by a [`Resolve`] object.
342#[derive(Debug, PartialEq)]
343pub struct ResolvedSym<'src> {
344    /// The name of the symbol.
345    pub name: &'src str,
346    /// The path to or name of the module containing the symbol.
347    pub module: Option<&'src OsStr>,
348    /// The symbol's normalized address.
349    pub addr: Addr,
350    /// The symbol's size, if available.
351    pub size: Option<usize>,
352    /// The source code language from which the symbol originates.
353    pub lang: SrcLang,
354    /// Source code location information.
355    pub code_info: Option<Box<CodeInfo<'src>>>,
356    /// Inlined function information.
357    pub inlined: Box<[InlinedFn<'src>]>,
358    /// The struct is non-exhaustive and open to extension.
359    // TODO: In the future we may want to make this type exhaustive to
360    //       allow users to construct it easily themselves, in order to
361    //       enable usage of custom "resolvers".
362    #[doc(hidden)]
363    pub _non_exhaustive: (),
364}
365
366
367/// The result of address symbolization by [`Symbolizer`].
368#[derive(Clone, Debug, PartialEq)]
369pub struct Sym<'src> {
370    /// The symbol name that an address belongs to.
371    pub name: Cow<'src, str>,
372    /// The path to or name of the module containing the symbol.
373    ///
374    /// Typically this would be the path to a executable or shared
375    /// object. Depending on the symbol source this member may not be
376    /// present or it could also just be a file name without path or a
377    /// symbolic name such as `[vdso]` representing the vDSO or `[bpf]`
378    /// for symbols in BPF programs. In case of an ELF file contained
379    /// inside an APK, this will be an Android style path of the form
380    /// `<apk>!<elf-in-apk>`. E.g., `/root/test.apk!/lib/libc.so`.
381    pub module: Option<Cow<'src, OsStr>>,
382    /// The address at which the symbol is located (i.e., its "start").
383    ///
384    /// This is the "normalized" address of the symbol, as present in
385    /// the file (and reported by tools such as `readelf(1)`,
386    /// `llvm-gsymutil`, or similar).
387    pub addr: Addr,
388    /// The byte offset of the address that got symbolized from the
389    /// start of the symbol (i.e., from `addr`).
390    ///
391    /// E.g., when symbolizing address 0x1337 of a function that starts at
392    /// 0x1330, the offset will be set to 0x07 (and `addr` will be 0x1330). This
393    /// member is especially useful in contexts when input addresses are not
394    /// already normalized, such as when symbolizing an address in a process
395    /// context (which may have been relocated and/or have layout randomizations
396    /// applied).
397    pub offset: usize,
398    /// The symbol's size, if available.
399    pub size: Option<usize>,
400    /// Source code location information for the symbol.
401    pub code_info: Option<Box<CodeInfo<'src>>>,
402    /// Inlined function information, if requested and available.
403    ///
404    /// Availability depends on both the underlying symbolization source (e.g.,
405    /// ELF does not contain inline information, but DWARF does) as well as
406    /// whether a function was actually inlined at the address in question.
407    ///
408    /// Inlined functions are reported in the order in which their calls are
409    /// nested. For example, if the instruction at the address to symbolize
410    /// falls into a function `f` at an inlined call to `g`, which in turn
411    /// contains an inlined call to `h`, the symbols will be reported in the
412    /// order `f`, `g`, `h`.
413    pub inlined: Box<[InlinedFn<'src>]>,
414    /// The struct is non-exhaustive and open to extension.
415    #[doc(hidden)]
416    pub _non_exhaustive: (),
417}
418
419impl Sym<'_> {
420    /// Convert this object into one with all references converted into
421    /// guaranteed owned (i.e., heap allocated) members.
422    pub fn into_owned(self) -> Sym<'static> {
423        let Self {
424            name,
425            module,
426            addr,
427            offset,
428            size,
429            code_info,
430            inlined,
431            _non_exhaustive,
432        } = self;
433
434        Sym {
435            name: Cow::Owned(name.into_owned()),
436            module: module.map(|module| Cow::Owned(module.into_owned())),
437            addr,
438            offset,
439            size,
440            code_info: code_info.map(|info| Box::new(info.into_owned())),
441            inlined: Vec::from(inlined)
442                .into_iter()
443                .map(InlinedFn::into_owned)
444                .collect::<Box<[_]>>(),
445            _non_exhaustive: (),
446        }
447    }
448}
449
450
451/// The reason why symbolization failed.
452///
453/// The reason is generally only meant as a hint. Reasons reported may change
454/// over time and, hence, should not be relied upon for the correctness of the
455/// application.
456#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
457#[non_exhaustive]
458pub enum Reason {
459    /// The absolute address was not found in the corresponding process' virtual
460    /// memory map.
461    Unmapped,
462    /// The file offset does not map to a valid piece of code/data.
463    InvalidFileOffset,
464    /// The `/proc/<pid>/maps` entry corresponding to the address does not have
465    /// a component (file system path, object, ...) associated with it.
466    MissingComponent,
467    /// The symbolization source has no or no relevant symbols.
468    ///
469    /// This reason could for instance be used if a shared object only
470    /// has dynamic symbols, but appears to be stripped aside from that.
471    MissingSyms,
472    /// The address belonged to an entity that is currently unsupported.
473    Unsupported,
474    /// The address could not be found in the symbolization source.
475    UnknownAddr,
476    /// An error prevented the symbolization of the address from
477    /// succeeding.
478    ///
479    /// This variant is used in batch operations to indicate a failure
480    /// preventing a single address from being symbolized.
481    IgnoredError,
482}
483
484impl Reason {
485    #[doc(hidden)]
486    #[inline]
487    pub fn as_bytes(&self) -> &'static [u8] {
488        match self {
489            Self::Unmapped => b"absolute address not found in virtual memory map of process\0",
490            Self::InvalidFileOffset => b"file offset does not map to a valid piece of code/data\0",
491            Self::MissingComponent => b"proc maps entry has no component\0",
492            Self::MissingSyms => b"symbolization source has no or no relevant symbols\0",
493            Self::Unsupported => b"address belongs to unsupported entity\0",
494            Self::UnknownAddr => b"address not found in symbolization source\0",
495            Self::IgnoredError => b"an error occurred but was ignored in batch mode\0",
496        }
497    }
498}
499
500impl Display for Reason {
501    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
502        let cstr = self.as_bytes();
503        // SAFETY: `as_bytes` always returns a valid string.
504        let s = unsafe { str::from_utf8_unchecked(&cstr[..cstr.len() - 1]) };
505
506        f.write_str(s)
507    }
508}
509
510
511/// An enumeration used as reporting vehicle for address symbolization.
512// We keep this enum as exhaustive because additions to it, should they occur,
513// are expected to be backwards-compatibility breaking.
514#[derive(Clone, Debug, PartialEq)]
515pub enum Symbolized<'src> {
516    /// The input address was symbolized as the provided symbol.
517    Sym(Sym<'src>),
518    /// The input address was not found and could not be symbolized.
519    ///
520    /// The provided reason is a best guess, hinting at what ultimately
521    /// prevented the symbolization from being successful.
522    Unknown(Reason),
523}
524
525impl<'src> Symbolized<'src> {
526    /// Convert the object into a [`Sym`] reference, if the corresponding
527    /// variant is active.
528    #[inline]
529    pub fn as_sym(&self) -> Option<&Sym<'src>> {
530        match self {
531            Self::Sym(sym) => Some(sym),
532            Self::Unknown(..) => None,
533        }
534    }
535
536    /// Convert the object into a [`Sym`] object, if the corresponding variant
537    /// is active.
538    #[inline]
539    pub fn into_sym(self) -> Option<Sym<'src>> {
540        match self {
541            Self::Sym(sym) => Some(sym),
542            Self::Unknown(..) => None,
543        }
544    }
545}
546
547
548/// A trait helping with upcasting into a `dyn Symbolize`.
549// TODO: This trait is currently necessary because our MSRV is <1.86.
550//       Once we upgrade to that, we can make use of "native" upcasting
551//       support on stable.
552#[doc(hidden)]
553pub trait AsSymbolize {
554    fn as_symbolize(&self) -> &dyn Symbolize;
555}
556
557/// The trait for types providing address symbolization services.
558pub trait Symbolize
559where
560    Self: AsSymbolize + Debug,
561{
562    /// Find the symbol corresponding to the given address.
563    fn find_sym(&self, addr: Addr, opts: &FindSymOpts) -> Result<Result<ResolvedSym<'_>, Reason>>;
564}
565
566impl<S> AsSymbolize for S
567where
568    S: Symbolize,
569{
570    fn as_symbolize(&self) -> &dyn Symbolize {
571        self
572    }
573}
574
575
576/// A meta-trait encompassing functionality necessary for plugging into
577/// the container symbolization logic.
578///
579/// Refer to [`Builder::set_apk_dispatcher`] and
580/// [`Builder::set_process_dispatcher`] for additional details.
581pub trait Resolve: Symbolize + TranslateFileOffset {}
582
583impl<R> Resolve for R where R: Symbolize + TranslateFileOffset {}
584
585
586/// A trait representing the ability to convert file offsets into virtual
587/// offsets.
588///
589/// Please refer to the [`Input`] enum for an overview of the various offset
590/// types.
591pub trait TranslateFileOffset
592where
593    Self: Debug,
594{
595    /// Convert the provided file offset into a virtual offset.
596    fn file_offset_to_virt_offset(&self, file_offset: u64) -> Result<Option<Addr>>;
597}
598
599
600#[cfg(test)]
601mod tests {
602    use super::*;
603
604
605    /// Exercise the `Debug` representation of various types.
606    #[test]
607    fn debug_repr() {
608        let lang = SrcLang::default();
609        assert_ne!(format!("{lang:?}"), "");
610
611        let input = Input::FileOffset(0x1337);
612        assert_ne!(format!("{input:?}"), "");
613
614        let code_info = CodeInfo {
615            dir: Some(Cow::Borrowed(Path::new("/tmp/some-dir"))),
616            file: Cow::Borrowed(OsStr::new("test.c")),
617            line: Some(1337),
618            column: None,
619            _non_exhaustive: (),
620        };
621
622        let sym = Sym {
623            name: Cow::Borrowed("test"),
624            module: Some(Cow::Borrowed(OsStr::new("module"))),
625            addr: 1337,
626            offset: 42,
627            size: None,
628            code_info: None,
629            inlined: Box::new([InlinedFn {
630                name: Cow::Borrowed("inlined_test"),
631                code_info: Some(code_info.clone()),
632                _non_exhaustive: (),
633            }]),
634            _non_exhaustive: (),
635        };
636        assert_ne!(format!("{sym:?}"), "");
637
638        let symbolized = Symbolized::Sym(sym);
639        assert_ne!(format!("{symbolized:?}"), "");
640    }
641
642    /// Exercise the `Display` representation of various types.
643    #[test]
644    fn display_repr() {
645        assert_eq!(
646            Reason::MissingSyms.to_string(),
647            "symbolization source has no or no relevant symbols"
648        );
649    }
650
651    /// Test forcing a double check of all `Sym` size changes.
652    #[cfg(target_pointer_width = "64")]
653    #[test]
654    fn sym_size() {
655        assert_eq!(size_of::<Sym>(), 104);
656    }
657
658    /// Check that [`Sym::into_owned`] works as expected.
659    #[test]
660    fn owned_conversion() {
661        let sym = Sym {
662            name: Cow::Borrowed("test"),
663            module: Some(Cow::Borrowed(OsStr::new("module"))),
664            addr: 1337,
665            offset: 42,
666            size: None,
667            code_info: None,
668            inlined: Box::new([InlinedFn {
669                name: Cow::Borrowed("inlined_test"),
670                code_info: Some(CodeInfo {
671                    dir: Some(Cow::Borrowed(Path::new("/tmp/some-dir"))),
672                    file: Cow::Borrowed(OsStr::new("test.c")),
673                    line: Some(1337),
674                    column: None,
675                    _non_exhaustive: (),
676                }),
677                _non_exhaustive: (),
678            }]),
679            _non_exhaustive: (),
680        };
681
682        assert_eq!(sym, sym.clone().into_owned());
683    }
684
685    /// Check that the [`Input::map`] helper works as expected.
686    #[test]
687    fn input_mapping() {
688        fn test<F>(f: F)
689        where
690            F: Fn(usize) -> Input<usize>,
691        {
692            let input = f(0x1337);
693            let input = input.map(|x| 2 * x);
694            assert_eq!(input, f(2 * 0x1337));
695            assert_eq!(input.into_inner(), 2 * 0x1337);
696        }
697
698        for variant in [Input::AbsAddr, Input::VirtOffset, Input::FileOffset] {
699            let () = test(variant);
700        }
701    }
702
703    /// Test the `Symbolized::*_sym()` conversion methods for the `Unknown`
704    /// variant.
705    #[test]
706    fn symbolized_unknown_conversions() {
707        let symbolized = Symbolized::Unknown(Reason::UnknownAddr);
708        assert_eq!(symbolized.as_sym(), None);
709        assert_eq!(symbolized.into_sym(), None);
710    }
711}