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::endian::AnyEndian;
833    use crate::hash::sysv_hash;
834    use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId};
835    use crate::relocation::Rela;
836
837    #[test]
838    fn simultaenous_segments_parsing() {
839        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
840        let file_data = std::fs::read(path).expect("Could not read file.");
841        let slice = file_data.as_slice();
842        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
843
844        // With the bytes interface, we should be able to get multiple lazy-parsing types concurrently,
845        // since the trait is implemented for shared references.
846        //
847        // Get the segment table
848        let iter = file.segments().expect("File should have a segment table");
849
850        // Concurrently get the segment table again as an iterator and collect the headers into a vec
851        let segments: Vec<ProgramHeader> = file
852            .segments()
853            .expect("File should have a segment table")
854            .iter()
855            .collect();
856
857        let expected_phdr = ProgramHeader {
858            p_type: abi::PT_PHDR,
859            p_offset: 64,
860            p_vaddr: 4194368,
861            p_paddr: 4194368,
862            p_filesz: 448,
863            p_memsz: 448,
864            p_flags: 5,
865            p_align: 8,
866        };
867
868        // Assert we parsed the first header correctly
869        assert_eq!(segments[0], expected_phdr);
870
871        // Now use the original lazy-parsing table to parse out the first entry
872        assert_eq!(
873            iter.get(0).expect("should be able to parse phdr"),
874            expected_phdr
875        )
876    }
877
878    #[test]
879    fn segments() {
880        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
881        let file_data = std::fs::read(path).expect("Could not read file.");
882        let slice = file_data.as_slice();
883        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
884
885        let segments: Vec<ProgramHeader> = file
886            .segments()
887            .expect("File should have a segment table")
888            .iter()
889            .collect();
890        assert_eq!(
891            segments[0],
892            ProgramHeader {
893                p_type: abi::PT_PHDR,
894                p_offset: 64,
895                p_vaddr: 4194368,
896                p_paddr: 4194368,
897                p_filesz: 448,
898                p_memsz: 448,
899                p_flags: 5,
900                p_align: 8,
901            }
902        );
903    }
904
905    #[test]
906    fn segments_phnum_in_shdr0() {
907        let path = std::path::PathBuf::from("sample-objects/phnum.m68k.so");
908        let file_data = std::fs::read(path).expect("Could not read file.");
909        let slice = file_data.as_slice();
910        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
911
912        let segments: Vec<ProgramHeader> = file
913            .segments()
914            .expect("File should have a segment table")
915            .iter()
916            .collect();
917        assert_eq!(
918            segments[0],
919            ProgramHeader {
920                p_type: abi::PT_PHDR,
921                p_offset: 92,
922                p_vaddr: 0,
923                p_paddr: 0,
924                p_filesz: 32,
925                p_memsz: 32,
926                p_flags: 0x20003,
927                p_align: 0x40000,
928            }
929        );
930    }
931
932    #[test]
933    fn section_headers() {
934        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
935        let file_data = std::fs::read(path).expect("Could not read file.");
936        let slice = file_data.as_slice();
937        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
938
939        let shdrs = file
940            .section_headers()
941            .expect("File should have a section table");
942
943        let shdrs_vec: Vec<SectionHeader> = shdrs.iter().collect();
944
945        assert_eq!(shdrs_vec[4].sh_type, SHT_GNU_HASH);
946    }
947
948    #[test]
949    fn section_headers_with_strtab() {
950        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
951        let file_data = std::fs::read(path).expect("Could not read file.");
952        let slice = file_data.as_slice();
953        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
954
955        let (shdrs, strtab) = file
956            .section_headers_with_strtab()
957            .expect("shdrs should be parsable");
958        let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap());
959
960        let with_names: Vec<(&str, SectionHeader)> = shdrs
961            .iter()
962            .map(|shdr| {
963                (
964                    strtab
965                        .get(shdr.sh_name as usize)
966                        .expect("Failed to get section name"),
967                    shdr,
968                )
969            })
970            .collect();
971
972        let (name, shdr) = with_names[4];
973        assert_eq!(name, ".gnu.hash");
974        assert_eq!(shdr.sh_type, abi::SHT_GNU_HASH);
975    }
976
977    #[test]
978    fn shnum_and_shstrndx_in_shdr0() {
979        let path = std::path::PathBuf::from("sample-objects/shnum.x86_64");
980        let file_data = std::fs::read(path).expect("Could not read file.");
981        let slice = file_data.as_slice();
982        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
983
984        let (shdrs, strtab) = file
985            .section_headers_with_strtab()
986            .expect("shdrs should be parsable");
987        let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap());
988
989        let shdrs_len = shdrs.len();
990        assert_eq!(shdrs_len, 0xFF15);
991
992        let shdr = shdrs.get(shdrs_len - 1).unwrap();
993        let name = strtab
994            .get(shdr.sh_name as usize)
995            .expect("Failed to get section name");
996
997        assert_eq!(name, ".shstrtab");
998        assert_eq!(shdr.sh_type, abi::SHT_STRTAB);
999    }
1000
1001    #[test]
1002    fn section_header_by_name() {
1003        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1004        let file_data = std::fs::read(path).expect("Could not read file.");
1005        let slice = file_data.as_slice();
1006        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1007
1008        let shdr = file
1009            .section_header_by_name(".gnu.hash")
1010            .expect("section table should be parseable")
1011            .expect("file should have .gnu.hash section");
1012
1013        assert_eq!(shdr.sh_type, SHT_GNU_HASH);
1014
1015        let shdr = file
1016            .section_header_by_name(".not.found")
1017            .expect("section table should be parseable");
1018
1019        assert_eq!(shdr, None);
1020    }
1021
1022    #[test]
1023    fn find_common_data() {
1024        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1025        let file_data = std::fs::read(path).expect("Could not read file.");
1026        let slice = file_data.as_slice();
1027        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1028
1029        let elf_scns = file.find_common_data().expect("file should parse");
1030
1031        // hello.so should find everything
1032        assert!(elf_scns.symtab.is_some());
1033        assert!(elf_scns.symtab_strs.is_some());
1034        assert!(elf_scns.dynsyms.is_some());
1035        assert!(elf_scns.dynsyms_strs.is_some());
1036        assert!(elf_scns.dynamic.is_some());
1037        assert!(elf_scns.sysv_hash.is_some());
1038        assert!(elf_scns.gnu_hash.is_some());
1039    }
1040
1041    #[test]
1042    fn section_data() {
1043        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1044        let file_data = std::fs::read(path).expect("Could not read file.");
1045        let slice = file_data.as_slice();
1046        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1047
1048        let shdr = file
1049            .section_headers()
1050            .expect("File should have section table")
1051            .get(26)
1052            .expect("shdr should be parsable");
1053
1054        assert_eq!(shdr.sh_type, SHT_NOBITS);
1055
1056        let (data, chdr) = file
1057            .section_data(&shdr)
1058            .expect("Failed to get section data");
1059
1060        assert_eq!(chdr, None);
1061        assert_eq!(data, &[]);
1062    }
1063
1064    // Test all the different section_data_as* with a section of the wrong type
1065    #[test]
1066    fn section_data_as_wrong_type() {
1067        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1068        let file_data = std::fs::read(path).expect("Could not read file.");
1069        let slice = file_data.as_slice();
1070        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1071
1072        // Section 0 is SHT_NULL, so all of the section_data_as* should error on it
1073        let shdr = file
1074            .section_headers()
1075            .expect("File should have section table")
1076            .get(0)
1077            .expect("shdr should be parsable");
1078
1079        let err = file
1080            .section_data_as_strtab(&shdr)
1081            .expect_err("shdr0 should be the wrong type");
1082        assert!(
1083            matches!(
1084                err,
1085                ParseError::UnexpectedSectionType((SHT_NULL, SHT_STRTAB))
1086            ),
1087            "Unexpected Error type found: {err}"
1088        );
1089
1090        let err = file
1091            .section_data_as_rels(&shdr)
1092            .expect_err("shdr0 should be the wrong type");
1093        assert!(
1094            matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_REL))),
1095            "Unexpected Error type found: {err}"
1096        );
1097
1098        let err = file
1099            .section_data_as_relas(&shdr)
1100            .expect_err("shdr0 should be the wrong type");
1101        assert!(
1102            matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_RELA))),
1103            "Unexpected Error type found: {err}"
1104        );
1105
1106        let err = file
1107            .section_data_as_notes(&shdr)
1108            .expect_err("shdr0 should be the wrong type");
1109        assert!(
1110            matches!(err, ParseError::UnexpectedSectionType((SHT_NULL, SHT_NOTE))),
1111            "Unexpected Error type found: {err}"
1112        );
1113    }
1114
1115    #[test]
1116    fn section_data_as_strtab() {
1117        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1118        let file_data = std::fs::read(path).expect("Could not read file.");
1119        let slice = file_data.as_slice();
1120        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1121
1122        let shdr = file
1123            .section_headers()
1124            .expect("File should have section table")
1125            .get(file.ehdr.e_shstrndx as usize)
1126            .expect("shdr should be parsable");
1127
1128        let strtab = file
1129            .section_data_as_strtab(&shdr)
1130            .expect("Failed to read strtab");
1131
1132        assert_eq!(
1133            strtab.get(1).expect("Failed to get strtab entry"),
1134            ".symtab"
1135        );
1136    }
1137
1138    #[test]
1139    fn section_data_as_relas() {
1140        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1141        let file_data = std::fs::read(path).expect("Could not read file.");
1142        let slice = file_data.as_slice();
1143        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1144
1145        let shdr = file
1146            .section_headers()
1147            .expect("File should have section table")
1148            .get(10)
1149            .expect("Failed to get rela shdr");
1150
1151        let mut relas = file
1152            .section_data_as_relas(&shdr)
1153            .expect("Failed to read relas section");
1154        assert_eq!(
1155            relas.next().expect("Failed to get rela entry"),
1156            Rela {
1157                r_offset: 6293704,
1158                r_sym: 1,
1159                r_type: 7,
1160                r_addend: 0,
1161            }
1162        );
1163        assert_eq!(
1164            relas.next().expect("Failed to get rela entry"),
1165            Rela {
1166                r_offset: 6293712,
1167                r_sym: 2,
1168                r_type: 7,
1169                r_addend: 0,
1170            }
1171        );
1172        assert!(relas.next().is_none());
1173    }
1174
1175    #[test]
1176    fn section_data_as_notes() {
1177        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1178        let file_data = std::fs::read(path).expect("Could not read file.");
1179        let slice = file_data.as_slice();
1180        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1181
1182        let shdr = file
1183            .section_headers()
1184            .expect("File should have section table")
1185            .get(2)
1186            .expect("Failed to get note shdr");
1187
1188        let mut notes = file
1189            .section_data_as_notes(&shdr)
1190            .expect("Failed to read note section");
1191        assert_eq!(
1192            notes.next().expect("Failed to get first note"),
1193            Note::GnuAbiTag(NoteGnuAbiTag {
1194                os: 0,
1195                major: 2,
1196                minor: 6,
1197                subminor: 32
1198            })
1199        );
1200        assert!(notes.next().is_none());
1201    }
1202
1203    #[test]
1204    fn segment_data_as_notes() {
1205        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1206        let file_data = std::fs::read(path).expect("Could not read file.");
1207        let slice = file_data.as_slice();
1208        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1209
1210        let phdr = file
1211            .segments()
1212            .expect("File should have segmetn table")
1213            .get(5)
1214            .expect("Failed to get notes phdr");
1215
1216        let mut notes = file
1217            .segment_data_as_notes(&phdr)
1218            .expect("Failed to read notes segment");
1219        assert_eq!(
1220            notes.next().expect("Failed to get first note"),
1221            Note::GnuAbiTag(NoteGnuAbiTag {
1222                os: 0,
1223                major: 2,
1224                minor: 6,
1225                subminor: 32
1226            })
1227        );
1228        assert_eq!(
1229            notes.next().expect("Failed to get second note"),
1230            Note::GnuBuildId(NoteGnuBuildId(&[
1231                119, 65, 159, 13, 165, 16, 131, 12, 87, 167, 200, 204, 176, 238, 133, 95, 238, 211,
1232                118, 163
1233            ]))
1234        );
1235        assert!(notes.next().is_none());
1236    }
1237
1238    #[test]
1239    fn dynamic() {
1240        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1241        let file_data = std::fs::read(path).expect("Could not read file.");
1242        let slice = file_data.as_slice();
1243        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1244
1245        let mut dynamic = file
1246            .dynamic()
1247            .expect("Failed to parse .dynamic")
1248            .expect("Failed to find .dynamic")
1249            .iter();
1250        assert_eq!(
1251            dynamic.next().expect("Failed to get dyn entry"),
1252            Dyn {
1253                d_tag: abi::DT_NEEDED,
1254                d_un: 1
1255            }
1256        );
1257        assert_eq!(
1258            dynamic.next().expect("Failed to get dyn entry"),
1259            Dyn {
1260                d_tag: abi::DT_INIT,
1261                d_un: 4195216
1262            }
1263        );
1264    }
1265
1266    #[test]
1267    fn symbol_table() {
1268        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1269        let file_data = std::fs::read(path).expect("Could not read file.");
1270        let slice = file_data.as_slice();
1271        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1272
1273        let (symtab, strtab) = file
1274            .symbol_table()
1275            .expect("Failed to read symbol table")
1276            .expect("Failed to find symbol table");
1277        let symbol = symtab.get(30).expect("Failed to get symbol");
1278        assert_eq!(
1279            symbol,
1280            Symbol {
1281                st_name: 19,
1282                st_value: 6293200,
1283                st_size: 0,
1284                st_shndx: 21,
1285                st_info: 1,
1286                st_other: 0,
1287            }
1288        );
1289        assert_eq!(
1290            strtab
1291                .get(symbol.st_name as usize)
1292                .expect("Failed to get name from strtab"),
1293            "__JCR_LIST__"
1294        );
1295    }
1296
1297    #[test]
1298    fn dynamic_symbol_table() {
1299        let path = std::path::PathBuf::from("sample-objects/basic.x86_64");
1300        let file_data = std::fs::read(path).expect("Could not read file.");
1301        let slice = file_data.as_slice();
1302        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1303
1304        let (symtab, strtab) = file
1305            .dynamic_symbol_table()
1306            .expect("Failed to read symbol table")
1307            .expect("Failed to find symbol table");
1308        let symbol = symtab.get(1).expect("Failed to get symbol");
1309        assert_eq!(
1310            symbol,
1311            Symbol {
1312                st_name: 11,
1313                st_value: 0,
1314                st_size: 0,
1315                st_shndx: 0,
1316                st_info: 18,
1317                st_other: 0,
1318            }
1319        );
1320        assert_eq!(
1321            strtab
1322                .get(symbol.st_name as usize)
1323                .expect("Failed to get name from strtab"),
1324            "memset"
1325        );
1326    }
1327
1328    #[test]
1329    fn symbol_version_table() {
1330        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1331        let file_data = std::fs::read(path).expect("Could not read file.");
1332        let slice = file_data.as_slice();
1333        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1334
1335        let vst = file
1336            .symbol_version_table()
1337            .expect("Failed to parse GNU symbol versions")
1338            .expect("Failed to find GNU symbol versions");
1339
1340        let req = vst
1341            .get_requirement(2)
1342            .expect("Failed to parse NEED")
1343            .expect("Failed to find NEED");
1344        assert_eq!(req.file, "libc.so.6");
1345        assert_eq!(req.name, "GLIBC_2.2.5");
1346        assert_eq!(req.hash, 0x9691A75);
1347
1348        let req = vst.get_requirement(3).expect("Failed to parse NEED");
1349        assert!(req.is_none());
1350
1351        let req = vst.get_requirement(4).expect("Failed to parse NEED");
1352        assert!(req.is_none());
1353
1354        let req = vst
1355            .get_requirement(5)
1356            .expect("Failed to parse NEED")
1357            .expect("Failed to find NEED");
1358        assert_eq!(req.file, "libc.so.6");
1359        assert_eq!(req.name, "GLIBC_2.2.5");
1360        assert_eq!(req.hash, 0x9691A75);
1361
1362        let def = vst
1363            .get_definition(3)
1364            .expect("Failed to parse DEF")
1365            .expect("Failed to find DEF");
1366        assert_eq!(def.hash, 0xC33237F);
1367        assert_eq!(def.flags, 1);
1368        assert!(!def.hidden);
1369        let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect();
1370        assert_eq!(def_names, &["hello.so"]);
1371
1372        let def = vst
1373            .get_definition(7)
1374            .expect("Failed to parse DEF")
1375            .expect("Failed to find DEF");
1376        assert_eq!(def.hash, 0x1570B62);
1377        assert_eq!(def.flags, 0);
1378        assert!(def.hidden);
1379        let def_names: Vec<&str> = def.names.map(|res| res.expect("should parse")).collect();
1380        assert_eq!(def_names, &["HELLO_1.42"]);
1381    }
1382
1383    #[test]
1384    fn sysv_hash_table() {
1385        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1386        let file_data = std::fs::read(path).expect("Could not read file.");
1387        let slice = file_data.as_slice();
1388        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("Open test1");
1389
1390        // Look up the SysV hash section header
1391        let common = file.find_common_data().expect("should parse");
1392        let hash_table = common.sysv_hash.expect("should have .hash section");
1393
1394        // Get the dynamic symbol table.
1395        let (symtab, strtab) = file
1396            .dynamic_symbol_table()
1397            .expect("Failed to read symbol table")
1398            .expect("Failed to find symbol table");
1399
1400        // Verify that these three symbols all collide in the hash table's buckets
1401        assert_eq!(sysv_hash(b"use_memset_v2"), 0x8080542);
1402        assert_eq!(sysv_hash(b"__gmon_start__"), 0xF4D007F);
1403        assert_eq!(sysv_hash(b"memset"), 0x73C49C4);
1404        assert_eq!(sysv_hash(b"use_memset_v2") % 3, 0);
1405        assert_eq!(sysv_hash(b"__gmon_start__") % 3, 0);
1406        assert_eq!(sysv_hash(b"memset") % 3, 0);
1407
1408        // Use the hash table to find a given symbol in it.
1409        let (sym_idx, sym) = hash_table
1410            .find(b"memset", &symtab, &strtab)
1411            .expect("Failed to parse hash")
1412            .expect("Failed to find hash");
1413
1414        // Verify that we got the same symbol from the hash table we expected
1415        assert_eq!(sym_idx, 2);
1416        assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "memset");
1417        assert_eq!(
1418            sym,
1419            symtab.get(sym_idx).expect("Failed to get expected sym")
1420        );
1421    }
1422
1423    #[test]
1424    fn gnu_hash_table() {
1425        let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so");
1426        let file_data = std::fs::read(path).expect("Could not read file.");
1427        let slice = file_data.as_slice();
1428        let file = ElfBytes::<AnyEndian>::minimal_parse(slice).unwrap();
1429
1430        // Look up the SysV hash section header
1431        let common = file.find_common_data().unwrap();
1432        let hash_table = common.gnu_hash.expect("should have .gnu.hash section");
1433
1434        // Get the dynamic symbol table.
1435        let (symtab, strtab) = (common.dynsyms.unwrap(), common.dynsyms_strs.unwrap());
1436
1437        // manually look one up by explicit name to make sure the above loop is doing something
1438        let (sym_idx, sym) = hash_table
1439            .find(b"use_memset", &symtab, &strtab)
1440            .expect("Failed to parse hash")
1441            .expect("Failed to find hash");
1442
1443        // Verify that we got the same symbol from the hash table we expected
1444        assert_eq!(sym_idx, 9);
1445        assert_eq!(strtab.get(sym.st_name as usize).unwrap(), "use_memset");
1446        assert_eq!(
1447            sym,
1448            symtab.get(sym_idx).expect("Failed to get expected sym")
1449        );
1450    }
1451}
1452
1453#[cfg(test)]
1454mod arch_tests {
1455    use super::*;
1456    use crate::endian::AnyEndian;
1457
1458    // Basic smoke test which parses out symbols and headers for a given sample object of a given architecture
1459    macro_rules! arch_test {
1460        ( $arch:expr, $e_machine:expr, $endian:expr) => {{
1461            let path_str = format!("sample-objects/symver.{}.so", $arch);
1462            let path = std::path::PathBuf::from(path_str);
1463            let file_data = std::fs::read(path).expect("file should exist");
1464            let slice = file_data.as_slice();
1465            let file = ElfBytes::<AnyEndian>::minimal_parse(slice).expect("should parse");
1466
1467            assert_eq!(file.ehdr.e_machine, $e_machine);
1468            assert_eq!(file.ehdr.endianness, $endian);
1469
1470            let (shdrs, strtab) = file.section_headers_with_strtab().expect("should parse");
1471            let (shdrs, strtab) = (shdrs.unwrap(), strtab.unwrap());
1472            let _: Vec<_> = shdrs
1473                .iter()
1474                .map(|shdr| {
1475                    (
1476                        strtab.get(shdr.sh_name as usize).expect("should parse"),
1477                        shdr,
1478                    )
1479                })
1480                .collect();
1481
1482            let common = file.find_common_data().expect("should parse");
1483
1484            // parse out all the normal symbol table symbols with their names
1485            {
1486                let symtab = common.symtab.unwrap();
1487                let strtab = common.symtab_strs.unwrap();
1488                let _: Vec<_> = symtab
1489                    .iter()
1490                    .map(|sym| (strtab.get(sym.st_name as usize).expect("should parse"), sym))
1491                    .collect();
1492            }
1493
1494            // parse out all the dynamic symbols and look them up in the gnu hash table
1495            {
1496                let symtab = common.dynsyms.unwrap();
1497                let strtab = common.dynsyms_strs.unwrap();
1498                let symbols_with_names: Vec<_> = symtab
1499                    .iter()
1500                    .map(|sym| (strtab.get_raw(sym.st_name as usize).expect("should parse"), sym))
1501                    .collect();
1502
1503                let hash_table = common.gnu_hash.unwrap();
1504
1505                // look up each entry that should be in the hash table and make sure its there
1506                let start_idx = hash_table.hdr.table_start_idx as usize;
1507                for sym_idx in 0..symtab.len() {
1508                    let (symbol_name, symbol) = symbols_with_names.get(sym_idx).unwrap();
1509
1510                    let result = hash_table
1511                        .find(symbol_name, &symtab, &strtab)
1512                        .expect("Failed to parse hash");
1513
1514                    if sym_idx < start_idx {
1515                        assert_eq!(result, None);
1516                    } else {
1517                        let (hash_sym_idx, hash_symbol) = result.unwrap();
1518
1519                        // Verify that we got the same symbol from the hash table we expected
1520                        assert_eq!(sym_idx, hash_sym_idx);
1521                        assert_eq!(
1522                            strtab.get_raw(hash_symbol.st_name as usize).unwrap(),
1523                            *symbol_name
1524                        );
1525                        assert_eq!(*symbol, hash_symbol);
1526                    }
1527                }
1528            }
1529
1530            let phdrs = file.segments().unwrap();
1531            let note_phdrs: Vec<_> = phdrs
1532                .iter()
1533                .filter(|phdr| phdr.p_type == abi::PT_NOTE)
1534                .collect();
1535            for phdr in note_phdrs {
1536                let _: Vec<_> = file
1537                    .segment_data_as_notes(&phdr)
1538                    .expect("should parse")
1539                    .collect();
1540                }
1541        }};
1542    }
1543
1544    #[test]
1545    fn x86_64() {
1546        arch_test!("x86_64", abi::EM_X86_64, AnyEndian::Little);
1547    }
1548
1549    #[test]
1550    fn m68k() {
1551        arch_test!("m68k", abi::EM_68K, AnyEndian::Big);
1552    }
1553
1554    #[test]
1555    fn aarch64() {
1556        arch_test!("aarch64", abi::EM_AARCH64, AnyEndian::Little);
1557    }
1558
1559    #[test]
1560    fn armhf() {
1561        arch_test!("armhf", abi::EM_ARM, AnyEndian::Little);
1562    }
1563
1564    #[test]
1565    fn powerpc64() {
1566        arch_test!("powerpc64", abi::EM_PPC64, AnyEndian::Big);
1567    }
1568
1569    #[test]
1570    fn powerpc64le() {
1571        arch_test!("powerpc64le", abi::EM_PPC64, AnyEndian::Little);
1572    }
1573
1574    #[test]
1575    fn riscv64() {
1576        arch_test!("riscv64", abi::EM_RISCV, AnyEndian::Little);
1577    }
1578}