summaryrefslogtreecommitdiff
path: root/dependency/elf_parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'dependency/elf_parser.py')
-rw-r--r--dependency/elf_parser.py276
1 files changed, 235 insertions, 41 deletions
diff --git a/dependency/elf_parser.py b/dependency/elf_parser.py
index 50e2584..1d3c7e5 100644
--- a/dependency/elf_parser.py
+++ b/dependency/elf_parser.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3.4
+#!/usr/bin/env python
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -16,66 +16,260 @@
#
import os
-import re
+import struct
+
+
+class ElfError(Exception):
+ """The exception raised by ElfParser."""
+ pass
-from vts.runners.host import utils
class ElfParser(object):
- """This class reads an ELF file by parsing output of the command readelf.
+ """The class reads information from an ELF file.
Attributes:
- _file_path: The path to the ELF file.
+ _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.
+ _section_headers: List of SectionHeader objects read from the ELF.
"""
+ _MAGIC_OFFSET = 0
+ _MAGIC_BYTES = b"\x7fELF"
+ _BITNESS_OFFSET = 4
+ _BITNESS_32 = 1
+ _BITNESS_64 = 2
+ # Section type
+ _SHT_DYNAMIC = 6
+ # Tag in dynamic section
+ _DT_NULL = 0
+ _DT_NEEDED = 1
+ _DT_STRTAB = 5
+
+ class ElfOffsets32(object):
+ """Offset of each entry in 32-bit ELF"""
+ # offset from ELF header
+ SECTION_HEADER_OFFSET = 0x20
+ SECTION_HEADER_SIZE = 0x2e
+ SECTION_HEADER_COUNT = 0x30
+ # offset from section header
+ SECTION_TYPE = 0x04
+ SECTION_ADDRESS = 0x0c
+ SECTION_OFFSET = 0x10
+
+ class ElfOffsets64(object):
+ """Offset of each entry in 64-bit ELF"""
+ # offset from ELF header
+ SECTION_HEADER_OFFSET = 0x28
+ SECTION_HEADER_SIZE = 0x3a
+ SECTION_HEADER_COUNT = 0x3c
+ # offset from section header
+ SECTION_TYPE = 0x04
+ SECTION_ADDRESS = 0x10
+ SECTION_OFFSET = 0x18
+
+ class SectionHeader(object):
+ """Contains section header entries as attributes.
+
+ Attributes:
+ 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.
+ """
+ def __init__(self, type, address, offset):
+ self.type = type
+ self.address = address
+ self.offset = offset
def __init__(self, file_path):
- self._file_path = file_path
+ """Creates a parser to open and read an ELF file.
+
+ Args:
+ file_path: The path to the ELF.
- @staticmethod
- def isSupported():
- """Checks whether readelf is available."""
+ 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:
- utils.exe_cmd("readelf", "--version")
- return True
- except OSError:
- return False
+ self._loadElfHeader()
+ self._section_headers = [
+ self._loadSectionHeader(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 isValid(self):
- """Checks size and first 4 bytes of the ELF file.
+ def close(self):
+ """Closes the ELF 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 boolean representing whether _file_path is a valid ELF.
+ 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:
- size = os.path.getsize(self._file_path)
- # must be larger than 32-bit file header
- if size < 52:
- return False
- except OSError:
- return False
+ 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 ElfHeader and initializes attributes"""
try:
- with open(self._file_path, "rb") as f:
- magic = f.read(4)
- if list(bytearray(magic)) != [0x7f, 0x45, 0x4c, 0x46]:
- return False
- except IOError:
- return False
+ self._file_size = os.fstat(self._file.fileno()).st_size
+ except OSError as e:
+ raise ElfError(e)
+
+ magic = self._seekRead(self._MAGIC_OFFSET, 4)
+ if magic != self._MAGIC_BYTES:
+ raise ElfError("Wrong magic bytes.")
+ bitness = self._seekRead8(self._BITNESS_OFFSET)
+ if bitness == self._BITNESS_32:
+ self.bitness = 32
+ self._address_size = 4
+ self._offsets = self.ElfOffsets32
+ self._seek_read_address = self._seekRead32
+ elif bitness == self._BITNESS_64:
+ self.bitness = 64
+ self._address_size = 8
+ self._offsets = self.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)
return True
- def listDependencies(self):
- """Lists the shared libraries that the ELF depends on.
+ def _loadSectionHeader(self, offset):
+ """Loads a section header from ELF file.
+
+ Args:
+ offset: The starting offset of the section header.
Returns:
- List of strings. The names of the depended libraries.
+ An instance of SectionHeader.
+ """
+ return self.SectionHeader(
+ self._seekRead32(offset + self._offsets.SECTION_TYPE),
+ self._seek_read_address(offset + self._offsets.SECTION_ADDRESS),
+ self._seek_read_address(offset + self._offsets.SECTION_OFFSET))
- Raises:
- OSError if readelf fails.
+ 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.
"""
- pattern = re.compile("\\(NEEDED\\)\\s*Shared library: \[(.+)\]")
- output = utils.exe_cmd("readelf", "--dynamic", self._file_path)
- results = []
- for line in output.split("\n"):
- match = pattern.search(line)
- if match:
- results.append(match.group(1))
- return results
+ 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 == self._DT_NULL:
+ break
+ if tag == self._DT_NEEDED:
+ name_offsets.append(value)
+ if tag == self._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 == self._SHT_DYNAMIC:
+ deps.extend(self._loadDtNeeded(sh.offset))
+ return deps