blob: 72d939dea05009b5a2b888dc1cda297a6784a71a [file] [log] [blame]
#
# 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