From f30fe6481d3d5424d46de78adadc8a92195edd3c Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Fri, 2 Jul 2021 16:44:27 +1000 Subject: [PATCH 1/4] read: avoid () for error types --- src/read/elf/section.rs | 6 ++++-- src/read/mod.rs | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/read/elf/section.rs b/src/read/elf/section.rs index 4cea033c..b0cfc656 100644 --- a/src/read/elf/section.rs +++ b/src/read/elf/section.rs @@ -457,9 +457,10 @@ pub trait SectionHeader: Debug + Pod { &self, endian: Self::Endian, data: R, - ) -> Result<&'data [u8], ()> { + ) -> read::Result<&'data [u8]> { if let Some((offset, size)) = self.file_range(endian) { data.read_bytes_at(offset, size) + .read_error("Invalid ELF section size or offset") } else { Ok(&[]) } @@ -474,9 +475,10 @@ pub trait SectionHeader: Debug + Pod { &self, endian: Self::Endian, data: R, - ) -> Result<&'data [T], ()> { + ) -> read::Result<&'data [T]> { let mut data = self.data(endian, data).map(Bytes)?; data.read_slice(data.len() / mem::size_of::()) + .read_error("Invalid ELF section size or offset") } /// Return the symbols in the section. diff --git a/src/read/mod.rs b/src/read/mod.rs index 856ed4d8..0ff9aece 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -87,6 +87,12 @@ impl ReadError for result::Result { } } +impl ReadError for result::Result { + fn read_error(self, error: &'static str) -> Result { + self.map_err(|_| Error(error)) + } +} + impl ReadError for Option { fn read_error(self, error: &'static str) -> Result { self.ok_or(Error(error)) From f6193d8a672283ecfec9d6a0dd0d01b560bd4a41 Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Fri, 9 Jul 2021 15:31:04 +1000 Subject: [PATCH 2/4] elf: more SHT definitions --- examples/readobj.rs | 10 ++++++++++ src/elf.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/examples/readobj.rs b/examples/readobj.rs index b849212b..41fe3a33 100644 --- a/examples/readobj.rs +++ b/examples/readobj.rs @@ -1173,6 +1173,16 @@ mod elf { SHT_PREINIT_ARRAY, SHT_GROUP, SHT_SYMTAB_SHNDX, + SHT_GNU_ATTRIBUTES, + SHT_GNU_HASH, + SHT_GNU_LIBLIST, + SHT_CHECKSUM, + SHT_SUNW_move, + SHT_SUNW_COMDAT, + SHT_SUNW_syminfo, + SHT_GNU_VERDEF, + SHT_GNU_VERNEED, + SHT_GNU_VERSYM, ); static FLAGS_SHT_MIPS: &[Flag] = &flags!( SHT_MIPS_LIBLIST, diff --git a/src/elf.rs b/src/elf.rs index 710f0ec8..2304ec6d 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -695,6 +695,33 @@ pub const SHT_GROUP: u32 = 17; pub const SHT_SYMTAB_SHNDX: u32 = 18; /// Start of OS-specific section types. pub const SHT_LOOS: u32 = 0x6000_0000; +/// Object attributes. +pub const SHT_GNU_ATTRIBUTES: u32 = 0x6fff_fff5; +/// GNU-style hash table. +pub const SHT_GNU_HASH: u32 = 0x6fff_fff6; +/// Prelink library list +pub const SHT_GNU_LIBLIST: u32 = 0x6fff_fff7; +/// Checksum for DSO content. +pub const SHT_CHECKSUM: u32 = 0x6fff_fff8; +/// Sun-specific low bound. +pub const SHT_LOSUNW: u32 = 0x6fff_fffa; +#[allow(missing_docs, non_upper_case_globals)] +pub const SHT_SUNW_move: u32 = 0x6fff_fffa; +#[allow(missing_docs)] +pub const SHT_SUNW_COMDAT: u32 = 0x6fff_fffb; +#[allow(missing_docs, non_upper_case_globals)] +pub const SHT_SUNW_syminfo: u32 = 0x6fff_fffc; +/// Version definition section. +#[allow(non_upper_case_globals)] +pub const SHT_GNU_VERDEF: u32 = 0x6fff_fffd; +/// Version needs section. +#[allow(non_upper_case_globals)] +pub const SHT_GNU_VERNEED: u32 = 0x6fff_fffe; +/// Version symbol table. +#[allow(non_upper_case_globals)] +pub const SHT_GNU_VERSYM: u32 = 0x6fff_ffff; +/// Sun-specific high bound. +pub const SHT_HISUNW: u32 = 0x6fff_ffff; /// End of OS-specific section types. pub const SHT_HIOS: u32 = 0x6fff_ffff; /// Start of processor-specific section types. From 4a61a697893d1af68c49b85e882a575631f5211d Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Tue, 13 Jul 2021 10:44:25 +1000 Subject: [PATCH 3/4] read/elf: hash support --- examples/readobj.rs | 66 ++++++++++++- src/elf.rs | 61 ++++++++++++ src/read/elf/hash.rs | 211 ++++++++++++++++++++++++++++++++++++++++ src/read/elf/mod.rs | 3 + src/read/elf/section.rs | 152 ++++++++++++++++++++++++++++- src/read/elf/symbol.rs | 6 ++ 6 files changed, 496 insertions(+), 3 deletions(-) create mode 100644 src/read/elf/hash.rs diff --git a/examples/readobj.rs b/examples/readobj.rs index 41fe3a33..652d8f74 100644 --- a/examples/readobj.rs +++ b/examples/readobj.rs @@ -540,8 +540,9 @@ mod elf { SHT_RELA => print_section_rela(p, endian, data, elf, sections, section), SHT_NOTE => print_section_notes(p, endian, data, elf, section), SHT_GROUP => print_section_group(p, endian, data, elf, sections, section), + SHT_HASH => print_hash(p, endian, data, elf, sections, section), + SHT_GNU_HASH => print_gnu_hash(p, endian, data, elf, sections, section), // TODO: - //SHT_HASH => //SHT_DYNAMIC => //SHT_SHLIB => //SHT_INIT_ARRAY => @@ -792,6 +793,69 @@ mod elf { } } + fn print_hash( + p: &mut Printer, + endian: Elf::Endian, + data: &[u8], + _elf: &Elf, + _sections: &SectionTable, + section: &Elf::SectionHeader, + ) { + if let Ok(Some(hash)) = section.hash_header(endian, data) { + p.group("Hash", |p| { + p.field("BucketCount", hash.bucket_count.get(endian)); + p.field("ChainCount", hash.chain_count.get(endian)); + }); + } + /* TODO: add this in a test somewhere + if let Ok(Some(hash_table)) = section.hash(endian, data) { + if let Ok(symbols) = _sections.symbols(endian, data, SHT_DYNSYM) { + for symbol in symbols.symbols() { + let name = symbols.symbol_name(endian, symbol).unwrap(); + if !symbol.is_definition(endian) { + continue; + } + let hash = hash(name); + let hash_symbol = hash_table.find(endian, name, hash, &symbols).unwrap(); + let hash_name = symbols.symbol_name(endian, hash_symbol).unwrap(); + assert_eq!(name, hash_name); + } + } + } + */ + } + + fn print_gnu_hash( + p: &mut Printer, + endian: Elf::Endian, + data: &[u8], + _elf: &Elf, + _sections: &SectionTable, + section: &Elf::SectionHeader, + ) { + if let Ok(Some(hash)) = section.gnu_hash_header(endian, data) { + p.group("GnuHash", |p| { + p.field("BucketCount", hash.bucket_count.get(endian)); + p.field("SymbolBase", hash.symbol_base.get(endian)); + p.field("BloomCount", hash.bloom_count.get(endian)); + p.field("BloomShift", hash.bloom_shift.get(endian)); + }); + } + /* TODO: add this in a test somewhere + if let Ok(Some(hash_table)) = section.gnu_hash(endian, data) { + if let Ok(symbols) = _sections.symbols(endian, data, SHT_DYNSYM) { + for symbol in &symbols.symbols()[hash_table.symbol_base() as usize..] { + let name = symbols.symbol_name(endian, symbol).unwrap(); + let hash = gnu_hash(name); + let hash_symbol = hash_table.find(endian, name, hash, &symbols).unwrap(); + let hash_name = symbols.symbol_name(endian, hash_symbol).unwrap(); + assert_eq!(name, hash_name); + } + } + } + */ + } + static FLAGS_EI_CLASS: &[Flag] = &flags!(ELFCLASSNONE, ELFCLASS32, ELFCLASS64); static FLAGS_EI_DATA: &[Flag] = &flags!(ELFDATANONE, ELFDATA2LSB, ELFDATA2MSB); static FLAGS_EV: &[Flag] = &flags!(EV_NONE, EV_CURRENT); diff --git a/src/elf.rs b/src/elf.rs index 2304ec6d..c8bb8439 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -1796,6 +1796,65 @@ pub const NT_GNU_PROPERTY_TYPE_0: u32 = 5; // TODO: GNU_PROPERTY_* // TODO: Elf*_Move +/// Header of `SHT_HASH` section. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct HashHeader { + /// The number of hash buckets. + pub bucket_count: U32, + /// The number of chain values. + pub chain_count: U32, + // Array of hash bucket start indices. + // buckets: U32[bucket_count] + // Array of hash chain links. An index of 0 terminates the chain. + // chains: U32[chain_count] +} + +/// Calculate the SysV hash for a symbol name. +/// +/// Used for `SHT_HASH`. +pub fn hash(name: &[u8]) -> u32 { + let mut hash = 0u32; + for byte in name { + hash = hash.wrapping_mul(16).wrapping_add(u32::from(*byte)); + hash ^= (hash >> 24) & 0xf0; + } + hash & 0xfff_ffff +} + +/// Header of `SHT_GNU_HASH` section. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct GnuHashHeader { + /// The number of hash buckets. + pub bucket_count: U32, + /// The symbol table index of the first symbol in the hash. + pub symbol_base: U32, + /// The number of words in the bloom filter. + /// + /// Must be a non-zero power of 2. + pub bloom_count: U32, + /// The bit shift count for the bloom filter. + pub bloom_shift: U32, + // Array of bloom filter words. + // bloom_filters: U32[bloom_count] or U64[bloom_count] + // Array of hash bucket start indices. + // buckets: U32[bucket_count] + // Array of hash values, one for each symbol starting at symbol_base. + // values: U32[symbol_count] +} + +/// Calculate the GNU hash for a symbol name. +/// +/// Used for `SHT_GNU_HASH`. +pub fn gnu_hash(name: &[u8]) -> u32 { + let mut hash = 5381u32; + for byte in name { + hash = hash.wrapping_mul(33).wrapping_add(u32::from(*byte)); + } + hash +} + // Motorola 68k specific definitions. // m68k values for `Rel*::r_type`. @@ -6097,4 +6156,6 @@ unsafe_impl_endian_pod!( Dyn64, NoteHeader32, NoteHeader64, + HashHeader, + GnuHashHeader, ); diff --git a/src/read/elf/hash.rs b/src/read/elf/hash.rs new file mode 100644 index 00000000..fab484e4 --- /dev/null +++ b/src/read/elf/hash.rs @@ -0,0 +1,211 @@ +use core::mem; + +use crate::elf; +use crate::read::{ReadError, ReadRef, Result}; +use crate::{U32, U64}; + +use super::{FileHeader, Sym, SymbolTable}; + +/// A SysV symbol hash table in an ELF file. +#[derive(Debug)] +pub struct HashTable<'data, Elf: FileHeader> { + buckets: &'data [U32], + chains: &'data [U32], +} + +impl<'data, Elf: FileHeader> HashTable<'data, Elf> { + /// Parse a SysV hash table. + /// + /// `data` should be from a `SHT_HASH` section, or from a + /// segment pointed to via the `DT_HASH` entry. + /// + /// The header is read at offset 0 in the given `data`. + pub fn parse(endian: Elf::Endian, data: &'data [u8]) -> Result { + let mut offset = 0; + let header = data + .read::>(&mut offset) + .read_error("Invalid hash header")?; + let buckets = data + .read_slice(&mut offset, header.bucket_count.get(endian) as usize) + .read_error("Invalid hash buckets")?; + let chains = data + .read_slice(&mut offset, header.chain_count.get(endian) as usize) + .read_error("Invalid hash chains")?; + Ok(HashTable { buckets, chains }) + } + + /// Return the symbol table length. + pub fn symbol_table_length(&self) -> u32 { + self.chains.len() as u32 + } + + /// Use the hash table to find the symbol table entry with the given name and hash. + pub fn find>( + &self, + endian: Elf::Endian, + name: &[u8], + hash: u32, + symbols: &SymbolTable<'data, Elf, R>, + ) -> Option<&'data Elf::Sym> { + // Get the chain start from the bucket for this hash. + let mut index = self.buckets[(hash as usize) % self.buckets.len()].get(endian) as usize; + // Avoid infinite loop. + let mut i = 0; + let strings = symbols.strings(); + while index != 0 && i < self.chains.len() { + if let Ok(symbol) = symbols.symbol(index) { + if symbol.name(endian, strings) == Ok(name) { + return Some(symbol); + } + } + index = self.chains.get(index)?.get(endian) as usize; + i += 1; + } + None + } +} + +/// A GNU symbol hash table in an ELF file. +#[derive(Debug)] +pub struct GnuHashTable<'data, Elf: FileHeader> { + symbol_base: u32, + bloom_shift: u32, + bloom_filters: &'data [u8], + buckets: &'data [U32], + values: &'data [U32], +} + +impl<'data, Elf: FileHeader> GnuHashTable<'data, Elf> { + /// Parse a GNU hash table. + /// + /// `data` should be from a `SHT_GNU_HASH` section, or from a + /// segment pointed to via the `DT_GNU_HASH` entry. + /// + /// The header is read at offset 0 in the given `data`. + /// + /// The header does not contain a length field, and so all of `data` + /// will be used as the hash table values. It does not matter if this + /// is longer than needed, and this will often the case when accessing + /// the hash table via the `DT_GNU_HASH` entry. + pub fn parse(endian: Elf::Endian, data: &'data [u8]) -> Result { + let mut offset = 0; + let header = data + .read::>(&mut offset) + .read_error("Invalid GNU hash header")?; + let bloom_len = + u64::from(header.bloom_count.get(endian)) * mem::size_of::() as u64; + let bloom_filters = data + .read_bytes(&mut offset, bloom_len) + .read_error("Invalid GNU hash bloom filters")?; + let buckets = data + .read_slice(&mut offset, header.bucket_count.get(endian) as usize) + .read_error("Invalid GNU hash buckets")?; + let chain_count = (data.len() - offset as usize) / 4; + let values = data + .read_slice(&mut offset, chain_count) + .read_error("Invalid GNU hash values")?; + Ok(GnuHashTable { + symbol_base: header.symbol_base.get(endian), + bloom_shift: header.bloom_shift.get(endian), + bloom_filters, + buckets, + values, + }) + } + + /// Return the symbol table index of the first symbol in the hash table. + pub fn symbol_base(&self) -> u32 { + self.symbol_base + } + + /// Determine the symbol table length by finding the last entry in the hash table. + /// + /// Returns `None` if the hash table is empty or invalid. + pub fn symbol_table_length(&self, endian: Elf::Endian) -> Option { + // Ensure we find a non-empty bucket. + if self.symbol_base == 0 { + return None; + } + + // Find the highest chain index in a bucket. + let mut max_symbol = 0; + for bucket in self.buckets { + let bucket = bucket.get(endian); + if max_symbol < bucket { + max_symbol = bucket; + } + } + + // Find the end of the chain. + for value in self + .values + .get(max_symbol.checked_sub(self.symbol_base)? as usize..)? + { + max_symbol += 1; + if value.get(endian) & 1 != 0 { + return Some(max_symbol); + } + } + + None + } + + /// Use the hash table to find the symbol table entry with the given name and hash. + pub fn find>( + &self, + endian: Elf::Endian, + name: &[u8], + hash: u32, + symbols: &SymbolTable<'data, Elf, R>, + ) -> Option<&'data Elf::Sym> { + let word_bits = mem::size_of::() as u32 * 8; + + // Test against bloom filter. + let bloom_count = self.bloom_filters.len() / mem::size_of::(); + let offset = + ((hash / word_bits) & (bloom_count as u32 - 1)) * mem::size_of::() as u32; + let filter = if word_bits == 64 { + self.bloom_filters + .read_at::>(offset.into()) + .ok()? + .get(endian) + } else { + self.bloom_filters + .read_at::>(offset.into()) + .ok()? + .get(endian) + .into() + }; + if filter & (1 << (hash % word_bits)) == 0 { + return None; + } + if filter & (1 << ((hash >> self.bloom_shift) % word_bits)) == 0 { + return None; + } + + // Get the chain start from the bucket for this hash. + let index = self.buckets[(hash as usize) % self.buckets.len()].get(endian) as usize; + if index == 0 { + return None; + } + + // Test symbols in the chain. + let strings = symbols.strings(); + let symbols = symbols.symbols().get(index..)?; + let values = self + .values + .get(index.checked_sub(self.symbol_base as usize)?..)?; + for (symbol, value) in symbols.iter().zip(values.iter()) { + let value = value.get(endian); + if value | 1 == hash | 1 { + if symbol.name(endian, strings) == Ok(name) { + return Some(symbol); + } + } + if value & 1 != 0 { + break; + } + } + None + } +} diff --git a/src/read/elf/mod.rs b/src/read/elf/mod.rs index ff6039a4..3a8b0934 100644 --- a/src/read/elf/mod.rs +++ b/src/read/elf/mod.rs @@ -31,3 +31,6 @@ pub use compression::*; mod note; pub use note::*; + +mod hash; +pub use hash::*; diff --git a/src/read/elf/section.rs b/src/read/elf/section.rs index b0cfc656..96c0629a 100644 --- a/src/read/elf/section.rs +++ b/src/read/elf/section.rs @@ -10,8 +10,8 @@ use crate::read::{ }; use super::{ - CompressionHeader, ElfFile, ElfSectionRelocationIterator, FileHeader, NoteIterator, - RelocationSections, SymbolTable, + CompressionHeader, ElfFile, ElfSectionRelocationIterator, FileHeader, GnuHashTable, HashTable, + NoteIterator, RelocationSections, SymbolTable, }; /// The table of section headers in an ELF file. @@ -132,6 +132,74 @@ impl<'data, Elf: FileHeader, R: ReadRef<'data>> SectionTable<'data, Elf, R> { ) -> read::Result { RelocationSections::parse(endian, self, symbol_section) } + + /// Return the header of a SysV hash section. + /// + /// Returns `Ok(None)` if there is no SysV GNU hash section. + /// Returns `Err` for invalid values. + pub fn hash_header( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result>> { + for section in self.sections { + if let Some(hash) = section.hash_header(endian, data)? { + return Ok(Some(hash)); + } + } + Ok(None) + } + + /// Return the contents of a SysV hash section. + /// + /// Returns `Ok(None)` if there is no SysV hash section. + /// Returns `Err` for invalid values. + pub fn hash( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result>> { + for section in self.sections { + if let Some(hash) = section.hash(endian, data)? { + return Ok(Some(hash)); + } + } + Ok(None) + } + + /// Return the header of a GNU hash section. + /// + /// Returns `Ok(None)` if there is no GNU hash section. + /// Returns `Err` for invalid values. + pub fn gnu_hash_header( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result>> { + for section in self.sections { + if let Some(hash) = section.gnu_hash_header(endian, data)? { + return Ok(Some(hash)); + } + } + Ok(None) + } + + /// Return the contents of a GNU hash section. + /// + /// Returns `Ok(None)` if there is no GNU hash section. + /// Returns `Err` for invalid values. + pub fn gnu_hash( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result>> { + for section in self.sections { + if let Some(hash) = section.gnu_hash(endian, data)? { + return Ok(Some(hash)); + } + } + Ok(None) + } } /// An iterator over the sections of an `ElfFile32`. @@ -603,6 +671,86 @@ pub trait SectionHeader: Debug + Pod { .read_error("Invalid ELF group section offset or size")?; Ok(Some((flag, sections))) } + + /// Return the header of a SysV hash section. + /// + /// Returns `Ok(None)` if the section does not contain a SysV hash. + /// Returns `Err` for invalid values. + fn hash_header<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result>> { + if self.sh_type(endian) != elf::SHT_HASH { + return Ok(None); + } + let data = self + .data(endian, data) + .read_error("Invalid ELF hash section offset or size")?; + let header = data + .read_at::>(0) + .read_error("Invalid hash header")?; + Ok(Some(header)) + } + + /// Return the contents of a SysV hash section. + /// + /// Returns `Ok(None)` if the section does not contain a SysV hash. + /// Returns `Err` for invalid values. + fn hash<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result>> { + if self.sh_type(endian) != elf::SHT_HASH { + return Ok(None); + } + let data = self + .data(endian, data) + .read_error("Invalid ELF hash section offset or size")?; + let hash = HashTable::parse(endian, data)?; + Ok(Some(hash)) + } + + /// Return the header of a GNU hash section. + /// + /// Returns `Ok(None)` if the section does not contain a GNU hash. + /// Returns `Err` for invalid values. + fn gnu_hash_header<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result>> { + if self.sh_type(endian) != elf::SHT_GNU_HASH { + return Ok(None); + } + let data = self + .data(endian, data) + .read_error("Invalid ELF GNU hash section offset or size")?; + let header = data + .read_at::>(0) + .read_error("Invalid GNU hash header")?; + Ok(Some(header)) + } + + /// Return the contents of a GNU hash section. + /// + /// Returns `Ok(None)` if the section does not contain a GNU hash. + /// Returns `Err` for invalid values. + fn gnu_hash<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result>> { + if self.sh_type(endian) != elf::SHT_GNU_HASH { + return Ok(None); + } + let data = self + .data(endian, data) + .read_error("Invalid ELF GNU hash section offset or size")?; + let hash = GnuHashTable::parse(endian, data)?; + Ok(Some(hash)) + } } impl SectionHeader for elf::SectionHeader32 { diff --git a/src/read/elf/symbol.rs b/src/read/elf/symbol.rs index 56c79331..ff2ed199 100644 --- a/src/read/elf/symbol.rs +++ b/src/read/elf/symbol.rs @@ -102,6 +102,12 @@ impl<'data, Elf: FileHeader, R: ReadRef<'data>> SymbolTable<'data, Elf, R> { self.strings } + /// Return the symbol table. + #[inline] + pub fn symbols(&self) -> &'data [Elf::Sym] { + self.symbols + } + /// Iterate over the symbols. #[inline] pub fn iter(&self) -> slice::Iter<'data, Elf::Sym> { From da856176ab1248dfe1c1c61f048438c93cd191a4 Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Tue, 20 Jul 2021 15:44:15 +1000 Subject: [PATCH 4/4] read/elf: symbol versions --- examples/readobj.rs | 89 ++++++++++++++++++++ src/elf.rs | 102 ++++++++++++++++++++++- src/read/elf/mod.rs | 3 + src/read/elf/section.rs | 106 +++++++++++++++++++++++- src/read/elf/version.rs | 176 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 471 insertions(+), 5 deletions(-) create mode 100644 src/read/elf/version.rs diff --git a/examples/readobj.rs b/examples/readobj.rs index 652d8f74..289e9dc1 100644 --- a/examples/readobj.rs +++ b/examples/readobj.rs @@ -542,6 +542,9 @@ mod elf { SHT_GROUP => print_section_group(p, endian, data, elf, sections, section), SHT_HASH => print_hash(p, endian, data, elf, sections, section), SHT_GNU_HASH => print_gnu_hash(p, endian, data, elf, sections, section), + SHT_GNU_VERDEF => print_gnu_verdef(p, endian, data, elf, sections, section), + SHT_GNU_VERNEED => print_gnu_verneed(p, endian, data, elf, sections, section), + SHT_GNU_VERSYM => print_gnu_versym(p, endian, data, elf, sections, section), // TODO: //SHT_DYNAMIC => //SHT_SHLIB => @@ -856,6 +859,90 @@ mod elf { */ } + fn print_gnu_verdef( + p: &mut Printer, + endian: Elf::Endian, + data: &[u8], + _elf: &Elf, + _sections: &SectionTable, + section: &Elf::SectionHeader, + ) { + if let Ok(Some(mut verdefs)) = section.gnu_verdef(endian, data) { + while let Ok(Some((verdef, mut verdauxs))) = verdefs.next() { + p.group("VersionDefinition", |p| { + // TODO: names + p.field("Version", verdef.vd_version.get(endian)); + p.field_hex("Flags", verdef.vd_flags.get(endian)); + p.flags(verdef.vd_flags.get(endian), 0, FLAGS_VER_FLG); + p.field("Index", verdef.vd_ndx.get(endian) & !VER_NDX_HIDDEN); + p.flags(verdef.vd_ndx.get(endian), 0, FLAGS_VER_NDX); + p.field("AuxCount", verdef.vd_cnt.get(endian)); + p.field_hex("Hash", verdef.vd_hash.get(endian)); + p.field("AuxOffset", verdef.vd_aux.get(endian)); + p.field("NextOffset", verdef.vd_next.get(endian)); + while let Ok(Some(verdaux)) = verdauxs.next() { + p.group("Aux", |p| { + p.field_hex("Name", verdaux.vda_name.get(endian)); + p.field("NextOffset", verdaux.vda_next.get(endian)); + }); + } + }); + } + } + } + + fn print_gnu_verneed( + p: &mut Printer, + endian: Elf::Endian, + data: &[u8], + _elf: &Elf, + _sections: &SectionTable, + section: &Elf::SectionHeader, + ) { + if let Ok(Some(mut verneeds)) = section.gnu_verneed(endian, data) { + while let Ok(Some((verneed, mut vernauxs))) = verneeds.next() { + p.group("VersionNeed", |p| { + // TODO: names + p.field("Version", verneed.vn_version.get(endian)); + p.field("AuxCount", verneed.vn_cnt.get(endian)); + p.field_hex("Filename", verneed.vn_file.get(endian)); + p.field("AuxOffset", verneed.vn_aux.get(endian)); + p.field("NextOffset", verneed.vn_next.get(endian)); + while let Ok(Some(vernaux)) = vernauxs.next() { + p.group("Aux", |p| { + p.field_hex("Hash", vernaux.vna_hash.get(endian)); + p.field_hex("Flags", vernaux.vna_flags.get(endian)); + p.flags(vernaux.vna_flags.get(endian), 0, FLAGS_VER_FLG); + p.field("Index", vernaux.vna_other.get(endian) & !VER_NDX_HIDDEN); + p.flags(vernaux.vna_other.get(endian), 0, FLAGS_VER_NDX); + p.field_hex("Name", vernaux.vna_name.get(endian)); + p.field("NextOffset", vernaux.vna_next.get(endian)); + }); + } + }); + } + } + } + + fn print_gnu_versym( + p: &mut Printer, + endian: Elf::Endian, + data: &[u8], + _elf: &Elf, + _sections: &SectionTable, + section: &Elf::SectionHeader, + ) { + if let Ok(Some(syms)) = section.gnu_versym(endian, data) { + for sym in syms { + p.group("VersionSymbol", |p| { + p.field("Version", sym.0.get(endian) & !VER_NDX_HIDDEN); + p.flags(sym.0.get(endian), 0, FLAGS_VER_NDX); + // TODO: version name + }); + } + } + } + static FLAGS_EI_CLASS: &[Flag] = &flags!(ELFCLASSNONE, ELFCLASS32, ELFCLASS64); static FLAGS_EI_DATA: &[Flag] = &flags!(ELFDATANONE, ELFDATA2LSB, ELFDATA2MSB); static FLAGS_EV: &[Flag] = &flags!(EV_NONE, EV_CURRENT); @@ -3254,6 +3341,8 @@ mod elf { DF_1_STUB, DF_1_PIE, ); + static FLAGS_VER_FLG: &[Flag] = &flags!(VER_FLG_BASE, VER_FLG_WEAK); + static FLAGS_VER_NDX: &[Flag] = &flags!(VER_NDX_HIDDEN); } mod macho { diff --git a/src/elf.rs b/src/elf.rs index c8bb8439..bc4462c9 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -1696,10 +1696,99 @@ pub const DF_1_STUB: u32 = 0x0400_0000; #[allow(missing_docs)] pub const DF_1_PIE: u32 = 0x0800_0000; -// TODO: ELF*_Verdef, VER_DEF_*, VER_FLG_*, VER_NDX_* -// TODO: Elf*_Verdaux -// TODO: Elf*_Verneed, VER_NEED_* -// TODO: Elf*_Vernaux, VER_FLG_* +/// Version symbol information +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Versym(pub U16); + +/// Version definition sections +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Verdef { + /// Version revision + pub vd_version: U16, + /// Version information + pub vd_flags: U16, + /// Version Index + pub vd_ndx: U16, + /// Number of associated aux entries + pub vd_cnt: U16, + /// Version name hash value + pub vd_hash: U32, + /// Offset in bytes to verdaux array + pub vd_aux: U32, + /// Offset in bytes to next verdef entry + pub vd_next: U32, +} + +// Legal values for vd_version (version revision). +/// No version +pub const VER_DEF_NONE: u16 = 0; +/// Current version +pub const VER_DEF_CURRENT: u16 = 1; + +// Legal values for vd_flags and vna_flags (version information flags). +/// Version definition of file itself +pub const VER_FLG_BASE: u16 = 0x1; +/// Weak version identifier +pub const VER_FLG_WEAK: u16 = 0x2; + +// Versym symbol index values. +/// Symbol is local. +pub const VER_NDX_LOCAL: u16 = 0; +/// Symbol is global. +pub const VER_NDX_GLOBAL: u16 = 1; +/// Symbol is hidden. +pub const VER_NDX_HIDDEN: u16 = 0x8000; + +/// Auxiliary version information. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Verdaux { + /// Version or dependency names + pub vda_name: U32, + /// Offset in bytes to next verdaux + pub vda_next: U32, +} + +/// Version dependency. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Verneed { + /// Version of structure + pub vn_version: U16, + /// Number of associated aux entries + pub vn_cnt: U16, + /// Offset of filename for this dependency + pub vn_file: U32, + /// Offset in bytes to vernaux array + pub vn_aux: U32, + /// Offset in bytes to next verneed entry + pub vn_next: U32, +} + +// Legal values for vn_version (version revision). +/// No version +pub const VER_NEED_NONE: u16 = 0; +/// Current version +pub const VER_NEED_CURRENT: u16 = 1; + +/// Auxiliary needed version information. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Vernaux { + /// Hash value of dependency name + pub vna_hash: U32, + /// Dependency specific information + pub vna_flags: U16, + /// Version Index + pub vna_other: U16, + /// Dependency name string offset + pub vna_name: U32, + /// Offset in bytes to next vernaux entry + pub vna_next: U32, +} + // TODO: Elf*_auxv_t, AT_* /// Note section entry header. @@ -6154,6 +6243,11 @@ unsafe_impl_endian_pod!( ProgramHeader64, Dyn32, Dyn64, + Versym, + Verdef, + Verdaux, + Verneed, + Vernaux, NoteHeader32, NoteHeader64, HashHeader, diff --git a/src/read/elf/mod.rs b/src/read/elf/mod.rs index 3a8b0934..5b7d7f9f 100644 --- a/src/read/elf/mod.rs +++ b/src/read/elf/mod.rs @@ -34,3 +34,6 @@ pub use note::*; mod hash; pub use hash::*; + +mod version; +pub use version::*; diff --git a/src/read/elf/section.rs b/src/read/elf/section.rs index 96c0629a..69e33d11 100644 --- a/src/read/elf/section.rs +++ b/src/read/elf/section.rs @@ -11,7 +11,7 @@ use crate::read::{ use super::{ CompressionHeader, ElfFile, ElfSectionRelocationIterator, FileHeader, GnuHashTable, HashTable, - NoteIterator, RelocationSections, SymbolTable, + NoteIterator, RelocationSections, SymbolTable, VerdefIterator, VerneedIterator, }; /// The table of section headers in an ELF file. @@ -200,6 +200,57 @@ impl<'data, Elf: FileHeader, R: ReadRef<'data>> SectionTable<'data, Elf, R> { } Ok(None) } + + /// Return the contents of a `SHT_GNU_VERSYM` section. + /// + /// Returns `Ok(None)` if there is no `SHT_GNU_VERSYM` section. + /// Returns `Err` for invalid values. + pub fn gnu_versym( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result]>> { + for section in self.sections { + if let Some(syms) = section.gnu_versym(endian, data)? { + return Ok(Some(syms)); + } + } + Ok(None) + } + + /// Return the contents of a `SHT_GNU_VERDEF` section. + /// + /// Returns `Ok(None)` if there is no `SHT_GNU_VERDEF` section. + /// Returns `Err` for invalid values. + pub fn gnu_verdef( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result>> { + for section in self.sections { + if let Some(defs) = section.gnu_verdef(endian, data)? { + return Ok(Some(defs)); + } + } + Ok(None) + } + + /// Return the contents of a `SHT_GNU_VERNEED` section. + /// + /// Returns `Ok(None)` if there is no `SHT_GNU_VERNEED` section. + /// Returns `Err` for invalid values. + pub fn gnu_verneed( + &self, + endian: Elf::Endian, + data: R, + ) -> read::Result>> { + for section in self.sections { + if let Some(needs) = section.gnu_verneed(endian, data)? { + return Ok(Some(needs)); + } + } + Ok(None) + } } /// An iterator over the sections of an `ElfFile32`. @@ -751,6 +802,59 @@ pub trait SectionHeader: Debug + Pod { let hash = GnuHashTable::parse(endian, data)?; Ok(Some(hash)) } + + /// Return the contents of a `SHT_GNU_VERSYM` section. + /// + /// Returns `Ok(None)` if the section type is not `SHT_GNU_VERSYM`. + /// Returns `Err` for invalid values. + fn gnu_versym<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result]>> { + if self.sh_type(endian) != elf::SHT_GNU_VERSYM { + return Ok(None); + } + self.data_as_array(endian, data) + .read_error("Invalid ELF GNU versym section offset or size") + .map(Some) + } + + /// Return an iterator for the entries of a `SHT_GNU_VERDEF` section. + /// + /// Returns `Ok(None)` if the section type is not `SHT_GNU_VERDEF`. + /// Returns `Err` for invalid values. + fn gnu_verdef<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result>> { + if self.sh_type(endian) != elf::SHT_GNU_VERDEF { + return Ok(None); + } + let data = self + .data_as_array(endian, data) + .read_error("Invalid ELF GNU verdef section offset or size")?; + Ok(Some(VerdefIterator::new(endian, data))) + } + + /// Return an iterator for the entries of a `SHT_GNU_VERNEED` section. + /// + /// Returns `Ok(None)` if the section type is not `SHT_GNU_VERNEED`. + /// Returns `Err` for invalid values. + fn gnu_verneed<'data, R: ReadRef<'data>>( + &self, + endian: Self::Endian, + data: R, + ) -> read::Result>> { + if self.sh_type(endian) != elf::SHT_GNU_VERNEED { + return Ok(None); + } + let data = self + .data_as_array(endian, data) + .read_error("Invalid ELF GNU verneed section offset or size")?; + Ok(Some(VerneedIterator::new(endian, data))) + } } impl SectionHeader for elf::SectionHeader32 { diff --git a/src/read/elf/version.rs b/src/read/elf/version.rs new file mode 100644 index 00000000..b3934910 --- /dev/null +++ b/src/read/elf/version.rs @@ -0,0 +1,176 @@ +use crate::elf; +use crate::pod::Bytes; +use crate::read::{ReadError, Result}; + +use super::FileHeader; + +/// An iterator over the entries in an ELF `SHT_GNU_verdef` section. +#[derive(Debug, Clone)] +pub struct VerdefIterator<'data, Elf: FileHeader> { + endian: Elf::Endian, + data: Bytes<'data>, +} + +impl<'data, Elf: FileHeader> VerdefIterator<'data, Elf> { + pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self { + VerdefIterator { + endian, + data: Bytes(data), + } + } + + /// Return the next `Verdef` entry. + pub fn next( + &mut self, + ) -> Result, VerdauxIterator<'data, Elf>)>> { + if self.data.is_empty() { + return Ok(None); + } + + let verdef = self + .data + .read_at::>(0) + .read_error("ELF verdef is too short")?; + + let mut verdaux_data = self.data; + verdaux_data + .skip(verdef.vd_aux.get(self.endian) as usize) + .read_error("Invalid ELF vd_aux")?; + let verdaux = + VerdauxIterator::new(self.endian, verdaux_data.0, verdef.vd_cnt.get(self.endian)); + + let next = verdef.vd_next.get(self.endian); + if next != 0 { + self.data + .skip(next as usize) + .read_error("Invalid ELF vd_next")?; + } else { + self.data = Bytes(&[]); + } + Ok(Some((verdef, verdaux))) + } +} + +/// An iterator over the auxiliary records for an entry in an ELF `SHT_GNU_verdef` section. +#[derive(Debug, Clone)] +pub struct VerdauxIterator<'data, Elf: FileHeader> { + endian: Elf::Endian, + data: Bytes<'data>, + count: u16, +} + +impl<'data, Elf: FileHeader> VerdauxIterator<'data, Elf> { + pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self { + VerdauxIterator { + endian, + data: Bytes(data), + count, + } + } + + /// Return the next `Verdaux` entry. + pub fn next(&mut self) -> Result>> { + if self.count == 0 { + return Ok(None); + } + + let verdaux = self + .data + .read_at::>(0) + .read_error("ELF verdaux is too short")?; + + self.data + .skip(verdaux.vda_next.get(self.endian) as usize) + .read_error("Invalid ELF vda_next")?; + self.count -= 1; + Ok(Some(verdaux)) + } +} + +/// An iterator over the entries in an ELF `SHT_GNU_verneed` section. +#[derive(Debug, Clone)] +pub struct VerneedIterator<'data, Elf: FileHeader> { + endian: Elf::Endian, + data: Bytes<'data>, +} + +impl<'data, Elf: FileHeader> VerneedIterator<'data, Elf> { + pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self { + VerneedIterator { + endian, + data: Bytes(data), + } + } + + /// Return the next `Verneed` entry. + pub fn next( + &mut self, + ) -> Result< + Option<( + &'data elf::Verneed, + VernauxIterator<'data, Elf>, + )>, + > { + if self.data.is_empty() { + return Ok(None); + } + + let verneed = self + .data + .read_at::>(0) + .read_error("ELF verneed is too short")?; + + let mut vernaux_data = self.data; + vernaux_data + .skip(verneed.vn_aux.get(self.endian) as usize) + .read_error("Invalid ELF vn_aux")?; + let vernaux = + VernauxIterator::new(self.endian, vernaux_data.0, verneed.vn_cnt.get(self.endian)); + + let next = verneed.vn_next.get(self.endian); + if next != 0 { + self.data + .skip(next as usize) + .read_error("Invalid ELF vn_next")?; + } else { + self.data = Bytes(&[]); + } + Ok(Some((verneed, vernaux))) + } +} + +/// An iterator over the auxiliary records for an entry in an ELF `SHT_GNU_verneed` section. +#[derive(Debug, Clone)] +pub struct VernauxIterator<'data, Elf: FileHeader> { + endian: Elf::Endian, + data: Bytes<'data>, + count: u16, +} + +impl<'data, Elf: FileHeader> VernauxIterator<'data, Elf> { + pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self { + VernauxIterator { + endian, + data: Bytes(data), + count, + } + } + + /// Return the next `Vernaux` entry. + pub fn next(&mut self) -> Result>> { + if self.count == 0 { + return Ok(None); + } + + let vernaux = self + .data + .read_at::>(0) + .read_error("ELF vernaux is too short")?; + + self.data + .skip(vernaux.vna_next.get(self.endian) as usize) + .read_error("Invalid ELF vna_next")?; + self.count -= 1; + Ok(Some(vernaux)) + } +}