| # |
| # Copyright (C) 2017 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| r"""This file contains an ELF parser and ELF header structures. |
| |
| Example usage: |
| import elf_parser |
| with elf_parser.ElfParser(file) as e: |
| print('\n'.join(e.ListGlobalDynamicSymbols())) |
| print('\n'.join(e.ListDependencies()[0])) |
| """ |
| |
| import ctypes |
| import os |
| import struct |
| |
| from vts.utils.python.library.elf import consts |
| from vts.utils.python.library.elf import structs |
| from vts.utils.python.library.elf import utils |
| |
| |
| class ElfError(Exception): |
| """The exception raised by ElfParser.""" |
| pass |
| |
| |
| class ElfParser(object): |
| """The class reads information from an ELF file. |
| |
| Attributes: |
| _file: The ELF file object. |
| _begin_offset: The offset of the ELF object in the file. The value is |
| non-zero if the ELF is in an archive, such as .a file. |
| _file_size: Size of the file. |
| bitness: Bitness of the ELF. |
| Ehdr: An Elf_Endr, the ELF header structure of the file. |
| Shdr: A list of Elf_Shdr, the section headers of the file. |
| Elf_Addr: ELF unsigned program address type. |
| Elf_Off: ELF unsigned file offset type. |
| Elf_Half: ELF unsigned medium integer type. |
| Elf_Word: ELF unsigned integer type. |
| Elf_Sword: ELF signed integer type. |
| Elf_Ehdr: ELF header class. |
| Elf_Shdr: ELF section header class. |
| Elf_Dyn: ELF dynamic entry class. |
| Elf_Sym: ELF symbol entry class. |
| Elf_Rel: ELF relocation entry class. |
| Elf_Rela: ELF relocation entry class with explicit addend. |
| Elf_Phdr: ELF program header class. |
| Elf_Nhdr: ELF note header class. |
| """ |
| |
| def __init__(self, file_path, begin_offset=0): |
| """Creates a parser to open and read an ELF file. |
| |
| Args: |
| file_path: The path to the file. |
| begin_offset: The offset of the ELF object in the file. |
| |
| Raises: |
| ElfError: File is not a valid ELF. |
| """ |
| self._begin_offset = begin_offset |
| try: |
| self._file = open(file_path, 'rb') |
| except IOError as e: |
| raise ElfError(e) |
| try: |
| self._file_size = os.fstat(self._file.fileno()).st_size |
| except OSError as e: |
| self.Close() |
| raise ElfError(e) |
| |
| try: |
| e_ident = self._SeekRead(0, consts.EI_NIDENT) |
| |
| if e_ident[:4] != consts.ELF_MAGIC_NUMBER: |
| raise ElfError('Unexpected magic bytes: {}'.format(e_ident[:4])) |
| |
| if utils.ByteToInt(e_ident[consts.EI_CLASS]) not in ( |
| consts.ELFCLASS32, consts.ELFCLASS64): |
| raise ElfError('Unexpected file class: {}' |
| .format(e_ident[consts.EI_CLASS])) |
| |
| if utils.ByteToInt(e_ident[consts.EI_DATA]) != consts.ELFDATA2LSB: |
| raise ElfError('Unexpected data encoding: {}' |
| .format(e_ident[consts.EI_DATA])) |
| except ElfError: |
| self.Close() |
| raise |
| |
| if utils.ByteToInt(e_ident[consts.EI_CLASS]) == consts.ELFCLASS32: |
| self.bitness = 32 |
| self.Elf_Addr = structs.Elf32_Addr |
| self.Elf_Off = structs.Elf32_Off |
| self.Elf_Half = structs.Elf32_Half |
| self.Elf_Word = structs.Elf32_Word |
| self.Elf_Sword = structs.Elf32_Sword |
| self.Elf_Ehdr = structs.Elf32_Ehdr |
| self.Elf_Shdr = structs.Elf32_Shdr |
| self.Elf_Dyn = structs.Elf32_Dyn |
| self.Elf_Sym = structs.Elf32_Sym |
| self.Elf_Rel = structs.Elf32_Rel |
| self.Elf_Rela = structs.Elf32_Rela |
| self.Elf_Phdr = structs.Elf32_Phdr |
| self.Elf_Nhdr = structs.Elf32_Nhdr |
| else: |
| self.bitness = 64 |
| self.Elf_Addr = structs.Elf64_Addr |
| self.Elf_Off = structs.Elf64_Off |
| self.Elf_Half = structs.Elf64_Half |
| self.Elf_Word = structs.Elf64_Word |
| self.Elf_Sword = structs.Elf64_Sword |
| self.Elf_Ehdr = structs.Elf64_Ehdr |
| self.Elf_Shdr = structs.Elf64_Shdr |
| self.Elf_Dyn = structs.Elf64_Dyn |
| self.Elf_Sym = structs.Elf64_Sym |
| self.Elf_Rel = structs.Elf64_Rel |
| self.Elf_Rela = structs.Elf64_Rela |
| self.Elf_Phdr = structs.Elf64_Phdr |
| self.Elf_Nhdr = structs.Elf64_Nhdr |
| |
| try: |
| self.Ehdr = self._SeekReadStruct(0, self.Elf_Ehdr) |
| shoff = self.Ehdr.e_shoff |
| shentsize = self.Ehdr.e_shentsize |
| self.Shdr = [self._SeekReadStruct(shoff + i * shentsize, |
| self.Elf_Shdr) |
| for i in range(self.Ehdr.e_shnum)] |
| except ElfError: |
| self.Close() |
| raise |
| |
| def __del__(self): |
| """Closes the ELF file.""" |
| self.Close() |
| |
| def __enter__(self): |
| return self |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| """Closes the ELF file.""" |
| self.Close() |
| |
| def Close(self): |
| """Closes the ELF file.""" |
| if hasattr(self, "_file"): |
| self._file.close() |
| |
| def _SeekRead(self, offset, read_size): |
| """Reads a byte string at specific offset in the file. |
| |
| Args: |
| offset: An integer, the offset from the beginning of the ELF. |
| read_size: An integer, number of bytes to read. |
| |
| Returns: |
| A bytes object which is the file content. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| if offset + read_size > self._file_size: |
| raise ElfError("Read beyond end of file.") |
| try: |
| self._file.seek(self._begin_offset + offset) |
| return self._file.read(read_size) |
| except IOError as e: |
| raise ElfError(e) |
| |
| def _SeekRead8(self, offset): |
| """Reads an 1-byte integer from file.""" |
| return struct.unpack("B", self._SeekRead(offset, 1))[0] |
| |
| def _SeekRead16(self, offset): |
| """Reads a 2-byte integer from file.""" |
| return struct.unpack("H", self._SeekRead(offset, 2))[0] |
| |
| def _SeekRead32(self, offset): |
| """Reads a 4-byte integer from file.""" |
| return struct.unpack("I", self._SeekRead(offset, 4))[0] |
| |
| def _SeekRead64(self, offset): |
| """Reads an 8-byte integer from file.""" |
| return struct.unpack("Q", self._SeekRead(offset, 8))[0] |
| |
| def _SeekReadString(self, offset): |
| """Reads a null-terminated string starting from specific offset. |
| |
| Args: |
| offset: The offset from the beginning of the ELF object. |
| |
| Returns: |
| A string, excluding the null character. |
| |
| Raises: |
| ElfError: String reaches end of file without null terminator. |
| """ |
| ret = b"" |
| buf_size = 16 |
| self._file.seek(self._begin_offset + offset) |
| while True: |
| try: |
| buf = self._file.read(buf_size) |
| except IOError as e: |
| raise ElfError(e) |
| end_index = buf.find(b"\0") |
| if end_index < 0: |
| ret += buf |
| else: |
| ret += buf[:end_index] |
| return utils.BytesToString(ret) |
| if len(buf) != buf_size: |
| raise ElfError("Null-terminated string reaches end of file.") |
| |
| def _SeekReadStruct(self, offset, struct_type): |
| """Reads a ctypes.Structure / ctypes.Union from file. |
| |
| Args: |
| offset: An integer, the offset from the beginning of the ELF. |
| struct_type: A class, the structure type to read. |
| |
| Returns: |
| An object of struct_type. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| Fails to create struct_type instance. |
| """ |
| raw_bytes = self._SeekRead(offset, ctypes.sizeof(struct_type)) |
| try: |
| return struct_type.from_buffer_copy(raw_bytes) |
| except ValueError as e: |
| raise ElfError(e) |
| |
| def GetString(self, strtab, offset): |
| """Retrieves a null-terminated string from string table. |
| |
| Args: |
| strtab: Section header of the string table. |
| offset: Section offset (string index) to start reading from. |
| |
| Returns: |
| A string without the null terminator. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| return self._SeekReadString(strtab.sh_offset + offset) |
| |
| def GetSectionName(self, sh): |
| """Returns a section name. |
| |
| Args: |
| sh: A section header. |
| |
| Returns: |
| A String. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| strtab = self.Shdr[self.Ehdr.e_shstrndx] |
| return self.GetString(strtab, sh.sh_name) |
| |
| def GetSectionsByName(self, name): |
| """Returns a generator of section headers from a given name. |
| |
| If multiple sections have the same name, yield them all. |
| |
| Args: |
| name: The section name to search for. |
| |
| Returns: |
| A generator of Elf_Shdr. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| return (sh for sh in self.Shdr if name == self.GetSectionName(sh)) |
| |
| def GetSectionByName(self, name): |
| """Returns a section header whose name equals a given name. |
| |
| Returns only the first match, assuming the section name is unique. |
| |
| Args: |
| name: The section name to search for. |
| |
| Returns: |
| An Elf_Shdr if found. |
| None if no sections have the given name. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| for sh in self.GetSectionsByName(name): |
| return sh |
| return None |
| |
| def GetDynamic(self, dynamic): |
| """Yields the _DYNAMIC array. |
| |
| Args: |
| dynamic: Section header of the dynamic section. |
| |
| Yields: |
| Elf_Dyn. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| off = dynamic.sh_offset |
| num = int(dynamic.sh_size // dynamic.sh_entsize) |
| for _ in range(num): |
| dyn = self._SeekReadStruct(off, self.Elf_Dyn) |
| yield dyn |
| if dyn.d_tag == consts.DT_NULL: |
| break |
| off += dynamic.sh_entsize |
| |
| def GetSymbol(self, symtab, idx): |
| """Retrieves a Elf_Sym entry from symbol table. |
| |
| Args: |
| symtab: A symbol table. |
| idx: An integer, symbol table index. |
| |
| Returns: |
| An Elf_Sym. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| off = symtab.sh_offset + idx * symtab.sh_entsize |
| return self._SeekReadStruct(off, self.Elf_Sym) |
| |
| def GetSymbols(self, symtab): |
| """Returns a generator of Elf_Sym in symbol table. |
| |
| Args: |
| symtab: A symbol table. |
| |
| Returns: |
| A generator of Elf_Sym. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| num = int(symtab.sh_size // symtab.sh_entsize) |
| return (self.GetSymbol(symtab, i) for i in range(num)) |
| |
| def GetRelocationSymbol(self, symtab, rel): |
| """Retrieves the Elf_Sym with respect to an Elf_Rel / Elf_Rela. |
| |
| Args: |
| symtab: A symbol table. |
| rel: A Elf_Rel or Elf_Rela. |
| |
| Returns: |
| An Elf_Sym. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| return self.GetSymbol(symtab, rel.GetSymbol()) |
| |
| def _CreateElfRel(self, offset, info): |
| """Creates an instance of Elf_Rel. |
| |
| Args: |
| offset: The initial value of r_offset. |
| info: The initial value of r_info. |
| |
| Returns: |
| An Elf_Rel. |
| """ |
| elf_rel = self.Elf_Rel() |
| elf_rel.r_offset = offset |
| elf_rel.r_info = info |
| return elf_rel |
| |
| def _DecodeAndroidRelr(self, rel): |
| """Decodes a SHT_RELR / SHT_ANDROID_RELR section. |
| |
| Args: |
| rel: A relocation table. |
| |
| Yields: |
| Elf_Rel. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| if self.bitness == 32: |
| addr_size = 4 |
| seek_read_entry = self._SeekRead32 |
| else: |
| addr_size = 8 |
| seek_read_entry = self._SeekRead64 |
| |
| rel_offset = 0 |
| for ent_offset in range(rel.sh_offset, rel.sh_offset + rel.sh_size, |
| rel.sh_entsize): |
| relr_entry = seek_read_entry(ent_offset) |
| if (relr_entry & 1) == 0: |
| # The entry is an address. |
| yield self._CreateElfRel(relr_entry, 0) |
| rel_offset = relr_entry + addr_size |
| else: |
| # The entry is a bitmap. |
| for bit_idx in range(1, rel.sh_entsize * 8): |
| if (relr_entry >> bit_idx) & 1: |
| yield self._CreateElfRel(rel_offset, 0) |
| rel_offset += addr_size |
| |
| def GetRelocation(self, rel, idx): |
| """Retrieves a Elf_Rel / Elf_Rela entry from relocation table. |
| |
| Args: |
| rel: A relocation table. |
| idx: An integer, relocation table index. |
| |
| Returns: |
| An Elf_Rel or Elf_Rela. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| off = rel.sh_offset + idx * rel.sh_entsize |
| if rel.sh_type == consts.SHT_RELA: |
| return self._SeekReadStruct(off, self.Elf_Rela) |
| return self._SeekReadStruct(off, self.Elf_Rel) |
| |
| def GetRelocations(self, rel): |
| """Returns a generator of Elf_Rel / Elf_Rela in relocation table. |
| |
| Args: |
| rel: A relocation table. |
| |
| Returns: |
| A generator of Elf_Rel or Elf_Rela. |
| |
| Raises: |
| ElfError: Fails to seek and read. |
| """ |
| if rel.sh_type in (consts.SHT_ANDROID_REL, consts.SHT_ANDROID_RELA): |
| relocations = self._UnpackAndroidRela(rel) |
| if rel.sh_type == consts.SHT_ANDROID_REL: |
| return (self.Elf_Rel(r_offset=rela.r_offset, r_info=rela.r_info) |
| for rela in relocations) |
| return relocations |
| elif rel.sh_type in (consts.SHT_RELR, consts.SHT_ANDROID_RELR): |
| return self._DecodeAndroidRelr(rel) |
| else: |
| num = int(rel.sh_size // rel.sh_entsize) |
| return (self.GetRelocation(rel, i) for i in range(num)) |
| |
| def _UnpackAndroidRela(self, android_rela): |
| """Unpacks a SHT_ANDROID_REL / SHT_ANDROID_RELA section. |
| |
| Args: |
| android_rela: The packed section's section header. |
| |
| Yields: |
| Elf_Rela. |
| |
| Raises: |
| ElfError: Fails to decode android rela section. |
| """ |
| data = self._SeekRead(android_rela.sh_offset, android_rela.sh_size) |
| # Check packed section header. |
| if len(data) < 4 or data[:4] != b'APS2': |
| raise ElfError('Unexpected SHT_ANDROID_RELA header: {}' |
| .format(data[:4])) |
| # Decode SLEB128 word stream. |
| def _PackedWordsGen(): |
| cur = 4 |
| while cur < len(data): |
| try: |
| value, num = utils.DecodeSLEB128(data, cur) |
| except IndexError: |
| raise ElfError('Decoding pass end of section.') |
| yield value |
| cur += num |
| raise ElfError('Decoding pass end of section.') |
| |
| _packed_words_gen = _PackedWordsGen() |
| _PopWord = lambda: next(_packed_words_gen) |
| # Decode delta encoded relocation data. |
| current_count = 0 |
| total_count = _PopWord() |
| offset = _PopWord() |
| addend = 0 |
| while current_count < total_count: |
| # Read relocaiton group info. |
| group_size = _PopWord() |
| group_flags = _PopWord() |
| group_offset_delta = 0 |
| # Read group flag and prepare delta values. |
| grouped_by_info = ( |
| group_flags & consts.RELOCATION_GROUPED_BY_INFO_FLAG) |
| grouped_by_offset_delta = ( |
| group_flags & consts.RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG) |
| grouped_by_addend = ( |
| group_flags & consts.RELOCATION_GROUPED_BY_ADDEND_FLAG) |
| group_has_addend = ( |
| group_flags & consts.RELOCATION_GROUP_HAS_ADDEND_FLAG) |
| if grouped_by_offset_delta: |
| group_offset_delta = _PopWord() |
| if grouped_by_info: |
| info = _PopWord() |
| if group_has_addend and grouped_by_addend: |
| addend += _PopWord() |
| if not group_has_addend: |
| addend = 0 |
| # Handle each relocation entry in group. |
| for _ in range(group_size): |
| if grouped_by_offset_delta: |
| offset += group_offset_delta |
| else: |
| offset += _PopWord() |
| if not grouped_by_info: |
| info = _PopWord() |
| if group_has_addend and not grouped_by_addend: |
| addend += _PopWord() |
| |
| relocation = self.Elf_Rela(r_offset=offset, |
| r_info=info, |
| r_addend=addend) |
| yield relocation |
| current_count += group_size |
| |
| def _LoadDynamicSection(self, dynamic): |
| """Reads entries from dynamic section. |
| |
| Args: |
| dynamic: Section header of the dynamic section. |
| |
| Returns: |
| A dict of {DT_NEEDED: [libraries names], DT_RUNPATH: [paths]} |
| where the library names and the paths are strings. |
| |
| Raises: |
| ElfError: Fails to find dynamic string table. |
| """ |
| strtab_addr = None |
| dt_needed_offsets = [] |
| dt_runpath_offsets = [] |
| for dyn in self.GetDynamic(dynamic): |
| if dyn.d_tag == consts.DT_NEEDED: |
| dt_needed_offsets.append(dyn.d_un.d_val) |
| elif dyn.d_tag == consts.DT_RUNPATH: |
| dt_runpath_offsets.append(dyn.d_un.d_val) |
| elif dyn.d_tag == consts.DT_STRTAB: |
| strtab_addr = dyn.d_un.d_ptr |
| |
| if strtab_addr is None: |
| raise ElfError("Cannot find string table address in dynamic " |
| "section.") |
| try: |
| strtab = next(sh for sh in self.Shdr if sh.sh_addr == strtab_addr) |
| except StopIteration: |
| raise ElfError("Cannot find dynamic string table.") |
| dt_needed = [self.GetString(strtab, off) for off in dt_needed_offsets] |
| dt_runpath = [] |
| for off in dt_runpath_offsets: |
| dt_runpath.extend(self.GetString(strtab, off).split(":")) |
| return {consts.DT_NEEDED: dt_needed, consts.DT_RUNPATH: dt_runpath} |
| |
| def IsExecutable(self): |
| """Returns whether the ELF is executable.""" |
| return self.Ehdr.e_type == consts.ET_EXEC |
| |
| def IsSharedObject(self): |
| """Returns whether the ELF is a shared object.""" |
| return self.Ehdr.e_type == consts.ET_DYN |
| |
| def HasAndroidIdent(self): |
| """Returns whether the ELF has a .note.android.ident section.""" |
| for sh in self.GetSectionsByName(".note.android.ident"): |
| nh = self._SeekReadStruct(sh.sh_offset, self.Elf_Nhdr) |
| name = self._SeekRead(sh.sh_offset + ctypes.sizeof(self.Elf_Nhdr), |
| nh.n_namesz) |
| if name == b"Android\0": |
| return True |
| return False |
| |
| def MatchCpuAbi(self, abi): |
| """Returns whether the ELF matches the ABI. |
| |
| Args: |
| abi: A string, the name of the ABI. |
| |
| Returns: |
| A boolean, whether the ELF matches the ABI. |
| """ |
| for abi_prefix, machine in (("arm64", consts.EM_AARCH64), |
| ("arm", consts.EM_ARM), |
| ("x86_64", consts.EM_X86_64), |
| ("x86", consts.EM_386)): |
| if abi.startswith(abi_prefix): |
| return self.Ehdr.e_machine == machine |
| return False |
| |
| def ListDependencies(self): |
| """Lists the shared libraries that the ELF depends on. |
| |
| Returns: |
| 2 lists of strings, the names of the depended libraries and the |
| search paths. |
| """ |
| deps = [] |
| runpaths = [] |
| for sh in self.Shdr: |
| if sh.sh_type == consts.SHT_DYNAMIC: |
| dynamic = self._LoadDynamicSection(sh) |
| deps.extend(dynamic[consts.DT_NEEDED]) |
| runpaths.extend(dynamic[consts.DT_RUNPATH]) |
| return deps, runpaths |
| |
| def ListGlobalSymbols(self, include_weak=False, |
| symtab_name=consts.SYMTAB, |
| strtab_name=consts.STRTAB): |
| """Lists the global symbols defined in the ELF. |
| |
| Args: |
| include_weak: A boolean, whether to include weak symbols. |
| symtab_name: A string, the name of the symbol table. |
| strtab_name: A string, the name of the string table. |
| |
| Returns: |
| A list of strings, the names of the symbols. |
| |
| Raises: |
| ElfError: Fails to find symbol table. |
| """ |
| symtab = self.GetSectionByName(symtab_name) |
| strtab = self.GetSectionByName(strtab_name) |
| if not symtab or not strtab or symtab.sh_size == 0: |
| raise ElfError("Cannot find symbol table.") |
| |
| include_bindings = [consts.STB_GLOBAL] |
| if include_weak: |
| include_bindings.append(consts.STB_WEAK) |
| |
| sym_names = [] |
| for sym in self.GetSymbols(symtab): |
| # Global symbols can be defined at most once at link time, |
| # while weak symbols may have multiple definitions. |
| if sym.GetType() == consts.STT_NOTYPE: |
| continue |
| if sym.GetBinding() not in include_bindings: |
| continue |
| if sym.st_shndx == consts.SHN_UNDEF: |
| continue |
| sym_names.append(self.GetString(strtab, sym.st_name)) |
| return sym_names |
| |
| def ListGlobalDynamicSymbols(self, include_weak=False): |
| """Lists the global dynamic symbols defined in the ELF. |
| |
| Args: |
| include_weak: A boolean, whether to include weak symbols. |
| |
| Returns: |
| A list of strings, the names of the symbols. |
| |
| Raises: |
| ElfError: Fails to find symbol table. |
| """ |
| return self.ListGlobalSymbols(include_weak, |
| consts.DYNSYM, consts.DYNSTR) |
| |
| def GetProgramInterpreter(self): |
| """Gets the path to the program interpreter of the ELF. |
| |
| Returns: |
| A string, the contents of .interp section. |
| None if the section is not found. |
| """ |
| for ph_index in range(self.Ehdr.e_phnum): |
| ph = self._SeekReadStruct( |
| self.Ehdr.e_phoff + ph_index * self.Ehdr.e_phentsize, |
| self.Elf_Phdr) |
| if ph.p_type == consts.PT_INTERP: |
| return self._SeekReadString(ph.p_offset) |