elf/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! The `elf` crate provides a pure-safe-rust interface for reading ELF object files.
//!
//! # Capabilities
//!
//! ### ✨ Works in `no_std` environments ✨
//! This crate provides an elf parsing interface which does not allocate or use any std
//! features, so it can be used in `no_std` environments such as kernels and bootloaders.
//! The no_std variant merely disables the additional stream-oriented `std:: Read + Seek` interface.
//! All core parsing functionality is the same!
//!
//! ### ✨ Endian-aware ✨
//! This crate handles translating between file and host endianness when
//! parsing the ELF contents and provides four endian parsing implementations
//! optimized to support the different common use-cases for an ELF parsing library.
//! Parsing is generic across the specifications and each trait impl represents a
//! specification that encapsulates an interface for parsing integers from some
//! set of allowed byte orderings.
//!
//! * [AnyEndian](endian::AnyEndian): Dynamically parsing either byte order at runtime based on the type of ELF object being parsed.
//! * [BigEndian](endian::BigEndian)/[LittleEndian](endian::LittleEndian): For tools that know they only want to parse a single given byte order known at compile time.
//! * [NativeEndian](type@endian::NativeEndian): For tools that know they want to parse the same byte order as the compilation target's byte order.
//!
//! When the limited specifications are used, errors are properly returned when asked to parse an ELF file
//! with an unexpected byte ordering.
//!
//! ### ✨ Zero-alloc parser ✨
//! This crate implements parsing in a way that avoids heap allocations. ELF structures
//! are parsed and stored on the stack and provided by patterns such as lazily parsed iterators
//! that yield stack allocated rust types, or lazily parsing tables that only parse out a particular
//! entry on table.get(index). The structures are copy-converted as needed from the underlying file
//! data into Rust's native struct representation.
//!
//! ### ✨ Fuzz Tested ✨
//! Various parts of the library are fuzz tested for panics and crashes (see `fuzz/`).
//!
//! Memory safety is a core goal, as is providing a safe interface that errors on bad data
//! over crashing or panicking. Checked integer math is used where appropriate, and ParseErrors are
//! returned when bad or corrupted ELF structures are encountered.
//!
//! ### ✨ Uses only safe interfaces ✨
//! With memory safety a core goal, this crate contains zero unsafe code blocks
//! of its own and only uses safe interface methods from core and std, so you can
//! trust in rust's memory safety guarantees without also having to trust this
//! library developer as having truly been "right" in why some unsafe block was
//! safe. 💃
//!
//! Note: I'd love to see this crate be enhanced further once rust provides safe transmutes.
//!
//! See: <https://github.com/rust-lang/project-safe-transmute>
//!
//! ### ✨ Some zero-copy interfaces ✨
//! The StringTable, for instance, yields `&[u8]` and `&str` backed by the raw string table bytes.
//!
//! The [ElfBytes] parser type also does not make raw copies of the underlying file data to back
//! the parser lazy parser interfaces `ParsingIterator` and `ParsingTable`. They merely wrap byte slices
//! internally, and yield rust repr values on demand, which does entail copying of the bytes into the
//! parsed rust-native format.
//!
//! Depending on the use-case, it can be more efficient to restructure the raw ELF into different layouts
//! for more efficient interpretation, say, by re-indexing a flat table into a HashMap. `ParsingIterator`s
//! make that easy and rustily-intuitive.
//!
//! The `ParsingIterator`s are also nice in that you can easily zip/enumerate/filter/collect them
//! how you wish. Do you know that you want to do multiple passes over pairs from different tables? Just
//! zip/collect them into another type so you only parse/endian-flip each entry once!
//!
//! ### ✨ Stream-based lazy i/o interface ✨
//! The [ElfStream] parser type takes a `std:: Read + Seek` (such as `std::fs::File`) where ranges of
//! file contents are read lazily on-demand based on what the user wants to parse.
//!
//! This, alongside the bytes-oriented interface, allow you to decide which tradeoffs
//! you want to make. If you're going to be working with the whole file contents,
//! then the byte slice approach is probably worthwhile to minimize i/o overhead by
//! streaming the whole file into memory at once. If you're only going to be
//! inspecting part of the file, then the [ElfStream] approach would help avoid the
//! overhead of reading a bunch of unused file data just to parse out a few things, (like
//! grabbing the `.gnu.note.build-id`)
//!
//! ### ✨ Tiny library with no dependencies and fast compilation times ✨
//! Release-target compilation times on this developer's 2021 m1 macbook are sub-second.
//!
//! Example using [ElfBytes]:
//! ```
//! use elf::ElfBytes;
//! use elf::endian::AnyEndian;
//! use elf::note::Note;
//! use elf::note::NoteGnuBuildId;
//! use elf::section::SectionHeader;
//!
//! let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
//! let file_data = std::fs::read(path).expect("Could not read file.");
//! let slice = file_data.as_slice();
//! let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
//!
//! // Get the ELF file's build-id
//! let abi_shdr: SectionHeader = file
//!     .section_header_by_name(".note.gnu.build-id")
//!     .expect("section table should be parseable")
//!     .expect("file should have a .note.ABI-tag section");
//!
//! let notes: Vec<Note> = file
//!     .section_data_as_notes(&abi_shdr)
//!     .expect("Should be able to get note section data")
//!     .collect();
//! assert_eq!(
//!     notes[0],
//!     Note::GnuBuildId(NoteGnuBuildId(
//!         &[140, 51, 19, 23, 221, 90, 215, 131, 169, 13,
//!           210, 183, 215, 77, 216, 175, 167, 110, 3, 209]))
//! );
//!
//! // Find lazy-parsing types for the common ELF sections (we want .dynsym, .dynstr, .hash)
//! let common = file.find_common_data().expect("shdrs should parse");
//! let (dynsyms, strtab) = (common.dynsyms.unwrap(), common.dynsyms_strs.unwrap());
//! let hash_table = common.sysv_hash.unwrap();
//!
//! // Use the hash table to find a given symbol in it.
//! let name = b"memset";
//! let (sym_idx, sym) = hash_table.find(name, &dynsyms, &strtab)
//!     .expect("hash table and symbols should parse").unwrap();
//!
//! // Verify that we got the same symbol from the hash table we expected
//! assert_eq!(sym_idx, 2);
//! assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset");
//! assert_eq!(sym, dynsyms.get(sym_idx).unwrap());
//! ```

#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(all(feature = "nightly", not(feature = "std")), feature(error_in_core))]
#![warn(rust_2018_idioms)]
#![deny(missing_debug_implementations)]
#![forbid(unsafe_code)]

pub mod abi;

pub mod compression;
pub mod dynamic;
pub mod file;
pub mod gnu_symver;
pub mod hash;
pub mod note;
pub mod relocation;
pub mod section;
pub mod segment;
pub mod string_table;
pub mod symbol;

#[cfg(feature = "to_str")]
pub mod to_str;

pub mod endian;
pub mod parse;

mod elf_bytes;
pub use elf_bytes::CommonElfData;
pub use elf_bytes::ElfBytes;

#[cfg(feature = "std")]
mod elf_stream;
#[cfg(feature = "std")]
pub use elf_stream::ElfStream;

pub use parse::ParseError;