Files
2017-11-13 10:05:18 -06:00

620 lines
20 KiB
Python
Executable File

#!/usr/bin/env python
import os, sys, re
class DWARFParser(object):
"""A parser for DWARF files."""
# Nasty, but appears to parse the lines we need
dwarf_header_regex = re.compile(
r'<(?P<level>\d+)><(?P<statement_id>[0-9+]+)><(?P<kind>\w+)>')
dwarf_key_val_regex = re.compile(
'\s*(?P<keyname>\w+)<(?P<val>[^>]*)>')
sz2tp = {8: 'long long', 4: 'long', 2: 'short', 1: 'char'}
tp2vol = {
'sizetype' : 'long long',
'bool' : 'int',
'_Bool': 'unsigned char',
'char': 'char',
'float': 'float',
'double': 'double',
'long double': 'double',
'int': 'int',
'long int': 'long',
'long long int': 'long long',
'long long unsigned int': 'unsigned long long',
'long unsigned int': 'unsigned long',
'short int': 'short',
'short unsigned int': 'unsigned short',
'unsigned short' : 'unsigned short',
'short' : 'short',
'signed char': 'signed char',
'unsigned char': 'unsigned char',
'unsigned int': 'unsigned int',
'unsigned __int128' : 'unsigned char', ### not sure if Vol 2 can represent 128-bit values natively??
}
def __init__(self):
self.current_level = -1
self.name_stack = []
self.id_to_name = {}
self.all_vtypes = {}
self.vtypes = {}
self.enums = {}
self.all_vars = {}
self.vars = {}
self.all_local_vars = []
self.local_vars = []
self.anons = 0
self.typedefs = {}
def resolve(self, memb):
"""Lookup anonymouse member and replace it with a well known one."""
# Reference to another type
if isinstance(memb, str) and memb.startswith('<'):
try:
resolved = self.id_to_name[memb[1:]]
except:
resolved = 0
ret = self.resolve(resolved)
elif isinstance(memb, list):
ret = [self.resolve(r) for r in memb]
else:
# Literal
ret = memb
return ret
def fix_typedefs(self):
tmp_types = self.vtypes.copy()
for vname,vdata in tmp_types.items():
if vname.startswith("__unnamed_"):
statement_id = vname.split("_")[3]
if statement_id in self.typedefs:
tmp_types[self.typedefs[statement_id]] = vdata
else:
tmp_types[vname] = vdata
else:
tmp_types[vname] = vdata
return tmp_types
def resolve_refs(self):
"""Replace references with types."""
for v in self.vtypes:
for m in self.vtypes[v][1]:
self.vtypes[v][1][m] = self.resolve(self.vtypes[v][1][m])
return self.vtypes
def deep_replace(self, t, search, repl):
"""Recursively replace anonymous references."""
if t == search:
return repl
elif isinstance(t, list):
return [self.deep_replace(x, search, repl) for x in t]
else: return t
def get_deepest(self, t):
if isinstance(t, list):
if len(t) == 1:
return t[0]
else:
for part in t:
res = self.get_deepest(part)
if res:
return res
return None
return None
def base_type_name(self, data):
"""Replace references to base types."""
if 'AT_name' in data:
return self.tp2vol[data['AT_name']]
else:
sz = int(data['AT_byte_size'])
if data['AT_encoding'] == 'ATE_unsigned':
return 'unsigned ' + self.sz2tp[sz]
else:
return self.sz2tp[sz]
def feed_line(self, line):
line = line.replace("\n", "")
# Does the header match?
m = self.dwarf_header_regex.match(line)
if m:
parsed = m.groupdict()
parsed['data'] = {}
# Now parse the key value pairs
while m:
i = m.end()
m = self.dwarf_key_val_regex.search(line, i)
if m:
d = m.groupdict()
parsed['data'][d['keyname']] = d['val']
if parsed['kind'] in ('TAG_formal_parameter','TAG_variable'):
self.process_variable(parsed['data'])
else:
self.process_statement(**parsed)
#else:
# print "line %s does not match" % line.strip()
def get_offset(self, data):
if 'AT_data_member_location' in data:
loc = data['AT_data_member_location']
if loc[0] == "x":
off = int(loc[1:], 16)
else:
off = int(loc)
else:
off = 0
return off
def process_statement(self, kind, level, data, statement_id):
"""Process a single parsed statement."""
new_level = int(level)
if new_level > self.current_level:
self.current_level = new_level
self.name_stack.append([])
elif new_level < self.current_level:
self.name_stack = self.name_stack[:new_level+1]
self.current_level = new_level
self.name_stack[-1] = [kind, statement_id]
try:
parent_kind, parent_name = self.name_stack[-2]
except IndexError:
parent_kind, parent_name = (None, None)
# TAG_compile_unit's have cross-dependencies in El Capitain
# so it doesn't make sense to finalize or clear with unmet dependencies anymore.
if kind == 'TAG_compile_unit':
self.all_local_vars += self.local_vars
self.local_vars = []
elif kind == 'TAG_structure_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
self.name_stack[-1][1] = name
self.id_to_name[statement_id] = [name]
# If it's just a forward declaration, we want the name around,
# but there won't be a size
if 'AT_declaration' not in data:
try:
self.vtypes[name] = [ int(data['AT_byte_size']), {} ]
except:
self.vtypes[name] = [ int(data['AT_byte_size'], 16), {} ]
elif kind == 'TAG_class_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
name = name + "_class"
self.name_stack[-1][1] = name
self.id_to_name[statement_id] = [name]
# If it's just a forward declaration, we want the name around,
# but there won't be a size
if 'AT_declaration' not in data:
try:
self.vtypes[name] = [ int(data['AT_byte_size']), {} ]
except:
self.vtypes[name] = [ int(data['AT_byte_size'], 16), {} ]
elif kind == 'TAG_union_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
self.name_stack[-1][1] = name
self.id_to_name[statement_id] = [name]
try:
self.vtypes[name] = [ int(data['AT_byte_size']), {} ]
except:
self.vtypes[name] = [ 0, {} ]
elif kind == 'TAG_array_type':
self.name_stack[-1][1] = statement_id
self.id_to_name[statement_id] = data['AT_type']
elif kind == 'TAG_enumeration_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
self.name_stack[-1][1] = name
self.id_to_name[statement_id] = [name]
# If it's just a forward declaration, we want the name around,
# but there won't be a size
if 'AT_declaration' not in data:
try:
sz = int(data['AT_byte_size'])
except:
sz = 0
self.enums[name] = [sz, {}]
elif kind == 'TAG_pointer_type' or kind == "TAG_ptr_to_member_type":
self.id_to_name[statement_id] = ['pointer', data.get('AT_type', ['void'])]
elif kind == 'TAG_base_type':
self.id_to_name[statement_id] = [self.base_type_name(data)]
elif kind == 'TAG_volatile_type':
self.id_to_name[statement_id] = data.get('AT_type', ['void'])
elif kind == 'TAG_const_type':
self.id_to_name[statement_id] = data.get('AT_type', ['void'])
elif kind == 'TAG_typedef':
try:
self.id_to_name[statement_id] = data['AT_type']
self.typedefs[data['AT_type'].replace("<","").replace(">","")] = data['AT_name']
except:
self.id_to_name[statement_id] = ['void']
elif kind == 'TAG_subroutine_type':
self.id_to_name[statement_id] = ['void'] # Don't need these
elif kind == 'TAG_variable' and level == '1':
loc = self.get_offset(data)
if loc != None:
self.vars[data['AT_name']] = [loc, data['AT_type']]
elif kind == 'TAG_subprogram':
# IDEK
pass
elif kind == 'TAG_member' and parent_kind == 'TAG_structure_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
off = self.get_offset(data)
if 'AT_bit_size' in data and ('AT_bit_offset' in data or 'AT_data_bit_offset' in data):
if 'AT_bit_offset' in data:
stbit = int(data['AT_bit_offset'])
edbit = stbit + int(data['AT_bit_size'])
full_size = int(data['AT_byte_size'])*8
stbit = full_size - stbit
edbit = full_size - edbit
stbit, edbit = edbit, stbit
assert stbit < edbit
# high sierra +
else:
stbit = int(data['AT_data_bit_offset'], 16)
off = stbit / 8
stbit = stbit % 8
edbit = stbit + int(data['AT_bit_size'], 16)
memb_tp = ['BitField', dict(start_bit = stbit, end_bit = edbit)]
else:
memb_tp = data['AT_type']
self.vtypes[parent_name][1][name] = [off, memb_tp]
elif kind == 'TAG_member' and parent_kind == 'TAG_class_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
off = self.get_offset(data)
if 'AT_bit_size' in data and ('AT_bit_offset' in data or 'AT_data_bit_offset' in data):
if 'AT_bit_offset' in data:
stbit = int(data['AT_bit_offset'])
edbit = stbit + int(data['AT_bit_size'])
full_size = int(data['AT_byte_size'])*8
stbit = full_size - stbit
edbit = full_size - edbit
stbit, edbit = edbit, stbit
assert stbit < edbit
# high sierra +
else:
stbit = int(data['AT_data_bit_offset'], 16)
off = stbit / 8
stbit = stbit % 8
edbit = stbit + int(data['AT_bit_size'], 16)
memb_tp = ['BitField', dict(start_bit = stbit, end_bit = edbit)]
else:
memb_tp = data['AT_type']
self.vtypes[parent_name][1][name] = [off, memb_tp]
elif kind == 'TAG_member' and parent_kind == 'TAG_union_type':
name = data.get('AT_name', "__unnamed_%s" % statement_id)
self.vtypes[parent_name][1][name] = [0, data['AT_type']]
elif kind == 'TAG_enumerator' and parent_kind == 'TAG_enumeration_type':
name = data['AT_name']
try:
val = int(data['AT_const_value'])
except ValueError:
val = int(data['AT_const_value'].split('(')[0])
self.enums[parent_name][1][name] = val
elif kind == 'TAG_subrange_type' and parent_kind == 'TAG_array_type':
if 'AT_upper_bound' in data:
try:
sz = int(data['AT_upper_bound'])
except ValueError:
try:
sz = int(data['AT_upper_bound'].split('(')[0])
except ValueError:
# Give up
sz = 0
sz += 1
else:
sz = 0
tp = self.id_to_name[parent_name]
self.id_to_name[parent_name] = ['array', sz, tp]
else:
pass
#if kind != "NULL":
# print "Skipping unsupported tag %s" % kind
def process_variable(self, data):
return
"""Process a local variable."""
if ('AT_name' in data and 'AT_decl_line' in data and
'AT_type' in data):
self.local_vars.append(
(data['AT_name'], int(data['AT_decl_line']),
data['AT_decl_file'].split()[1], data['AT_type']) )
def finalize(self):
"""Finalize the output."""
if self.vtypes:
self.vtypes = self.fix_typedefs()
self.vtypes = self.resolve_refs()
self.all_vtypes.update(self.vtypes)
if self.vars:
self.vars = dict(((k, self.resolve(v)) for k, v in self.vars.items()))
self.all_vars.update(self.vars)
if self.local_vars:
self.local_vars = [ (name, lineno, decl_file, self.resolve(tp)) for
(name, lineno, decl_file, tp) in self.local_vars ]
self.all_local_vars += self.local_vars
# Get rid of unneeded unknowns (shades of Rumsfeld here)
# Needs to be done in fixed point fashion
changed = True
while changed:
changed = False
s = set()
for m in self.all_vtypes:
for t in self.all_vtypes[m][1].values():
s.add(self.get_deepest(t))
for m in self.all_vars:
s.add(self.get_deepest(self.all_vars[m][1]))
for v in list(self.all_vtypes):
if v.startswith('__unnamed_') and v not in s:
del self.all_vtypes[v]
changed = True
# Merge the enums into the types directly:
for t in self.all_vtypes:
for m in list(self.all_vtypes[t][1]):
memb = self.all_vtypes[t][1][m]
d = self.get_deepest(memb)
if d in self.enums:
sz = self.enums[d][0]
vals = dict((v, k) for k, v in self.enums[d][1].items())
self.all_vtypes[t][1][m] = self.deep_replace(
memb, [d],
['Enumeration', dict(target = 'int', choices = vals)]
)
return self.all_vtypes
def print_output(self):
self.finalize()
print "mac_types = {"
for t in self.all_vtypes:
print " '%s': [ %#x, {" % (t, self.all_vtypes[t][0])
for m in sorted(self.all_vtypes[t][1], key=lambda m: self.all_vtypes[t][1][m][0]):
print " '%s': [%#x, %s]," % (m, self.all_vtypes[t][1][m][0], self.all_vtypes[t][1][m][1])
print "}],"
print "}"
print
print "mac_gvars = {"
for v in sorted(self.all_vars, key=lambda v: self.all_vars[v][0]):
print " '%s': [%#010x, %s]," % (v, self.all_vars[v][0], self.all_vars[v][1])
print "}"
def parse_dwarf():
"""Parse the dwarf file."""
parser = DWARFParser()
for line in open(sys.argv[1],"r").readlines():
parser.feed_line(line)
parser.print_output()
#for k in parser.wtf:
# print k
def write_line(outfile, level, id, name):
outfile.write("<%s><%s><%s> " % (level, id, name))
def convert_file(mac_file, outfile):
'''
5 spaces, level 1
0x00000428: TAG_typedef [15]
9 spaces, level 2, (struct member)
0x00000446: TAG_member [30]
at
AT_type( {0x0000008b}
'''
# skip the first (entry 0)
re_compiles = ["<BAD COMPILE>"]
parse_depth = 27
string_idx = 5
for i in range(1, parse_depth):
s = r'^(0x[0-9a-fA-F]+):\s{' + "%d" % string_idx + r'}(\w+)\s'
re_compiles.append(re.compile(s))
string_idx = string_idx + 4
at_re = re.compile(r'^\s+(\w+)\((.+)')
level = 0
dontbreak = 0
for line in mac_file.readlines():
if len(line) < 2:
outfile.write("\n")
level = 0
continue
if line.find("-------------") != -1:
level = 0
continue
if line.find("File:") != -1:
level = 0
continue
if line.find(".debug_info") != -1:
level = 0
continue
if line.find("Compile Unit:") != -1:
level = 0
continue
if line.find("TAG_compile_unit") != -1:
outfile.write("<1><999999999999999><TAG_compile_unit> ")
level = 1
continue
# new declaration
if level == 0:
line_wrote = False
for check_idx in range(1, parse_depth):
re_check = re_compiles[check_idx]
matchobj = re_check.match(line)
if matchobj:
(id, name) = matchobj.groups()
id = "%d" % int(id, 16)
level = 1
write_line(outfile, check_idx, id, name)
line_wrote = True
break
if not line_wrote:
print "State machine broken! level 0! %s" % line
sys.exit(1)
# can either be: new declaration
# AT_xxxx
# blank
elif level == 1:
m = re_compiles[2].match(line)
a = at_re.match(line)
if m:
(id, name) = m.groups()
id = "%d" % int(id, 16)
level = 2
# <1><41><DW_TAG_structure_type>
outfile.write("<%s><%s><%s> " % (level, id, name))
elif a:
(name, val) = a.groups()
#DW_AT_byte_size<2>
val = val[:-2]
if val[0] == " ":
val = val[1:]
# remove the " surround type name
if name == "AT_name":
val = val[1:-1]
if name == "AT_const_value":
ents = val.split()
if len(ents) > 1:
ents = ents[1:]
try:
val = "%d" % int("0x" + "".join([x for x in ents]),16)
except:
val = "Bad const list val"
else:
try:
val = "%d" % int(val, 16)
except:
val = "Bad const value"
if name in ["AT_byte_size", "AT_bit_offset", "AT_bit_size", "AT_upper_bound"]:
val = "%d" % int(val, 16)
if name == "AT_data_member_location":
if val.startswith("+"):
val = int(val, 10)
else:
val = int(val, 16)
if name == "AT_type":
# convert {0x00000550} ( queue_chain_t )
# to decimal of int
val = val.split()[0]
val = val[1:-1]
val = "<%d>" % int(val, 16)
outfile.write("%s<%s> " % (name, val))
outfile.flush()
#else:
#print "State machine broken! level %d!%s" % (level, line)
#sys.exit(1)
def main():
if len(sys.argv) == 3:
print "converting file"
mac_file = open(sys.argv[1], "r")
outfile = open(sys.argv[2], "w")
convert_file(mac_file, outfile)
outfile.close()
else:
parse_dwarf()
if __name__ == "__main__":
main()