Skip to main content

gsym/
error.rs

1use thiserror::Error;
2
3#[cfg(feature = "convert")]
4/// Opaque source error from an ELF or DWARF parser.
5///
6/// Use [`std::error::Error::source`] to inspect the underlying diagnostic.
7#[derive(Error)]
8#[error("{0}")]
9pub struct ParserError(#[source] ParserErrorKind);
10
11#[cfg(feature = "convert")]
12impl std::fmt::Debug for ParserError {
13    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        write!(formatter, "ParserError({})", self.0)
15    }
16}
17
18#[cfg(feature = "convert")]
19#[derive(Error)]
20enum ParserErrorKind {
21    #[error("{0}")]
22    Object(#[source] object::Error),
23    #[error("{0}")]
24    Dwarf(#[source] gimli::Error),
25}
26
27#[cfg(feature = "convert")]
28impl std::fmt::Debug for ParserErrorKind {
29    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(formatter, "ParserSource({self})")
31    }
32}
33
34#[cfg(feature = "convert")]
35impl ParserError {
36    pub(crate) const fn object(source: object::Error) -> Self {
37        Self(ParserErrorKind::Object(source))
38    }
39
40    pub(crate) const fn dwarf(source: gimli::Error) -> Self {
41        Self(ParserErrorKind::Dwarf(source))
42    }
43}
44
45#[cfg(feature = "convert")]
46/// Identifies an ELF input in conversion diagnostics.
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48#[non_exhaustive]
49pub enum ElfInputKind {
50    /// Linked executable or shared-object image.
51    Image,
52    /// Main DWARF-bearing debug file.
53    Debug,
54    /// Symbol-table input.
55    Symbols,
56    /// Supplementary DWARF object referenced by the main debug file.
57    Supplementary,
58    /// Packaged split-DWARF file.
59    Dwp,
60    /// Individual split-DWARF object.
61    Dwo,
62    /// Mini debug ELF extracted from `.gnu_debugdata`.
63    EmbeddedDebugData,
64}
65
66#[cfg(feature = "convert")]
67impl std::fmt::Display for ElfInputKind {
68    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        formatter.write_str(match self {
70            Self::Image => "ELF image",
71            Self::Debug => "ELF debug file",
72            Self::Symbols => "ELF symbol file",
73            Self::Supplementary => "supplementary ELF debug file",
74            Self::Dwp => "DWP file",
75            Self::Dwo => "DWO file",
76            Self::EmbeddedDebugData => ".gnu_debugdata ELF",
77        })
78    }
79}
80
81#[cfg(feature = "convert")]
82/// Property by which a companion ELF disagrees with its linked image.
83#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84#[non_exhaustive]
85pub enum CompanionMismatch {
86    /// Machine architecture, address size, or byte order differs.
87    Architecture,
88    /// GNU build ID differs.
89    BuildId,
90}
91
92#[cfg(feature = "convert")]
93impl std::fmt::Display for CompanionMismatch {
94    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        formatter.write_str(match self {
96            Self::Architecture => "architecture",
97            Self::BuildId => "build ID",
98        })
99    }
100}
101
102/// Errors produced while parsing, building, or writing GSYM data.
103///
104/// Variants fall into a few groups. Format errors report malformed input
105/// ([`InvalidMagic`](Self::InvalidMagic),
106/// [`UnexpectedEof`](Self::UnexpectedEof),
107/// [`InvalidFormat`](Self::InvalidFormat) and friends). Model errors report an
108/// invalid value handed to the builder or writer
109/// ([`InvalidModel`](Self::InvalidModel),
110/// [`V1LimitExceeded`](Self::V1LimitExceeded)). The rest cover I/O and, with
111/// the `convert` feature, ELF and DWARF diagnostics.
112///
113/// Matching on it needs a fallback arm.
114///
115/// ```
116/// use gsym::{Error, Gsym};
117///
118/// match Gsym::parse(&[0_u8; 48][..]) {
119///     Ok(_) => unreachable!("not a GSYM file"),
120///     // "Not GSYM at all" is often worth treating as a normal answer.
121///     Err(Error::InvalidMagic(_)) => {}
122///     Err(other) => return Err(other),
123/// }
124/// # Ok::<(), gsym::Error>(())
125/// ```
126///
127/// `Display` renders the full context, so `{error}` is enough for a log line.
128/// Wrapped causes are available through
129/// [`std::error::Error::source`](std::error::Error::source).
130#[derive(Debug, Error)]
131#[non_exhaustive]
132pub enum Error {
133    /// A read required bytes beyond the end of the input.
134    #[error("unexpected end of input at offset {offset}: need {needed} bytes, have {remaining}")]
135    UnexpectedEof {
136        /// Byte offset at which the read began.
137        offset: usize,
138        /// Number of bytes requested.
139        needed: usize,
140        /// Number of bytes still available.
141        remaining: usize,
142    },
143
144    /// Integer arithmetic overflowed while computing the named value.
145    #[error("integer overflow while computing {0}")]
146    Overflow(&'static str),
147
148    /// A numeric field exceeded its format-defined maximum.
149    #[error("invalid {field} value {value}; maximum is {max}")]
150    OutOfRange {
151        /// Name of the invalid field.
152        field: &'static str,
153        /// Observed value.
154        value: u64,
155        /// Inclusive maximum accepted value.
156        max: u64,
157    },
158
159    /// A requested address-table function index does not exist.
160    #[error("function index {index} is out of bounds for {count} functions")]
161    FunctionIndexOutOfBounds {
162        /// Requested zero-based index.
163        index: usize,
164        /// Number of indexed functions.
165        count: usize,
166    },
167
168    /// The input does not begin with the GSYM magic in either byte order.
169    #[error("invalid GSYM magic 0x{0:08x}")]
170    InvalidMagic(u32),
171
172    /// The input uses a GSYM version this crate does not implement.
173    #[error("unsupported GSYM version {0}")]
174    UnsupportedVersion(u16),
175
176    /// The address-offset width is illegal for the selected format version.
177    #[error("invalid address offset size {size} for GSYM v{version}")]
178    InvalidAddressOffsetSize {
179        /// GSYM version whose width rules apply.
180        version: u16,
181        /// Encoded width in bytes.
182        size: u8,
183    },
184
185    /// GSYM v2 requested an unsupported string-table encoding.
186    #[error("unsupported string table encoding {0}")]
187    UnsupportedStringTableEncoding(u8),
188
189    /// A GSYM v1 build identifier exceeds its fixed 20-byte field.
190    #[error("invalid UUID size {0}; GSYM v1 supports at most 20 bytes")]
191    InvalidUuidSize(usize),
192
193    /// A requested GSYM v1 build identifier exceeds its fixed 20-byte field.
194    #[error("GSYM v1 build identifier is {size} bytes; maximum is 20 bytes")]
195    V1BuildIdTooLong {
196        /// Requested build-identifier size.
197        size: usize,
198    },
199
200    /// A required GSYM v2 global-data section is absent.
201    #[error("missing required GSYM section type {0}")]
202    MissingSection(u32),
203
204    /// A GSYM v2 global-data section type appears more than once.
205    #[error("duplicate GSYM section type {0}")]
206    DuplicateSection(u32),
207
208    /// A required GSYM v2 section has an empty byte range.
209    #[error("GSYM section type {section_type} has zero size")]
210    ZeroSizedSection {
211        /// Numeric global-data section type.
212        section_type: u32,
213    },
214
215    /// A GSYM v2 section extends outside the input.
216    #[error(
217        "GSYM section type {section_type} is outside the input: offset={offset}, size={size}, input={input_len}"
218    )]
219    SectionOutOfBounds {
220        /// Numeric global-data section type.
221        section_type: u32,
222        /// Absolute file offset.
223        offset: u64,
224        /// Declared section size.
225        size: u64,
226        /// Actual input length.
227        input_len: usize,
228    },
229
230    /// A referenced absolute file offset is outside the input.
231    #[error("invalid offset {offset} for input of {input_len} bytes")]
232    InvalidOffset {
233        /// Referenced file offset.
234        offset: u64,
235        /// Actual input length.
236        input_len: usize,
237    },
238
239    /// A requested binary alignment is zero or unsupported.
240    #[error("invalid alignment {0}")]
241    InvalidAlignment(usize),
242
243    /// An unsigned LEB128 value is truncated, overlong, or too wide.
244    #[error("malformed unsigned LEB128 at offset {offset}: {reason}")]
245    MalformedUleb {
246        /// Offset of the first encoded byte.
247        offset: usize,
248        /// Static validation failure description.
249        reason: &'static str,
250    },
251
252    /// A signed LEB128 value is truncated, overlong, or too wide.
253    #[error("malformed signed LEB128 at offset {offset}: {reason}")]
254    MalformedSleb {
255        /// Offset of the first encoded byte.
256        offset: usize,
257        /// Static validation failure description.
258        reason: &'static str,
259    },
260
261    /// Full decoding encountered a `FunctionInfo` record type it cannot preserve.
262    #[error("unsupported FunctionInfo type {0}")]
263    UnsupportedInfoType(u32),
264
265    /// A function or inline record references string-table offset zero as its name.
266    #[error("FunctionInfo name offset must not be zero")]
267    ZeroNameOffset,
268
269    /// The input violates a GSYM format rule.
270    #[error("invalid GSYM data: {0}")]
271    InvalidFormat(&'static str),
272
273    /// A value handed to the builder or writer is not valid.
274    #[error("invalid GSYM model: {0}")]
275    InvalidModel(&'static str),
276
277    /// A count, size, or nesting depth exceeds an implementation or wire limit.
278    #[error("{context} value {value} exceeds the supported limit of {limit}")]
279    Limit {
280        /// Value being bounded.
281        context: &'static str,
282        /// Observed value.
283        value: u64,
284        /// Inclusive supported limit.
285        limit: u64,
286    },
287
288    /// Contextual malformed-data diagnostic requiring a dynamic explanation.
289    #[error("malformed {context}: {detail}")]
290    Malformed {
291        /// Data structure or input being parsed.
292        context: &'static str,
293        /// Detailed validation failure.
294        detail: Box<str>,
295    },
296
297    #[cfg(feature = "convert")]
298    /// An ELF input could not be parsed.
299    #[error("failed to parse {input}: {source}")]
300    ElfParse {
301        /// Role of the rejected ELF input.
302        input: ElfInputKind,
303        #[source]
304        /// Underlying parser error.
305        source: ParserError,
306    },
307
308    #[cfg(feature = "convert")]
309    /// Gimli rejected malformed DWARF data.
310    #[error("malformed DWARF: {source}")]
311    Dwarf {
312        #[source]
313        /// Underlying DWARF parser error.
314        source: ParserError,
315    },
316
317    #[cfg(feature = "convert")]
318    /// A conversion input is a supported object format other than ELF.
319    #[error("{input} is not an ELF file")]
320    NotElf {
321        /// Role of the non-ELF input.
322        input: ElfInputKind,
323    },
324
325    #[cfg(feature = "convert")]
326    /// A separate debug, symbol, or package file does not match the image.
327    #[error("{input} {mismatch} does not match the linked image")]
328    CompanionMismatch {
329        /// Role of the mismatching companion.
330        input: ElfInputKind,
331        /// Identity property that differs.
332        mismatch: CompanionMismatch,
333    },
334
335    /// A filesystem operation failed at a known path.
336    #[error("failed to {operation} {}", path.display())]
337    IoAtPath {
338        /// Human-readable operation in progress.
339        operation: &'static str,
340        /// Filesystem path involved in the operation.
341        path: std::path::PathBuf,
342        #[source]
343        /// Underlying I/O error.
344        source: std::io::Error,
345    },
346
347    /// A 64-bit semantic model cannot be narrowed into GSYM v1 fields.
348    #[error("GSYM v1 limit exceeded for {field}: {value}; write version 2 explicitly")]
349    V1LimitExceeded {
350        /// Field that cannot be represented.
351        field: &'static str,
352        /// Value requiring more than 32 bits.
353        value: u64,
354    },
355
356    /// An unscoped I/O operation failed.
357    #[error(transparent)]
358    Io(#[from] std::io::Error),
359}
360
361/// Crate-wide result type using [`enum@Error`].
362pub type Result<T> = std::result::Result<T, Error>;
363
364impl Error {
365    pub(crate) fn malformed(context: &'static str, detail: impl Into<Box<str>>) -> Self {
366        Self::Malformed {
367            context,
368            detail: detail.into(),
369        }
370    }
371}
372
373#[cfg(feature = "convert")]
374impl From<gimli::Error> for Error {
375    fn from(error: gimli::Error) -> Self {
376        Self::Dwarf {
377            source: ParserError::dwarf(error),
378        }
379    }
380}