| #------------------------------------------------------------------------------- |
| # elftools: elf/elffile.py |
| # |
| # ELFFile - main class for accessing ELF files |
| # |
| # Eli Bendersky (eliben@gmail.com) |
| # This code is in the public domain |
| #------------------------------------------------------------------------------- |
| from ..common.py3compat import BytesIO |
| from ..common.exceptions import ELFError |
| from ..common.utils import struct_parse, elf_assert |
| from ..construct import ConstructError |
| from .structs import ELFStructs |
| from .sections import ( |
| Section, StringTableSection, SymbolTableSection, NullSection) |
| from .relocation import RelocationSection, RelocationHandler |
| from .segments import Segment, InterpSegment |
| from .enums import ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64 |
| from ..dwarf.dwarfinfo import DWARFInfo, DebugSectionDescriptor, DwarfConfig |
| |
| |
| class ELFFile(object): |
| """ Creation: the constructor accepts a stream (file-like object) with the |
| contents of an ELF file. |
| |
| Accessible attributes: |
| |
| stream: |
| The stream holding the data of the file - must be a binary |
| stream (bytes, not string). |
| |
| elfclass: |
| 32 or 64 - specifies the word size of the target machine |
| |
| little_endian: |
| boolean - specifies the target machine's endianness |
| |
| header: |
| the complete ELF file header |
| |
| e_ident_raw: |
| the raw e_ident field of the header |
| """ |
| def __init__(self, stream): |
| self.stream = stream |
| self._identify_file() |
| self.structs = ELFStructs( |
| little_endian=self.little_endian, |
| elfclass=self.elfclass) |
| self.header = self._parse_elf_header() |
| |
| self.stream.seek(0) |
| self.e_ident_raw = self.stream.read(16) |
| |
| self._file_stringtable_section = self._get_file_stringtable() |
| self._section_name_map = None |
| |
| def num_sections(self): |
| """ Number of sections in the file |
| """ |
| return self['e_shnum'] |
| |
| def get_section(self, n): |
| """ Get the section at index #n from the file (Section object or a |
| subclass) |
| """ |
| section_header = self._get_section_header(n) |
| return self._make_section(section_header) |
| |
| def get_section_by_name(self, name): |
| """ Get a section from the file, by name. Return None if no such |
| section exists. |
| """ |
| # The first time this method is called, construct a name to number |
| # mapping |
| # |
| if self._section_name_map is None: |
| self._section_name_map = {} |
| for i, sec in enumerate(self.iter_sections()): |
| self._section_name_map[sec.name] = i |
| secnum = self._section_name_map.get(name, None) |
| return None if secnum is None else self.get_section(secnum) |
| |
| def iter_sections(self): |
| """ Yield all the sections in the file |
| """ |
| for i in range(self.num_sections()): |
| yield self.get_section(i) |
| |
| def num_segments(self): |
| """ Number of segments in the file |
| """ |
| return self['e_phnum'] |
| |
| def get_segment(self, n): |
| """ Get the segment at index #n from the file (Segment object) |
| """ |
| segment_header = self._get_segment_header(n) |
| return self._make_segment(segment_header) |
| |
| def iter_segments(self): |
| """ Yield all the segments in the file |
| """ |
| for i in range(self.num_segments()): |
| yield self.get_segment(i) |
| |
| def has_dwarf_info(self): |
| """ Check whether this file appears to have debugging information. |
| We assume that if it has the debug_info section, it has all theother |
| required sections as well. |
| """ |
| return bool(self.get_section_by_name(b'.debug_info')) |
| |
| def get_dwarf_info(self, relocate_dwarf_sections=True): |
| """ Return a DWARFInfo object representing the debugging information in |
| this file. |
| |
| If relocate_dwarf_sections is True, relocations for DWARF sections |
| are looked up and applied. |
| """ |
| # Expect that has_dwarf_info was called, so at least .debug_info is |
| # present. |
| # Sections that aren't found will be passed as None to DWARFInfo. |
| # |
| debug_sections = {} |
| for secname in (b'.debug_info', b'.debug_abbrev', b'.debug_str', |
| b'.debug_line', b'.debug_frame', b'.debug_loc', |
| b'.debug_ranges'): |
| section = self.get_section_by_name(secname) |
| if section is None: |
| debug_sections[secname] = None |
| else: |
| debug_sections[secname] = self._read_dwarf_section( |
| section, |
| relocate_dwarf_sections) |
| |
| return DWARFInfo( |
| config=DwarfConfig( |
| little_endian=self.little_endian, |
| default_address_size=self.elfclass / 8, |
| machine_arch=self.get_machine_arch()), |
| debug_info_sec=debug_sections[b'.debug_info'], |
| debug_abbrev_sec=debug_sections[b'.debug_abbrev'], |
| debug_frame_sec=debug_sections[b'.debug_frame'], |
| debug_str_sec=debug_sections[b'.debug_str'], |
| debug_loc_sec=debug_sections[b'.debug_loc'], |
| debug_ranges_sec=debug_sections[b'.debug_ranges'], |
| debug_line_sec=debug_sections[b'.debug_line']) |
| |
| def get_machine_arch(self): |
| """ Return the machine architecture, as detected from the ELF header. |
| At the moment the only supported architectures are x86 and x64. |
| """ |
| if self['e_machine'] == 'EM_X86_64': |
| return 'x64' |
| elif self['e_machine'] in ('EM_386', 'EM_486'): |
| return 'x86' |
| else: |
| return '<unknown>' |
| |
| #-------------------------------- PRIVATE --------------------------------# |
| |
| def __getitem__(self, name): |
| """ Implement dict-like access to header entries |
| """ |
| return self.header[name] |
| |
| def _identify_file(self): |
| """ Verify the ELF file and identify its class and endianness. |
| """ |
| # Note: this code reads the stream directly, without using ELFStructs, |
| # since we don't yet know its exact format. ELF was designed to be |
| # read like this - its e_ident field is word-size and endian agnostic. |
| # |
| self.stream.seek(0) |
| magic = self.stream.read(4) |
| elf_assert(magic == b'\x7fELF', 'Magic number does not match') |
| |
| ei_class = self.stream.read(1) |
| if ei_class == b'\x01': |
| self.elfclass = 32 |
| elif ei_class == b'\x02': |
| self.elfclass = 64 |
| else: |
| raise ELFError('Invalid EI_CLASS %s' % repr(ei_class)) |
| |
| ei_data = self.stream.read(1) |
| if ei_data == b'\x01': |
| self.little_endian = True |
| elif ei_data == b'\x02': |
| self.little_endian = False |
| else: |
| raise ELFError('Invalid EI_DATA %s' % repr(ei_data)) |
| |
| def _section_offset(self, n): |
| """ Compute the offset of section #n in the file |
| """ |
| return self['e_shoff'] + n * self['e_shentsize'] |
| |
| def _segment_offset(self, n): |
| """ Compute the offset of segment #n in the file |
| """ |
| return self['e_phoff'] + n * self['e_phentsize'] |
| |
| def _make_segment(self, segment_header): |
| """ Create a Segment object of the appropriate type |
| """ |
| segtype = segment_header['p_type'] |
| if segtype == 'PT_INTERP': |
| return InterpSegment(segment_header, self.stream) |
| else: |
| return Segment(segment_header, self.stream) |
| |
| def _get_section_header(self, n): |
| """ Find the header of section #n, parse it and return the struct |
| """ |
| return struct_parse( |
| self.structs.Elf_Shdr, |
| self.stream, |
| stream_pos=self._section_offset(n)) |
| |
| def _get_section_name(self, section_header): |
| """ Given a section header, find this section's name in the file's |
| string table |
| """ |
| name_offset = section_header['sh_name'] |
| return self._file_stringtable_section.get_string(name_offset) |
| |
| def _make_section(self, section_header): |
| """ Create a section object of the appropriate type |
| """ |
| name = self._get_section_name(section_header) |
| sectype = section_header['sh_type'] |
| |
| if sectype == 'SHT_STRTAB': |
| return StringTableSection(section_header, name, self.stream) |
| elif sectype == 'SHT_NULL': |
| return NullSection(section_header, name, self.stream) |
| elif sectype in ('SHT_SYMTAB', 'SHT_DYNSYM'): |
| return self._make_symbol_table_section(section_header, name) |
| elif sectype in ('SHT_REL', 'SHT_RELA'): |
| return RelocationSection( |
| section_header, name, self.stream, self) |
| else: |
| return Section(section_header, name, self.stream) |
| |
| def _make_symbol_table_section(self, section_header, name): |
| """ Create a SymbolTableSection |
| """ |
| linked_strtab_index = section_header['sh_link'] |
| strtab_section = self.get_section(linked_strtab_index) |
| return SymbolTableSection( |
| section_header, name, self.stream, |
| elffile=self, |
| stringtable=strtab_section) |
| |
| def _get_segment_header(self, n): |
| """ Find the header of segment #n, parse it and return the struct |
| """ |
| return struct_parse( |
| self.structs.Elf_Phdr, |
| self.stream, |
| stream_pos=self._segment_offset(n)) |
| |
| def _get_file_stringtable(self): |
| """ Find the file's string table section |
| """ |
| stringtable_section_num = self['e_shstrndx'] |
| return StringTableSection( |
| header=self._get_section_header(stringtable_section_num), |
| name='', |
| stream=self.stream) |
| |
| def _parse_elf_header(self): |
| """ Parses the ELF file header and assigns the result to attributes |
| of this object. |
| """ |
| return struct_parse(self.structs.Elf_Ehdr, self.stream, stream_pos=0) |
| |
| def _read_dwarf_section(self, section, relocate_dwarf_sections): |
| """ Read the contents of a DWARF section from the stream and return a |
| DebugSectionDescriptor. Apply relocations if asked to. |
| """ |
| self.stream.seek(section['sh_offset']) |
| # The section data is read into a new stream, for processing |
| section_stream = BytesIO() |
| section_stream.write(self.stream.read(section['sh_size'])) |
| |
| if relocate_dwarf_sections: |
| reloc_handler = RelocationHandler(self) |
| reloc_section = reloc_handler.find_relocations_for_section(section) |
| if reloc_section is not None: |
| reloc_handler.apply_section_relocations( |
| section_stream, reloc_section) |
| |
| return DebugSectionDescriptor( |
| stream=section_stream, |
| name=section.name, |
| global_offset=section['sh_offset'], |
| size=section['sh_size']) |
| |
| |