#!/usr/bin/env python

#
#*
#* elf.py -- ELF binary interface module
#* Copyright (C) 2002 Netgraft Corporation
#*
#* This program is free software; you can redistribute it and/or
#* modify it under the terms of the GNU General Public License
#* as published by the Free Software Foundation; either version 2
#* of the License, or (at your option) any later version.
#*
#* This program is distributed in the hope that it will be useful,
#* but WITHOUT ANY WARRANTY; without even the implied warranty of
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#* GNU General Public License for more details.
#*
#* You should have received a copy of the GNU General Public License
#* along with this program; if not, write to the Free Software
#* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#*
#

import string
import types
import struct
import sys

# Exceptions...

class NoELFMagic: pass

class ELFProgHdr:
    def __init__(s,data):
        s.p_type,s.p_off,s.p_vaddr = struct.unpack('III',data[0:12])
        s.p_paddr,s.p_filesz,s.p_memsz = struct.unpack('III',data[12:24])
        s.p_flags,s.p_align = struct.unpack('II',data[24:32])

    def type(self):
        d = {    0   : 'Null',
                 1   : 'Loadable',
                 2   : 'Dynamic',
                 3   : 'Interpreter',
                 4   : 'Note',
                 5   : 'Reserved',
                 6   : 'Program Header Table' }

        if d.has_key(self.p_type):
            return d[self.p_type]

        if self.p_type >= 0x70000000 and self.p_type <= 0x7fffffff:
            return 'Processor specific'

        return '!! Unrecognized Segment Type %u !!' % self.p_type

class ELFSectHdr:
    def __init__(s,data):
        s.sh_name,s.sh_type,s.sh_flags,s.sh_addr = struct.unpack('IIII',data[0:16])
        s.sh_offset,s.sh_size,s.sh_link,s.sh_info = struct.unpack('IIII',data[16:32])
        s.sh_addralign,s.sh_entsize = struct.unpack('II',data[32:40])

    def name_self(self,strtab):
        off = int(self.sh_name)
        len = string.index(strtab[off:],'\000')
        self.name = strtab[off:off+len]

    def type(self):
        d = {   0   : 'Null',
                1   : 'Program Defined',
                2   : 'Symbol Table',
                3   : 'String Table',
                11  : 'Dynamic Symbol Table', 
                4   : 'Relocation Entries (w/ addends)',
                5   : 'Symbol Hash Table',
                6   : 'Dynamic',
                7   : 'Note',
                8   : 'No Bits',
                9   : 'Relocation Entries (w/o addends)',
                10  : 'Reserved' }

        if d.has_key(self.sh_type):
            return d[self.sh_type]

        if self.sh_type >= 0x70000000 and self.sh_type <= 0x7fffffff:
            return 'Processor Specific'
        if self.sh_type >= 0x80000000 and self.sh_type <= 0xffffffff:
            return 'User Specific'

        return '!! Unrecognized Segment Type %u !!' % self.sh_type

    def flags(self):
        res = []
        flags = {   0x1 : 'write',
                    0x2 : 'alloc',
                    0x4 : 'exec',
                    0xf0000000 : 'proc' }

        for i in flags.keys():
            if self.sh_flags & i:
                res.append(flags[i])

        return res

class ELFDynSectHdr:
    def __init__(self,data):
        self.d_tag, = struct.unpack('I',data[0:4])
        for i in [0,16,22]:
            if self.d_tag == i:
                return

        for i in [1,2,8,9,10,11,14,15,18,19,20]:
            if self.d_tag == i:
                self.d_val, = struct.unpack('i',data[4:8])

        for i in [3,4,5,6,7,12,13,17,21,23]:
            if self.d_tag == i:
                self.d_ptr, = struct.unpack('I',data[4:8])

    def type(self):
        d = {   0   : 'Null',
                1   : 'Needed',
                2   : 'Relocation Entry Size',
                3   : 'Procedure Linkage Table',
                4   : 'Symbol Hash Table Address',
                5   : 'String Table Address',
                6   : 'Symbol Table Address',
                7   : 'Relocation Table',
                8   : 'Relocation Table Size',
                9   : 'Relocation Table Entry Size',
                10  : 'String Table Size',
                11  : 'Symbol Table Entry Size',
                12  : 'Initialization Function',
                13  : 'Termination Function',
                14  : 'Shared Object Name',
                15  : 'Search Path String',
                16  : 'Symbolic',
                17  : 'Relocation Table (implicit)',
                18  : 'Relocation Table Size (implicit)',
                19  : 'Relocation Table Entry Size (implicit)',
                20  : 'Procedure Linkage Type',
                21  : 'Debugging',
                22  : 'DT_TEXTREL',
                23  : 'DT_JMPREL' }

        if d.has_key(self.d_tag):
            return d[self.d_tag]
 
        if self.d_tag >= 0x70000000 and self.d_tag <= 0x7fffffff:
            return 'Processor Specific'

        return '!! Unknown Section %u !!' % self.d_tag

    ##
    ## parse and provide information about ELF files
    ##

class ELF:
    def __init__(self,src):
        if type(src) == type(''):
            self.__file = open(src)
        elif type(src) == type(types.FileType):
            self.__file = src
        else:
            raise ValueError            
        f = self.__file

        try:
            eh = f.read(52)
        except IOError:
            raise NoELFMagic
        if len(eh) < 52:
            raise NoELFMagic
        self.e_ident = eh[0:16]

        b,elf = struct.unpack('B3s',self.e_ident[0:4])
        if b != 0x7F or elf != 'ELF':
            raise NoELFMagic

        self.ei_mag0 = 0x7F
        self.ei_mag1,self.ei_mag2,self.ei_mag3 = 'E','L','F'

        self.ei_class,self.ei_data,self.ei_version = struct.unpack('BBB',self.e_ident[4:7])

        self.e_type,self.e_machine,self.e_version = struct.unpack('HHI',eh[16:24])
        self.e_entry,self.e_phoff,self.e_shoff,self.e_flags = struct.unpack('IIII',eh[24:40])
        self.e_ehsize,self.e_phentsize,self.e_phnum = struct.unpack('HHH',eh[40:46])
        self.e_shentsize,self.e_shnum,self.e_shstrndx = struct.unpack('HHH',eh[46:52])

        ## load program headers

        self.progheadtab = []

        if self.e_phoff != 0:
            f.seek(self.e_phoff)
            for i in range(0,self.e_phnum):
                data = f.read(self.e_phentsize)
                self.progheadtab.append(ELFProgHdr(data))

        ## load section headers

        self.sectheadtab = []

        if self.e_shoff != 0:
            f.seek(self.e_shoff)
            for i in range(0,self.e_shnum):
                data = f.read(self.e_shentsize)
                self.sectheadtab.append(ELFSectHdr(data))

            shtaboff = self.sectheadtab[self.e_shstrndx].sh_offset
            shtablen = self.sectheadtab[self.e_shstrndx].sh_size

            f.seek(shtaboff)
            self.strtab = f.read(shtablen)

            for i in self.sectheadtab:
                i.name_self(self.strtab)


        ## load dynamic table section

        self.dynheadtab = []

        for i in self.progheadtab:
            if i.type() == 'Dynamic':
                f.seek(i.p_off)
                while 1:
                    dt = ELFDynSectHdr(f.read(8))
                    self.dynheadtab.append(dt)
                    if dt.type() == 'Null':
                        break

        ## find dynstrtab

        for i in self.dynheadtab:
            if i.type()  == 'String Table Address':
                for j in self.sectheadtab:
                    if j.type() == 'String Table' and j.sh_addr == i.d_ptr:
                        f.seek(j.sh_offset)
                        self.dynstrtab = f.read(j.sh_size)


    def type(self):
        return {    0       : 'No file type',
                    1       : 'Relocatable file',
                    2       : 'Executable file',
                    3       : 'Shared object file',
                    4       : 'Core file',
                    0xff00  : 'Processor-specific',
                    0xffff  : 'Processor-specific' }[self.e_type]

    def machine(self):
        return {    0       : 'No machine',
                    1       : 'AT&T WE 32100',
                    2       : 'SPARC',
                    3       : 'Intel 80386',
                    4       : 'Motorola 68000',
                    5       : 'Motorola 88000',
                    7       : 'Intel 80860',
                    8       : 'MIPS RS3000' }[self.e_machine]

    def version(self):
        return {    0       : 'Invalid version',
                    1       : 'Version 1' }[self.e_version]

    def i_class(self):
        return {    0 : 'Invalid class',
                    1 : '32-bit objects',
                    2 : '64-bit objects' }[self.ei_class]

    def i_data(self):
        return {    0 : 'Invalid data encoding',
                    1 : 'LSB',
                    2 : 'MSB'   }[self.ei_data]

    def i_version(self):
        return self.version()

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print 'Usage: ./elf.py <elf-file>'
        sys.exit()

    e = ELF(sys.argv[1])
    f = open(sys.argv[1])
    print 'ELF structure of %s:' % sys.argv[1]
    print '\t%s, %s, %s' % (e.version(),e.machine(),e.type())

    print '\n%u Program Headers' % len(e.progheadtab)
    for i in e.progheadtab:
        print '\t%-15s %u %u' % (i.type(),i.p_vaddr,i.p_off)

    print '\n%u Section Headers' % len(e.sectheadtab)
    for i in e.sectheadtab:
        print '\t%-15s %-45s %u %u' % (i.name,i.type(),i.sh_addr,i.sh_offset)
        if i.type() == 'String Table' or (
            i.type() == 'Program Defined' and i.name in
            ('.comment', '.interp')):
            f.seek(i.sh_offset)
            data = f.read(i.sh_size)
            for el in data.split('\0'):
                print '  %r' % el

    print '\n%u Dynamic Table Entries' % len(e.dynheadtab)
    for i in e.dynheadtab:
        try:
            if i.type() == 'Needed':
                off = i.d_val
                len = string.index(e.dynstrtab[off:],'\000')
                libname = e.dynstrtab[off:off+len]
                print '\t%-15s %u %-45s' % (i.type(),i.d_val,libname)
            else:
                print '\t%-15s %u' % (i.type(),i.d_val) 
        except AttributeError:
            try:
                print '\t%-15s %u' % (i.type(),i.d_ptr)
            except AttributeError:
                print '\t%-15s' % i.type()

