elf/
elf_bytes.rs

1use crate::abi;
2use crate::compression::CompressionHeader;
3use crate::dynamic::{Dyn, DynamicTable};
4use crate::endian::EndianParse;
5use crate::file::{parse_ident, Class, FileHeader};
6use crate::gnu_symver::{
7    SymbolVersionTable, VerDefIterator, VerNeedIterator, VersionIndex, VersionIndexTable,
8};
9use crate::hash::{GnuHashTable, SysVHashTable};
10use crate::note::NoteIterator;
11use crate::parse::{ParseAt, ParseError, ReadBytesExt};
12use crate::relocation::{RelIterator, RelaIterator};
13use crate::section::{SectionHeader, SectionHeaderTable};
14use crate::segment::{ProgramHeader, SegmentTable};
15use crate::string_table::StringTable;
16use crate::symbol::{Symbol, SymbolTable};
17
18//  _____ _     _____ ____        _
19// | ____| |   |  ___| __ ) _   _| |_ ___  ___
20// |  _| | |   | |_  |  _ \| | | | __/ _ \/ __|
21// | |___| |___|  _| | |_) | |_| | ||  __/\__ \
22// |_____|_____|_|   |____/ \__, |\__\___||___/
23//                          |___/
24//
25
26/// This type encapsulates the bytes-oriented interface for parsing ELF objects from `&[u8]`.
27///
28/// This parser is no_std and zero-alloc, returning lazy-parsing interfaces wrapped around
29/// subslices of the provided ELF bytes `&[u8]`. The various ELF structures are
30/// parsed on-demand into a native Rust representation.
31///
32/// Example usage:
33/// ```
34/// use elf::abi::PT_LOAD;
35/// use elf::endian::AnyEndian;
36/// use elf::ElfBytes;
37/// use elf::segment::ProgramHeader;
38///
39/// let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
40/// let file_data = std::fs::read(path).unwrap();
41///
42/// let slice = file_data.as_slice();
43/// let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
44///
45/// // Get all the common ELF sections (if any). We have a lot of ELF work to do!
46/// let common_sections = file.find_common_data().unwrap();
47/// // ... do some stuff with the symtab, dynsyms etc
48///
49/// // It can also yield iterators on which we can do normal iterator things, like filtering
50/// // for all the segments of a specific type. Parsing is done on each iter.next() call, so
51/// // if you end iteration early, it won't parse the rest of the table.
52/// let first_load_phdr: Option<ProgramHeader> = file.segments().unwrap()
53///     .iter()
54///     .find(|phdr|{phdr.p_type == PT_LOAD});
55/// println!("First load segment is at: {}", first_load_phdr.unwrap().p_vaddr);
56///
57/// // Or if you do things like this to get a vec of only the PT_LOAD segments.
58/// let all_load_phdrs: Vec<ProgramHeader> = file.segments().unwrap()
59///     .iter()
60///     .filter(|phdr|{phdr.p_type == PT_LOAD})
61///     .collect();
62/// println!("There are {} PT_LOAD segments", all_load_phdrs.len());
63/// ```
64#[derive(Debug)]
65pub struct ElfBytes<'data, E: EndianParse> {
66    pub ehdr: FileHeader<E>,
67    data: &'data [u8],
68    shdrs: Option<SectionHeaderTable<'data, E>>,
69    phdrs: Option<SegmentTable<'data, E>>,
70}
71
72/// Find the location (if any) of the section headers in the given data buffer and take a
73/// subslice of their data and wrap it in a lazy-parsing SectionHeaderTable.
74/// If shnum > SHN_LORESERVE (0xff00), then this will additionally parse out shdr[0] to calculate
75/// the full table size, but all other parsing of SectionHeaders is deferred.
76fn find_shdrs<'data, E: EndianParse>(
77    ehdr: &FileHeader<E>,
78    data: &'data [u8],
79) -> Result<Option<SectionHeaderTable<'data, E>>, ParseError> {
80    // It's Ok to have no section headers
81    if ehdr.e_shoff == 0 {
82        return Ok(None);
83    }
84
85    // If the number of sections is greater than or equal to SHN_LORESERVE (0xff00),
86    // e_shnum is zero and the actual number of section header table entries
87    // is contained in the sh_size field of the section header at index 0.
88    let shoff: usize = ehdr.e_shoff.try_into()?;
89    let mut shnum = ehdr.e_shnum as usize;
90    if shnum == 0 {
91        let mut offset = shoff;
92        let shdr0 = SectionHeader::parse_at(ehdr.endianness, ehdr.class, &mut offset, data)?;
93        shnum = shdr0.sh_size.try_into()?;
94    }
95
96    // Validate shentsize before trying to read the table so that we can error early for corrupted files
97    let entsize = SectionHeader::validate_entsize(ehdr.class, ehdr.e_shentsize as usize)?;
98
99    let size = entsize
100        .checked_mul(shnum)
101        .ok_or(ParseError::IntegerOverflow)?;
102    let end = shoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?;
103    let buf = data.get_bytes(shoff..end)?;
104    Ok(Some(SectionHeaderTable::new(
105        ehdr.endianness,
106        ehdr.class,
107        buf,
108    )))
109}
110
111/// Find the location (if any) of the program headers in the given data buffer and take a
112/// subslice of their data and wrap it in a lazy-parsing SegmentTable.
113fn find_phdrs<'data, E: EndianParse>(
114    ehdr: &FileHeader<E>,
115    data: &'data [u8],
116) -> Result<Option<SegmentTable<'data, E>>, ParseError> {
117    // It's Ok to have no program headers
118    if ehdr.e_phoff == 0 {
119        return Ok(None);
120    }
121
122    // If the number of segments is greater than or equal to PN_XNUM (0xffff),
123    // e_phnum is set to PN_XNUM, and the actual number of program header table
124    // entries is contained in the sh_info field of the section header at index 0.
125    let mut phnum = ehdr.e_phnum as usize;
126    if phnum == abi::PN_XNUM as usize {
127        let shoff: usize = ehdr.e_shoff.try_into()?;
128        let mut offset = shoff;
129        let shdr0 = SectionHeader::parse_at(ehdr.endianness, ehdr.class, &mut offset, data)?;
130        phnum = shdr0.sh_info.try_into()?;
131    }
132
133    // Validate phentsize before trying to read the table so that we can error early for corrupted files
134    let entsize = ProgramHeader::validate_entsize(ehdr.class, ehdr.e_phentsize as usize)?;
135
136    let phoff: usize = ehdr.e_phoff.try_into()?;
137    let size = entsize
138        .checked_mul(phnum)
139        .ok_or(ParseError::IntegerOverflow)?;
140    let end = phoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?;
141    let buf = data.get_bytes(phoff..end)?;
142    Ok(Some(SegmentTable::new(ehdr.endianness, ehdr.class, buf)))
143}
144
145/// This struct collects the common sections found in ELF objects
146#[derive(Debug, Default)]
147pub struct CommonElfData<'data, E: EndianParse> {
148    /// .symtab section
149    pub symtab: Option<SymbolTable<'data, E>>,
150    /// strtab for .symtab
151    pub symtab_strs: Option<StringTable<'data>>,
152
153    /// .dynsym section
154    pub dynsyms: Option<SymbolTable<'data, E>>,
155    /// strtab for .dynsym
156    pub dynsyms_strs: Option<StringTable<'data>>,
157
158    /// .dynamic section or PT_DYNAMIC segment (both point to the same table)
159    pub dynamic: Option<DynamicTable<'data, E>>,
160
161    /// .hash section
162    pub sysv_hash: Option<SysVHashTable<'data, E>>,
163
164    /// .gnu.hash section
165    pub gnu_hash: Option<GnuHashTable<'data, E>>,
166}
167
168impl<'data, E: EndianParse> ElfBytes<'data, E> {
169    /// Do the minimal parsing work to get an [ElfBytes] handle from a byte slice containing an ELF object.
170    ///
171    /// This parses the ELF [FileHeader], and locates (but does not parse) the
172    /// Section Header Table and Segment Table.
173    ///
174    // N.B. I thought about calling this "sparse_parse", but it felt too silly for a serious lib like this
175    pub fn minimal_parse(data: &'data [u8]) -> Result<Self, ParseError> {
176        let ident_buf = data.get_bytes(0..abi::EI_NIDENT)?;
177        let ident = parse_ident(ident_buf)?;
178
179        let tail_start = abi::EI_NIDENT;
180        let tail_end = match ident.1 {
181            Class::ELF32 => tail_start + crate::file::ELF32_EHDR_TAILSIZE,
182            Class::ELF64 => tail_start + crate::file::ELF64_EHDR_TAILSIZE,
183        };
184        let tail_buf = data.get_bytes(tail_start..tail_end)?;
185
186        let ehdr = FileHeader::parse_tail(ident, tail_buf)?;
187
188        let shdrs = find_shdrs(&ehdr, data)?;
189        let phdrs = find_phdrs(&ehdr, data)?;
190        Ok(ElfBytes {
191            ehdr,
192            data,
193            shdrs,
194            phdrs,
195        })
196    }
197
198    /// Get this Elf object's zero-alloc lazy-parsing [SegmentTable] (if any).
199    ///
200    /// This table parses [ProgramHeader]s on demand and does not make any internal heap allocations
201    /// when parsing.
202    pub fn segments(&self) -> Option<SegmentTable<'data, E>> {
203        self.phdrs
204    }
205
206    /// Get this Elf object's zero-alloc lazy-parsing [SectionHeaderTable] (if any).
207    ///
208    /// This table parses [SectionHeader]s on demand and does not make any internal heap allocations
209    /// when parsing.
210    pub fn section_headers(&self) -> Option<SectionHeaderTable<'data, E>> {
211        self.shdrs
212    }
213
214    /// Get this ELF object's [SectionHeaderTable] alongside its corresponding [StringTable].
215    ///
216    /// This is useful if you want to know the string name of sections.
217    ///
218    /// Example usage:
219    /// ```
220    /// use std::collections::HashMap;
221    /// use elf::endian::AnyEndian;
222    /// use elf::ElfBytes;
223    /// use elf::note::Note;
224    /// use elf::note::NoteGnuBuildId;
225    /// use elf::section::SectionHeader;
226    ///
227    /// let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
228    /// let file_data = std::fs::read(path).unwrap();
229    ///
230    /// let slice = file_data.as_slice();
231    /// let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
232    ///
233    /// // Get the section header table alongside its string table
234    /// let (shdrs_opt, strtab_opt) = file
235    ///     .section_headers_with_strtab()
236    ///     .expect("shdrs offsets should be valid");
237    /// let (shdrs, strtab) = (
238    ///     shdrs_opt.expect("Should have shdrs"),
239    ///     strtab_opt.expect("Should have strtab")
240    /// );
241    ///
242    /// // Parse the shdrs and collect them into a map keyed on their zero-copied name
243    /// let with_names: HashMap<&str, SectionHeader> = shdrs
244    ///     .iter()
245    ///     .map(|shdr| {
246    ///         (
247    ///             strtab.get(shdr.sh_name as usize).expect("Failed to get section name"),
248    ///             shdr,
249    ///         )
250    ///     })
251    ///     .collect();
252    ///
253    /// // Get the zero-copy parsed type for the the build id note
254    /// let build_id_note_shdr: &SectionHeader = with_names
255    ///     .get(".note.gnu.build-id")
256    ///     .expect("Should have build id note section");
257    /// let notes: Vec<_> = file
258    ///     .section_data_as_notes(build_id_note_shdr)
259    ///     .expect("Should be able to get note section data")
260    ///     .collect();
261    /// println!("{:?}", notes[0]);
262    /// ```
263    pub fn section_headers_with_strtab(
264        &self,
265    ) -> Result<
266        (
267            Option<SectionHeaderTable<'data, E>>,
268            Option<StringTable<'data>>,
269        ),
270        ParseError,
271    > {
272        // It's Ok to have no section headers
273        let shdrs = match self.section_headers() {
274            Some(shdrs) => shdrs,
275            None => {
276                return Ok((None, None));
277            }
278        };
279
280        // It's Ok to not have a string table
281        if self.ehdr.e_shstrndx == abi::SHN_UNDEF {
282            return Ok((Some(shdrs), None));
283        }
284
285        // If the section name string table section index is greater than or
286        // equal to SHN_LORESERVE (0xff00), e_shstrndx has the value SHN_XINDEX
287        // (0xffff) and the actual index of the section name string table section
288        // is contained in the sh_link field of the section header at index 0.
289        let mut shstrndx = self.ehdr.e_shstrndx as usize;
290        if self.ehdr.e_shstrndx == abi::SHN_XINDEX {
291            let shdr_0 = shdrs.get(0)?;
292            shstrndx = shdr_0.sh_link as usize;
293        }
294
295        let strtab = shdrs.get(shstrndx)?;
296        let (strtab_start, strtab_end) = strtab.get_data_range()?;
297        let strtab_buf = self.data.get_bytes(strtab_start..strtab_end)?;
298        Ok((Some(shdrs), Some(StringTable::new(strtab_buf))))
299    }
300
301    /// Parse section headers until one is found with the given name
302    ///
303    /// Example to get the ELF file's ABI-tag note
304    /// ```
305    /// use elf::ElfBytes;
306    /// use elf::endian::AnyEndian;
307    /// use elf::section::SectionHeader;
308    /// use elf::note::Note;
309    /// use elf::note::NoteGnuAbiTag;
310    ///
311    /// let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
312    /// let file_data = std::fs::read(path).unwrap();
313    /// let slice = file_data.as_slice();
314    /// let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
315    ///
316    /// let shdr: SectionHeader = file
317    ///     .section_header_by_name(".note.ABI-tag")
318    ///     .expect("section table should be parseable")
319    ///     .expect("file should have a .note.ABI-tag section");
320    ///
321    /// let notes: Vec<_> = file
322    ///     .section_data_as_notes(&shdr)
323    ///     .expect("Should be able to get note section data")
324    ///     .collect();
325    /// assert_eq!(
326    ///     notes[0],
327    ///     Note::GnuAbiTag(NoteGnuAbiTag {
328    ///         os: 0,
329    ///         major: 2,
330    ///         minor: 6,
331    ///         subminor: 32
332    ///     }));
333    /// ```
334    pub fn section_header_by_name(&self, name: &str) -> Result<Option<SectionHeader>, ParseError> {
335        let (shdrs, strtab) = match self.section_headers_with_strtab()? {
336            (Some(shdrs), Some(strtab)) => (shdrs, strtab),
337            _ => {
338                // If we don't have shdrs, or don't have a strtab, we can't find a section by its name
339                return Ok(None);
340            }
341        };
342
343        Ok(shdrs.iter().find(|shdr| {
344            let sh_name = match strtab.get(shdr.sh_name as usize) {
345                Ok(name) => name,
346                _ => {
347                    return false;
348                }
349            };
350            name == sh_name
351        }))
352    }
353
354    /// Efficiently locate the set of common sections found in ELF files by doing a single iteration
355    /// over the SectionHeaders table.
356    ///
357    /// This is useful for those who know they're going to be accessing multiple common sections, like
358    /// symbol tables, string tables. Many of these can also be accessed by the more targeted
359    /// helpers like [ElfBytes::symbol_table] or [ElfBytes::dynamic], though those each do their own
360    /// internal searches through the shdrs to find the section.
361    pub fn find_common_data(&self) -> Result<CommonElfData<'data, E>, ParseError> {
362        let mut result: CommonElfData<'data, E> = CommonElfData::default();
363
364        // Iterate once over the shdrs to collect up any known sections
365        if let Some(shdrs) = self.shdrs {
366            for shdr in shdrs.iter() {
367                match shdr.sh_type {
368                    abi::SHT_SYMTAB => {
369                        let strtab_shdr = shdrs.get(shdr.sh_link as usize)?;
370                        let (symtab, strtab) =
371                            self.section_data_as_symbol_table(&shdr, &strtab_shdr)?;
372
373                        result.symtab = Some(symtab);
374                        result.symtab_strs = Some(strtab);
375                    }
376                    abi::SHT_DYNSYM => {
377                        let strtab_shdr = shdrs.get(shdr.sh_link as usize)?;
378                        let (symtab, strtab) =
379                            self.section_data_as_symbol_table(&shdr, &strtab_shdr)?;
380
381                        result.dynsyms = Some(symtab);
382                        result.dynsyms_strs = Some(strtab);
383                    }
384                    abi::SHT_DYNAMIC => {
385                        result.dynamic = Some(self.section_data_as_dynamic(&shdr)?);
386                    }
387                    abi::SHT_HASH => {
388                        let (start, end) = shdr.get_data_range()?;
389                        let buf = self.data.get_bytes(start..end)?;
390                        result.sysv_hash = Some(SysVHashTable::new(
391                            self.ehdr.endianness,
392                            self.ehdr.class,
393                            buf,
394                        )?);
395                    }
396                    abi::SHT_GNU_HASH => {
397                        let (start, end) = shdr.get_data_range()?;
398                        let buf = self.data.get_bytes(start..end)?;
399                        result.gnu_hash = Some(GnuHashTable::new(
400                            self.ehdr.endianness,
401                            self.ehdr.class,
402                            buf,
403                        )?);
404                    }
405                    _ => {
406                        continue;
407                    }
408                }
409            }
410        }
411
412        // If we didn't find SHT_DYNAMIC from the section headers, try the program headers
413        if result.dynamic.is_none() {
414            if let Some(phdrs) = self.phdrs {
415                if let Some(dyn_phdr) = phdrs.iter().find(|phdr| phdr.p_type == abi::PT_DYNAMIC) {
416                    let (start, end) = dyn_phdr.get_file_data_range()?;
417                    let buf = self.data.get_bytes(start..end)?;
418                    result.dynamic = Some(DynamicTable::new(
419                        self.ehdr.endianness,
420                        self.ehdr.class,
421                        buf,
422                    ));
423                }
424            }
425        }
426
427        Ok(result)
428    }
429
430    /// Get the section data for a given [SectionHeader], alongside an optional compression context.
431    ///
432    /// This library does not do any decompression for the user, but merely returns the raw compressed
433    /// section data if the section is compressed alongside its ELF compression structure describing the
434    /// compression algorithm used.
435    ///
436    /// Users who wish to work with compressed sections must pick their compression library of choice
437    /// and do the decompression themselves. The only two options supported by the ELF spec for section
438    /// compression are: [abi::ELFCOMPRESS_ZLIB] and [abi::ELFCOMPRESS_ZSTD].
439    pub fn section_data(
440        &self,
441        shdr: &SectionHeader,
442    ) -> Result<(&'data [u8], Option<CompressionHeader>), ParseError> {
443        if shdr.sh_type == abi::SHT_NOBITS {
444            return Ok((&[], None));
445        }
446
447        let (start, end) = shdr.get_data_range()?;
448        let buf = self.data.get_bytes(start..end)?;
449
450        if shdr.sh_flags & abi::SHF_COMPRESSED as u64 == 0 {
451            Ok((buf, None))
452        } else {
453            let mut offset = 0;
454            let chdr = CompressionHeader::parse_at(
455                self.ehdr.endianness,
456                self.ehdr.class,
457                &mut offset,
458                buf,
459            )?;
460            let compressed_buf = buf.get(offset..).ok_or(ParseError::SliceReadError((
461                offset,
462                shdr.sh_size.try_into()?,
463            )))?;
464            Ok((compressed_buf, Some(chdr)))
465        }
466    }
467
468    /// Get the section data for a given [SectionHeader], and interpret it as a [StringTable]
469    ///
470    /// Returns a ParseError if the section is not of type [abi::SHT_STRTAB]
471    pub fn section_data_as_strtab(
472        &self,
473        shdr: &SectionHeader,
474    ) -> Result<StringTable<'data>, ParseError> {
475        if shdr.sh_type != abi::SHT_STRTAB {
476            return Err(ParseError::UnexpectedSectionType((
477                shdr.sh_type,
478                abi::SHT_STRTAB,
479            )));
480        }
481
482        let (buf, _) = self.section_data(shdr)?;
483        Ok(StringTable::new(buf))
484    }
485
486    /// Get the section data for a given [SectionHeader], and interpret it as an
487    /// iterator over no-addend relocations [Rel](crate::relocation::Rel)
488    ///
489    /// Returns a ParseError if the section is not of type [abi::SHT_REL]
490    pub fn section_data_as_rels(
491        &self,
492        shdr: &SectionHeader,
493    ) -> Result<RelIterator<'data, E>, ParseError> {
494        if shdr.sh_type != abi::SHT_REL {
495            return Err(ParseError::UnexpectedSectionType((
496                shdr.sh_type,
497                abi::SHT_REL,
498            )));
499        }
500
501        let (buf, _) = self.section_data(shdr)?;
502        Ok(RelIterator::new(self.ehdr.endianness, self.ehdr.class, buf))
503    }
504
505    /// Get the section data for a given [SectionHeader], and interpret it as an
506    /// iterator over relocations with addends [Rela](crate::relocation::Rela)
507    ///
508    /// Returns a ParseError if the section is not of type [abi::SHT_RELA]
509    pub fn section_data_as_relas(
510        &self,
511        shdr: &SectionHeader,
512    ) -> Result<RelaIterator<'data, E>, ParseError> {
513        if shdr.sh_type != abi::SHT_RELA {
514            return Err(ParseError::UnexpectedSectionType((
515                shdr.sh_type,
516                abi::SHT_RELA,
517            )));
518        }
519
520        let (buf, _) = self.section_data(shdr)?;
521        Ok(RelaIterator::new(
522            self.ehdr.endianness,
523            self.ehdr.class,
524            buf,
525        ))
526    }
527
528    /// Get the section data for a given [SectionHeader], and interpret it as an
529    /// iterator over [Note](crate::note::Note)s
530    ///
531    /// Returns a ParseError if the section is not of type [abi::SHT_NOTE]
532    pub fn section_data_as_notes(
533        &self,
534        shdr: &SectionHeader,
535    ) -> Result<NoteIterator<'data, E>, ParseError> {
536        if shdr.sh_type != abi::SHT_NOTE {
537            return Err(ParseError::UnexpectedSectionType((
538                shdr.sh_type,
539                abi::SHT_NOTE,
540            )));
541        }
542
543        let (buf, _) = self.section_data(shdr)?;
544        Ok(NoteIterator::new(
545            self.ehdr.endianness,
546            self.ehdr.class,
547            shdr.sh_addralign as usize,
548            buf,
549        ))
550    }
551
552    /// Internal helper to get the section data for an SHT_DYNAMIC section as a .dynamic section table.
553    /// See [ElfBytes::dynamic] or [ElfBytes::find_common_data] for the public interface
554    fn section_data_as_dynamic(
555        &self,
556        shdr: &SectionHeader,
557    ) -> Result<DynamicTable<'data, E>, ParseError> {
558        if shdr.sh_type != abi::SHT_DYNAMIC {
559            return Err(ParseError::UnexpectedSectionType((
560                shdr.sh_type,
561                abi::SHT_DYNAMIC,
562            )));
563        }
564
565        // Validate entsize before trying to read the table so that we can error early for corrupted files
566        Dyn::validate_entsize(self.ehdr.class, shdr.sh_entsize.try_into()?)?;
567        let (buf, _) = self.section_data(shdr)?;
568        Ok(DynamicTable::new(
569            self.ehdr.endianness,
570            self.ehdr.class,
571            buf,
572        ))
573    }
574
575    /// Get the segment's file data for a given segment/[ProgramHeader].
576    ///
577    /// This is the segment's data as found in the file.
578    pub fn segment_data(&self, phdr: &ProgramHeader) -> Result<&'data [u8], ParseError> {
579        let (start, end) = phdr.get_file_data_range()?;
580        self.data.get_bytes(start..end)
581    }
582
583    /// Get the segment's file data for a given [ProgramHeader], and interpret it as an
584    /// iterator over [Note](crate::note::Note)s
585    ///
586    /// Returns a ParseError if the section is not of type [abi::PT_NOTE]
587    pub fn segment_data_as_notes(
588        &self,
589        phdr: &ProgramHeader,
590    ) -> Result<NoteIterator<'data, E>, ParseError> {
591        if phdr.p_type != abi::PT_NOTE {
592            return Err(ParseError::UnexpectedSegmentType((
593                phdr.p_type,
594                abi::PT_NOTE,
595            )));
596        }
597
598        let buf = self.segment_data(phdr)?;
599        Ok(NoteIterator::new(
600            self.ehdr.endianness,
601            self.ehdr.class,
602            phdr.p_align as usize,
603            buf,
604        ))
605    }
606
607    /// Get the .dynamic section or [abi::PT_DYNAMIC] segment contents.
608    pub fn dynamic(&self) -> Result<Option<DynamicTable<'data, E>>, ParseError> {
609        // If we have section headers, look for the SHT_DYNAMIC section
610        if let Some(shdrs) = self.section_headers() {
611            if let Some(shdr) = shdrs.iter().find(|shdr| shdr.sh_type == abi::SHT_DYNAMIC) {
612                return Ok(Some(self.section_data_as_dynamic(&shdr)?));
613            }
614        // Otherwise, look up the PT_DYNAMIC segment (if any)
615        } else if let Some(phdrs) = self.segments() {
616            if let Some(phdr) = phdrs.iter().find(|phdr| phdr.p_type == abi::PT_DYNAMIC) {
617                let (start, end) = phdr.get_file_data_range()?;
618                let buf = self.data.get_bytes(start..end)?;
619                return Ok(Some(DynamicTable::new(
620                    self.ehdr.endianness,
621                    self.ehdr.class,
622                    buf,
623                )));
624            }
625        }
626
627        Ok(None)
628    }
629
630    /// Helper method to get the section data for a given pair of [SectionHeader] for the symbol
631    /// table and its linked strtab, and interpret them as [SymbolTable] and [StringTable].
632    fn section_data_as_symbol_table(
633        &self,
634        shdr: &SectionHeader,
635        strtab_shdr: &SectionHeader,
636    ) -> Result<(SymbolTable<'data, E>, StringTable<'data>), ParseError> {
637        // Validate entsize before trying to read the table so that we can error early for corrupted files
638        Symbol::validate_entsize(self.ehdr.class, shdr.sh_entsize.try_into()?)?;
639
640        // Load the section bytes for the symtab
641        // (we want immutable references to both the symtab and its strtab concurrently)
642        let (symtab_start, symtab_end) = shdr.get_data_range()?;
643        let symtab_buf = self.data.get_bytes(symtab_start..symtab_end)?;
644
645        // Load the section bytes for the strtab
646        // (we want immutable references to both the symtab and its strtab concurrently)
647        let (strtab_start, strtab_end) = strtab_shdr.get_data_range()?;
648        let strtab_buf = self.data.get_bytes(strtab_start..strtab_end)?;
649
650        let symtab = SymbolTable::new(self.ehdr.endianness, self.ehdr.class, symtab_buf);
651        let strtab = StringTable::new(strtab_buf);
652        Ok((symtab, strtab))
653    }
654
655    /// Get the ELF file's `.symtab` and associated strtab (if any)
656    pub fn symbol_table(
657        &self,
658    ) -> Result<Option<(SymbolTable<'data, E>, StringTable<'data>)>, ParseError> {
659        let shdrs = match self.section_headers() {
660            Some(shdrs) => shdrs,
661            None => {
662                return Ok(None);
663            }
664        };
665
666        // Get the symtab header for the symtab. The GABI states there can be zero or one per ELF file.
667        let symtab_shdr = match shdrs.iter().find(|shdr| shdr.sh_type == abi::SHT_SYMTAB) {
668            Some(shdr) => shdr,
669            None => {
670                return Ok(None);
671            }
672        };
673
674        let strtab_shdr = shdrs.get(symtab_shdr.sh_link as usize)?;
675        Ok(Some(self.section_data_as_symbol_table(
676            &symtab_shdr,
677            &strtab_shdr,
678        )?))
679    }
680
681    /// Get the ELF file's `.dynsym` and associated strtab (if any)
682    pub fn dynamic_symbol_table(
683        &self,
684    ) -> Result<Option<(SymbolTable<'data, E>, StringTable<'data>)>, ParseError> {
685        let shdrs = match self.section_headers() {
686            Some(shdrs) => shdrs,
687            None => {
688                return Ok(None);
689            }
690        };
691
692        // Get the symtab header for the symtab. The GABI states there can be zero or one per ELF file.
693        let symtab_shdr = match shdrs.iter().find(|shdr| shdr.sh_type == abi::SHT_DYNSYM) {
694            Some(shdr) => shdr,
695            None => {
696                return Ok(None);
697            }
698        };
699
700        let strtab_shdr = shdrs.get(symtab_shdr.sh_link as usize)?;
701        Ok(Some(self.section_data_as_symbol_table(
702            &symtab_shdr,
703            &strtab_shdr,
704        )?))
705    }
706
707    /// Locate the section data for the various GNU Symbol Versioning sections (if any)
708    /// and return them in a [SymbolVersionTable] that which can interpret them in-place to
709    /// yield [SymbolRequirement](crate::gnu_symver::SymbolRequirement)s
710    /// and [SymbolDefinition](crate::gnu_symver::SymbolDefinition)s
711    ///
712    /// This is a GNU extension and not all objects use symbol versioning.
713    /// Returns an empty Option if the object does not use symbol versioning.
714    pub fn symbol_version_table(&self) -> Result<Option<SymbolVersionTable<'data, E>>, ParseError> {
715        // No sections means no GNU symbol versioning sections, which is ok
716        let shdrs = match self.section_headers() {
717            Some(shdrs) => shdrs,
718            None => {
719                return Ok(None);
720            }
721        };
722
723        let mut versym_opt: Option<SectionHeader> = None;
724        let mut needs_opt: Option<SectionHeader> = None;
725        let mut defs_opt: Option<SectionHeader> = None;
726        // Find the GNU Symbol versioning sections (if any)
727        for shdr in shdrs.iter() {
728            if shdr.sh_type == abi::SHT_GNU_VERSYM {
729                versym_opt = Some(shdr);
730            } else if shdr.sh_type == abi::SHT_GNU_VERNEED {
731                needs_opt = Some(shdr);
732            } else if shdr.sh_type == abi::SHT_GNU_VERDEF {
733                defs_opt = Some(shdr);
734            }
735
736            // If we've found all three sections, then we're done
737            if versym_opt.is_some() && needs_opt.is_some() && defs_opt.is_some() {
738                break;
739            }
740        }
741
742        let versym_shdr = match versym_opt {
743            Some(shdr) => shdr,
744            // No VERSYM section means the object doesn't use symbol versioning, which is ok.
745            None => {
746                return Ok(None);
747            }
748        };
749
750        // Load the versym table
751        // Validate VERSYM entsize before trying to read the table so that we can error early for corrupted files
752        VersionIndex::validate_entsize(self.ehdr.class, versym_shdr.sh_entsize.try_into()?)?;
753        let (versym_start, versym_end) = versym_shdr.get_data_range()?;
754        let version_ids = VersionIndexTable::new(
755            self.ehdr.endianness,
756            self.ehdr.class,
757            self.data.get_bytes(versym_start..versym_end)?,
758        );
759
760        // Wrap the VERNEED section and strings data in an iterator and string table (if any)
761        let verneeds = match needs_opt {
762            Some(shdr) => {
763                let (start, end) = shdr.get_data_range()?;
764                let needs_buf = self.data.get_bytes(start..end)?;
765
766                let strs_shdr = shdrs.get(shdr.sh_link as usize)?;
767                let (strs_start, strs_end) = strs_shdr.get_data_range()?;
768                let strs_buf = self.data.get_bytes(strs_start..strs_end)?;
769
770                Some((
771                    VerNeedIterator::new(
772                        self.ehdr.endianness,
773                        self.ehdr.class,
774                        shdr.sh_info as u64,
775                        0,
776                        needs_buf,
777                    ),
778                    StringTable::new(strs_buf),
779                ))
780            }
781            // It's possible to have symbol versioning with no NEEDs if we're an object that only
782            // exports defined symbols.
783            None => None,
784        };
785
786        // Wrap the VERDEF section and strings data in an iterator and string table (if any)
787        let verdefs = match defs_opt {
788            Some(shdr) => {
789                let (start, end) = shdr.get_data_range()?;
790                let defs_buf = self.data.get_bytes(start..end)?;
791
792                let strs_shdr = shdrs.get(shdr.sh_link as usize)?;
793                let (strs_start, strs_end) = strs_shdr.get_data_range()?;
794                let strs_buf = self.data.get_bytes(strs_start..strs_end)?;
795
796                Some((
797                    VerDefIterator::new(
798                        self.ehdr.endianness,
799                        self.ehdr.class,
800                        shdr.sh_info as u64,
801                        0,
802                        defs_buf,
803                    ),
804                    StringTable::new(strs_buf),
805                ))
806            }
807            // It's possible to have symbol versioning with no NEEDs if we're an object that only
808            // exports defined symbols.
809            None => None,
810        };
811
812        // whew, we're done here!
813        Ok(Some(SymbolVersionTable::new(
814            version_ids,
815            verneeds,
816            verdefs,
817        )))
818    }
819}
820
821//  _            _
822// | |_ ___  ___| |_ ___
823// | __/ _ \/ __| __/ __|
824// | ||  __/\__ \ |_\__ \
825//  \__\___||___/\__|___/
826//
827
828#[cfg(test)]
829mod interface_tests {
830    use super::*;
831    use crate::abi::{SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB};
832    use crate::dynamic::Dyn;
833    use crate::endian::AnyEndian;
834    use crate::hash::sysv_hash;
835    use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId};
836    use crate::relocation::Rela;
837    use crate::segment::ProgramHeader;
838
839    #[test]
840    fn simultaenous_segments_parsing() {
841        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
842        let file_data = std::fs::read(path).expect("Could not read file.");
843        let slice = file_data.as_slice();
844        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
845
846        // With the bytes interface, we should be able to get multiple lazy-parsing types concurrently,
847        // since the trait is implemented for shared references.
848        //
849        // Get the segment table
850        let iter = file.segments().expect("File should have a segment table");
851
852        // Concurrently get the segment table again as an iterator and collect the headers into a vec
853        let segments: Vec<ProgramHeader> = file
854            .segments()
855            .expect("File should have a segment table")
856            .iter()
857            .collect();
858
859        let expected_phdr = ProgramHeader {
860            p_type: abi::PT_PHDR,
861            p_offset: 64,
862            p_vaddr: 4194368,
863            p_paddr: 4194368,
864            p_filesz: 448,
865            p_memsz: 448,
866            p_flags: 5,
867            p_align: 8,
868        };
869
870        // Assert we parsed the first header correctly
871        assert_eq!(segments[0], expected_phdr);
872
873        // Now use the original lazy-parsing table to parse out the first entry
874        assert_eq!(
875            iter.get(0).expect("should be able to parse phdr"),
876            expected_phdr
877        )
878    }
879
880    #[test]
881    fn segments() {
882        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
883        let file_data = std::fs::read(path).expect("Could not read file.");
884        let slice = file_data.as_slice();
885        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
886
887        let segments: Vec<ProgramHeader> = file
888            .segments()
889            .expect("File should have a segment table")
890            .iter()
891            .collect();
892        assert_eq!(
893            segments[0],
894            ProgramHeader {
895                p_type: abi::PT_PHDR,
896                p_offset: 64,
897                p_vaddr: 4194368,
898                p_paddr: 4194368,
899                p_filesz: 448,
900                p_memsz: 448,
901                p_flags: 5,
902                p_align: 8,
903            }
904        );
905    }
906
907    #[test]
908    fn segments_phnum_in_shdr0() {
909        let path = std::path::PathBuf::from("sample-objects/phnum.m68k.so");
910        let file_data = std::fs::read(path).expect("Could not read file.");
911        let slice = file_data.as_slice();
912        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
913
914        let segments: Vec<ProgramHeader> = file
915            .segments()
916            .expect("File should have a segment table")
917            .iter()
918            .collect();
919        assert_eq!(
920            segments[0],
921            ProgramHeader {
922                p_type: abi::PT_PHDR,
923                p_offset: 92,
924                p_vaddr: 0,
925                p_paddr: 0,
926                p_filesz: 32,
927                p_memsz: 32,
928                p_flags: 0x20003,
929                p_align: 0x40000,
930            }
931        );
932    }
933
934    #[test]
935    fn section_headers() {
936        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
937        let file_data = std::fs::read(path).expect("Could not read file.");
938        let slice = file_data.as_slice();
939        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
940
941        let shdrs = file
942            .section_headers()
943            .expect("File should have a section table");
944
945        let shdrs_vec: Vec<SectionHeader> = shdrs.iter().collect();
946
947        assert_eq!(shdrs_vec[4].sh_type, SHT_GNU_HASH);
948    }
949
950    #[test]
951    fn section_headers_with_strtab() {
952        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
953        let file_data = std::fs::read(path).expect("Could not read file.");
954        let slice = file_data.as_slice();
955        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
956
957        let (shdrs, strtab) = file
958            .section_headers_with_strtab()
959            .expect("shdrs should be parsable");
960        let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap());
961
962        let with_names: Vec<(&str, SectionHeader)> = shdrs
963            .iter()
964            .map(|shdr| {
965                (
966                    strtab
967                        .get(shdr.sh_name as usize)
968                        .expect("Failed to get section name"),
969                    shdr,
970                )
971            })
972            .collect();
973
974        let (name, shdr) = with_names[4];
975        assert_eq!(name, ".gnu.hash");
976        assert_eq!(shdr.sh_type, abi::SHT_GNU_HASH);
977    }
978
979    #[test]
980    fn shnum_and_shstrndx_in_shdr0() {
981        let path = std::path::PathBuf::from("sample-objects/shnum.x86_64");
982        let file_data = std::fs::read(path).expect("Could not read file.");
983        let slice = file_data.as_slice();
984        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
985
986        let (shdrs, strtab) = file
987            .section_headers_with_strtab()
988            .expect("shdrs should be parsable");
989        let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap());
990
991        let shdrs_len = shdrs.len();
992        assert_eq!(shdrs_len, 0xFF15);
993
994        let shdr = shdrs.get(shdrs_len - 1).unwrap();
995        let name = strtab
996            .get(shdr.sh_name as usize)
997            .expect("Failed to get section name");
998
999        assert_eq!(name, ".shstrtab");
1000        assert_eq!(shdr.sh_type, abi::SHT_STRTAB);
1001    }
1002
1003    #[test]
1004    fn section_header_by_name() {
1005        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1006        let file_data = std::fs::read(path).expect("Could not read file.");
1007        let slice = file_data.as_slice();
1008        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1009
1010        let shdr = file
1011            .section_header_by_name(".gnu.hash")
1012            .expect("section table should be parseable")
1013            .expect("file should have .gnu.hash section");
1014
1015        assert_eq!(shdr.sh_type, SHT_GNU_HASH);
1016
1017        let shdr = file
1018            .section_header_by_name(".not.found")
1019            .expect("section table should be parseable");
1020
1021        assert_eq!(shdr, None);
1022    }
1023
1024    #[test]
1025    fn find_common_data() {
1026        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1027        let file_data = std::fs::read(path).expect("Could not read file.");
1028        let slice = file_data.as_slice();
1029        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1030
1031        let elf_scns = file.find_common_data().expect("file should parse");
1032
1033        // hello.so should find everything
1034        assert!(elf_scns.symtab.is_some());
1035        assert!(elf_scns.symtab_strs.is_some());
1036        assert!(elf_scns.dynsyms.is_some());
1037        assert!(elf_scns.dynsyms_strs.is_some());
1038        assert!(elf_scns.dynamic.is_some());
1039        assert!(elf_scns.sysv_hash.is_some());
1040        assert!(elf_scns.gnu_hash.is_some());
1041    }
1042
1043    #[test]
1044    fn section_data() {
1045        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1046        let file_data = std::fs::read(path).expect("Could not read file.");
1047        let slice = file_data.as_slice();
1048        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1049
1050        let shdr = file
1051            .section_headers()
1052            .expect("File should have section table")
1053            .get(26)
1054            .expect("shdr should be parsable");
1055
1056        assert_eq!(shdr.sh_type, SHT_NOBITS);
1057
1058        let (data, chdr) = file
1059            .section_data(&shdr)
1060            .expect("Failed to get section data");
1061
1062        assert_eq!(chdr, None);
1063        assert_eq!(data, &[]);
1064    }
1065
1066    // Test all the different section_data_as* with a section of the wrong type
1067    #[test]
1068    fn section_data_as_wrong_type() {
1069        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1070        let file_data = std::fs::read(path).expect("Could not read file.");
1071        let slice = file_data.as_slice();
1072        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1073
1074        // Section 0 is SHT_NULL, so all of the section_data_as* should error on it
1075        let shdr = file
1076            .section_headers()
1077            .expect("File should have section table")
1078            .get(0)
1079            .expect("shdr should be parsable");
1080
1081        let err = file
1082            .section_data_as_strtab(&shdr)
1083            .expect_err("shdr0 should be the wrong type");
1084        assert!(
1085            matches!(
1086                err,
1087                ParseError::UnexpectedSectionType((SHT_NULL, SHT_STRTAB))
1088            ),
1089            "Unexpected Error type found: {err}"
1090        );
1091
1092        let err = file
1093            .section_data_as_rels(&shdr)
1094            .expect_err("shdr0 should be the wrong type");
1095        assert!(
1096            matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_REL))),
1097            "Unexpected Error type found: {err}"
1098        );
1099
1100        let err = file
1101            .section_data_as_relas(&shdr)
1102            .expect_err("shdr0 should be the wrong type");
1103        assert!(
1104            matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_RELA))),
1105            "Unexpected Error type found: {err}"
1106        );
1107
1108        let err = file
1109            .section_data_as_notes(&shdr)
1110            .expect_err("shdr0 should be the wrong type");
1111        assert!(
1112            matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_NOTE))),
1113            "Unexpected Error type found: {err}"
1114        );
1115    }
1116
1117    #[test]
1118    fn section_data_as_strtab() {
1119        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1120        let file_data = std::fs::read(path).expect("Could not read file.");
1121        let slice = file_data.as_slice();
1122        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1123
1124        let shdr = file
1125            .section_headers()
1126            .expect("File should have section table")
1127            .get(file.ehdr.e_shstrndx as usize)
1128            .expect("shdr should be parsable");
1129
1130        let strtab = file
1131            .section_data_as_strtab(&shdr)
1132            .expect("Failed to read strtab");
1133
1134        assert_eq!(
1135            strtab.get(1).expect("Failed to get strtab entry"),
1136            ".symtab"
1137        );
1138    }
1139
1140    #[test]
1141    fn section_data_as_relas() {
1142        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1143        let file_data = std::fs::read(path).expect("Could not read file.");
1144        let slice = file_data.as_slice();
1145        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1146
1147        let shdr = file
1148            .section_headers()
1149            .expect("File should have section table")
1150            .get(10)
1151            .expect("Failed to get rela shdr");
1152
1153        let mut relas = file
1154            .section_data_as_relas(&shdr)
1155            .expect("Failed to read relas section");
1156        assert_eq!(
1157            relas.next().expect("Failed to get rela entry"),
1158            Rela {
1159                r_offset: 6293704,
1160                r_sym: 1,
1161                r_type: 7,
1162                r_addend: 0,
1163            }
1164        );
1165        assert_eq!(
1166            relas.next().expect("Failed to get rela entry"),
1167            Rela {
1168                r_offset: 6293712,
1169                r_sym: 2,
1170                r_type: 7,
1171                r_addend: 0,
1172            }
1173        );
1174        assert!(relas.next().is_none());
1175    }
1176
1177    #[test]
1178    fn section_data_as_notes() {
1179        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1180        let file_data = std::fs::read(path).expect("Could not read file.");
1181        let slice = file_data.as_slice();
1182        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1183
1184        let shdr = file
1185            .section_headers()
1186            .expect("File should have section table")
1187            .get(2)
1188            .expect("Failed to get note shdr");
1189
1190        let mut notes = file
1191            .section_data_as_notes(&shdr)
1192            .expect("Failed to read note section");
1193        assert_eq!(
1194            notes.next().expect("Failed to get first note"),
1195            Note::GnuAbiTag(NoteGnuAbiTag {
1196                os: 0,
1197                major: 2,
1198                minor: 6,
1199                subminor: 32
1200            })
1201        );
1202        assert!(notes.next().is_none());
1203    }
1204
1205    #[test]
1206    fn segment_data_as_notes() {
1207        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1208        let file_data = std::fs::read(path).expect("Could not read file.");
1209        let slice = file_data.as_slice();
1210        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1211
1212        let phdr = file
1213            .segments()
1214            .expect("File should have segmetn table")
1215            .get(5)
1216            .expect("Failed to get notes phdr");
1217
1218        let mut notes = file
1219            .segment_data_as_notes(&phdr)
1220            .expect("Failed to read notes segment");
1221        assert_eq!(
1222            notes.next().expect("Failed to get first note"),
1223            Note::GnuAbiTag(NoteGnuAbiTag {
1224                os: 0,
1225                major: 2,
1226                minor: 6,
1227                subminor: 32
1228            })
1229        );
1230        assert_eq!(
1231            notes.next().expect("Failed to get second note"),
1232            Note::GnuBuildId(NoteGnuBuildId(&[
1233                119, 65, 159, 13, 165, 16, 131, 12, 87, 167, 200, 204, 176, 238, 133, 95, 238, 211,
1234                118, 163
1235            ]))
1236        );
1237        assert!(notes.next().is_none());
1238    }
1239
1240    #[test]
1241    fn dynamic() {
1242        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1243        let file_data = std::fs::read(path).expect("Could not read file.");
1244        let slice = file_data.as_slice();
1245        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1246
1247        let mut dynamic = file
1248            .dynamic()
1249            .expect("Failed to parse .dynamic")
1250            .expect("Failed to find .dynamic")
1251            .iter();
1252        assert_eq!(
1253            dynamic.next().expect("Failed to get dyn entry"),
1254            Dyn {
1255                d_tag: abi::DT_NEEDED,
1256                d_un: 1
1257            }
1258        );
1259        assert_eq!(
1260            dynamic.next().expect("Failed to get dyn entry"),
1261            Dyn {
1262                d_tag: abi::DT_INIT,
1263                d_un: 4195216
1264            }
1265        );
1266    }
1267
1268    #[test]
1269    fn symbol_table() {
1270        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1271        let file_data = std::fs::read(path).expect("Could not read file.");
1272        let slice = file_data.as_slice();
1273        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1274
1275        let (symtab, strtab) = file
1276            .symbol_table()
1277            .expect("Failed to read symbol table")
1278            .expect("Failed to find symbol table");
1279        let symbol = symtab.get(30).expect("Failed to get symbol");
1280        assert_eq!(
1281            symbol,
1282            Symbol {
1283                st_name: 19,
1284                st_value: 6293200,
1285                st_size: 0,
1286                st_shndx: 21,
1287                st_info: 1,
1288                st_other: 0,
1289            }
1290        );
1291        assert_eq!(
1292            strtab
1293                .get(symbol.st_name as usize)
1294                .expect("Failed to get name from strtab"),
1295            "__JCR_LIST__"
1296        );
1297    }
1298
1299    #[test]
1300    fn dynamic_symbol_table() {
1301        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1302        let file_data = std::fs::read(path).expect("Could not read file.");
1303        let slice = file_data.as_slice();
1304        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1305
1306        let (symtab, strtab) = file
1307            .dynamic_symbol_table()
1308            .expect("Failed to read symbol table")
1309            .expect("Failed to find symbol table");
1310        let symbol = symtab.get(1).expect("Failed to get symbol");
1311        assert_eq!(
1312            symbol,
1313            Symbol {
1314                st_name: 11,
1315                st_value: 0,
1316                st_size: 0,
1317                st_shndx: 0,
1318                st_info: 18,
1319                st_other: 0,
1320            }
1321        );
1322        assert_eq!(
1323            strtab
1324                .get(symbol.st_name as usize)
1325                .expect("Failed to get name from strtab"),
1326            "memset"
1327        );
1328    }
1329
1330    #[test]
1331    fn symbol_version_table() {
1332        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1333        let file_data = std::fs::read(path).expect("Could not read file.");
1334        let slice = file_data.as_slice();
1335        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1336
1337        let vst = file
1338            .symbol_version_table()
1339            .expect("Failed to parse GNU symbol versions")
1340            .expect("Failed to find GNU symbol versions");
1341
1342        let req = vst
1343            .get_requirement(2)
1344            .expect("Failed to parse NEED")
1345            .expect("Failed to find NEED");
1346        assert_eq!(req.file, "libc.so.6");
1347        assert_eq!(req.name, "GLIBC_2.2.5");
1348        assert_eq!(req.hash, 0x9691A75);
1349
1350        let req = vst.get_requirement(3).expect("Failed to parse NEED");
1351        assert!(req.is_none());
1352
1353        let req = vst.get_requirement(4).expect("Failed to parse NEED");
1354        assert!(req.is_none());
1355
1356        let req = vst
1357            .get_requirement(5)
1358            .expect("Failed to parse NEED")
1359            .expect("Failed to find NEED");
1360        assert_eq!(req.file, "libc.so.6");
1361        assert_eq!(req.name, "GLIBC_2.2.5");
1362        assert_eq!(req.hash, 0x9691A75);
1363
1364        let def = vst
1365            .get_definition(3)
1366            .expect("Failed to parse DEF")
1367            .expect("Failed to find DEF");
1368        assert_eq!(def.hash, 0xC33237F);
1369        assert_eq!(def.flags, 1);
1370        assert!(!def.hidden);
1371        let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect();
1372        assert_eq!(def_names, &["hello.so"]);
1373
1374        let def = vst
1375            .get_definition(7)
1376            .expect("Failed to parse DEF")
1377            .expect("Failed to find DEF");
1378        assert_eq!(def.hash, 0x1570B62);
1379        assert_eq!(def.flags, 0);
1380        assert!(def.hidden);
1381        let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect();
1382        assert_eq!(def_names, &["HELLO_1.42"]);
1383    }
1384
1385    #[test]
1386    fn sysv_hash_table() {
1387        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1388        let file_data = std::fs::read(path).expect("Could not read file.");
1389        let slice = file_data.as_slice();
1390        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1391
1392        // Look up the SysV hash section header
1393        let common = file.find_common_data().expect("should parse");
1394        let hash_table = common.sysv_hash.expect("should have .hash section");
1395
1396        // Get the dynamic symbol table.
1397        let (symtab, strtab) = file
1398            .dynamic_symbol_table()
1399            .expect("Failed to read symbol table")
1400            .expect("Failed to find symbol table");
1401
1402        // Verify that these three symbols all collide in the hash table's buckets
1403        assert_eq!(sysv_hash(b"use_memset_v2"), 0x8080542);
1404        assert_eq!(sysv_hash(b"__gmon_start__"), 0xF4D007F);
1405        assert_eq!(sysv_hash(b"memset"), 0x73C49C4);
1406        assert_eq!(sysv_hash(b"use_memset_v2") % 3, 0);
1407        assert_eq!(sysv_hash(b"__gmon_start__") % 3, 0);
1408        assert_eq!(sysv_hash(b"memset") % 3, 0);
1409
1410        // Use the hash table to find a given symbol in it.
1411        let (sym_idx, sym) = hash_table
1412            .find(b"memset", &symtab, &strtab)
1413            .expect("Failed to parse hash")
1414            .expect("Failed to find hash");
1415
1416        // Verify that we got the same symbol from the hash table we expected
1417        assert_eq!(sym_idx, 2);
1418        assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset");
1419        assert_eq!(
1420            sym,
1421            symtab.get(sym_idx).expect("Failed to get expected sym")
1422        );
1423    }
1424
1425    #[test]
1426    fn gnu_hash_table() {
1427        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1428        let file_data = std::fs::read(path).expect("Could not read file.");
1429        let slice = file_data.as_slice();
1430        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
1431
1432        // Look up the SysV hash section header
1433        let common = file.find_common_data().unwrap();
1434        let hash_table = common.gnu_hash.expect("should have .gnu.hash section");
1435
1436        // Get the dynamic symbol table.
1437        let (symtab, strtab) = (common.dynsyms.unwrap(), common.dynsyms_strs.unwrap());
1438
1439        // manually look one up by explicit name to make sure the above loop is doing something
1440        let (sym_idx, sym) = hash_table
1441            .find(b"use_memset", &symtab, &strtab)
1442            .expect("Failed to parse hash")
1443            .expect("Failed to find hash");
1444
1445        // Verify that we got the same symbol from the hash table we expected
1446        assert_eq!(sym_idx, 9);
1447        assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "use_memset");
1448        assert_eq!(
1449            sym,
1450            symtab.get(sym_idx).expect("Failed to get expected sym")
1451        );
1452    }
1453}
1454
1455#[cfg(test)]
1456mod arch_tests {
1457    use super::*;
1458    use crate::endian::AnyEndian;
1459
1460    // Basic smoke test which parses out symbols and headers for a given sample object of a given architecture
1461    macro_rules! arch_test {
1462        ( $arch:expr, $e_machine:expr, $endian:expr) => {{
1463            let path_str = format!("sample-objects/symver.{}.so", $arch);
1464            let path = std::path::PathBuf::from(path_str);
1465            let file_data = std::fs::read(path).expect("file should exist");
1466            let slice = file_data.as_slice();
1467            let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("should parse");
1468
1469            assert_eq!(file.ehdr.e_machine, $e_machine);
1470            assert_eq!(file.ehdr.endianness, $endian);
1471
1472            let (shdrs, strtab) = file.section_headers_with_strtab().expect("should parse");
1473            let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap());
1474            let _: Vec<_> = shdrs
1475                .iter()
1476                .map(|shdr| {
1477                    (
1478                        strtab.get(shdr.sh_name as usize).expect("should parse"),
1479                        shdr,
1480                    )
1481                })
1482                .collect();
1483
1484            let common = file.find_common_data().expect("should parse");
1485
1486            // parse out all the normal symbol table symbols with their names
1487            {
1488                let symtab = common.symtab.unwrap();
1489                let strtab = common.symtab_strs.unwrap();
1490                let _: Vec<_> = symtab
1491                    .iter()
1492                    .map(|sym| (strtab.get(sym.st_name as usize).expect("should parse"), sym))
1493                    .collect();
1494            }
1495
1496            // parse out all the dynamic symbols and look them up in the gnu hash table
1497            {
1498                let symtab = common.dynsyms.unwrap();
1499                let strtab = common.dynsyms_strs.unwrap();
1500                let symbols_with_names: Vec<_> = symtab
1501                    .iter()
1502                    .map(|sym| (strtab.get_raw(sym.st_name as usize).expect("should parse"), sym))
1503                    .collect();
1504
1505                let hash_table = common.gnu_hash.unwrap();
1506
1507                // look up each entry that should be in the hash table and make sure its there
1508                let start_idx = hash_table.hdr.table_start_idx as usize;
1509                for sym_idx in 0..symtab.len() {
1510                    let (symbol_name, symbol) = symbols_with_names.get(sym_idx).unwrap();
1511
1512                    let result = hash_table
1513                        .find(symbol_name, &symtab, &strtab)
1514                        .expect("Failed to parse hash");
1515
1516                    if sym_idx < start_idx {
1517                        assert_eq!(result, None);
1518                    } else {
1519                        let (hash_sym_idx, hash_symbol) = result.unwrap();
1520
1521                        // Verify that we got the same symbol from the hash table we expected
1522                        assert_eq!(sym_idx, hash_sym_idx);
1523                        assert_eq!(
1524                            strtab.get_raw(hash_symbol.st_name as usize).unwrap(),
1525                            *symbol_name
1526                        );
1527                        assert_eq!(*symbol, hash_symbol);
1528                    }
1529                }
1530            }
1531
1532            let phdrs = file.segments().unwrap();
1533            let note_phdrs: Vec<_> = phdrs
1534                .iter()
1535                .filter(|phdr| phdr.p_type == abi::PT_NOTE)
1536                .collect();
1537            for phdr in note_phdrs {
1538                let _: Vec<_> = file
1539                    .segment_data_as_notes(&phdr)
1540                    .expect("should parse")
1541                    .collect();
1542                }
1543        }};
1544    }
1545
1546    #[test]
1547    fn x86_64() {
1548        arch_test!("x86_64", abi::EM_X86_64, AnyEndian::Little);
1549    }
1550
1551    #[test]
1552    fn m68k() {
1553        arch_test!("m68k", abi::EM_68K, AnyEndian::Big);
1554    }
1555
1556    #[test]
1557    fn aarch64() {
1558        arch_test!("aarch64", abi::EM_AARCH64, AnyEndian::Little);
1559    }
1560
1561    #[test]
1562    fn armhf() {
1563        arch_test!("armhf", abi::EM_ARM, AnyEndian::Little);
1564    }
1565
1566    #[test]
1567    fn powerpc64() {
1568        arch_test!("powerpc64", abi::EM_PPC64, AnyEndian::Big);
1569    }
1570
1571    #[test]
1572    fn powerpc64le() {
1573        arch_test!("powerpc64le", abi::EM_PPC64, AnyEndian::Little);
1574    }
1575
1576    #[test]
1577    fn riscv64() {
1578        arch_test!("riscv64", abi::EM_RISCV, AnyEndian::Little);
1579    }
1580}