# Copyright 2018, 2020, 2023-2025 NXP
# SPDX-License-Identifier: BSD-3-Clause

# -*- coding: utf-8 -*-
"""
    elfutils.elfparser
    ~~~~~~~~~~~~~~~~~~

    Minimalistic ELF parser.

Copyright 2015 Freescale Semiconductor, Inc.

SPDX-License-Identifier: BSD-3-Clause
"""

import sys
import os
from array import array
from struct import unpack

########################################################################
# Python 2/3 compatibility code.
########################################################################

PY3 = sys.version_info[0] == 3

if PY3:
    range_iter = range
else:
    range_iter = xrange

########################################################################
# Utilities code.
########################################################################

class _NamedAttributesMeta(type):
    """Metaclass used to inject a dictionary attribute in the constructed
    class.
    The injected dictionary maps class attribute values to their names.
    This metaclass is used so that we can programatically obtain a class
    attribute name given it's value.
    """
    def __init__(cls, name, bases, dct):
        if issubclass(cls, NamedAttributesInject):
            cls._attrib_dct = {}
            for name in dct:
                if not name.startswith('_'):
                    cls._attrib_dct[dct[name]] = name
        super(_NamedAttributesMeta, cls).__init__(name, bases, dct)


class NamedAttributesInject(object):
    """Class used for tagging that subclasses shuld have an
    attribute value->name dictionary injected.
    """
    pass


class NamedAttributesBase(object):
    """Class acting as a base to all classes for which we want to be able
    to obtain attribute names.
    """

    __metaclass__ = _NamedAttributesMeta
    @classmethod
    def name(cls, val):
        """Given an attribute value, return its name."""
        attrib_name = cls._attrib_dct.get(val, None)
        if attrib_name is None:
            return str(val)
        return attrib_name

    @classmethod
    def names(cls, val):
        """Given a value which represents attribute values OR'ed
        togheter, return a string containing the attribute names.
        """
        attrib_names = []
        for attrib_val in cls._attrib_dct:
            if (val & attrib_val) != 0:
                attrib_names.append(cls._attrib_dct[attrib_val])
        if len(attrib_names) > 0:
            return "|".join(attrib_names)
        return str(val)

########################################################################
# Elf related constants.
########################################################################

# Elf header related constants.
class ElfIdent(object):
    """Elf ident indexes (e_ident[] field in Elf header)."""
    EI_MAG0 = 0       # File identification.
    EI_MAG1 = 1       # File identification.
    EI_MAG2 = 2       # File identification.
    EI_MAG3 = 3       # File identification.
    EI_CLASS = 4      # File class.
    EI_DATA = 5       # Data encoding.
    EI_VERSION = 6    # ELF format version.
    EI_OSABI = 7      # Operating system / ABI identification.
    EI_ABIVERSION = 8 # ABI version.
    EI_PAD = 9        # Start of padding.
    EI_NIDENT = 16    # Size of the e_ident array.


class ElfType(NamedAttributesBase, NamedAttributesInject):
    """Types of Elf objects (e_type field in Elf header)."""
    ET_NONE = 0        # No file type.
    ET_REL = 1         # Relocatable file.
    ET_EXEC = 2        # Executable file.
    ET_DYN = 3         # Shared object file.
    ET_CORE = 4        # Core file.
    ET_LOOS = 0xfe00   # Operating system specific.
    ET_HIOS = 0xfeff   # Operating system specific.
    ET_LOPROC = 0xff00 # Processor-specific.
    ET_HIPROC = 0xffff # Processor-specific.


class ElfVersion(NamedAttributesBase, NamedAttributesInject):
    """Object file version (e_version field in Elf header)."""
    EV_NONE = 0
    EV_CURRENT = 1


class ElfClass(NamedAttributesBase, NamedAttributesInject):
    """Elf file class constants."""
    ELFCLASSNONE = 0 # Invalid class.
    ELFCLASS32 = 1   # 32-bit Elf objects.
    ELFCLASS64 = 2   # 64-bit Elf objects.


class ElfData(NamedAttributesBase, NamedAttributesInject):
    """Elf data encoding."""
    ELFDATANONE = 0 # Invalid data encoding.
    ELFDATA2LSB = 1 # 2's complement little-endian.
    ELFDATA2MSB = 2 # 2's complement big-endian.


class ElfOSABI(NamedAttributesBase, NamedAttributesInject):
    """OS ABI's (from ElfHeader.Ident[EI_OSABI])."""
    ELFOSABI_NONE = 0
    ELFOSABI_HPUX = 1
    ELFOSABI_NETBSD = 2
    ELFOSABI_LINUX = 3
    ELFOSABI_HURD = 4
    ELFOSABI_86OPEN = 5
    ELFOSABI_SOLARIS = 6
    ELFOSABI_AIX = 7
    ELFOSABI_IRIX = 8
    ELFOSABI_FREEBSD = 9
    ELFOSABI_TRU64 = 10
    ELFOSABI_MODESTO = 11
    ELFOSABI_OPENBSD = 12
    ELFOSABI_OPENVMS = 13
    ELFOSABI_NSK = 14
    ELFOSABI_ARM = 97
    ELFOSABI_STANDALONE = 255


class ElfMachine(NamedAttributesBase, NamedAttributesInject):
    """Machine type (e_machine field in Elf header)."""
    EM_NONE = 0
    EM_M32 = 1
    EM_SPARC = 2
    EM_386 = 3
    EM_68K = 4
    EM_88K = 5
    EM_486 = 6
    EM_860 = 7
    EM_MIPS = 8
    EM_S370 = 9
    EM_MIPS_RS3_LE = 10
    EM_PARISC = 15
    EM_VPP500 = 17
    EM_SPARC32PLUS = 18
    EM_960 = 19
    EM_PPC = 20
    EM_PPC64 = 21
    EM_S390 = 22
    EM_SPU = 23
    EM_V800 = 36
    EM_FR20 = 37
    EM_RH32 = 38
    EM_RCE = 39
    EM_ARM = 40
    EM_ALPHA = 41
    EM_SH = 42
    EM_SPARCV9 = 43
    EM_TRICORE = 44
    EM_ARC = 45
    EM_H8_300 = 46
    EM_H8_300H = 47
    EM_H8S = 48
    EM_H8_500 = 49
    EM_IA_64 = 50
    EM_MIPS_X = 51
    EM_COLDFIRE = 52
    EM_68HC12 = 53
    EM_MMA = 54
    EM_PCP = 55
    EM_NCPU = 56
    EM_NDR1 = 57
    EM_STARCORE = 58
    EM_ME16 = 59
    EM_ST100 = 60
    EM_TINYJ = 61
    EM_X86_64 = 62
    EM_PDSP = 63
    EM_PDP10 = 64
    EM_PDP11 = 65
    EM_FX66 = 66
    EM_ST9PLUS = 67
    EM_ST7 = 68
    EM_68HC16 = 69
    EM_68HC11 = 70
    EM_68HC08 = 71
    EM_68HC05 = 72
    EM_SVX = 73
    EM_ST19 = 74
    EM_VAX = 75
    EM_CRIS = 76
    EM_JAVELIN = 77
    EM_FIREPATH = 78
    EM_ZSP = 79
    EM_MMIX = 80
    EM_HUANY = 81
    EM_PRISM = 82
    EM_AVR = 83
    EM_FR30 = 84
    EM_D10V = 85
    EM_D30V = 86
    EM_V850 = 87
    EM_M32R = 88
    EM_MN10300 = 89
    EM_MN10200 = 90
    EM_PJ = 91
    EM_OPENRISC = 92
    EM_ARC_COMPACT = 93
    EM_XTENSA = 94
    EM_VIDEOCORE = 95
    EM_TMM_GPP = 96
    EM_NS32K = 97
    EM_TPC = 98
    EM_SNP1K = 99
    EM_ST200 = 100
    EM_IP2K = 101
    EM_MAX = 102
    EM_CR = 103
    EM_F2MC16 = 104
    EM_MSP430 = 105
    EM_BLACKFIN = 106
    EM_SE_C33 = 107
    EM_SEP = 108
    EM_ARCA = 109
    EM_UNICORE = 110
    EM_EXCESS = 111
    EM_DXP = 112
    EM_ALTERA_NIOS2 = 113
    EM_CRX = 114
    EM_XGATE = 115
    EM_C166 = 116
    EM_M16C = 117
    EM_DSPIC30F = 118
    EM_CE = 119
    EM_M32C = 120
    EM_TSK3000 = 131
    EM_RS08 = 132
    EM_ECOG2 = 134
    EM_SCORE7 = 135
    EM_DSP24 = 136
    EM_VIDEOCORE3 = 137
    EM_LATTICEMICO32 = 138
    EM_SE_C17 = 139
    EM_TI_C6000 = 140
    EM_TI_C2000 = 141
    EM_TI_C5500 = 142
    EM_MMDSP_PLUS = 160
    EM_CYPRESS_M8C = 161
    EM_R32C = 162
    EM_TRIMEDIA = 163
    EM_QDSP6 = 164
    EM_8051 = 165
    EM_STXP7X = 166
    EM_NDS32 = 167
    EM_ECOG1 = 168
    EM_MAXQ30 = 169
    EM_XIMO16 = 170
    EM_MANIK = 171
    EM_CRAYNV2 = 172
    EM_RX = 173
    EM_METAG = 174
    EM_MCST_ELBRUS = 175
    EM_ECOG16 = 176
    EM_CR16 = 177
    EM_ETPU = 178
    EM_SLE9X = 179
    EM_AARCH64 = 183
    EM_AVR32 = 185
    EM_STM8 = 186
    EM_TILE64 = 187
    EM_TILEPRO = 188
    EM_MICROBLAZE = 189
    EM_CUDA = 190
    EM_TILEGX = 191
    EM_CLOUDSHIELD = 192
    EM_RISCV = 243


# Elf sections related constants.
class SectionIndex(NamedAttributesBase, NamedAttributesInject):
    """Section indexes."""
    SHN_UNDEF = 0           # Undefined.
    SHN_LORESERVE = 0xff00  # First of reserved range.
    SHN_LOPROC = 0xff00     # First processor-specific.
    SHN_HIPROC = 0xff1f     # Last processor-specific.
    SHN_LOOS = 0xff20       # First OS-specific.
    SHN_HIOS = 0xff3f       # Last OS-specific.
    SHN_ABS = 0xfff1        # Absolute values.
    SHN_COMMON = 0xfff2     # Common data.
    SHN_XINDEX = 0xffff     # Index stored elsewhere.
    SHN_HIRESERVE = 0xffff  # Last of reserved range.


class SectionType(NamedAttributesBase, NamedAttributesInject):
    """Section types."""
    SHT_NULL = 0                    # Inactive
    SHT_PROGBITS = 1                # Program defined information.
    SHT_SYMTAB = 2                  # Symbol table section.
    SHT_STRTAB = 3                  # String table section.
    SHT_RELA = 4                    # Relocation section with addends.
    SHT_HASH = 5                    # Symbol hash table section.
    SHT_DYNAMIC = 6                 # Dynamic section.
    SHT_NOTE = 7                    # Note section.
    SHT_NOBITS = 8                  # No space section.
    SHT_REL = 9                     # Relocation section.
    SHT_SHLIB = 10                  # Reserved - purpose unknown.
    SHT_DYNSYM = 11                 # Dynamic symbol table section.
    SHT_INIT_ARRAY = 12             # Initialization function pointers.
    SHT_FINI_ARRAY = 13             # Termination function pointers.
    SHT_PREINIT_ARRAY = 14          # Pre-initialization function ptrs.
    SHT_GROUP = 15                  # Section group.
    SHT_SYMTAB_SHNDX = 16           # Section indexes (see SHN_XINDEX).
    SHT_LOOS = 0x60000000           # First of OS specific semantics.
    SHT_GNU_ATTRIBUTES = 0x6ffffff5 # GNU object attributes.
    SHT_GNU_HASH = 0x6ffffff6       # GNU hash table.
    SHT_GNU_LIBLIST = 0x6ffffff7    # GNU prelink library list.
    SHT_GNU_VERDEF = 0x6ffffffd     # GNU version definition section.
    SHT_GNU_VERNEED = 0x6ffffffe    # GNU version needs section.
    SHT_GNU_VERSYM = 0x6fffffff     # GNU version symbol table.
    SHT_LOPROC = 0x70000000         # Reserved range for processor.
    SHT_HIPROC = 0x7fffffff         # Specific section header types.
    SHT_LOUSER = 0x80000000         # Reserved range for application.
    SHT_HIUSER = 0xffffffff         # Specific indexes.


class SectionFlag(NamedAttributesBase, NamedAttributesInject):
    """Specific section flag."""
    SHF_WRITE = 0x1              # Section contains writable data.
    SHF_ALLOC = 0x2              # Section occupies memory.
    SHF_EXECINSTR = 0x4          # Section contains instructions.
    SHF_MERGE = 0x10             # Section may be merged.
    SHF_STRINGS = 0x20           # Section contains strings.
    SHF_INFO_LINK = 0x40         # sh_info contains section index.
    SHF_LINK_ORDER = 0x80        # Special ordering requirements.
    SHF_OS_NONCONFORMING = 0x100 # OS-specific processing required.
    SHF_GROUP = 0x200            # Member of section group.
    SHF_TLS = 0x400              # Section contains TLS data.
    SHF_MASKOS = 0x0ff00000      # OS-specific semantics.
    SHF_MASKPROC = 0xf0000000    # Processor-specific semantics.


# Program header related constants.
class ProgType(NamedAttributesBase, NamedAttributesInject):
    """Segment types."""
    PT_NULL = 0            # Unused entry.
    PT_LOAD = 1            # Loadable segment.
    PT_DYNAMIC = 2         # Dynamic linking information segment.
    PT_INTERP = 3          # Pathname of interpreter.
    PT_NOTE = 4            # Auxiliary information.
    PT_SHLIB = 5           # Reserved (not used).
    PT_PHDR = 6            # Location of program header itself.
    PT_TLS = 7             # Thread local storage segment.
    PT_LOOS = 0x60000000   # First OS-specific.
    PT_HIOS = 0x6fffffff   # Last OS-specific.
    PT_LOPROC = 0x70000000 # First processor-specific type.
    PT_HIPROC = 0x7fffffff # Last processor-specific type.


class ProgFlag(NamedAttributesBase, NamedAttributesInject):
    """Program header segment flags."""
    PF_X = 0x1               # Executable.
    PF_W = 0x2               # Writable.
    PF_R = 0x4               # Readable.
    PF_MASKOS = 0x0ff00000   # OS-specific.
    PF_MASKPROC = 0xf0000000 # Processor-specific.


# Symbols and symbol tables related constants.
class SymBind(NamedAttributesBase, NamedAttributesInject):
    """Symbol binding types."""
    STB_LOCAL = 0   # Local symbol.
    STB_GLOBAL = 1  # Global symbol.
    STB_WEAK = 2    # Weak symbol.
    STB_LOOS = 10   # Reserved range start for OS.
    STB_HIOS = 12   # Reserved range end for OS.
    STB_LOPROC = 13 # Reserved range start for processor.
    STB_HIPROC = 15 # Reserved range end for processor.


class SymType(NamedAttributesBase, NamedAttributesInject):
    """Symbol types."""
    STT_NOTYPE = 0   # Type not specified.
    STT_OBJECT = 1   # Data object.
    STT_FUNC = 2     # Function.
    STT_SECTION = 3  # Section.
    STT_FILE = 4     # Source file.
    STT_COMMON = 5   # Uninitialized common block.
    STT_TLS = 6      # TLS object.
    STT_LOOS = 10    # Reserved range start for OS.
    STT_HIOS = 12    # Reserved range end for OS.
    STT_LOPROC = 13  # Reserved range start for processor.
    STT_HIPROC = 15  # Reserved range end for processor.


class SymVis(NamedAttributesBase, NamedAttributesInject):
    """Symbol visibility."""
    STV_DEFAULT = 0   # Default visibility.
    STV_INTERNAL = 1  # Internal.
    STV_HIDDEN = 2    # Not visible.
    STV_PROTECTED = 3 # Protected.

########################################################################
# Elf related data structures.
########################################################################


class InvalidElfDataException(Exception):
    """Raised when the Elf file contains invalid data."""
    pass


class ElfFileStream(object):
    """Class modelling an Elf file stream.

    :param string file_name:
        The name of the Elf file.
    """
    def __init__(self, file_name):
        self.io_stream = open(file_name, 'rb')
        self._elf_class = None
        self._elf_data = None
        # Functions to read integers fron the input string, depending
        # on the Elf data endianness.
        self.read_uint16 = None
        self.read_uint32 = None
        self.read_uint64 = None

    def close(self):
        """Close the stream."""
        self.io_stream.close()

    @property
    def elf_class(self):
        """Return the Elf class."""
        return self._elf_class

    @elf_class.setter
    def elf_class(self, val):
        """Set the ELF class."""
        if val != ElfClass.ELFCLASS32 and val != ElfClass.ELFCLASS64:
            raise InvalidElfDataException("Invalid Elf class.")
        self._elf_class = val

    @property
    def elf_data(self):
        """Return the Elf data endianness."""
        return self._elf_data

    @elf_data.setter
    def elf_data(self, elf_data):
        """Set the elf data endianness."""
        self._elf_data = elf_data
        # Set the functions reading integers.
        if self.elf_data == ElfData.ELFDATA2LSB:
            self.read_uint16 = self._read_uint16_le
            self.read_uint32 = self._read_uint32_le
            self.read_uint64 = self._read_uint64_le
        elif self.elf_data == ElfData.ELFDATA2MSB:
            self.read_uint16 = self._read_uint16_be
            self.read_uint32 = self._read_uint32_be
            self.read_uint64 = self._read_uint64_be
        else:
            raise InvalidElfDataException("Invalid Elf data endianness.")

    def tell(self):
        """Return the current stream file position."""
        return self.io_stream.tell()

    def seek(self, offset, whence=os.SEEK_SET):
        """Set the current stream file position."""
        self.io_stream.seek(offset, whence)

    def skip(self, num_bytes):
        """Skip a number of bytes from the input stream."""
        self.io_stream.seek(num_bytes, os.SEEK_CUR)

    def read_byte(self):
        """Read one byte from the input stream."""
        return unpack('b', self.io_stream.read(1))[0]

    def read_bytes(self, num_bytes):
        """Read the specified number of bytes from the input stream."""
        return self.io_stream.read(num_bytes)

    def _read_uint16_le(self):
        """Read a 2 bytes LE unsigned integer from the input stream."""
        return unpack('<H', self.io_stream.read(2))[0]

    def _read_uint32_le(self):
        """Read a 4 bytes LE unsigned integer from the input stream."""
        return unpack('<I', self.io_stream.read(4))[0]

    def _read_uint64_le(self):
        """Read an 8 bytes LE unsigned integer from the input stream."""
        return unpack('<Q', self.io_stream.read(8))[0]

    def _read_uint16_be(self):
        """Read a 2 bytes BE unsigned integer from the input stream."""
        return unpack('>H', self.io_stream.read(2))[0]

    def _read_uint32_be(self):
        """Read a 4 bytes BE unsigned integer from the input stream."""
        return unpack('>I', self.io_stream.read(4))[0]

    def _read_uint64_be(self):
        """Read an 8 bytes BE unsigned integer from the input stream."""
        return unpack('>Q', self.io_stream.read(8))[0]


class ElfFileHeader(object):
    """Class modelling the header of an Elf file.

    :param ElfFileStream elf_stream:
        An opened Elf file stream.
    """
    def __init__(self, elf_file):
        # Read and check the Elf magic bytes.
        elf_ident_mag = elf_file.read_bytes(4)
        if elf_ident_mag != b"\x7fELF":
            raise InvalidElfDataException("Not an Elf file.")

        # Read the rest of the e_ident[] field.
        self.elf_class = elf_file.read_byte()
        elf_file.elf_class = self.elf_class
        self.elf_data = elf_file.read_byte()
        elf_file.elf_data = self.elf_data
        self.elf_version = elf_file.read_byte()
        # The size of the e_ident[] field is 16 bytes, skip the remaining
        # 9 padding bytes.
        elf_file.skip(9)

        self.elf_type = elf_file.read_uint16()
        self.machine = elf_file.read_uint16()
        self.elf_version = elf_file.read_uint32()

        if self.elf_class == ElfClass.ELFCLASS32:
            self.entry = elf_file.read_uint32()
            self.ph_offset = elf_file.read_uint32()
            self.sh_offset = elf_file.read_uint32()
        else:
            self.entry = elf_file.read_uint64()
            self.ph_offset = elf_file.read_uint64()
            self.sh_offset = elf_file.read_uint64()

        self.flags = elf_file.read_uint32()
        self.header_size = elf_file.read_uint16()
        self.ph_entry_size = elf_file.read_uint16()
        self.ph_count = elf_file.read_uint16()
        self.sh_entry_size = elf_file.read_uint16()
        self.sh_count = elf_file.read_uint16()
        self.shstrndx = elf_file.read_uint16()

    def __str__(self):
        return ""\
            + "elf_class     %s\n" % ElfClass.name(self.elf_class)     \
            + "elf_data      %s\n" % ElfData.name(self.elf_data)       \
            + "elf_version   %s\n" % ElfVersion.name(self.elf_version) \
            + "elf_type      %s\n" % ElfType.name(self.elf_type)       \
            + "machine       %s\n" % ElfMachine.name(self.machine)     \
            + "entry         0x%x\n" % self.entry                      \
            + "ph_offset     0x%x\n" % self.ph_offset                  \
            + "sh_offset     0x%x\n" % self.sh_offset                  \
            + "flags         0x%x\n" % self.flags                      \
            + "header_size   0x%x\n" % self.header_size                \
            + "ph_entry_size 0x%x\n" % self.ph_entry_size              \
            + "ph_count      0x%x\n" % self.ph_count                   \
            + "sh_entry_size 0x%x\n" % self.sh_entry_size              \
            + "sh_count      0x%x\n" % self.sh_count                   \
            + "shstrndx      0x%x\n" % self.shstrndx


class SectionHeader(object):
    """Keeps data related to an Elf section header.
    The ElfFile object to which this section belongs to should be kept
    opened while using the section header object.
    At construction, we assume that the elf stream file position was already
    set accordingly.

    :param ElfFile elf_file:
        The Elf file to which this section header belongs.
    :param int sec_index:
        The index of the section.
    """
    def __init__(self, elf_file, sec_index):
        self.elf_file = elf_file
        self.sec_index = sec_index
        if elf_file.elf_class == ElfClass.ELFCLASS32:
            self.name_index = elf_file.read_uint32()
            self.sec_type = elf_file.read_uint32()
            self.flags = elf_file.read_uint32()
            self.addr = elf_file.read_uint32()
            self.offset = elf_file.read_uint32()
            self.size = elf_file.read_uint32()
            self.link = elf_file.read_uint32()
            self.info = elf_file.read_uint32()
            self.addralign = elf_file.read_uint32()
            self.entsize = elf_file.read_uint32()
        elif elf_file.elf_class == ElfClass.ELFCLASS64:
            self.name_index = elf_file.read_uint32()
            self.sec_type = elf_file.read_uint32()
            self.flags = elf_file.read_uint64()
            self.addr = elf_file.read_uint64()
            self.offset = elf_file.read_uint64()
            self.size = elf_file.read_uint64()
            self.link = elf_file.read_uint32()
            self.info = elf_file.read_uint32()
            self.addralign = elf_file.read_uint64()
            self.entsize = elf_file.read_uint64()

        # True if the section is loadable.
        self.is_loadable = self.sec_type != SectionType.SHT_NOBITS and \
            (self.flags & SectionFlag.SHF_ALLOC) != 0
        # True if the section contains execuable instructions.
        self.is_execinstr = (self.flags & SectionFlag.SHF_EXECINSTR) != 0
        # The section name, lazy loaded.
        self._name = None
        # The section content, lazy loaded.
        self._data = None

    def __str__(self):
        return ""\
            + "sec_index  0x%x\n" % self.sec_index                \
            + "name_index 0x%x\n" % self.name_index               \
            + "name       %s\n" % self.name                       \
            + "sec_type   %s\n" % SectionType.name(self.sec_type) \
            + "flags      %s\n" % SectionFlag.names(self.flags)   \
            + "addr       0x%x\n" % self.addr                     \
            + "offset     0x%x\n" % self.offset                   \
            + "size       0x%x\n" % self.size                     \
            + "link       0x%x\n" % self.link                     \
            + "info       0x%x\n" % self.info                     \
            + "addralign  0x%x\n" % self.addralign                \
            + "entsize    0x%x\n" % self.entsize

    def read_data(self):
        """Read and return the section content."""
        if self._data is None:
            curr_offset = self.elf_file.tell()
            self.elf_file.seek(self.offset, os.SEEK_SET)
            self._data = self.elf_file.read_bytes(self.size)
            self.elf_file.seek(curr_offset, os.SEEK_SET)
        return self._data

    def read_data_chunk(self, offset_in_sect, num_bytes):
        """Read bytes from the section data at the specified offset.

        :param int offset_in_sect:
            The offset, inside the section data, at which to read.
        :param num_bytes:
            The number of bytes to read.
        :return:
            A buffer containing the read bytes.
        """
        # Adjust the number of bytes to be read, if needed.
        if offset_in_sect + num_bytes > self.size:
            num_bytes = self.size - offset_in_sect
        # See if we already have the entire section content in memory.
        if self._data is not None:
            return self._data[offset_in_sect:offset_in_sect + num_bytes]
        # Read the specified chunk from the Elf stream.
        curr_offset = self.elf_file.tell()
        self.elf_file.seek(self.offset + offset_in_sect, os.SEEK_SET)
        data = self.elf_file.read_bytes(num_bytes)
        self.elf_file.seek(curr_offset, os.SEEK_SET)
        return data

    @property
    def name(self):
        """Get the name of the section."""
        if self._name is None:
            self._name = self.elf_file.shstrtab[self.name_index]
        return self._name


class ProgHeader(object):
    """Keeps data related to an Elf program header.
    At construction, we assume that the elf stream file position was already
    set accordingly.

    :param ElfFile elf_file:
        The Elf file to which this program header belongs.
    :param int prog_index:
        The index of the program header.
    """
    def __init__(self, elf_file, prog_index):
        self.prog_index = prog_index
        if elf_file.elf_class == ElfClass.ELFCLASS32:
            self.prog_type = elf_file.read_uint32()
            self.offset = elf_file.read_uint32()
            self.vaddr = elf_file.read_uint32()
            self.paddr = elf_file.read_uint32()
            self.filesz = elf_file.read_uint32()
            self.memsz = elf_file.read_uint32()
            self.flags = elf_file.read_uint32()
            self.align = elf_file.read_uint32()
        elif elf_file.elf_class == ElfClass.ELFCLASS64:
            self.prog_type = elf_file.read_uint32()
            self.flags = elf_file.read_uint32()
            self.offset = elf_file.read_uint64()
            self.vaddr = elf_file.read_uint64()
            self.paddr = elf_file.read_uint64()
            self.filesz = elf_file.read_uint64()
            self.memsz = elf_file.read_uint64()
            self.align = elf_file.read_uint64()
    def __str__(self):
        return ""\
            + "prog_index 0x%x\n" % self.prog_index             \
            + "prog_type  %s\n" % ProgType.name(self.prog_type) \
            + "offset     0x%x\n" % self.offset                 \
            + "vaddr      0x%x\n" % self.vaddr                  \
            + "paddr      0x%x\n" % self.paddr                  \
            + "filesz     0x%x\n" % self.filesz                 \
            + "memsz      0x%x\n" % self.memsz                  \
            + "flags      %s\n" % ProgFlag.names(self.flags)    \
            + "align      0x%x\n" % self.align
            



class StringTable(object):
    """An Elf string table.

    :param ElfFile elf_file:
        The Elf file to which this string table belongs to.
    :param int offset:
        The offset, in the Elf file, at which the string table starts.
    :param int size:
        The size of the string table, in bytes.
    """
    def __init__(self, elf_file, offset, size):
        curr_offset = elf_file.tell()
        elf_file.seek(offset, os.SEEK_SET)
        self._table = array('B', elf_file.read_bytes(size))
        elf_file.seek(curr_offset, os.SEEK_SET)

    def __getitem__(self, str_index):
        """Get an the string at a specified index.

        :param int str_index:
            The string index.
        :return:
            The string.
        """
        # Calculate the position of the NUL byte for our string.
        str_nul_pos = self._table[str_index:].index(0) + str_index
        if str_nul_pos == 0:
            return None
        try:
            return self._table[str_index:str_nul_pos].tostring().decode('utf8')
        except AttributeError:
            return self._table[str_index:str_nul_pos].tobytes().decode('utf8')

    @property
    def byte_size(self):
        """Get the length of the string table, in bytes."""
        return len(self._table)


class Symbol(object):
    """A symbol (an entry in the symbol table).

    :param ElfFile elf_file:
        The Elf file to which this symbol belongs to.
    :param int sym_index:
        The symbol index.
    """
    def __init__(self, elf_file, sym_index):
        self.sym_index = sym_index
        if elf_file.elf_class == ElfClass.ELFCLASS32:
            self.name_index = elf_file.read_uint32()
            self.value = elf_file.read_uint32()
            self.size = elf_file.read_uint32()
            self.info = elf_file.read_byte()
            self.other = elf_file.read_byte()
            self.shndx = elf_file.read_uint16()
        else:
            self.name_index = elf_file.read_uint32()
            self.info = elf_file.read_byte()
            self.other = elf_file.read_byte()
            self.shndx = elf_file.read_uint16()
            self.value = elf_file.read_uint64()
            self.size = elf_file.read_uint64()
        if elf_file.strtab is not None:
            self.name = elf_file.strtab[self.name_index]
        else:
            self.name = None

    def __str__(self):
        return ""\
            + "type       %s\n" % SymType.name(self.sym_type) \
            + "name       %s\n" % self.name                   \
            + "value      0x%x\n" % self.value                \
            + "size       0x%x\n" % self.size                 \
            + "bind       %s\n" % SymBind.name(self.bind)     \
            + "info       0x%x\n" % self.info                 \
            + "other      0x%x\n" % self.other                \
            + "shndx      0x%x\n" % self.shndx

    @property
    def bind(self):
        """Get the symbol binding."""
        return self.info >> 4

    @property
    def sym_type(self):
        """Get the symbol type."""
        return self.info & 0xF


class ElfFile(ElfFileStream):
    """An Elf file class, providing access to the Elf information.

    :param string file_name:
        The name of the Elf file.

    Example:

    .. code:: python

        with ElfFile("test.elf") as elf_file:
            # Iterate over the section headers.
            for sect_header in elf_file.iter_section_headers():
                ...
            # Iterate over the program headers.
            for prog_header in elf_file.iter_prog_headers():
                ...
            # Iterate ovder the Elf symbols.
            for sym in elf_file.iter_symbols():
                ...
            # Read the .text section content.
            sect = elf_file.get_section_header_by_name(".text")
            buf = sect.read_data()
            ...
    """
    def __init__(self, file_name):
        super(ElfFile, self).__init__(file_name)
        # The program headers, lazy loaded.
        self._prog_headers = None
        # The sections headers, lazy loaded.
        self._section_headers = None
        # The sections headers string table, lazy loaded.
        self._shstrtab = None
        # A dictionary containing the section headers by name, lazy loaded.
        self._section_headers_dict = None
        # The string table, if present, lazy loaded.
        self._strtab = None
        self._have_strtab = None
        # The symbols in sym sections, if present, lazy loaded.
        self._symbols = None
        # The number if symbols, lazy loaded.
        self._sym_num = None
        # Load the Elf header.
        self.elf_header = ElfFileHeader(self)

    def close(self):
        """Close the Elf file."""
        super(ElfFile, self).close()
        self._prog_headers = None
        self._section_headers = None
        self._shstrtab = None
        self._section_headers_dict = None
        self._strtab = None
        self._symbols = None
        self._sym_num = None
        self.elf_header = None

    def __del__(self):
        self.close()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    @property
    def num_program_headers(self):
        """Get the number of program headers."""
        return self.elf_header.ph_count

    @property
    def num_sections(self):
        """Get the number of sections."""
        return self.elf_header.sh_count

    @property
    def shstrtab(self):
        """Get the sections headers string table."""
        if self._section_headers is None:
            self._load_section_headers()
        return self._shstrtab

    @property
    def strtab(self):
        """Get the string table."""
        if self._have_strtab is None:
            strtab = self.get_section_header_by_name('.strtab')
            if strtab is not None:
                self._have_strtab = True
                self._strtab = StringTable(self, strtab.offset, strtab.size)
            else:
                self._have_strtab = False
        return self._strtab

    def iter_prog_headers(self):
        """Iterate over the program headers."""
        if self._prog_headers is None:
            self._load_prog_headers()
        for prog_header in self._prog_headers:
            yield prog_header

    def iter_section_headers(self):
        """Iterate over the section headers."""
        if self._section_headers is None:
            self._load_section_headers()
        for sect_header in self._section_headers:
            yield sect_header

    def iter_symbols(self):
        """Iterate over the symtab symbols."""
        if self._symbols is None:
            self._load_symbols()
        for symbol in self._symbols:
            yield symbol

    def get_section_header_by_index(self, sect_index):
        """Get a section header given it's index.

        :param int sect_index:
            The section index.
        :return:
            The section header.
        """
        if self._section_headers is None:
            self._load_section_headers()
        return self._section_headers[sect_index]

    def get_section_header_by_name(self, name):
        """Find and return a section header given it's name.

        :param string name:
            The section name.
        :return:
            The section header or None is not found.
        """
        if self._section_headers_dict is None:
            self._section_headers_dict = {}
            if self._section_headers is None:
                self._load_section_headers()
            for sect_header in self._section_headers:
                self._section_headers_dict[sect_header.name] = sect_header
        return self._section_headers_dict.get(name, None)

    def get_prog_header_by_index(self, prog_index):
        """Get a program header given it's index.

        :param int prog_index:
            The program header index.
        :return:
            The program header.
        """
        if self._prog_headers is None:
            self._load_prog_headers()
        return self._prog_headers[prog_index]

    def get_symbols_num(self):
        """Get the number of symbols in the symbol table."""
        if self._sym_num is None:
            self._sym_num = 0
            for sect_header in self.iter_section_headers():
                if sect_header.sec_type == SectionType.SHT_SYMTAB:
                    self._sym_num += self._get_symbols_num_for_sect(sect_header)
        return self._sym_num

    def _load_prog_headers(self):
        """Load the Elf program headers."""
        self._prog_headers = []
        if self.elf_header.ph_offset > 0:
            self.seek(self.elf_header.ph_offset)
            for i in range_iter(self.elf_header.ph_count):
                self._prog_headers.append(ProgHeader(self, i))

    def _load_section_headers(self):
        """Load the Elf section headers."""
        self._section_headers = []
        if self.elf_header.sh_offset > 0:
            self.seek(self.elf_header.sh_offset)
            for i in range_iter(self.elf_header.sh_count):
                self._section_headers.append(SectionHeader(self, i))
        # Load section headers string table.
        strtab = self._section_headers[self.elf_header.shstrndx]
        self._shstrtab = StringTable(self, strtab.offset, strtab.size)


    def _load_symbols_for_sect(self, sect_header):
        """Assuming that the given section is a SYMTAB section, load the
        contained symbols."""
        sym_num = self._get_symbols_num_for_sect(sect_header)
        if sym_num > 0 and sect_header.offset > 0:
            self.seek(sect_header.offset)
            for i in range_iter(sym_num):
                self._symbols.append(Symbol(self, i))

    def _load_symbols(self):
        """Load symbols from all SYMTAB sections."""
        self._symbols = []
        for sect_header in self.iter_section_headers():
            if sect_header.sec_type == SectionType.SHT_SYMTAB:
                self._load_symbols_for_sect(sect_header)

    def _get_symbols_num_for_sect(self, sect_header):
        """Assuming that the given section is a SYMTAB section, return the
        number of contained symbols."""
        if self.elf_class == ElfClass.ELFCLASS32:
            return sect_header.size / 16
        else:
            return sect_header.size / 24
