378 lines
14 KiB
Python
378 lines
14 KiB
Python
|
#!/bin/python
|
||
|
|
||
|
import sys
|
||
|
from bear import formats
|
||
|
from elftools.dwarf.locationlists import LocationParser, LocationExpr
|
||
|
from elftools.dwarf.dwarf_expr import DWARFExprParser, DWARFExprOp, DW_OP_opcode2name
|
||
|
from bear.dwarfone import DWARFExprParserV1
|
||
|
|
||
|
from pprint import pprint
|
||
|
|
||
|
|
||
|
configuration = {
|
||
|
"include_file_name": False,
|
||
|
}
|
||
|
|
||
|
|
||
|
def eprint(*args, **kwargs):
|
||
|
print(*args, file=sys.stderr, **kwargs)
|
||
|
|
||
|
|
||
|
class DWARFParseError(Exception):
|
||
|
""" "Opened, could not parse" """
|
||
|
def __init__(self, exc, di):
|
||
|
Exception.__init__(self, "DWARF parsing error: " + format(exc))
|
||
|
self.dwarfinfo = di
|
||
|
|
||
|
|
||
|
# Some additional data for every DIE
|
||
|
def decorate_die(die, i):
|
||
|
die._i = i
|
||
|
die._children = None
|
||
|
return die
|
||
|
|
||
|
def load_children(parent_die):
|
||
|
# Load and cache child DIEs in the parent DIE, if necessary
|
||
|
# Assumes the check if the DIE has children has been already performed
|
||
|
if not hasattr(parent_die, "_children") or parent_die._children is None:
|
||
|
# TODO: wait cursor here. It may cause disk I/O
|
||
|
try:
|
||
|
parent_die._children = [decorate_die(die, i) for (i, die) in enumerate(parent_die.iter_children())]
|
||
|
except KeyError as ke:
|
||
|
# Catching #1516
|
||
|
# QMessageBox(QMessageBox.Icon.Warning, "DWARF Explorer",
|
||
|
# "This executable file is corrupt or incompatible with the current version of DWARF Explorer. Please consider creating a new issue at https://github.com/sevaa/dwex/, and share this file with the tech support.",
|
||
|
# QMessageBox.StandardButton.Ok, QApplication.instance().win).show()
|
||
|
print("This executable file is corrupt or incompatible with the current version of Bear.")
|
||
|
parent_die._children = []
|
||
|
|
||
|
|
||
|
|
||
|
def safe_DIE_name(die, default = ''):
|
||
|
return die.attributes['DW_AT_name'].value.decode('utf-8', errors='ignore') if 'DW_AT_name' in die.attributes else default
|
||
|
|
||
|
|
||
|
class Bear():
|
||
|
def __init__(self, filename):
|
||
|
di = formats.read_dwarf(filename, self.resolve_arch)
|
||
|
if not di: # Covers both False and None
|
||
|
print("Something went wrong")
|
||
|
exit(1)
|
||
|
|
||
|
# Some degree of graceful handling of wrong format
|
||
|
try:
|
||
|
# Some cached top level stuff
|
||
|
# Notably, iter_CUs doesn't cache
|
||
|
di._ranges = None # Loaded on first use
|
||
|
def decorate_cu(cu, i):
|
||
|
cu._i = i
|
||
|
cu._lineprogram = None
|
||
|
cu._exprparser = None
|
||
|
return cu
|
||
|
di._unsorted_CUs = [decorate_cu(cu, i) for (i, cu) in enumerate(di.iter_CUs())] # We'll need them first thing, might as well load here
|
||
|
if not len(di._unsorted_CUs):
|
||
|
return None # Weird, but saw it once - debug sections present, but no CUs
|
||
|
# For quick CU search by offset within the info section, regardless of sorting
|
||
|
di._CU_offsets = [cu.cu_offset for cu in di._unsorted_CUs]
|
||
|
di._CUs = list(di._unsorted_CUs)
|
||
|
|
||
|
di._locparser = None # Created on first use
|
||
|
|
||
|
self.dwarfinfo = di
|
||
|
self.filename = filename
|
||
|
except AssertionError as ass: # Covers exeptions during parsing
|
||
|
raise DWARFParseError(ass, di)
|
||
|
|
||
|
# A list containing variables in a disctionary
|
||
|
# Description of used fields:
|
||
|
# name: variable name
|
||
|
# type: test description of the type
|
||
|
# size: size of the variable
|
||
|
# address: absolute address of the variable
|
||
|
# children: a dictionary of child elements
|
||
|
self.myVariables = []
|
||
|
self.top_dies = [decorate_die(CU.get_top_DIE(), i) for (i, CU) in enumerate(di._CUs)]
|
||
|
|
||
|
for top_die in self.top_dies:
|
||
|
# top dies only contain Compile Units
|
||
|
|
||
|
# Preload children
|
||
|
load_children(top_die)
|
||
|
|
||
|
children_dies = []
|
||
|
|
||
|
for child_die in top_die._children:
|
||
|
if child_die.tag == 'DW_TAG_variable':
|
||
|
# pprint(child_die)
|
||
|
entry = {
|
||
|
# Name should be on every element, if not set something so it can be printed
|
||
|
'name': safe_DIE_name(child_die, '?')
|
||
|
}
|
||
|
|
||
|
if 'DW_AT_location' in child_die.attributes:
|
||
|
if LocationParser.attribute_has_location(child_die.attributes['DW_AT_location'], child_die.cu['version']):
|
||
|
ll = self.parse_location(child_die, child_die.attributes['DW_AT_location'])
|
||
|
# pprint(ll.loc_expr)
|
||
|
# pprint(self.dump_expr(child_die, ll.loc_expr))
|
||
|
lloc = self.dump_expr(child_die, ll.loc_expr)
|
||
|
# print(hex(lloc[0].args[0]))
|
||
|
entry['address'] = hex(lloc[0].args[0])
|
||
|
# if isinstance(ll, LocationExpr):
|
||
|
# return '; '.join(self.dump_expr(child_die, ll.loc_expr))
|
||
|
# else:
|
||
|
# return "Loc list: 0x%x" % child_die.attributes['DW_AT_location'].value
|
||
|
|
||
|
if 'DW_AT_type' in child_die.attributes:
|
||
|
typ_die = child_die.get_DIE_from_attribute('DW_AT_type')
|
||
|
entry['type'] = self.resolve_type(typ_die)
|
||
|
|
||
|
children_dies.append(entry)
|
||
|
|
||
|
self.myVariables.append({
|
||
|
'name': safe_DIE_name(top_die, '?'),
|
||
|
'children': children_dies
|
||
|
})
|
||
|
|
||
|
pprint(self.myVariables)
|
||
|
|
||
|
def resolve_type(self, die_type):
|
||
|
if die_type.tag == 'DW_TAG_volatile_type':
|
||
|
die_type = die_type.get_DIE_from_attribute('DW_AT_type')
|
||
|
|
||
|
entry = {
|
||
|
# Name should be on every element, if not set something so it can be printed
|
||
|
'name': safe_DIE_name(die_type, '?')
|
||
|
}
|
||
|
|
||
|
if 'DW_AT_data_member_location' in die_type.attributes:
|
||
|
entry['offset'] = die_type.attributes['DW_AT_data_member_location'].value * 8
|
||
|
|
||
|
if 'DW_AT_type' in die_type.attributes and die_type.tag not in ['DW_TAG_base_type', 'DW_TAG_structure_type', 'DW_TAG_array_type']:
|
||
|
# Check if the type is a redefinition of a base type
|
||
|
die_type_test = die_type
|
||
|
while 'DW_AT_type' in die_type_test.attributes:
|
||
|
die_type_test = die_type_test.get_DIE_from_attribute('DW_AT_type')
|
||
|
if die_type_test.tag in ['DW_TAG_base_type', 'DW_TAG_structure_type', 'DW_TAG_array_type', 'DW_TAG_union_type']:
|
||
|
die_type = die_type_test
|
||
|
break
|
||
|
|
||
|
if die_type.tag == 'DW_TAG_base_type':
|
||
|
entry['type'] = safe_DIE_name(die_type, '?')
|
||
|
elif die_type.tag == "DW_TAG_structure_type":
|
||
|
load_children(die_type)
|
||
|
child_dies = []
|
||
|
for child_die in die_type._children:
|
||
|
child_entry = self.resolve_type(child_die)
|
||
|
child_dies.append(child_entry)
|
||
|
entry['children'] = child_dies;
|
||
|
elif die_type.tag == "DW_TAG_array_type":
|
||
|
array_type = self.resolve_type(die_type.get_DIE_from_attribute('DW_AT_type'))
|
||
|
load_children(die_type)
|
||
|
children_num = die_type._children[0].attributes['DW_AT_upper_bound'].value
|
||
|
child_entries = []
|
||
|
for child in range(0, children_num + 1):
|
||
|
child_entry = array_type.copy()
|
||
|
child_entry['offset'] = array_type['size_bit'] * child
|
||
|
child_entries.append(child_entry)
|
||
|
entry['children'] = child_entries
|
||
|
elif die_type.tag == 'DW_TAG_union_type':
|
||
|
load_children(die_type)
|
||
|
child_entries = []
|
||
|
for child_die in die_type._children:
|
||
|
child_entry = self.resolve_type(child_die)
|
||
|
child_entries.append(child_entry)
|
||
|
entry['children'] = child_entries
|
||
|
else:
|
||
|
eprint("Unsupported type:", die_type.tag)
|
||
|
|
||
|
if 'DW_AT_byte_size' in die_type.attributes:
|
||
|
entry['size_bit'] = die_type.attributes['DW_AT_byte_size'].value * 8
|
||
|
|
||
|
return entry
|
||
|
|
||
|
def flatten_type(self, parent=None):
|
||
|
# Structure of resulting list of dictionaries
|
||
|
# address - The address
|
||
|
# name - The long name of a variable after out rolling the type
|
||
|
vars = []
|
||
|
# Iterate over CUs
|
||
|
# - name - filename
|
||
|
# - children - variables
|
||
|
for CU in self.myVariables:
|
||
|
vars.append(CU['name'])
|
||
|
|
||
|
for child in CU['children']:
|
||
|
if configuration["include_file_name"]:
|
||
|
vars.append(self.flatten_child(child, CU['name']))
|
||
|
else:
|
||
|
vars.append(self.flatten_child(child))
|
||
|
return vars
|
||
|
|
||
|
def flatten_child(self, child, name='', address=0):
|
||
|
var = {}
|
||
|
kids = []
|
||
|
|
||
|
if name:
|
||
|
var['name'] = '{parent}.{child}'.format(parent=name, child=child['name'])
|
||
|
else:
|
||
|
var['name'] = child['name']
|
||
|
|
||
|
if address:
|
||
|
var['address'] = address
|
||
|
else:
|
||
|
var['address'] = child['address']
|
||
|
|
||
|
if 'children' in child:
|
||
|
for kid in child['children']:
|
||
|
self.flatten_child(kid, var['name'], var['address'])
|
||
|
|
||
|
if 'type' in child:
|
||
|
self.flatten_child()
|
||
|
|
||
|
return kids
|
||
|
|
||
|
def pretty_print(self):
|
||
|
vars = []
|
||
|
# Iterate over CUs
|
||
|
# - name - filename
|
||
|
# - children - variables
|
||
|
for CU in self.myVariables:
|
||
|
vars.append(CU['name'])
|
||
|
|
||
|
for child in CU['children']:
|
||
|
self.pretty_child(child)
|
||
|
return vars
|
||
|
|
||
|
def pretty_child(self, child, prefix='', address=0):
|
||
|
name = ''
|
||
|
if 'children' in child:
|
||
|
pass
|
||
|
else:
|
||
|
pass
|
||
|
|
||
|
def print_top_DIE(self, die):
|
||
|
if die.tag == 'DW_TAG_variable':
|
||
|
name = safe_DIE_name(die)
|
||
|
if name:
|
||
|
typ_name = ''
|
||
|
if 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
print(self.describe_type(typ))
|
||
|
# typ_name = safe_DIE_name(typ)
|
||
|
# if not typ_name:
|
||
|
# print (typ)
|
||
|
print('{name} {typ_name}'.format(name=name, typ_name=typ_name))
|
||
|
|
||
|
def print_DIE(self, die, prefix=''):
|
||
|
name = ''
|
||
|
# print(die)
|
||
|
if die.tag == 'DW_TAG_variable':
|
||
|
name = safe_DIE_name(die)
|
||
|
if name and 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
if 'DW_AT_location' in die.attributes:
|
||
|
ll = self.parse_location(die, die.attributes['DW_AT_location'])
|
||
|
# if isinstance(ll, LocationExpr):
|
||
|
# print(self.dump_expr(die, ll.loc_expr))
|
||
|
|
||
|
self.print_DIE(typ, name)
|
||
|
return
|
||
|
# print(typ)
|
||
|
elif die.tag == 'DW_TAG_compile_unit':
|
||
|
name = safe_DIE_name(die, '.')
|
||
|
elif prefix and die.tag == 'DW_TAG_base_type':
|
||
|
name = safe_DIE_name(die)
|
||
|
elif prefix and die.tag == 'DW_TAG_const_type':
|
||
|
if 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
self.print_DIE(typ, prefix)
|
||
|
return
|
||
|
elif prefix and die.tag == 'DW_TAG_array_type':
|
||
|
if 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
name = prefix + '[]'
|
||
|
self.print_DIE(typ, name)
|
||
|
load_children(die)
|
||
|
|
||
|
if die._children:
|
||
|
for child in die._children:
|
||
|
print(child)
|
||
|
self.print_DIE(child, name)
|
||
|
|
||
|
return
|
||
|
elif prefix and die.tag == 'DW_TAG_volatile_type':
|
||
|
if 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
self.print_DIE(typ, prefix)
|
||
|
return
|
||
|
elif prefix and die.tag == 'DW_TAG_typedef':
|
||
|
print(die.attributes["DW_AT_name"].value)
|
||
|
if 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
name = prefix + '[]'
|
||
|
self.print_DIE(typ, name)
|
||
|
load_children(die)
|
||
|
|
||
|
if die._children:
|
||
|
for child in die._children:
|
||
|
print(child)
|
||
|
self.print_DIE(child, name)
|
||
|
elif prefix and die.tag == 'DW_TAG_enumeration_type':
|
||
|
# print(die.attributes["DW_AT_name"].value)
|
||
|
print ("mylittlepony")
|
||
|
print(die)
|
||
|
if 'DW_AT_type' in die.attributes:
|
||
|
typ = die.get_DIE_from_attribute('DW_AT_type')
|
||
|
name = prefix
|
||
|
self.print_DIE(typ, name)
|
||
|
load_children(die)
|
||
|
print (typ)
|
||
|
elif prefix:
|
||
|
print (prefix)
|
||
|
print(die)
|
||
|
|
||
|
if name:
|
||
|
if prefix:
|
||
|
print (prefix, name)
|
||
|
else:
|
||
|
print (name)
|
||
|
|
||
|
def parse_location(self, die, attr):
|
||
|
di = die.dwarfinfo
|
||
|
if di._locparser is None:
|
||
|
di._locparser = LocationParser(di.location_lists())
|
||
|
return di._locparser.parse_from_attribute(attr, die.cu['version'], die = die)
|
||
|
|
||
|
# Expr is an expression blob
|
||
|
# Returns a list of strings for ops
|
||
|
# Format: op arg, arg...
|
||
|
def dump_expr(self, die, expr):
|
||
|
if die.cu._exprparser is None:
|
||
|
die.cu._exprparser = DWARFExprParser(die.cu.structs) if die.cu['version'] > 1 else DWARFExprParserV1(die.cu.structs)
|
||
|
|
||
|
# Challenge: for nested expressions, args is a list with a list of commands
|
||
|
# For those, the format is: op {op arg, arg; op arg, arg}
|
||
|
# Can't just check for iterable, str is iterable too
|
||
|
return die.cu._exprparser.parse_expr(expr)
|
||
|
|
||
|
def resolve_arch(self, arches):
|
||
|
print("resolve_arch: Unsupported feature")
|
||
|
return None
|
||
|
|
||
|
|
||
|
def main():
|
||
|
from bear.patch import monkeypatch
|
||
|
monkeypatch()
|
||
|
|
||
|
bear = Bear("/home/juraj/projects/Playground_C/build/playground_c")
|
||
|
vars = bear.flatten_type()
|
||
|
pprint(vars)
|
||
|
|
||
|
#bear = Bear("main.elf")
|
||
|
# bear = Bear("LED_Cube.elf")
|
||
|
# bear = Bear("serialplay")
|
||
|
pass
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|