# # 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. # import os import struct from vts.utils.python.library import elf_consts 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. _file_size: Size of the ELF. bitness: Bitness of the ELF. _address_size: Size of address or offset in the ELF. _offsets: Offset of each entry in the ELF. _seek_read_address: The function to read an address or offset entry from the ELF. _sh_offset: Offset of section header table in the file. _sh_size: Size of section header table entry. _sh_count: Number of section header table entries. _sh_strtab_index: Index of the section that contains section names. _section_headers: List of SectionHeader objects read from the ELF. """ class SectionHeader(object): """Contains section header entries as attributes. Attributes: name_offset: Offset in the section header string table. type: Type of the section. address: The virtual memory address where the section is loaded. offset: The offset of the section in the ELF file. size: Size of the section. entry_size: Size of each entry in the section. """ def __init__(self, elf, offset): """Loads a section header from ELF file. Args: elf: The instance of ElfParser. offset: The starting offset of the section header. """ self.name_offset = elf._SeekRead32( offset + elf._offsets.SECTION_NAME_OFFSET) self.type = elf._SeekRead32( offset + elf._offsets.SECTION_TYPE) self.address = elf._seek_read_address( offset + elf._offsets.SECTION_ADDRESS) self.offset = elf._seek_read_address( offset + elf._offsets.SECTION_OFFSET) self.size = elf._seek_read_address( offset + elf._offsets.SECTION_SIZE) self.entry_size = elf._seek_read_address( offset + elf._offsets.SECTION_ENTRY_SIZE) def __init__(self, file_path): """Creates a parser to open and read an ELF file. Args: file_path: The path to the ELF. Raises: ElfError if the file is not a valid ELF. """ try: self._file = open(file_path, "rb") except IOError as e: raise ElfError(e) try: self._LoadElfHeader() self._section_headers = [ self.SectionHeader(self, self._sh_offset + i * self._sh_size) for i in range(self._sh_count)] except: self._file.close() raise def __del__(self): """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 file. read_size: An integer, number of bytes to read. Returns: A byte string which is the file content. Raises: ElfError if fails to seek and read. """ if offset + read_size > self._file_size: raise ElfError("Read beyond end of file.") try: self._file.seek(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 file. Returns: A byte string, excluding the null character. """ ret = "" buf_size = 16 self._file.seek(offset) while True: try: buf = self._file.read(buf_size) except IOError as e: raise ElfError(e) end_index = buf.find('\0') if end_index < 0: ret += buf else: ret += buf[:end_index] return ret if len(buf) != buf_size: raise ElfError("Null-terminated string reaches end of file.") def _LoadElfHeader(self): """Loads ELF header and initializes attributes.""" try: self._file_size = os.fstat(self._file.fileno()).st_size except OSError as e: raise ElfError(e) magic = self._SeekRead(elf_consts.MAGIC_OFFSET, 4) if magic != elf_consts.MAGIC_BYTES: raise ElfError("Wrong magic bytes.") bitness = self._SeekRead8(elf_consts.BITNESS_OFFSET) if bitness == elf_consts.BITNESS_32: self.bitness = 32 self._address_size = 4 self._offsets = elf_consts.ElfOffsets32 self._seek_read_address = self._SeekRead32 elif bitness == elf_consts.BITNESS_64: self.bitness = 64 self._address_size = 8 self._offsets = elf_consts.ElfOffsets64 self._seek_read_address = self._SeekRead64 else: raise ElfError("Wrong bitness value.") self._sh_offset = self._seek_read_address( self._offsets.SECTION_HEADER_OFFSET) self._sh_size = self._SeekRead16(self._offsets.SECTION_HEADER_SIZE) self._sh_count = self._SeekRead16(self._offsets.SECTION_HEADER_COUNT) self._sh_strtab_index = self._SeekRead16( self._offsets.SECTION_HEADER_STRTAB_INDEX) if self._sh_strtab_index >= self._sh_count: raise ElfError("Wrong section header string table index.") def _LoadSectionName(self, sh): """Reads the name of a section. Args: sh: An instance of SectionHeader. Returns: A string, the name of the section. """ strtab = self._section_headers[self._sh_strtab_index] return self._SeekReadString(strtab.offset + sh.name_offset) def _LoadDtNeeded(self, offset): """Reads DT_NEEDED entries from dynamic section. Args: offset: The offset of the dynamic section from the beginning of the file. Returns: A list of strings, the names of libraries. """ strtab_address = None name_offsets = [] while True: tag = self._seek_read_address(offset) offset += self._address_size value = self._seek_read_address(offset) offset += self._address_size if tag == elf_consts.DT_NULL: break if tag == elf_consts.DT_NEEDED: name_offsets.append(value) if tag == elf_consts.DT_STRTAB: strtab_address = value if strtab_address is None: raise ElfError("Cannot find string table offset in dynamic section.") try: strtab_offset = next(x.offset for x in self._section_headers if x.address == strtab_address) except StopIteration: raise ElfError("Cannot find dynamic string table.") names = [self._SeekReadString(strtab_offset + x) for x in name_offsets] return names def ListDependencies(self): """Lists the shared libraries that the ELF depends on. Returns: A list of strings, the names of the depended libraries. """ deps = [] for sh in self._section_headers: if sh.type == elf_consts.SHT_DYNAMIC: deps.extend(self._LoadDtNeeded(sh.offset)) return deps def ListGlobalDynamicSymbols(self): """Lists the dynamic symbols defined in the ELF. Returns: A list of strings, the names of the symbols. """ dynstr = None dynsym = None for sh in self._section_headers: name = self._LoadSectionName(sh) if name == elf_consts.DYNSYM: dynsym = sh elif name == elf_consts.DYNSTR: dynstr = sh if not dynsym or not dynstr or dynsym.size == 0: raise ElfError("Cannot find dynamic symbol table.") sym_names = [] for offset in range( dynsym.offset, dynsym.offset + dynsym.size, dynsym.entry_size): sym_info = self._SeekRead8(offset + self._offsets.SYMBOL_INFO) if (sym_info & 0xf) == elf_consts.SYMBOL_NOTYPE: continue if sym_info >> 4 not in (elf_consts.SYMBOL_BINDING_GLOBAL, elf_consts.SYMBOL_BINDING_WEAK): continue sym_sh_index = self._SeekRead16( offset + self._offsets.SYMBOL_SECTION_INDEX) if sym_sh_index == elf_consts.SHN_UNDEFINED: continue name_offset = self._SeekRead32(offset + self._offsets.SYMBOL_NAME) sym_names.append(self._SeekReadString(dynstr.offset + name_offset)) return sym_names