##############################################################################
# Australian Public Licence B (OZPLB)
# 
# Version 1-0
# 
# Copyright (c) 2007, Open Kernel Labs, Inc.
# 
# All rights reserved. 
# 
# Developed by: Embedded, Real-time and Operating Systems Program (ERTOS)
#               National ICT Australia
#               http://www.ertos.nicta.com.au
# 
# Permission is granted by National ICT Australia, free of charge, to
# any person obtaining a copy of this software and any associated
# documentation files (the "Software") to deal with the Software without
# restriction, including (without limitation) the rights to use, copy,
# modify, adapt, merge, publish, distribute, communicate to the public,
# sublicense, and/or sell, lend or rent out copies of the Software, and
# to permit persons to whom the Software is furnished to do so, subject
# to the following conditions:
# 
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimers.
# 
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimers in the documentation and/or other materials provided
#       with the distribution.
# 
#     * Neither the name of National ICT Australia, nor the names of its
#       contributors, may be used to endorse or promote products derived
#       from this Software without specific prior written permission.
# 
# EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT
# PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS", AND
# NATIONAL ICT AUSTRALIA AND ITS CONTRIBUTORS MAKE NO REPRESENTATIONS,
# WARRANTIES OR CONDITIONS OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO ANY REPRESENTATIONS, WARRANTIES OR CONDITIONS
# REGARDING THE CONTENTS OR ACCURACY OF THE SOFTWARE, OR OF TITLE,
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT,
# THE ABSENCE OF LATENT OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF
# ERRORS, WHETHER OR NOT DISCOVERABLE.
# 
# TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL
# NATIONAL ICT AUSTRALIA OR ITS CONTRIBUTORS BE LIABLE ON ANY LEGAL
# THEORY (INCLUDING, WITHOUT LIMITATION, IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM, LOSS, DAMAGES OR OTHER
# LIABILITY, INCLUDING (WITHOUT LIMITATION) LOSS OF PRODUCTION OR
# OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF DATA OR RECORDS; OR LOSS
# OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR
# OTHER ECONOMIC LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT,
# CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR IN
# CONNECTION WITH THIS LICENCE, THE SOFTWARE OR THE USE OF OR OTHER
# DEALINGS WITH THE SOFTWARE, EVEN IF NATIONAL ICT AUSTRALIA OR ITS
# CONTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS,
# DAMAGES OR OTHER LIABILITY.
# 
# If applicable legislation implies representations, warranties, or
# conditions, or imposes obligations or liability on National ICT
# Australia or one of its contributors in respect of the Software that
# cannot be wholly or partly excluded, restricted or modified, the
# liability of National ICT Australia or the contributor is limited, to
# the full extent permitted by the applicable legislation, at its
# option, to:
# a.  in the case of goods, any one or more of the following:
# i.  the replacement of the goods or the supply of equivalent goods;
# ii.  the repair of the goods;
# iii. the payment of the cost of replacing the goods or of acquiring
#  equivalent goods;
# iv.  the payment of the cost of having the goods repaired; or
# b.  in the case of services:
# i.  the supplying of the services again; or
# ii.  the payment of the cost of having the services supplied again.
# 
# The construction, validity and performance of this licence is governed
# by the laws in force in New South Wales, Australia.

# We ignore the design warning about having too many methods
# and instance variables. While this is a good design guideline
# in general, here we are modelling something underlying that
# has lots of different methods
#pylint: disable-msg=R0902,R0904
"""
This library contains the classes that represent ELF structures such
as the ELF header itself, program and section headers. These don't
provide a nice way of directly manipulating the structure, but it
is probably best to use the abstract method in elf.py rather than
using these directly.
"""

from sys import stdout
from elf.constants import SHF_WRITE, SHF_ALLOC, SHF_EXECINSTR, SHF_MERGE, \
     SHF_STRINGS, SHF_LINK_ORDER, SHF_MASKPROC, PF_R, PF_W, PF_X, EV_CURRENT, \
     \
     EF_MIPS_NOREORDER, EF_MIPS_PIC, EF_MIPS_CPIC, EF_MIPS_XGOT, \
     EF_MIPS_N32, EF_MIPS_ABI_O32, EF_MIPS_ABI_O64, \
     EF_MIPS_ARCH, EF_MIPS_ARCH_1, EF_MIPS_ARCH_2, EF_MIPS_ARCH_3, \
     EF_MIPS_ARCH_4, EF_MIPS_ARCH_5, EF_MIPS_ARCH_32, EF_MIPS_ARCH_64, \
     \
     ELFCLASSNONE, ELFCLASS32, ELFCLASS64, \
     ELFDATANONE, ELFDATA2LSB, ELFDATA2MSB, ELFOSABI_NONE, \
     SHT_NULL, SHN_UNDEF, ET_NONE, EM_NONE, \
     ElfFormatError, \
     ElfClass, ElfData, ElfVersion, ElfOsabi, ElfType, \
     ElfMachine, ElfPhType, ElfShType
import struct
from elf.ByteArray import ByteArray
from elf.util import is_integer

class InvalidArgument(Exception):
    """This exception is raised when invalid argument are passed
    to a function. Methods that may raise this exception are documented
    appropriately."""

class ElfIdentification(object):
    """Provides a class for abstracting over the ELF e_ident array. (Ref 1-6)"""
    # Indenfification indexes. (Ref: Fig 1-4)
    EI_MAG0 = 0
    EI_MAG1 = 1
    EI_MAG2 = 2
    EI_MAG3 = 3
    EI_CLASS = 4
    EI_DATA = 5
    EI_VERSION = 6

    # Extensions: see
    # http://www.caldera.com/developers/gabi/2000-07-17/ch4.eheader.html
    EI_OSABI = 7
    EI_ABIVERSION = 8
    
    EI_PAD = 9

    # ELF magic (Ref: 1-7)
    ELFMAG0 = 0x7f
    ELFMAG1 = ord('E')
    ELFMAG2 = ord('L')
    ELFMAG3 = ord('F')

    def get_size(cls):
        """Return the size of the Elf ident structure."""
        return 16
    get_size = classmethod(get_size)

    def __init__(self):
        """ELF identification constructor. Initially all values are set
        to None or zero."""
        self.ei_class = ELFCLASSNONE
        self.ei_data = ELFDATANONE
        self.ei_version = EV_CURRENT
        self.ei_osabi = ELFOSABI_NONE
        self.ei_abiversion = 0

    def todata(self):
        """Convert the ELF identification header to an array of bytes"""
        data = ByteArray('\0' * 16)
        data[self.EI_MAG0] = self.ELFMAG0
        data[self.EI_MAG1] = self.ELFMAG1
        data[self.EI_MAG2] = self.ELFMAG2
        data[self.EI_MAG3] = self.ELFMAG3
        data[self.EI_CLASS] = self.ei_class
        data[self.EI_DATA] = self.ei_data
        data[self.EI_VERSION] = self.ei_version
        data[self.EI_OSABI] = self.ei_osabi
        data[self.EI_ABIVERSION] = self.ei_abiversion
        return data

    def fromdata(self, data):
        """ElfIdentification constructor. Data should be a byte array
        of length 16. Raises ElfFormatError if data is wrong length, or
        the magic identifier doesn't match."""
        if len(data) != 16:
            raise ElfFormatError, "ElfIdentification except 16 bytes of data"
        if not self.check_magic(data):
            raise ElfFormatError, \
                  "ElfIdentification doesn't match. [%x,%x,%x,%x]" % \
                  (data[self.EI_MAG0],
                   data[self.EI_MAG1],
                   data[self.EI_MAG2],
                   data[self.EI_MAG3])

        self.set_class(data[self.EI_CLASS])
        if self.get_class() == ELFCLASSNONE:
            raise ElfFormatError, "ElfIdentification class is invalid"

        self.set_data(data[self.EI_DATA])
        if self.get_data() == ELFDATANONE:
            raise ElfFormatError, "ElfIdentification data is invalid"

        self.set_version(data[self.EI_VERSION])
        
        self.set_osabi(data[self.EI_OSABI])
        self.set_abiversion(data[self.EI_ABIVERSION])                

    def check_magic(cls, data):
        """Check that the magic is correct."""
        return ((data[cls.EI_MAG0] == cls.ELFMAG0) and
                (data[cls.EI_MAG1] == cls.ELFMAG1) and
                (data[cls.EI_MAG2] == cls.ELFMAG2) and
                (data[cls.EI_MAG3] == cls.ELFMAG3))
    check_magic = classmethod(check_magic)

    def get_class(self):
        """Return the ELF class data"""
        return self.ei_class

    def set_class(self, elf_class):
        """Set the ELF class field"""
        self.ei_class = ElfClass(elf_class)

    def get_word_size(self):
        """Return the word size based on the class information"""
        elf_class = self.get_class()
        if elf_class == ELFCLASS32:
            return 32
        elif elf_class == ELFCLASS64:
            return 64
        else:
            raise ElfFormatError, "Unknown Elf Class unknown: %x" % elf_class

    def set_word_size(self, wordsize):
        """Set the class information based on the given wordsize"""
        if wordsize == 32:
            self.ei_class = ELFCLASS32
        elif wordsize == 64:
            self.ei_class = ELFCLASS64
        else:
            raise ElfFormatError, "Invalid wordsize %d" % wordsize

    def get_data(self):
        """Return elf data field"""
        return self.ei_data

    def set_data(self, data):
        """Set the elf data field"""
        self.ei_data = ElfData(data)

    def get_endianess(self):
        """Return the endianess. '>' For big endian, '<' for little
        endian. (Following struct module.)"""
        if self.ei_data == ELFDATA2LSB:
            return "<"
        elif self.ei_data == ELFDATA2MSB:
            return ">"
        else:
            raise ElfFormatError, "Unknown data encoding format: %x" % \
                  self.ei_data

    def set_endianess(self, endian):
        """Set the endianess in the ELF header. '>' for big endian,
        '<' for little."""
        if endian == '<':
            self.ei_data = ELFDATA2LSB
        elif endian == '>':
            self.ei_data = ELFDATA2MSB
        else:
            raise ElfFormatError, "Unknown endianess %s" % endian

    def get_version(self):
        """Return the ELF version"""
        return self.ei_version

    def set_version(self, version):
        """Set the ELF version"""
        self.ei_version = ElfVersion(version)

    def get_osabi(self):
        """Return the OS/ABI"""
        return self.ei_osabi

    def set_osabi(self, osabi):
        """Set the OS/ABI"""
        self.ei_osabi = ElfOsabi(osabi)

    def get_abiversion(self):
        """Return the OS/ABI version"""
        return self.ei_abiversion

    def set_abiversion(self, abiversion):
        """Set the OS/ABI version"""
        self.ei_abiversion = abiversion

class ElfHeader(object):
    """This class abstracts the ELF header. (Ref 1-4).
    This is an abstract class and should not be instantiated,
    it is designed to be overloaded by Elf32Header and Elf64Header.
    """
    layout = None # Subclasses should redefine this
    wordsize = None # Subclasses should redefine this

    def size(cls):
        """Return the structure size of the ElfHeader"""
        return struct.calcsize(cls.layout) + 16
    size = classmethod(size)

    def __init__(self, endianess = '<'):
        self.ident = ElfIdentification()
        self.ident.set_endianess(endianess)
        self.ident.set_word_size(self.wordsize)
        self.e_type = ET_NONE
        self.e_machine = EM_NONE
        self.e_version = EV_CURRENT
        self.e_entry = 0
        self.e_phoff = 0
        self.e_shoff = 0
        self.e_flags = 0
        self.e_ehsize = self.size()
        self.e_phentsize = 0
        self.e_phnum = 0
        self.e_shentsize = 0
        self.e_shnum = 0
        self.e_shstrndx = 0

    def fromdata(self, data):
        """Initialise an ElfHeader object from provided data"""
        if len(data) != self.size():
            raise ElfFormatError, "Data size must be %s. %s provided." % \
                  (self.size(), len(data))

        self.ident = ElfIdentification()
        self.ident.fromdata(data[:16])
        fields = struct.unpack(self.ident.get_endianess() + self.layout,
                               data[16:])
        self.set_type(fields[0])
        self.set_machine(fields[1])
        self.set_version(fields[2])
        self.set_entry(fields[3])
        self.set_phoff(fields[4])
        self.set_shoff(fields[5])
        self.set_flags(fields[6])
        self.set_ehsize(fields[7])
        self.set_phentsize (fields[8])
        self.set_phnum(fields[9])
        self.set_shentsize(fields[10])
        self.set_shnum(fields[11])
        self.set_shstrndx(fields[12])

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        data = self.ident.todata()
        packed = struct.pack(self.ident.get_endianess() + self.layout,
                             self.get_type(),
                             self.get_machine(),
                             self.get_version(),
                             self.get_entry(),
                             self.get_phoff(),
                             self.get_shoff(),
                             self.get_flags(),
                             self.get_ehsize(),
                             self.get_phentsize(),
                             self.get_phnum(),
                             self.get_shentsize(),
                             self.get_shnum(),
                             self.get_shstrndx())
        data.extend(ByteArray(packed))
        return data


    def get_type(self):
        """Return the object file type."""
        return self.e_type

    def set_type(self, elf_type):
        """Set the object file type."""
        assert is_integer(elf_type)
        self.e_type = ElfType(elf_type)
        
    def get_machine(self):
        """Return the machine architecture."""
        return self.e_machine

    def set_machine(self, machine):
        """Set the machine architecture."""
        assert is_integer(machine)
        self.e_machine = ElfMachine(machine)

    def get_version(self):
        """Return the object file version."""
        return self.e_version

    def set_version(self, version):
        """Set the version field."""
        assert is_integer(version)
        self.e_version = ElfVersion(version)

    def get_entry(self):
        """Return the file entry point."""
        return self.e_entry

    def set_entry(self, entry):
        """Set the entry point."""
        assert is_integer(entry)
        self.e_entry = entry

    def get_phoff(self):
        """Return the file offset to the program headers."""
        return self.e_phoff

    def set_phoff(self, phoff):
        """Set the file offset to the program headers."""
        assert is_integer(phoff)
        self.e_phoff = phoff

    def get_shoff(self):
        """Return the file offset to the section headers."""
        return self.e_shoff

    def set_shoff(self, shoff):
        """Set the file offset to the section headers."""
        assert is_integer(shoff)
        self.e_shoff = shoff

    def get_flags(self):
        """Return processor specific flags"""
        return self.e_flags

    def set_flags(self, flags):
        """Set processor specific flags"""
        assert is_integer(flags)
        self.e_flags = flags

    def get_ehsize(self):
        """Return the elf header size field."""
        return self.e_ehsize

    def set_ehsize(self, ehsize):
        """Set the elf header size field."""
        assert is_integer(ehsize)
        self.e_ehsize = ehsize

    def get_phentsize(self):
        """Return the size of a single entry in the program header table."""
        return self.e_phentsize

    def set_phentsize(self, phentsize):
        """Set the size of a single entry in the program header table."""
        assert is_integer(phentsize)
        self.e_phentsize = phentsize

    def get_phnum(self):
        """Return number of entries in the program header table."""
        return self.e_phnum

    def set_phnum(self, phnum):
        """Set the number of entries in the program header table."""
        assert is_integer(phnum)
        self.e_phnum = phnum

    def get_shentsize(self):
        """Return the size a single entry in the section header table."""
        return self.e_shentsize

    def set_shentsize(self, shentsize):
        """Set the size a single entry in the section header table."""
        assert is_integer(shentsize)
        self.e_shentsize = shentsize

    def get_shnum(self):
        """Return the number of entries in the section header tables."""
        return self.e_shnum

    def set_shnum(self, shnum):
        """Return the number of entries in the section header tables."""
        assert is_integer(shnum)
        self.e_shnum = shnum

    def get_shstrndx(self):
        """Return the section header table index of the entry
        associated with the section name string table."""
        return self.e_shstrndx

    def set_shstrndx(self, shstrndx):
        """Return the section header table index of the entry
        associated with the section name string table."""
        assert is_integer(shstrndx)
        self.e_shstrndx = shstrndx

    def get_markedup_flags(self):
        """Decode the flags and return as a string."""
        f = ""
        if self.get_machine() == ElfMachine(8):
            if self.get_flags() & EF_MIPS_NOREORDER:
                f += ", " + EF_MIPS_NOREORDER.__str__()
            if self.get_flags() & EF_MIPS_PIC:
                f += ", " + EF_MIPS_PIC.__str__()
            if self.get_flags() & EF_MIPS_CPIC:
                f += ", " + EF_MIPS_CPIC.__str__()
            if self.get_flags() & EF_MIPS_XGOT:
                f += ", " + EF_MIPS_XGOT.__str__()
            if self.get_flags() & EF_MIPS_N32:
                if self.get_flags() & EF_MIPS_ABI_O32:
                    f += ", n32"
                if self.get_flags() & EF_MIPS_ABI_O64:
                    f += ", n64"
            else:
                if self.get_flags() & EF_MIPS_ABI_O32:
                    f += ", " + EF_MIPS_ABI_O32.__str__()
                if self.get_flags() & EF_MIPS_ABI_O64:
                    f += ", " + EF_MIPS_ABI_O64.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_1:
                f += ", " + EF_MIPS_ARCH_1.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_2:
                f += ", " + EF_MIPS_ARCH_2.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_3:
                f += ", " + EF_MIPS_ARCH_3.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_4:
                f += ", " + EF_MIPS_ARCH_4.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_5:
                f += ", " + EF_MIPS_ARCH_5.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_32:
                f += ", " + EF_MIPS_ARCH_32.__str__()
            if (self.get_flags() & EF_MIPS_ARCH) == EF_MIPS_ARCH_64:
                f += ", " + EF_MIPS_ARCH_64.__str__()
            return f
        # FIXME
        if self.get_flags() == 0x202:
            return ", has entry point, GNU EABI, software FP"
        elif self.get_flags() == 0x10:
            return ", 64-bit"
        elif self.get_flags() == 0x4000016:
            return ", has entry point, Version4 EABI, <unknown>"
        else:
            return ""

    def output(self, f=stdout):
        """Print the ELF header in the format seen in the readelf utility."""
        print >> f, "ELF Header:"
        print >> f, "  Magic:  ",
        for i_d in self.ident.todata():
            print >> f, "%02x" % i_d,
        print >> f, ""
        print >> f, "  Class:                             %s" % \
              self.ident.get_class()
        print >> f, "  Data:                              %s" % \
              self.ident.get_data()
        print >> f, "  Version:                           %s" % \
              self.ident.get_version()
        print >> f, "  OS/ABI:                            %s" % \
              self.ident.get_osabi()
        print >> f, "  ABI Version:                       %s" % \
              self.ident.get_abiversion()
        print >> f, "  Type:                              %s" % \
              self.get_type()
        print >> f, "  Machine:                           %s" % \
              self.get_machine()
        print >> f, "  Version:                           0x%x" % \
              self.get_version()
        print >> f, "  Entry point address:               0x%x" % \
              self.get_entry()
        print >> f, "  Start of program headers:          %d " \
              "(bytes into file)" % self.get_phoff()
        print >> f, "  Start of section headers:          %d " \
              "(bytes into file)" % self.get_shoff()
        print >> f, "  Flags:                             0x%x%s" % \
              (self.get_flags(), self.get_markedup_flags())
        print >> f, "  Size of this header:               %d (bytes)" % \
              self.get_ehsize()
        print >> f, "  Size of program headers:           %d (bytes)" % \
              self.get_phentsize()
        print >> f, "  Number of program headers:         %d" % \
              self.get_phnum()        
        print >> f, "  Size of section headers:           %d (bytes)" % \
              self.get_shentsize()        
        print >> f, "  Number of section headers:         %d" % \
              self.get_shnum()        
        print >> f, "  Section header string table index: %d" % \
              self.get_shstrndx()

class Elf32Header(ElfHeader):
    """Class abstracting a 32-bit ELF header."""
    layout = "HHIIIIIHHHHHH"
    wordsize = 32

class Elf64Header(ElfHeader):
    """Class abstracting a 64-bit ELF header."""
    layout = "HHIQQQIHHHHHH"
    wordsize = 64

ElfHeaderClasses = { 32: Elf32Header,
                     64: Elf64Header }

class ElfProgramHeader(object):
    """Abstract the ELF Program header structure. These structures
    describe segments in the ELF file."""
    layout = None # Subclasses should redefine this

    def size(cls):
        """Return the structure size of the ElfHeader"""
        return struct.calcsize(cls.layout)
    size = classmethod(size)

    def __init__(self, endianess):
        if endianess not in ["<", ">"]:
            raise InvalidArgument, "Endianess must be either < or >"
        self.endianess = endianess
        self.p_type = 0
        self.p_offset = 0
        self.p_vaddr = 0
        self.p_paddr = 0
        self.p_filesz = 0
        self.p_memsz = 0
        self.p_flags = 0
        self.p_align = 0

    def get_type(self):
        """Return the kind of segment this header describes."""
        return self.p_type

    def set_type(self, ph_type):
        """Set the kind of segment this header describes."""
        assert is_integer(ph_type)
        self.p_type = ElfPhType(ph_type)

    def get_offset(self):
        """Return the offset from the begging of the while at which the
        segment resides."""
        return self.p_offset

    def set_offset(self, offset):
        """Set the offset from the begging of the while at which the
        segment resides."""
        assert is_integer(offset)
        self.p_offset = offset

    def get_vaddr(self):
        """Return the virtual address at which the first byte of the segment
        resides."""
        return self.p_vaddr

    def set_vaddr(self, vaddr):
        """Set the virtual address at which the first byte of the segment
        resides."""
        assert is_integer(vaddr)
        self.p_vaddr = vaddr

    def get_paddr(self):
        """Return the physical address at which the first byte of the segment
        resides."""
        return self.p_paddr

    def set_paddr(self, paddr):
        """Set the physical address at which the first byte of the segment
        resides."""
        assert is_integer(paddr)
        self.p_paddr = paddr

    def get_filesz(self):
        """Return the number of bytes in the file image of the segment."""
        return self.p_filesz

    def set_filesz(self, filesz):
        """Set the number of bytes in the file image of the segment."""
        assert is_integer(filesz)
        self.p_filesz = filesz

    def get_memsz(self):
        """Return the number of bytes in the memory image of the segment."""
        return self.p_memsz

    def set_memsz(self, memsz):
        """Set the number of bytes in the memory image of the segment."""
        assert is_integer(memsz)
        self.p_memsz = memsz

    def get_flags(self):
        """Return the number of bytes in the memory image of the segment."""
        return self.p_flags

    def set_flags(self, flags):
        """Set the number of bytes in the memory image of the segment."""
        assert is_integer(flags)
        self.p_flags = flags

    def get_flags_str(self):
        """Return a string describing the flags"""
        out = [' ', ' ', ' ']
        if self.p_flags & PF_R:
            out[0] =  "R"
        if self.p_flags & PF_W:
            out[1] =  "W"
        if self.p_flags & PF_X:
            out[2] =  "E"
        return "".join(out)

    def get_align(self):
        """Return segment alignment."""
        return self.p_align

    def set_align(self, align):
        """Set segment alignment."""
        assert is_integer(align)
        self.p_align = align

class Elf32ProgramHeader(ElfProgramHeader):
    """32-bit ELf Program header"""
    layout = "IIIIIIII"

    def fromdata(self, data, ehdr):
        """Initialise an ElfHeader object from provided data"""
        if len(data) != self.size():
            raise ElfFormatError, "Data size must be %s. %s provided." % \
                  (self.size(), len(data))

        fields = struct.unpack(self.endianess + self.layout, data)
        self.set_type(fields[0])
        self.set_offset(fields[1])
        vaddr = fields[2]
        if ehdr.get_machine() == ElfMachine(8):
            if ehdr.get_flags() & EF_MIPS_ABI_O64:
                if vaddr & 0x80000000:
                    vaddr |= 0xffffffff00000000L
        self.set_vaddr(vaddr)
        self.set_paddr(fields[3])
        self.set_filesz(fields[4])
        self.set_memsz(fields[5])
        self.set_flags(fields[6])
        self.set_align(fields[7])

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_type(),
                             self.get_offset(),
                             (self.get_vaddr() & 0xffffffffL),
                             self.get_paddr(),
                             self.get_filesz(),
                             self.get_memsz(),
                             self.get_flags(),
                             self.get_align())
        data = ByteArray(packed)
        return data

    def output(self, f=stdout):
        """Pretty print the program header to a given file, which
        defaults to stdout."""
        print >> f, "  %-14.14s" % self.get_type(), 
        print >> f, "0x%6.6x" % self.get_offset(),
        print >> f, "0x%8.8x" % (self.get_vaddr() & 0xffffffffL),
        print >> f, "0x%8.8x" % self.get_paddr(),
        print >> f, "0x%5.5x" % self.get_filesz(),
        print >> f, "0x%5.5x" % self.get_memsz(),
        print >> f, "%s" % self.get_flags_str(),
        if self.get_align() != 0:
            print >> f, "0x%x" % self.get_align()
        else:
            print >> f, "%x" % self.get_align()

class Elf64ProgramHeader(ElfProgramHeader):
    """64-bit ELf Program header"""
    layout = "IIQQQQQQ"

    def fromdata(self, data, ehdr):
        """Initialise an ElfHeader object from provided data"""
        if len(data) != self.size():
            raise ElfFormatError, "Data size must be %s. %s provided." % \
                  (self.size(), len(data))

        fields = struct.unpack(self.endianess + self.layout, data)
        self.set_type(fields[0])
        self.set_flags(fields[1])
        self.set_offset(fields[2])
        self.set_vaddr(fields[3])
        self.set_paddr(fields[4])
        self.set_filesz(fields[5])
        self.set_memsz(fields[6])
        self.set_align(fields[7])

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_type(),
                             self.get_flags(),
                             self.get_offset(),
                             self.get_vaddr(),
                             self.get_paddr(),
                             self.get_filesz(),
                             self.get_memsz(),
                             self.get_align())
        data = ByteArray(packed)
        return data

    def output(self, f=stdout):
        """Pretty print the program header to a given file, which
        defaults to stdout."""
        print >> f, "  %-14.14s" % self.get_type(), 
        print >> f, "0x%16.16x" % self.get_offset(),
        print >> f, "0x%16.16x" % self.get_vaddr(),
        print >> f, "0x%16.16x" % self.get_paddr()
        print >> f, "                ",
        print >> f, "0x%16.16x" % self.get_filesz(),
        print >> f, "0x%16.16x" % self.get_memsz(),
        print >> f, " %s   " % self.get_flags_str(),
        print >> f, "%x" % self.get_align()

ElfPhClasses = { 32: Elf32ProgramHeader,
                 64: Elf64ProgramHeader }

class ElfSectionHeader(object):
    """Class representing an ELF section header. (Ref 1-9). This class should not
    be directly instantatied and should be subclassed, by Elf32Section and
    Elf64Section."""

    def __init__(self, endianess):
        if endianess not in ["<", ">"]:
            raise InvalidArgument, "Endianess must be either < or >"
        self.endianess = endianess
        self.sh_name = 0
        self.sh_type = SHT_NULL
        self.sh_flags = 0
        self.sh_addr = 0
        self.sh_offset = 0
        self.sh_size = 0
        self.sh_link = SHN_UNDEF
        self.sh_info = 0
        self.sh_addralign = 0
        self.sh_entsize = 0

        self._name = None
        self._index = None

    layout = None # Subclasses should redefine this

    def size(cls):
        """Return the structure size of the ElfHeader"""
        return struct.calcsize(cls.layout)
    size = classmethod(size)

    def fromdata(self, data, ehdr):
        """Initialise an ElfHeader object from provided data"""
        if len(data) != self.size():
            raise ElfFormatError, "Data size must be %s. %s provided." % \
                  (self.size(), len(data))

        fields = struct.unpack(self.endianess + self.layout, data)
        self.set_name(fields[0])
        self.set_type(fields[1])
        self.set_flags(fields[2])
        vaddr = fields[3]
        if ehdr.get_machine() == ElfMachine(8):
            if ehdr.get_flags() & EF_MIPS_ABI_O64:
                if vaddr & 0x80000000:
                    vaddr |= 0xffffffff00000000L
        self.set_addr(vaddr)
        self.set_offset(fields[4])
        self.set_size(fields[5])
        self.set_link(fields[6])
        self.set_info(fields[7])
        self.set_addralign (fields[8])
        self.set_entsize(fields[9])

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_name(),
                             self.get_type(),
                             self.get_flags(),
                             (self.get_addr() & 0xffffffffL),
                             self.get_offset(),
                             self.get_size(),
                             self.get_link(),
                             self.get_info(),
                             self.get_addralign(),
                             self.get_entsize())
        data = ByteArray(packed)
        return data

    def get_name(self):
        """Return the index into the section header string table."""
        return self.sh_name

    def set_name(self, name):
        """Set the index into the section header string table."""
        assert is_integer(name)
        self.sh_name = name

    def get_type(self):
        """Return the class the section header represents."""
        return self.sh_type

    def set_type(self, sh_type):
        """Set the class of the section header."""
        assert is_integer(sh_type)
        self.sh_type = ElfShType(sh_type)

    def get_flags(self):
        """Return the flags set on the section."""
        return self.sh_flags

    def set_flags(self, flags):
        """Set the flags on the section."""
        assert is_integer(flags)
        self.sh_flags = flags

    def get_addr(self):
        """Return the address at which the section appears
        in the memory image."""
        return self.sh_addr

    def set_addr(self, addr):
        """Set the address at which the section will appear in the memory
        image of the process."""
        assert is_integer(addr)
        self.sh_addr = addr

    def get_offset(self):
        """Return the offset of the section in the file."""
        return self.sh_offset

    def set_offset(self, offset):
        """Set the offset of the section in the file."""
        assert is_integer(offset)
        self.sh_offset = offset

    def get_size(self):
        """Return the size of the section in bytes."""
        return self.sh_size
    
    def set_size(self, size):
        """Set the size of the section in bytes."""
        assert is_integer(size)
        self.sh_size = size

    def get_link(self):
        """Return the section header table index link."""
        return self.sh_link

    def set_link(self, link):
        """Set the section header table index link."""
        assert is_integer(link)
        self.sh_link = link

    def get_info(self):
        """Return extra info. Interpretation depends on the
        section type."""
        return self.sh_info

    def set_info(self, info):
        """Set the section info."""
        assert is_integer(info)
        self.sh_info = info

    def get_addralign(self):
        """Return the alignment constraint of the section."""
        return self.sh_addralign

    def set_addralign(self, addralign):
        """Set the alignment constraint of the section."""
        assert is_integer(addralign)
        self.sh_addralign = addralign

    def get_entsize(self):
        """Return the entry size. This will be non-zero if this
        section holds fixed size records."""
        return self.sh_entsize

    def set_entsize(self, entsize):
        """Set the entry size."""
        assert is_integer(entsize)
        self.sh_entsize = entsize

    def get_markedup_flags(self):
        """Return the flags as a string showing which bits are set."""
        ret = ""
        if self.get_flags() & SHF_WRITE:
            ret = ret + 'W'
        if self.get_flags() & SHF_ALLOC:
            ret = ret + 'A'
        if self.get_flags() & SHF_EXECINSTR:
            ret = ret + 'X'
        if self.get_flags() & SHF_MERGE:
            ret = ret + 'M'
        if self.get_flags() & SHF_STRINGS:
            ret = ret + 'S'
        if self.get_flags() & SHF_LINK_ORDER:
            ret = ret + 'L'
        if self.get_flags() & SHF_MASKPROC:
            ret = ret + 'p'

        return ret

class Elf32SectionHeader(ElfSectionHeader):
    """This class is used to represent 32-bit ELF section
    headers."""
    layout = "IIIIIIIIII"

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_name(),
                             self.get_type(),
                             self.get_flags(),
                             (self.get_addr() & 0xffffffffL),
                             self.get_offset(),
                             self.get_size(),
                             self.get_link(),
                             self.get_info(),
                             self.get_addralign(),
                             self.get_entsize())
        data = ByteArray(packed)
        return data

    def output(self, f=stdout):
        """Pretty print the section header to a given file, which
        defaults to stdout."""
        if self._index is not None:
            print >> f, "  [%2d]" % self._index,
            print >> f, "%-17.17s" % self._name,
        print >> f, "%-15.15s" % self.get_type(),
        print >> f, "%8.8x" % (self.get_addr() & 0xffffffffL),
        print >> f, "%6.6x" % self.get_offset(),
        print >> f, "%6.6x" % self.get_size(),
        print >> f, "%2.2x" % self.get_entsize(),
        print >> f, "%3.3s" % self.get_markedup_flags(),
        print >> f, "%2d" % self.get_link(),
        print >> f, "%3d" % self.get_info(),
        print >> f, "%2d" % self.get_addralign()

class Elf64SectionHeader(ElfSectionHeader):
    """This class is used to represent 64-bit ELF section
    headers."""
    layout = "IIQQQQIIQQ"

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_name(),
                             self.get_type(),
                             self.get_flags(),
                             self.get_addr(),
                             self.get_offset(),
                             self.get_size(),
                             self.get_link(),
                             self.get_info(),
                             self.get_addralign(),
                             self.get_entsize())
        data = ByteArray(packed)
        return data

    def output(self, f=stdout):
        """Pretty print the section header to a given file, which
        defaults to stdout."""
        if self._index is not None:
            print >> f, "  [%2d]" % self._index,
            print >> f, "%-17.17s" % self._name,
        print >> f, "%-16.16s" % self.get_type(),
        print >> f, "%16.16x " % self.get_addr(),
        print >> f, "%8.8x" % self.get_offset()
         
        print >> f, "       %16.16x " % self.get_size(),
        print >> f, "%16.16x" % self.get_entsize(),
        print >> f, "%3.3s" % self.get_markedup_flags(),
        print >> f, "     %2d" % self.get_link(),
        print >> f, "  %3d" % self.get_info(),
        print >> f, "    %d" % self.get_addralign()

ElfShClasses = { 32: Elf32SectionHeader,
                 64: Elf64SectionHeader }

class ElfSymbol(object):
    """Abstract the ELF symbol header structure. These structures
    describe segments in the ELF file."""
    layout = None # Subclasses should redefine this

    def size(cls):
        """Return the structure size of the ElfHeader"""
        return struct.calcsize(cls.layout)
    size = classmethod(size)

    def __init__(self, endianess):
        if endianess not in ["<", ">"]:
            raise InvalidArgument, "Endianess must be either < or >"
        self.endianess = endianess
        self.st_name = 0
        self.st_value = 0
        self.st_size = 0
        self.st_info = 0
        self.st_other = 0
        self.st_shndx = 0

    def get_name(self):
        """Return the name of this symbol."""
        return self.st_name

    def set_name(self, name):
        """Set the name of this symbol."""
        assert is_integer(name)
        self.st_name = name

    def get_value(self):
        """Return the value of this symbol."""
        return self.st_value

    def set_value(self, value):
        """Set the value of this symbol."""
        assert is_integer(value)
        self.st_value = value

    def get_size(self):
        """Return the size of this symbol."""
        return self.st_size

    def set_size(self, size):
        """Set the size of this symbol."""
        assert is_integer(size)
        self.st_size = size

    def get_info(self):
        """Return the info of this symbol."""
        return self.st_info

    def set_info(self, info):
        """Set the info of this symbol."""
        assert is_integer(info)
        self.st_info = info

    def get_other(self):
        """Return the other of this symbol."""
        return self.st_other

    def set_other(self, other):
        """Set the other of this symbol."""
        assert is_integer(other)
        self.st_other = other

    def get_shndx(self):
        """Return the shndx of this symbol."""
        return self.st_shndx

    def set_shndx(self, shndx):
        """Set the shndx of this symbol."""
        assert is_integer(shndx)
        self.st_shndx = shndx

class Elf32Symbol(ElfSymbol):
    """This class is used to represent 32-bit ELF Symbols."""
    layout = "IIIBBH"

    def output(self, f=stdout):
        """Pretty print the symbol to a given file, which
        defaults to stdout."""
        print >> f, "%8.8x" % self.get_name(),
        print >> f, "%8.8x" % self.get_value(),
        print >> f, "%8.8x" % self.get_size(),
        print >> f, "%2.2x" % self.get_info(),
        print >> f, "%2.2x" % self.get_other(),
        print >> f, "%4.4x" % self.get_shndx()

    def fromdata(self, data):
        """Initialise a Symbol object from provided data"""
        if len(data) != self.size():
            raise ElfFormatError, "Data size must be %s. %s provided." % \
                  (self.size(), len(data))

        fields = struct.unpack(self.endianess + self.layout, data)
        self.set_name(fields[0])
        self.set_value(fields[1])
        self.set_size(fields[2])
        self.set_info(fields[3])
        self.set_other(fields[4])
        self.set_shndx(fields[5])

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_name(),
                             self.get_value(),
                             self.get_size(),
                             self.get_info(),
                             self.get_other(),
                             self.get_shndx())
        data = ByteArray(packed)
        return data

class Elf64Symbol(ElfSymbol):
    """This class is used to represent 64-bit ELF Symbols."""
    layout = "IBBHQQ"

    def output(self, f=stdout):
        """Pretty print the symbol to a given file, which
        defaults to stdout."""
        print >> f, "%8.8x" % self.get_name(),
        print >> f, "%2.2x" % self.get_info(),
        print >> f, "%2.2x" % self.get_other(),
        print >> f, "%4.4x" % self.get_shndx(),
        print >> f, "%16.16x" % self.get_value(),
        print >> f, "%16.16x" % self.get_size()

    def fromdata(self, data):
        """Initialise a Symbol object from provided data"""
        if len(data) != self.size():
            raise ElfFormatError, "Data size must be %s. %s provided." % \
                  (self.size(), len(data))

        fields = struct.unpack(self.endianess + self.layout, data)
        self.set_name(fields[0])
        self.set_info(fields[1])
        self.set_other(fields[2])
        self.set_shndx(fields[3])
        self.set_value(fields[4])
        self.set_size(fields[5])

    def todata(self):
        """Convert the ELF header to an array of bytes"""
        packed = struct.pack(self.endianess + self.layout,
                             self.get_name(),
                             self.get_info(),
                             self.get_other(),
                             self.get_shndx(),
                             self.get_value(),
                             self.get_size())
        data = ByteArray(packed)
        return data


ElfSymbolClasses = { 32: Elf32Symbol,
                 64: Elf64Symbol }


