gsym/lib.rs
1//! Reader, writer, and Linux ELF/DWARF converter for [LLVM GSYM], in safe Rust.
2//!
3//! GSYM maps an instruction address to a function name, a source file and line,
4//! and the chain of inlined calls at that address. It stores nothing else, so a
5//! GSYM file is far smaller than the DWARF it was built from, and its sorted
6//! address index can be memory-mapped and queried without parsing the rest of
7//! the file.
8//!
9//! # Quick start
10//!
11//! Build a file in memory and resolve an address:
12//!
13//! ```
14//! use gsym::{AddressRange, FileEntry, Function, Gsym, GsymBuilder, LineEntry};
15//!
16//! let mut builder = GsymBuilder::new().base_address(0x4000);
17//! let source = builder.add_file(FileEntry::new(b"src", b"main.rs"))?;
18//! builder.add_function(Function {
19//! lines: vec![LineEntry::new(0x4010, source, 12)],
20//! ..Function::new(AddressRange::new(0x4010, 0x4020), b"example")
21//! })?;
22//! let bytes = builder.to_bytes()?;
23//!
24//! let gsym = Gsym::parse(&bytes)?;
25//! let hit = gsym.lookup(0x4014)?.expect("address is covered");
26//! assert_eq!(hit.frames()[0].name, b"example");
27//! assert_eq!(hit.frames()[0].line, 12);
28//! assert_eq!(hit.frames()[0].offset, 4);
29//! # Ok::<(), gsym::Error>(())
30//! ```
31//!
32//! Read a file written by this crate or by `llvm-gsymutil`:
33//!
34//! ```no_run
35//! use gsym::Gsym;
36//!
37//! let gsym = Gsym::open("app.gsym")?;
38//! if let Some(hit) = gsym.lookup(0x401120)? {
39//! for frame in hit.frames() {
40//! println!(
41//! "{}{} at {}:{}",
42//! String::from_utf8_lossy(frame.name),
43//! if frame.inlined { " (inlined)" } else { "" },
44//! String::from_utf8_lossy(frame.basename),
45//! frame.line,
46//! );
47//! }
48//! }
49//! # Ok::<(), gsym::Error>(())
50//! ```
51//!
52//! Build one from an executable's own DWARF (`convert` feature, on by default):
53//!
54//! ```no_run
55//! # #[cfg(feature = "convert")]
56//! # fn run() -> gsym::Result<()> {
57//! use gsym::convert::ElfConverter;
58//!
59//! let report = ElfConverter::default().convert_path("./app")?;
60//! std::fs::write("./app.gsym", report.builder.to_bytes()?)?;
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! # Choosing an entry point
66//!
67//! | To … | Use | Notes |
68//! | --- | --- | --- |
69//! | query a file on disk | [`Gsym::open`] | safe; reads one owned snapshot |
70//! | query bytes you already hold | [`Gsym::parse`] | any `AsRef<[u8]>`, borrowed or owned, no copy |
71//! | query a large file without reading it all | `MappedGsym::map` | `mmap` feature; `unsafe` |
72//! | create a file | [`GsymBuilder`] | deterministic output from semantic records |
73//! | import ELF and DWARF | `convert::ElfConverter` | `convert` feature |
74//! | change version or byte order | [`Gsym::transcode`] | or [`DecodedGsym`] to edit in between |
75//! | split into shards | [`DecodedGsym::segments`] | size-bounded, independently readable |
76//! | check an untrusted file | [`Gsym::verify`] | checks the whole file up front |
77//!
78//! All readers are the same type, [`Gsym<D>`](Gsym) over different byte
79//! storage, so the query API does not change with the choice.
80//!
81//! # Usage notes
82//!
83//! * Addresses are unslid. A GSYM file records the virtual addresses of the ELF
84//! image it was built from, so an address captured from a running PIE
85//! executable or shared object must have its load bias subtracted first.
86//! Skipping that step is the usual cause of an empty or wrong result; see
87//! [`docs::symbolication`].
88//!
89//! * Names and paths are bytes. The format stores raw bytes and this crate
90//! preserves them, so results are `&[u8]`. Use `String::from_utf8_lossy` to
91//! display them, or `str::from_utf8` when invalid data should be reported.
92//!
93//! * Version 1 is the default and is what current tooling reads. Version 2
94//! raises v1's 4 GiB offset limits and its 20-byte build-ID limit but needs
95//! LLVM 23 or newer, so it must be requested with [`GsymVersion::V2`].
96//!
97//! * Lookups take `&self` and keep no interior state. There is no global cache
98//! and no lock, so one reader can be shared across threads. The one reusable
99//! buffer, [`LookupScratch`], belongs to the caller.
100//!
101//! # Feature flags
102//!
103//! | Feature | Default | Adds |
104//! | --- | --- | --- |
105//! | `mmap` | yes | `MappedGsym`, a reader backed by a read-only memory map |
106//! | `convert` | yes | the `convert` module: Linux ELF and DWARF import |
107//! | `debuginfod` | yes | debuginfod network lookup during conversion |
108//!
109//! The codec needs neither default feature:
110//!
111//! ```toml
112//! [dependencies]
113//! gsym-rs = { version = "0.1", default-features = false }
114//! ```
115//!
116//! # Errors
117//!
118//! Fallible entry points return [`Result<T>`](Result). Its error type is
119//! [`Error`], which covers malformed input, model violations, exceeded limits,
120//! I/O, and, with `convert`, ELF and DWARF diagnostics. It is
121//! `#[non_exhaustive]`, so a match on it needs a fallback arm.
122//!
123//! Lookup validates only the records it reads, so an untrusted file is worth
124//! one [`Gsym::verify`] at load time.
125//!
126//! # Guides
127//!
128//! - [`docs::symbolication`]: unslid addresses, reading frames, storage choices,
129//! performance, threading.
130//! - [`docs::cookbook`]: worked examples for every part of the API.
131#![cfg_attr(
132 feature = "convert",
133 doc = " - [`docs::conversion`]: building GSYM from Linux ELF files and their DWARF."
134)]
135//! - [`docs::format`]: the on-disk format, version 1 and version 2.
136//!
137//! [LLVM GSYM]: https://llvm.org/doxygen/namespacellvm_1_1gsym.html
138
139#![deny(unsafe_code)]
140#![warn(missing_docs)]
141#![warn(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
142#![cfg_attr(docsrs, feature(doc_cfg))]
143
144pub mod docs;
145
146mod builder;
147mod endian;
148mod error;
149mod format;
150mod model;
151mod normalize;
152mod reader;
153mod transform;
154mod validation;
155mod version;
156mod writer;
157
158#[cfg(feature = "convert")]
159#[cfg_attr(docsrs, doc(cfg(feature = "convert")))]
160/// Linux ELF and DWARF conversion support.
161pub mod convert;
162#[cfg(feature = "mmap")]
163#[expect(
164 unsafe_code,
165 reason = "memory mapping a file is inherently unsafe and is confined to this module"
166)]
167mod mapped;
168
169pub use builder::{BuilderOptions, FunctionSetPolicy, GsymBuilder};
170pub use endian::Endian;
171#[cfg(feature = "convert")]
172#[cfg_attr(docsrs, doc(cfg(feature = "convert")))]
173pub use error::{CompanionMismatch, ElfInputKind, ParserError};
174pub use error::{Error, Result};
175pub use model::{
176 AddressRange, CallSite, CallSiteFlags, FileEntry, FileIndex, Function, InlineNode, LineEntry,
177 Lookup, LookupFrame,
178};
179pub use reader::{
180 FrameLookupOptions, FunctionRef, Functions, Gsym, Header, LookupOptions, LookupScratch,
181 VerifyReport,
182};
183pub use transform::{DecodedGsym, GsymSegment, TranscodeOptions};
184pub use version::GsymVersion;
185pub use writer::WriterOptions;
186
187#[cfg(feature = "mmap")]
188#[cfg_attr(docsrs, doc(cfg(feature = "mmap")))]
189pub use mapped::{MappedBytes, MappedGsym};
190
191/// Compiles the README's examples as doctests without rendering it twice.
192#[cfg(doctest)]
193#[doc = include_str!("../README.md")]
194pub struct ReadmeDoctests;