elf_mem_map 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python3
  2. import argparse
  3. import elftools.elf.elffile
  4. import elftools.dwarf.descriptions
  5. from collections import namedtuple
  6. from struct import unpack
  7. SRAM_OFFSET = 0x800000
  8. EEPROM_OFFSET = 0x810000
  9. FILL_BYTE = b'\0'
  10. Entry = namedtuple('Entry', ['name', 'loc', 'size'])
  11. def array_inc(loc, dim, idx=0):
  12. if idx == len(dim):
  13. return True
  14. loc[idx] += 1
  15. if loc[idx] == dim[idx]:
  16. loc[idx] = 0
  17. return array_inc(loc, dim, idx+1)
  18. return False
  19. def get_elf_globals(path):
  20. fd = open(path, "rb")
  21. if fd is None:
  22. return
  23. elffile = elftools.elf.elffile.ELFFile(fd)
  24. if elffile is None or not elffile.has_dwarf_info():
  25. return
  26. # probably not needed, since we're decoding expressions manually
  27. elftools.dwarf.descriptions.set_global_machine_arch(elffile.get_machine_arch())
  28. dwarfinfo = elffile.get_dwarf_info()
  29. grefs = []
  30. for CU in dwarfinfo.iter_CUs():
  31. for DIE in CU.iter_DIEs():
  32. # handle only variable types
  33. if DIE.tag != 'DW_TAG_variable':
  34. continue
  35. if 'DW_AT_location' not in DIE.attributes:
  36. continue
  37. if 'DW_AT_name' not in DIE.attributes and \
  38. 'DW_AT_abstract_origin' not in DIE.attributes:
  39. continue
  40. # handle locations encoded directly as DW_OP_addr (leaf globals)
  41. at_loc = DIE.attributes['DW_AT_location']
  42. if at_loc.form != 'DW_FORM_block1' or at_loc.value[0] != 3:
  43. continue
  44. loc = (at_loc.value[1]) + (at_loc.value[2] << 8) \
  45. + (at_loc.value[3] << 16) + (at_loc.value[4] << 24)
  46. if loc < SRAM_OFFSET or loc >= EEPROM_OFFSET:
  47. continue
  48. loc -= SRAM_OFFSET
  49. # variable name/type
  50. if 'DW_AT_name' not in DIE.attributes and \
  51. 'DW_AT_abstract_origin' in DIE.attributes:
  52. DIE = DIE.get_DIE_from_attribute('DW_AT_abstract_origin')
  53. if 'DW_AT_location' in DIE.attributes:
  54. # duplicate reference (handled directly), skip
  55. continue
  56. if 'DW_AT_name' not in DIE.attributes:
  57. continue
  58. if 'DW_AT_type' not in DIE.attributes:
  59. continue
  60. name = DIE.attributes['DW_AT_name'].value.decode('ascii')
  61. # recurse on type to find the final storage definition
  62. type_DIE = DIE
  63. byte_size = None
  64. array_dim = []
  65. while True:
  66. if 'DW_AT_byte_size' in type_DIE.attributes:
  67. byte_size = type_DIE.attributes.get('DW_AT_byte_size')
  68. if 'DW_AT_type' not in type_DIE.attributes:
  69. break
  70. type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
  71. if type_DIE.tag == 'DW_TAG_array_type':
  72. # fetch array dimensions (if known)
  73. for range_DIE in type_DIE.iter_children():
  74. if range_DIE.tag == 'DW_TAG_subrange_type' and \
  75. 'DW_AT_upper_bound' in range_DIE.attributes:
  76. array_dim.append(range_DIE.attributes['DW_AT_upper_bound'].value + 1)
  77. if byte_size is None:
  78. continue
  79. size = byte_size.value
  80. if len(array_dim) == 0 or (len(array_dim) == 1 and array_dim[0] == 1):
  81. # plain entry
  82. grefs.append(Entry(name, loc, size))
  83. elif len(array_dim) == 1 and size == 1:
  84. # likely string, avoid expansion
  85. grefs.append(Entry('{}[]'.format(name), loc, array_dim[0]))
  86. else:
  87. # expand array entries
  88. array_pos = loc
  89. array_loc = [0] * len(array_dim)
  90. while True:
  91. # location index
  92. sfx = ''
  93. for d in range(len(array_dim)):
  94. sfx += '[{}]'.format(array_loc[d])
  95. grefs.append(Entry(name + sfx, array_pos, size))
  96. # advance
  97. array_pos += size
  98. if array_inc(array_loc, array_dim):
  99. break
  100. return grefs
  101. def decode_dump(path):
  102. fd = open(path, 'r')
  103. if fd is None:
  104. return None
  105. buf_addr = None # starting address
  106. buf_data = None # data
  107. for line in fd:
  108. tokens = line.split(maxsplit=1)
  109. if len(tokens) == 0 or tokens[0] == 'ok':
  110. break
  111. elif len(tokens) < 2 or tokens[0] == 'D2':
  112. continue
  113. addr = int.from_bytes(bytes.fromhex(tokens[0]), 'big')
  114. data = bytes.fromhex(tokens[1])
  115. if buf_addr is None:
  116. buf_addr = addr
  117. buf_data = data
  118. else:
  119. # grow buffer as needed
  120. if addr < buf_addr:
  121. buf_data = FILL_BYTE * (buf_addr - addr)
  122. buf_addr = addr
  123. addr_end = addr + len(data)
  124. buf_end = buf_addr + len(buf_data)
  125. if addr_end > buf_end:
  126. buf_data += FILL_BYTE * (addr_end - buf_end)
  127. # replace new part
  128. rep_start = addr - buf_addr
  129. rep_end = rep_start + len(data)
  130. buf_data = buf_data[:rep_start] + data + buf_data[rep_end:]
  131. return (buf_addr, buf_data)
  132. def annotate_refs(grefs, addr, data, width=45, gaps=True):
  133. last_end = None
  134. for entry in grefs:
  135. if entry.loc < addr:
  136. continue
  137. if entry.loc + entry.size > addr + len(data):
  138. continue
  139. pos = entry.loc-addr
  140. end_pos = pos + entry.size
  141. buf = data[pos:end_pos]
  142. buf_repr = ''
  143. if len(buf) in [1, 2, 4]:
  144. # attempt to decode as integers
  145. buf_repr += ' I:' + str(int.from_bytes(buf, 'big')).rjust(10)
  146. if len(buf) in [4, 8]:
  147. # attempt to decode as floats
  148. typ = 'f' if len(buf) == 4 else 'd'
  149. buf_repr += ' F:' + '{:10.3f}'.format(unpack(typ, buf)[0])
  150. if gaps and last_end is not None and last_end < pos:
  151. # decode gaps
  152. gap_size = pos - last_end
  153. gap_buf = data[last_end:pos]
  154. print('{:04x} {} {:4} R:{}'.format(addr+last_end, "*UNKNOWN*".ljust(width),
  155. gap_size, gap_buf.hex()))
  156. print('{:04x} {} {:4}{} R:{}'.format(entry.loc, entry.name.ljust(width),
  157. entry.size, buf_repr, buf.hex()))
  158. last_end = end_pos
  159. def print_map(grefs):
  160. print('OFFSET\tSIZE\tNAME')
  161. for entry in grefs:
  162. print('{:x}\t{}\t{}'.format(entry.loc, entry.size, entry.name))
  163. def main():
  164. ap = argparse.ArgumentParser(description="""
  165. Generate a symbol table map starting directly from an ELF
  166. firmware with DWARF2 debugging information.
  167. When used along with a memory dump obtained from the D2 g-code,
  168. show the value of each symbol which is within the address range.
  169. """)
  170. ap.add_argument('elf', help='ELF file containing DWARF2 debugging information')
  171. g = ap.add_mutually_exclusive_group(required=True)
  172. g.add_argument('dump', nargs='?', help='RAM dump obtained from D2 g-code')
  173. g.add_argument('--map', action='store_true', help='dump global memory map')
  174. args = ap.parse_args()
  175. grefs = get_elf_globals(args.elf)
  176. grefs = list(sorted(grefs, key=lambda x: x.loc))
  177. if args.dump is None:
  178. print_map(grefs)
  179. else:
  180. addr, data = decode_dump(args.dump)
  181. annotate_refs(grefs, addr, data)
  182. if __name__ == '__main__':
  183. exit(main())