blob: 1d3c7e5d85d4bf5fd06090a272b3cb1df7c501e7 [file] [log] [blame]
#!/usr/bin/env python
#
# 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
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.
_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):
"""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._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 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 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 ElfHeader and initializes attributes"""
try:
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 _loadSectionHeader(self, offset):
"""Loads a section header from ELF file.
Args:
offset: The starting offset of the section header.
Returns:
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))
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 == 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