### ------------------------------------------------------------------------
### Copyright (C) 2013 BlackBerry Ltd. All Rights Reserved.
### ------------------------------------------------------------------------
### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions
### are met:
###
### * Redistributions of source code must retain the above copyright
###   notice, this list of conditions and the following disclaimer.
###
### * Redistributions in binary form must reproduce the above copyright
###   notice, this list of conditions and the following disclaimer in the
###   documentation and/or other materials provided with the distribution.
###
### * Neither the name of BlackBerry Ltd. nor the names of its contributors
###   may be used to endorse or promote products derived from this software
###   without specific prior written permission.
###
### THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
### "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
### TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
### PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBERRY LTD BE LIABLE FOR ANY
### DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
### (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
### SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
### CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
### LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
### OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
### SUCH DAMAGE.
__all__ = ["read_elf_sects"]
import collections
import struct

ElfHeaderCommon = collections.namedtuple("ElfHeaderCommon",
    ("ei_magic", "ei_class", "ei_data", "ei_version", "ei_osabi", 
        "ei_abiversion"))
ElfHeader = collections.namedtuple("ElfHeader",
    ("e_type", "e_machine", "e_version", "e_entry", "e_phoff", "e_shoff", 
        "e_flags", "e_ehsize", "e_phentsize", "e_phnum", "e_shentsize", 
        "e_shnum", "e_shstrndx"))
ElfSectionHeader = collections.namedtuple("ElfSectionHeader",
    ("sh_name", "sh_type", "sh_flags", "sh_addr", "sh_offset", "sh_size", 
        "sh_link", "sh_info", "sh_addralign", "sh_entsize"))

def read_elf_sects(filename):
    """
    Lists the sections in an ELF file. Returns a dictionary mapping section 
    names to (address, size) tuples. Only sections with non-zero sh_address 
    and sh_size will be returned.
    """
    with open(filename, "rb") as f:
        hdr_s = struct.Struct("=4s5B7x")
        hdr = ElfHeaderCommon(*hdr_s.unpack_from(f.read(hdr_s.size)))
        if hdr.ei_magic != "\x7FELF":
            raise RuntimeError("Unrecognized file format")
        try:
            bits = {1: "I", 2: "Q"}[hdr.ei_class]
        except KeyError:
            raise RuntimeError("Unrecognized value for word size ({})".format(hdr.ei_class))
        try:
            endian = {1: "<", 2: ">"}[hdr.ei_data]
        except KeyError:
            raise RuntimeError("Unrecognized value for endianness ({})".format(hdr.ei_data))
        hdr_s = struct.Struct("{}HHI3{}I6H".format(endian, bits))
        hdr = ElfHeader(*hdr_s.unpack_from(f.read(hdr_s.size)))
        if hdr.e_shoff == 0:
            raise RuntimeError("No section info")
        f.seek(hdr.e_shoff)
        shdr_s = struct.Struct("{0}II4{1}II2{1}".format(endian, bits))
        first = ElfSectionHeader(*shdr_s.unpack_from(f.read(shdr_s.size)))
        if hdr.e_shnum == 0:
            hdr = hdr._replace(e_shnum=first.sh_size)
        if hdr.e_shstrndx == 0xFFFF:
            hdr = hdr._replace(e_shstrndx=first.sh_link)
        sects = [ElfSectionHeader(*shdr_s.unpack_from(f.read(shdr_s.size)))
                 for _ in range(hdr.e_shnum-1)]
        f.seek(sects[hdr.e_shstrndx-1].sh_offset)
        strings = f.read(sects[hdr.e_shstrndx-1].sh_size)
    return {strings[s.sh_name:strings.index("\0", s.sh_name)]: (s.sh_addr, s.sh_size)
            for s in sects if s.sh_addr and s.sh_size}

