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