Skip to main content

object_lang/
error.rs

1//! Errors: what is wrong with an object model, why a file could not be written, and why
2//! bytes could not be read.
3
4use core::fmt;
5
6use crate::model::{SectionId, SymbolId};
7
8/// What is wrong with one section. Reported inside [`ModelError::Section`].
9///
10/// # Examples
11///
12/// ```
13/// use object_lang::SectionProblem;
14///
15/// assert_eq!(SectionProblem::BadAlignment.to_string(), "alignment is not a power of two");
16/// ```
17#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
18#[non_exhaustive]
19pub enum SectionProblem {
20    /// The name contains a NUL byte, which no format can store.
21    NameContainsNul,
22    /// The alignment is zero or not a power of two.
23    BadAlignment,
24    /// The flags are not allowed for the section's kind (see
25    /// [`SectionKind`](crate::SectionKind)).
26    FlagsDoNotMatchKind,
27    /// [`MERGE`](crate::SectionFlags::MERGE) is set but the entry size is zero.
28    MergeWithoutEntrySize,
29    /// A [`Bss`](crate::SectionKind::Bss) section has bytes, or another kind has a BSS
30    /// size.
31    ContentsDoNotMatchKind,
32    /// The address is not a multiple of the alignment.
33    MisalignedAddress,
34    /// The address plus the size overflows the 64-bit address space.
35    AddressOverflow,
36}
37
38impl fmt::Display for SectionProblem {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str(match self {
41            SectionProblem::NameContainsNul => "name contains a NUL byte",
42            SectionProblem::BadAlignment => "alignment is not a power of two",
43            SectionProblem::FlagsDoNotMatchKind => "flags are not allowed for the section's kind",
44            SectionProblem::MergeWithoutEntrySize => "mergeable section has an entry size of zero",
45            SectionProblem::ContentsDoNotMatchKind => {
46                "contents do not match the kind (bytes in a BSS section, or a BSS size on another kind)"
47            }
48            SectionProblem::MisalignedAddress => "address is not a multiple of the alignment",
49            SectionProblem::AddressOverflow => "address plus size overflows the address space",
50        })
51    }
52}
53
54/// What is wrong with one symbol. Reported inside [`ModelError::Symbol`].
55///
56/// # Examples
57///
58/// ```
59/// use object_lang::SymbolProblem;
60///
61/// assert_eq!(SymbolProblem::LocalUndefined.to_string(), "a local symbol must be defined");
62/// ```
63#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
64#[non_exhaustive]
65pub enum SymbolProblem {
66    /// The name contains a NUL byte.
67    NameContainsNul,
68    /// The symbol is defined in a section the object does not have.
69    UnknownSection,
70    /// The symbol (its value, or value plus size) lies outside its section.
71    OutOfSection,
72    /// A local symbol is undefined or common: nothing outside the object could define it.
73    LocalUndefined,
74    /// A [`File`](crate::SymbolKind::File) symbol is not local and absolute with value
75    /// and size zero.
76    BadFileSymbol,
77    /// A [`Common`](crate::SymbolSection::Common) symbol is not global, or its alignment
78    /// (its value) is not a power of two.
79    BadCommonSymbol,
80    /// A [`Common`](crate::SymbolSection::Common) symbol in an executable: an
81    /// executable's storage is already allocated.
82    CommonInExecutable,
83    /// A non-weak undefined symbol in an executable: nothing is left to define it.
84    UndefinedInExecutable,
85    /// A [`Tls`](crate::SymbolKind::Tls) symbol defined in a section without the
86    /// [`TLS`](crate::SectionFlags::TLS) flag.
87    TlsOutsideTlsSection,
88}
89
90impl fmt::Display for SymbolProblem {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        f.write_str(match self {
93            SymbolProblem::NameContainsNul => "name contains a NUL byte",
94            SymbolProblem::UnknownSection => "defined in a section the object does not have",
95            SymbolProblem::OutOfSection => "lies outside its section",
96            SymbolProblem::LocalUndefined => "a local symbol must be defined",
97            SymbolProblem::BadFileSymbol => {
98                "a file symbol must be local and absolute, with value and size zero"
99            }
100            SymbolProblem::BadCommonSymbol => {
101                "a common symbol must be global, with a power-of-two alignment as its value"
102            }
103            SymbolProblem::CommonInExecutable => "an executable cannot hold a common symbol",
104            SymbolProblem::UndefinedInExecutable => {
105                "an executable cannot hold a non-weak undefined symbol"
106            }
107            SymbolProblem::TlsOutsideTlsSection => {
108                "a thread-local symbol is defined outside a thread-local section"
109            }
110        })
111    }
112}
113
114/// What is wrong with one relocation. Reported inside [`ModelError::Relocation`].
115///
116/// # Examples
117///
118/// ```
119/// use object_lang::RelocationProblem;
120///
121/// assert_eq!(RelocationProblem::OutOfSection.to_string(), "patches bytes outside its section");
122/// ```
123#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
124#[non_exhaustive]
125pub enum RelocationProblem {
126    /// The relocation kind belongs to another architecture.
127    WrongArchitecture,
128    /// The patched bytes run past the end of the section.
129    OutOfSection,
130    /// An instruction relocation whose offset is not a multiple of 4.
131    Misaligned,
132    /// The target symbol or section does not exist.
133    UnknownTarget,
134    /// The section is [`Bss`](crate::SectionKind::Bss): there are no bytes to patch.
135    InUninitializedSection,
136    /// The object is an executable, which is fully linked and carries no relocations.
137    InExecutable,
138}
139
140impl fmt::Display for RelocationProblem {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        f.write_str(match self {
143            RelocationProblem::WrongArchitecture => "kind belongs to another architecture",
144            RelocationProblem::OutOfSection => "patches bytes outside its section",
145            RelocationProblem::Misaligned => "instruction relocation is not 4-byte aligned",
146            RelocationProblem::UnknownTarget => "target symbol or section does not exist",
147            RelocationProblem::InUninitializedSection => {
148                "patches a BSS section, which has no bytes"
149            }
150            RelocationProblem::InExecutable => "executables carry no relocations",
151        })
152    }
153}
154
155/// A rule of the object model that an [`Object`](crate::Object) breaks, from
156/// [`Object::validate`](crate::Object::validate), from the builders on
157/// [`Section`](crate::Section), or from a reader whose input decodes to an invalid model.
158///
159/// The enum is `#[non_exhaustive]`: match it with a wildcard arm.
160///
161/// # Examples
162///
163/// ```
164/// use object_lang::{Architecture, ModelError, Object, Symbol, SymbolProblem, Binding};
165///
166/// let mut obj = Object::relocatable(Architecture::X86_64);
167/// let sym = obj.add_symbol(Symbol::undefined("x").with_binding(Binding::Local));
168/// assert_eq!(
169///     obj.validate(),
170///     Err(ModelError::Symbol { symbol: sym, problem: SymbolProblem::LocalUndefined }),
171/// );
172/// ```
173#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
174#[non_exhaustive]
175pub enum ModelError {
176    /// A section breaks a rule.
177    Section {
178        /// The section.
179        section: SectionId,
180        /// The rule it breaks.
181        problem: SectionProblem,
182    },
183    /// A symbol breaks a rule.
184    Symbol {
185        /// The symbol.
186        symbol: SymbolId,
187        /// The rule it breaks.
188        problem: SymbolProblem,
189    },
190    /// A relocation breaks a rule.
191    Relocation {
192        /// The section the relocation patches.
193        section: SectionId,
194        /// The relocation's position in that section's
195        /// [`relocations`](crate::Section::relocations).
196        index: usize,
197        /// The rule it breaks.
198        problem: RelocationProblem,
199    },
200    /// Two loadable sections of an executable overlap in memory.
201    SectionsOverlap {
202        /// The section at the lower address.
203        first: SectionId,
204        /// The section that starts inside it.
205        second: SectionId,
206    },
207    /// An executable's entry point is not inside an executable section.
208    EntryNotExecutable {
209        /// The entry address.
210        entry: u64,
211    },
212    /// A section id that the object does not have, passed to
213    /// [`Object::add_relocation`](crate::Object::add_relocation).
214    UnknownSection {
215        /// The id.
216        section: SectionId,
217    },
218    /// An alignment that is zero or not a power of two, passed to
219    /// [`Section::append`](crate::Section::append) or
220    /// [`Section::reserve`](crate::Section::reserve).
221    InvalidAlignment {
222        /// The alignment.
223        align: u64,
224    },
225    /// [`Section::append`](crate::Section::append) on a
226    /// [`Bss`](crate::SectionKind::Bss) section, which holds no bytes; use
227    /// [`reserve`](crate::Section::reserve).
228    AppendToBss,
229    /// A section would grow past the 64-bit size range or past available memory.
230    SizeOverflow,
231    /// The object holds more sections or symbols than ids can number (2³¹ − 1).
232    TooManyItems,
233}
234
235impl fmt::Display for ModelError {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        match self {
238            ModelError::Section { section, problem } => {
239                write!(f, "section #{}: {problem}", section.index())
240            }
241            ModelError::Symbol { symbol, problem } => {
242                write!(f, "symbol #{}: {problem}", symbol.index())
243            }
244            ModelError::Relocation {
245                section,
246                index,
247                problem,
248            } => write!(
249                f,
250                "relocation #{index} of section #{}: {problem}",
251                section.index()
252            ),
253            ModelError::SectionsOverlap { first, second } => write!(
254                f,
255                "sections #{} and #{} overlap in memory",
256                first.index(),
257                second.index()
258            ),
259            ModelError::EntryNotExecutable { entry } => {
260                write!(
261                    f,
262                    "entry point {entry:#x} is not inside an executable section"
263                )
264            }
265            ModelError::UnknownSection { section } => {
266                write!(f, "the object has no section #{}", section.index())
267            }
268            ModelError::InvalidAlignment { align } => {
269                write!(f, "alignment {align} is not a power of two")
270            }
271            ModelError::AppendToBss => {
272                f.write_str("cannot append bytes to a BSS section; reserve space instead")
273            }
274            ModelError::SizeOverflow => f.write_str("section size overflows"),
275            ModelError::TooManyItems => f.write_str("too many sections or symbols to number"),
276        }
277    }
278}
279
280impl core::error::Error for ModelError {}
281
282/// Why an object could not be written.
283///
284/// The enum is `#[non_exhaustive]`: match it with a wildcard arm.
285///
286/// # Examples
287///
288/// ```
289/// use object_lang::{Architecture, Object, Section, SectionKind, WriteError};
290///
291/// let mut obj = Object::relocatable(Architecture::X86_64);
292/// let note = obj.add_section(Section::new(".note.GNU-stack", SectionKind::Other));
293/// assert_eq!(
294///     object_lang::elf::write(&obj),
295///     Err(WriteError::ReservedSectionName { section: note }),
296/// );
297/// ```
298#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
299#[non_exhaustive]
300pub enum WriteError {
301    /// The object breaks a rule of the model.
302    Invalid(ModelError),
303    /// A section uses a name the writer generates itself (ELF: `.note.GNU-stack` in a
304    /// relocatable object, which records [`executable_stack`](crate::Object::executable_stack)).
305    ReservedSectionName {
306        /// The section.
307        section: SectionId,
308    },
309    /// Two loadable sections of an executable with different permissions, or a BSS
310    /// section followed by initialized data, share a page. Each permission change must
311    /// start on a fresh [page](crate::Architecture::page_size).
312    SegmentsSharePage {
313        /// The last section of the earlier segment.
314        first: SectionId,
315        /// The first section of the later one.
316        second: SectionId,
317    },
318    /// An executable's lowest section is too close to address zero to map the file
319    /// headers on the pages below it.
320    NoRoomForHeaders,
321    /// The object uses something this writer does not support yet; the message says
322    /// what.
323    Unsupported {
324        /// What is unsupported.
325        what: &'static str,
326    },
327    /// The file would exceed the format's size limits or the address space.
328    TooLarge,
329}
330
331impl fmt::Display for WriteError {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        match self {
334            WriteError::Invalid(err) => write!(f, "invalid object: {err}"),
335            WriteError::ReservedSectionName { section } => write!(
336                f,
337                "section #{} uses a name the writer reserves for itself",
338                section.index()
339            ),
340            WriteError::SegmentsSharePage { first, second } => write!(
341                f,
342                "sections #{} and #{} need different page permissions but share a page",
343                first.index(),
344                second.index()
345            ),
346            WriteError::NoRoomForHeaders => f.write_str(
347                "the lowest section is too close to address zero to map the headers below it",
348            ),
349            WriteError::Unsupported { what } => write!(f, "unsupported: {what}"),
350            WriteError::TooLarge => f.write_str("the file would exceed the format's size limits"),
351        }
352    }
353}
354
355impl core::error::Error for WriteError {
356    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
357        match self {
358            WriteError::Invalid(err) => Some(err),
359            _ => None,
360        }
361    }
362}
363
364impl From<ModelError> for WriteError {
365    fn from(err: ModelError) -> Self {
366        WriteError::Invalid(err)
367    }
368}
369
370/// Why bytes could not be read as an object.
371///
372/// Readers are strict: anything the model cannot represent exactly, or that a careful
373/// writer would never produce, is refused rather than guessed at. The enum is
374/// `#[non_exhaustive]`: match it with a wildcard arm.
375///
376/// # Examples
377///
378/// ```
379/// use object_lang::ReadError;
380///
381/// assert_eq!(object_lang::elf::read(b"MZ\x90\x00"), Err(ReadError::Truncated { what: "file header" }));
382/// assert_eq!(object_lang::elf::read(&[0u8; 64]), Err(ReadError::BadMagic));
383/// ```
384#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
385#[non_exhaustive]
386pub enum ReadError {
387    /// The bytes do not start with the format's magic number.
388    BadMagic,
389    /// A structure runs past the end of the input.
390    Truncated {
391        /// The structure.
392        what: &'static str,
393    },
394    /// A field holds a value the format does not allow, or structures contradict each
395    /// other.
396    Malformed {
397        /// What is wrong.
398        what: &'static str,
399    },
400    /// The file is valid but uses something this reader does not support.
401    Unsupported {
402        /// What is unsupported.
403        what: &'static str,
404        /// The raw value of the unsupported field (a type, flag set, or class).
405        value: u64,
406    },
407    /// A [`Limits`](crate::Limits) budget was exceeded.
408    LimitExceeded {
409        /// Which limit.
410        limit: &'static str,
411    },
412    /// The file decodes to an object that breaks a rule of the model.
413    Invalid(ModelError),
414}
415
416impl fmt::Display for ReadError {
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        match self {
419            ReadError::BadMagic => {
420                f.write_str("not an object file of this format (bad magic number)")
421            }
422            ReadError::Truncated { what } => {
423                write!(f, "truncated: {what} runs past the end of the input")
424            }
425            ReadError::Malformed { what } => write!(f, "malformed: {what}"),
426            ReadError::Unsupported { what, value } => write!(f, "unsupported {what} ({value:#x})"),
427            ReadError::LimitExceeded { limit } => write!(f, "limit exceeded: {limit}"),
428            ReadError::Invalid(err) => write!(f, "invalid object: {err}"),
429        }
430    }
431}
432
433impl core::error::Error for ReadError {
434    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
435        match self {
436            ReadError::Invalid(err) => Some(err),
437            _ => None,
438        }
439    }
440}
441
442impl From<ModelError> for ReadError {
443    fn from(err: ModelError) -> Self {
444        ReadError::Invalid(err)
445    }
446}