//! 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: //! //! ### ✨ 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::::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 = 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))] 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;