| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 | 
							- #!/usr/bin/env python3
 
- import argparse
 
- import elftools.elf.elffile
 
- import elftools.dwarf.descriptions
 
- from collections import namedtuple
 
- from struct import unpack
 
- import os
 
- from lib.dump import decode_dump
 
- from lib.avr import *
 
- Entry = namedtuple('Entry', ['name', 'loc', 'size', 'declpos'])
 
- Member = namedtuple('Member', ['name', 'off', 'size'])
 
- def array_inc(loc, dim, idx=0):
 
-     if idx == len(dim):
 
-         return True
 
-     loc[idx] += 1
 
-     if loc[idx] == dim[idx]:
 
-         loc[idx] = 0
 
-         return array_inc(loc, dim, idx+1)
 
-     return False
 
- def get_type_size(type_DIE):
 
-     while True:
 
-         if 'DW_AT_byte_size' in type_DIE.attributes:
 
-             return type_DIE, type_DIE.attributes.get('DW_AT_byte_size').value
 
-         if 'DW_AT_type' not in type_DIE.attributes:
 
-             return None
 
-         type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
 
- def get_type_arrsize(type_DIE):
 
-     size = get_type_size(type_DIE)
 
-     if size is None:
 
-         return None
 
-     byte_size = size[1]
 
-     if size[0].tag != 'DW_TAG_pointer_type':
 
-         array_DIE = get_type_def(type_DIE, 'DW_TAG_array_type')
 
-         if array_DIE is not None:
 
-             for range_DIE in array_DIE.iter_children():
 
-                 if range_DIE.tag == 'DW_TAG_subrange_type' and \
 
-                    'DW_AT_upper_bound' in range_DIE.attributes:
 
-                     dim = range_DIE.attributes['DW_AT_upper_bound'].value + 1
 
-                     byte_size *= dim
 
-     return byte_size
 
- def get_type_def(type_DIE, type_tag):
 
-     while True:
 
-         if type_DIE.tag == type_tag:
 
-             return type_DIE
 
-         if 'DW_AT_type' not in type_DIE.attributes:
 
-             return None
 
-         type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
 
- def get_FORM_block1(attr):
 
-     if attr.form != 'DW_FORM_block1':
 
-         return None
 
-     if attr.value[0] == 3: # OP_addr
 
-         return int.from_bytes(attr.value[1:], 'little')
 
-     if attr.value[0] == 35: # OP_plus_uconst (ULEB128)
 
-         v = 0
 
-         s = 0
 
-         for b in attr.value[1:]:
 
-             v |= (b & 0x7f) << s
 
-             if b & 0x80 == 0:
 
-                 break
 
-             s += 7
 
-         return v
 
-     return None
 
- def get_array_dims(DIE):
 
-     array_DIE = get_type_def(DIE, 'DW_TAG_array_type')
 
-     if array_DIE is None:
 
-         return []
 
-     array_dim = []
 
-     for range_DIE in array_DIE.iter_children():
 
-         if range_DIE.tag == 'DW_TAG_subrange_type' and \
 
-            'DW_AT_upper_bound' in range_DIE.attributes:
 
-             array_dim.append(range_DIE.attributes['DW_AT_upper_bound'].value + 1)
 
-     return array_dim
 
- def get_struct_members(DIE, entry, expand_structs, struct_gaps):
 
-     struct_DIE = get_type_def(DIE, 'DW_TAG_structure_type')
 
-     if struct_DIE is None:
 
-         return []
 
-     members = []
 
-     for member_DIE in struct_DIE.iter_children():
 
-         if member_DIE.tag == 'DW_TAG_member' and 'DW_AT_name' in member_DIE.attributes:
 
-             m_name = member_DIE.attributes['DW_AT_name'].value.decode('ascii')
 
-             m_off = get_FORM_block1(member_DIE.attributes['DW_AT_data_member_location'])
 
-             m_byte_size = get_type_size(member_DIE)[1]
 
-             # still expand member arrays
 
-             m_array_dim = get_array_dims(member_DIE)
 
-             if m_byte_size == 1 and len(m_array_dim) > 1:
 
-                 # likely string, remove one dimension
 
-                 m_byte_size *= m_array_dim.pop()
 
-             if len(m_array_dim) == 0 or (len(m_array_dim) == 1 and m_array_dim[0] == 1):
 
-                 # plain entry
 
-                 members.append(Member(m_name, m_off, m_byte_size))
 
-             elif len(m_array_dim) == 1 and m_byte_size == 1:
 
-                 # likely string, avoid expansion
 
-                 members.append(Member(m_name + '[]', m_off, m_array_dim[0]))
 
-             else:
 
-                 # expand array entries
 
-                 m_array_pos = m_off
 
-                 m_array_loc = [0] * len(m_array_dim)
 
-                 while True:
 
-                     # location index
 
-                     sfx = ''
 
-                     for d in range(len(m_array_dim)):
 
-                         sfx += '[{}]'.format(str(m_array_loc[d]).rjust(len(str(m_array_dim[d]-1)), '0'))
 
-                     members.append(Member(m_name + sfx, m_array_pos, m_byte_size))
 
-                     # advance
 
-                     if array_inc(m_array_loc, m_array_dim):
 
-                         break
 
-                     m_array_pos += m_byte_size
 
-     if struct_gaps and len(members):
 
-         # fill gaps in the middle
 
-         members = list(sorted(members, key=lambda x: x.off))
 
-         last_end = 0
 
-         for n in range(len(members)):
 
-             member = members[n]
 
-             if member.off > last_end:
 
-                 members.append(Member('*UNKNOWN*', last_end, member.off - last_end))
 
-             last_end = member.off + member.size
 
-     if struct_gaps and len(members):
 
-         # fill gap at the end
 
-         members = list(sorted(members, key=lambda x: x.off))
 
-         last = members[-1]
 
-         last_end = last.off + last.size
 
-         if entry.size > last_end:
 
-             members.append(Member('*UNKNOWN*', last_end, entry.size - last_end))
 
-     return members
 
- def get_elf_globals(path, expand_structs, struct_gaps=True):
 
-     fd = open(path, "rb")
 
-     if fd is None:
 
-         return
 
-     elffile = elftools.elf.elffile.ELFFile(fd)
 
-     if elffile is None or not elffile.has_dwarf_info():
 
-         return
 
-     # probably not needed, since we're decoding expressions manually
 
-     elftools.dwarf.descriptions.set_global_machine_arch(elffile.get_machine_arch())
 
-     dwarfinfo = elffile.get_dwarf_info()
 
-     grefs = []
 
-     for CU in dwarfinfo.iter_CUs():
 
-         file_entries = dwarfinfo.line_program_for_CU(CU).header["file_entry"]
 
-         for DIE in CU.iter_DIEs():
 
-             # handle only variable types
 
-             if DIE.tag != 'DW_TAG_variable':
 
-                 continue
 
-             if 'DW_AT_location' not in DIE.attributes:
 
-                 continue
 
-             if 'DW_AT_name' not in DIE.attributes and \
 
-                'DW_AT_abstract_origin' not in DIE.attributes:
 
-                 continue
 
-             # handle locations encoded directly as DW_OP_addr (leaf globals)
 
-             loc = get_FORM_block1(DIE.attributes['DW_AT_location'])
 
-             if loc is None or loc < SRAM_OFFSET or loc >= EEPROM_OFFSET:
 
-                 continue
 
-             loc -= SRAM_OFFSET
 
-             # variable name/type
 
-             if 'DW_AT_name' not in DIE.attributes and \
 
-                'DW_AT_abstract_origin' in DIE.attributes:
 
-                 DIE = DIE.get_DIE_from_attribute('DW_AT_abstract_origin')
 
-                 if 'DW_AT_location' in DIE.attributes:
 
-                     # duplicate reference (handled directly), skip
 
-                     continue
 
-             if 'DW_AT_name' not in DIE.attributes:
 
-                 continue
 
-             if 'DW_AT_type' not in DIE.attributes:
 
-                 continue
 
-             name = DIE.attributes['DW_AT_name'].value.decode('ascii')
 
-             # get final storage size
 
-             size = get_type_size(DIE)
 
-             if size is None:
 
-                 continue
 
-             byte_size = size[1]
 
-             # location of main definition
 
-             declpos = ''
 
-             if 'DW_AT_decl_file' in DIE.attributes and \
 
-                'DW_AT_decl_line' in DIE.attributes:
 
-                 line = DIE.attributes['DW_AT_decl_line'].value
 
-                 fname = DIE.attributes['DW_AT_decl_file'].value
 
-                 if fname and fname - 1 < len(file_entries):
 
-                     fname = file_entries[fname-1].name.decode('ascii')
 
-                     declpos = '{}:{}'.format(fname, line)
 
-             # fetch array dimensions (if known)
 
-             array_dim = get_array_dims(DIE)
 
-             # fetch structure members (one level only)
 
-             entry = Entry(name, loc, byte_size, declpos)
 
-             if not expand_structs or size[0].tag == 'DW_TAG_pointer_type':
 
-                 members = []
 
-             else:
 
-                 members = get_struct_members(DIE, entry, expand_structs, struct_gaps)
 
-             def expand_members(entry, members):
 
-                 if len(members) == 0:
 
-                     grefs.append(entry)
 
-                 else:
 
-                     for member in members:
 
-                         grefs.append(Entry(entry.name + '.' + member.name,
 
-                                            entry.loc + member.off, member.size,
 
-                                            entry.declpos))
 
-             if byte_size == 1 and len(array_dim) > 1:
 
-                 # likely string, remove one dimension
 
-                 byte_size *= array_dim.pop()
 
-             if len(array_dim) == 0 or (len(array_dim) == 1 and array_dim[0] == 1):
 
-                 # plain entry
 
-                 expand_members(entry, members)
 
-             elif len(array_dim) == 1 and byte_size == 1:
 
-                 # likely string, avoid expansion
 
-                 grefs.append(Entry(entry.name + '[]', entry.loc,
 
-                                    array_dim[0], entry.declpos))
 
-             else:
 
-                 # expand array entries
 
-                 array_pos = loc
 
-                 array_loc = [0] * len(array_dim)
 
-                 while True:
 
-                     # location index
 
-                     sfx = ''
 
-                     for d in range(len(array_dim)):
 
-                         sfx += '[{}]'.format(str(array_loc[d]).rjust(len(str(array_dim[d]-1)), '0'))
 
-                     expand_members(Entry(entry.name + sfx, array_pos,
 
-                                          byte_size, entry.declpos), members)
 
-                     # advance
 
-                     if array_inc(array_loc, array_dim):
 
-                         break
 
-                     array_pos += byte_size
 
-     return grefs
 
- def annotate_refs(grefs, addr, data, width, gaps=True, overlaps=True):
 
-     last_end = None
 
-     for entry in grefs:
 
-         if entry.loc < addr:
 
-             continue
 
-         if entry.loc + entry.size > addr + len(data):
 
-             continue
 
-         pos = entry.loc-addr
 
-         end_pos = pos + entry.size
 
-         buf = data[pos:end_pos]
 
-         buf_repr = ''
 
-         if len(buf) in [1, 2, 4]:
 
-             # attempt to decode as integers
 
-             buf_repr += ' I:' + str(int.from_bytes(buf, 'little')).rjust(10)
 
-         if len(buf) in [4, 8]:
 
-             # attempt to decode as floats
 
-             typ = 'f' if len(buf) == 4 else 'd'
 
-             buf_repr += ' F:' + '{:10.3f}'.format(unpack(typ, buf)[0])
 
-         if last_end is not None:
 
-             if gaps and last_end < pos:
 
-                 # decode gaps
 
-                 gap_size = pos - last_end
 
-                 gap_buf = data[last_end:pos]
 
-                 print('{:04x} {} {:4} R:{}'.format(addr+last_end, "*UNKNOWN*".ljust(width),
 
-                                                    gap_size, gap_buf.hex()))
 
-             if overlaps and last_end > pos + 1:
 
-                 gap_size = pos - last_end
 
-                 print('{:04x} {} {:4}'.format(addr+last_end, "*OVERLAP*".ljust(width), gap_size))
 
-         print('{:04x} {} {:4}{} R:{}'.format(entry.loc, entry.name.ljust(width),
 
-                                              entry.size, buf_repr, buf.hex()))
 
-         last_end = end_pos
 
- def print_map(grefs):
 
-     print('OFFSET\tSIZE\tNAME\tDECLPOS')
 
-     for entry in grefs:
 
-         print('{:x}\t{}\t{}\t{}'.format(entry.loc, entry.size, entry.name, entry.declpos))
 
- def print_qdirstat(grefs):
 
-     print('[qdirstat 1.0 cache file]')
 
-     entries = {}
 
-     for entry in grefs:
 
-         # do not output registers when looking at space usage
 
-         if entry.loc < SRAM_START:
 
-             continue
 
-         paths = list(filter(None, re.split(r'[\[\].]', entry.name)))
 
-         base = entries
 
-         for i in range(len(paths) - 1):
 
-             name = paths[i]
 
-             if name not in base:
 
-                 base[name] = {}
 
-             base = base[name]
 
-         name = paths[-1]
 
-         if name in base:
 
-             name = '{}_{:x}'.format(entry.name, entry.loc)
 
-         base[name] = entry.size
 
-     def walker(root, prefix):
 
-         files = []
 
-         dirs = []
 
-         for name, entries in root.items():
 
-             if type(entries) == int:
 
-                 files.append([name, entries])
 
-             else:
 
-                 dirs.append([name, entries])
 
-         # print files
 
-         print('D\t{}\t{}\t0x0'.format(prefix, 0))
 
-         for name, size in files:
 
-             print('F\t{}\t{}\t0x0'.format(name, size))
 
-         # recurse directories
 
-         for name, entries in dirs:
 
-             walker(entries, prefix + '/' + name)
 
-     walker(entries, '/')
 
- def main():
 
-     ap = argparse.ArgumentParser(description="""
 
-         Generate a symbol table map starting directly from an ELF
 
-         firmware with DWARF3 debugging information.
 
-         When used along with a memory dump obtained from the D2/D21/D23 g-code,
 
-         show the value of each symbol which is within the address range.
 
-     """)
 
-     ap.add_argument('elf', help='ELF file containing DWARF debugging information')
 
-     ap.add_argument('--no-gaps', action='store_true',
 
-                     help='do not dump memory inbetween known symbols')
 
-     ap.add_argument('--no-expand-structs', action='store_true',
 
-                     help='do not decode structure data')
 
-     ap.add_argument('--overlaps', action='store_true',
 
-                     help='annotate overlaps greater than 1 byte')
 
-     ap.add_argument('--name-width', type=int, default=50,
 
-                     help='set name column width')
 
-     g = ap.add_mutually_exclusive_group(required=True)
 
-     g.add_argument('dump', nargs='?', help='RAM dump obtained from D2 g-code')
 
-     g.add_argument('--map', action='store_true', help='dump global memory map')
 
-     g.add_argument('--qdirstat', action='store_true',
 
-                    help='dump qdirstat-compatible size usage map')
 
-     args = ap.parse_args()
 
-     grefs = get_elf_globals(args.elf, expand_structs=not args.no_expand_structs)
 
-     grefs = list(sorted(grefs, key=lambda x: x.loc))
 
-     if args.map:
 
-         print_map(grefs)
 
-     elif args.qdirstat:
 
-         print_qdirstat(grefs)
 
-     else:
 
-         # fetch the memory data
 
-         dump = decode_dump(args.dump)
 
-         if dump is None:
 
-             return os.EX_DATAERR
 
-         # strip padding, if present
 
-         addr_start = dump.ranges[0][0]
 
-         addr_end = dump.ranges[-1][0]+dump.ranges[-1][1]
 
-         data = dump.data[addr_start:addr_end]
 
-         annotate_refs(grefs, addr_start, data,
 
-                       width=args.name_width,
 
-                       gaps=not args.no_gaps,
 
-                       overlaps=args.overlaps)
 
- if __name__ == '__main__':
 
-     exit(main())
 
 
  |