#!/usr/bin/python
from __future__ import print_function
import subprocess, os, sys, re

re_locstr = '< *(?:0x)?([0-9a-f]+)>'
re_tag =  re.compile(' *{0}{0}: Abbrev Number: \d+ \((DW_TAG_[a-z0-9_]+)\)'.format(re_locstr))
re_at =   re.compile(' *{0} *(DW_AT_[a-z0-9_]*) *: (.*)'.format(re_locstr))
re_strp = re.compile('\(indirect string, offset: 0x[0-9a-f]+\): (.*)')
re_offset = re.compile('.*\(DW_OP_plus_uconst: (\d+)\)')

class dw_tag(object):
    def __init__(self, loc, type):
        self.loc = loc
        self.type = type
        self.at = {}

    def add_at(self, at):
        self.at[at.name] = at

class dw_at(object):
    def __init__(self, loc, name, value):
        self.loc = loc
        self.name = name
        self.value = value

class structure(object):
    def __init__(self):
        self.name = None
        self.size = None
        self.members = []
        self.loc = None

    def add_member(self, member):
        self.members.append(member)

class member(object):
    def __init__(self, name, offset):
        self.name = name
        self.offset = offset

def parse_info(info):
    tags = []
    last_tag = None
    for line in info.splitlines():
        match_tag = re_tag.match(line)
        match_at = re_at.match(line)
        if match_tag:
            tag_loc = match_tag.group(2)
            tag_type = match_tag.group(3)
            last_tag = dw_tag(tag_loc, tag_type)
            tags.append(last_tag)
        elif match_at:
            at_loc = match_at.group(1)
            at_name = match_at.group(2)
            at_value = match_at.group(3)
            last_tag.add_at(dw_at(at_loc, at_name, at_value))
    return tags

def get_string(value):
    match = re_strp.match(value)
    if match:
        value = match.group(1)
    return value.strip()

def get_loc(value):
    match = re.match(re_locstr, value)
    if match:
        return match.group(1)
    else:
        return None

def parse_structures(tags):
    structures = {}  # indexed by location
    last = None
    for tag in tags:
        if tag.type == 'DW_TAG_structure_type':
            s = structure()
            if 'DW_AT_name' in tag.at:
                s.name = get_string(tag.at['DW_AT_name'].value)
            if 'DW_AT_byte_size' in tag.at:
                s.size = int(tag.at['DW_AT_byte_size'].value)
            s.loc = tag.loc
            structures[s.loc] = s
            last = s
        elif tag.type == 'DW_TAG_member':
            offset = None
            if 'DW_AT_data_member_location' in tag.at:
                offset_str = tag.at['DW_AT_data_member_location'].value
                match = re_offset.match(offset_str)
                if match:
                    offset = int(match.group(1))
            name = get_string(tag.at['DW_AT_name'].value)
            m = member(name, offset)
            last.add_member(m)
        elif tag.type == 'DW_TAG_typedef':
            if 'DW_AT_type' in tag.at:
                type_loc = get_loc(tag.at['DW_AT_type'].value)
                if type_loc in structures.keys():
                    s = structure()
                    s.name = get_string(tag.at['DW_AT_name'].value)
                    s.loc = tag.loc
                    s.size = structures[type_loc].size
                    s.members = structures[type_loc].members
                    structures[s.loc] = s
    return structures

def check_output(*args, **kw):
    '''
    Like the check_output function in the subprocess module of a recent
    Python, but backported to 2.6.
    '''

    p = subprocess.Popen(*args, stdout = subprocess.PIPE, **kw)
    output, dummy = p.communicate()
    retcode = p.poll()
    if retcode != 0:
        raise subprocess.CalledProcessError(retcode, args[0])
    return output

def main(argv):
    if len(argv) != 2:
        print("Usage: {0}".format(argv[0]), file = sys.stderr)
        return 2
    try:
        data = check_output(['readelf', '--debug-dump=info', '--', argv[1]])
    except EnvironmentError as e:
        print("Could not execute readelf:", e.strerror)
        return 1
    except subprocess.CalledProcessError as e:
        # readelf will have printed an error
        return 1
    info = data.decode()
    tags = parse_info(info)
    structures = parse_structures(tags)

    for s in structures.values():
        if s.name is not None and s.size is not None:
            print('STRUCT {0} {1}'.format(s.name, s.size))
            for m in s.members:
                print('MEMBER {0} {1}'.format(m.offset, m.name))

if __name__ == '__main__':
    sys.exit(main(sys.argv))
