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}