elf_mem_map 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. Member = namedtuple('Member', ['name', 'off', 'size'])
  12. def array_inc(loc, dim, idx=0):
  13. if idx == len(dim):
  14. return True
  15. loc[idx] += 1
  16. if loc[idx] == dim[idx]:
  17. loc[idx] = 0
  18. return array_inc(loc, dim, idx+1)
  19. return False
  20. def get_type_size(type_DIE):
  21. while True:
  22. if 'DW_AT_byte_size' in type_DIE.attributes:
  23. return type_DIE, type_DIE.attributes.get('DW_AT_byte_size').value
  24. if 'DW_AT_type' not in type_DIE.attributes:
  25. return None
  26. type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
  27. def get_type_arrsize(type_DIE):
  28. size = get_type_size(type_DIE)
  29. if size is None:
  30. return None
  31. byte_size = size[1]
  32. if size[0].tag != 'DW_TAG_pointer_type':
  33. array_DIE = get_type_def(type_DIE, 'DW_TAG_array_type')
  34. if array_DIE is not None:
  35. for range_DIE in array_DIE.iter_children():
  36. if range_DIE.tag == 'DW_TAG_subrange_type' and \
  37. 'DW_AT_upper_bound' in range_DIE.attributes:
  38. dim = range_DIE.attributes['DW_AT_upper_bound'].value + 1
  39. byte_size *= dim
  40. return byte_size
  41. def get_type_def(type_DIE, type_tag):
  42. while True:
  43. if type_DIE.tag == type_tag:
  44. return type_DIE
  45. if 'DW_AT_type' not in type_DIE.attributes:
  46. return None
  47. type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
  48. def get_FORM_block1(attr):
  49. if attr.form != 'DW_FORM_block1':
  50. return None
  51. if attr.value[0] == 3: # OP_addr
  52. return int.from_bytes(attr.value[1:], 'little')
  53. if attr.value[0] == 35: # OP_plus_uconst (ULEB128)
  54. v = 0
  55. s = 0
  56. for b in attr.value[1:]:
  57. v |= b
  58. s += 7
  59. if not b & 0x100:
  60. break
  61. return v
  62. return None
  63. def get_elf_globals(path, expand_structs, struct_gaps=True):
  64. fd = open(path, "rb")
  65. if fd is None:
  66. return
  67. elffile = elftools.elf.elffile.ELFFile(fd)
  68. if elffile is None or not elffile.has_dwarf_info():
  69. return
  70. # probably not needed, since we're decoding expressions manually
  71. elftools.dwarf.descriptions.set_global_machine_arch(elffile.get_machine_arch())
  72. dwarfinfo = elffile.get_dwarf_info()
  73. grefs = []
  74. for CU in dwarfinfo.iter_CUs():
  75. for DIE in CU.iter_DIEs():
  76. # handle only variable types
  77. if DIE.tag != 'DW_TAG_variable':
  78. continue
  79. if 'DW_AT_location' not in DIE.attributes:
  80. continue
  81. if 'DW_AT_name' not in DIE.attributes and \
  82. 'DW_AT_abstract_origin' not in DIE.attributes:
  83. continue
  84. # handle locations encoded directly as DW_OP_addr (leaf globals)
  85. loc = get_FORM_block1(DIE.attributes['DW_AT_location'])
  86. if loc is None or loc < SRAM_OFFSET or loc >= EEPROM_OFFSET:
  87. continue
  88. loc -= SRAM_OFFSET
  89. # variable name/type
  90. if 'DW_AT_name' not in DIE.attributes and \
  91. 'DW_AT_abstract_origin' in DIE.attributes:
  92. DIE = DIE.get_DIE_from_attribute('DW_AT_abstract_origin')
  93. if 'DW_AT_location' in DIE.attributes:
  94. # duplicate reference (handled directly), skip
  95. continue
  96. if 'DW_AT_name' not in DIE.attributes:
  97. continue
  98. if 'DW_AT_type' not in DIE.attributes:
  99. continue
  100. name = DIE.attributes['DW_AT_name'].value.decode('ascii')
  101. # get final storage size
  102. size = get_type_size(DIE)
  103. if size is None:
  104. continue
  105. byte_size = size[1]
  106. # fetch array dimensions (if known)
  107. array_dim = []
  108. array_DIE = get_type_def(DIE, 'DW_TAG_array_type')
  109. if array_DIE is not None:
  110. for range_DIE in array_DIE.iter_children():
  111. if range_DIE.tag == 'DW_TAG_subrange_type' and \
  112. 'DW_AT_upper_bound' in range_DIE.attributes:
  113. array_dim.append(range_DIE.attributes['DW_AT_upper_bound'].value + 1)
  114. # fetch structure members (one level only)
  115. members = []
  116. if expand_structs and size[0].tag != 'DW_TAG_pointer_type':
  117. struct_DIE = get_type_def(DIE, 'DW_TAG_structure_type')
  118. if struct_DIE is not None:
  119. for member_DIE in struct_DIE.iter_children():
  120. if member_DIE.tag == 'DW_TAG_member' and 'DW_AT_name' in member_DIE.attributes:
  121. m_name = member_DIE.attributes['DW_AT_name'].value.decode('ascii')
  122. m_off = get_FORM_block1(member_DIE.attributes['DW_AT_data_member_location'])
  123. m_size = get_type_arrsize(member_DIE)
  124. members.append(Member(m_name, m_off, m_size))
  125. if struct_gaps and len(members):
  126. # fill gaps in the middle
  127. members = list(sorted(members, key=lambda x: x.off))
  128. last_end = 0
  129. for member in members:
  130. if member.off > last_end:
  131. members.append(Member('*UNKNOWN*', last_end, member.off - last_end))
  132. last_end = member.off + member.size
  133. if struct_gaps and len(members):
  134. # fill gap at the end
  135. members = list(sorted(members, key=lambda x: x.off))
  136. last = members[-1]
  137. last_end = last.off + last.size
  138. if byte_size > last_end:
  139. members.append(Member('*UNKNOWN*', last_end, byte_size - last_end))
  140. def expand_members(entry, members):
  141. if len(members) == 0:
  142. grefs.append(entry)
  143. else:
  144. for member in members:
  145. grefs.append(Entry(entry.name + '.' + member.name,
  146. entry.loc + member.off, member.size))
  147. if len(array_dim) == 0 or (len(array_dim) == 1 and array_dim[0] == 1):
  148. # plain entry
  149. expand_members(Entry(name, loc, byte_size), members)
  150. elif len(array_dim) == 1 and byte_size == 1:
  151. # likely string, avoid expansion
  152. grefs.append(Entry(name + '[]', loc, array_dim[0]))
  153. else:
  154. # expand array entries
  155. array_pos = loc
  156. array_loc = [0] * len(array_dim)
  157. while True:
  158. # location index
  159. sfx = ''
  160. for d in range(len(array_dim)):
  161. sfx += '[{}]'.format(array_loc[d])
  162. expand_members(Entry(name + sfx, array_pos, byte_size), members)
  163. # advance
  164. array_pos += byte_size
  165. if array_inc(array_loc, array_dim):
  166. break
  167. return grefs
  168. def decode_dump(path):
  169. fd = open(path, 'r')
  170. if fd is None:
  171. return None
  172. buf_addr = None # starting address
  173. buf_data = None # data
  174. for line in fd:
  175. tokens = line.split(maxsplit=1)
  176. if len(tokens) == 0 or tokens[0] == 'ok':
  177. break
  178. elif len(tokens) < 2 or tokens[0] == 'D2':
  179. continue
  180. addr = int.from_bytes(bytes.fromhex(tokens[0]), 'big')
  181. data = bytes.fromhex(tokens[1])
  182. if buf_addr is None:
  183. buf_addr = addr
  184. buf_data = data
  185. else:
  186. # grow buffer as needed
  187. if addr < buf_addr:
  188. buf_data = FILL_BYTE * (buf_addr - addr)
  189. buf_addr = addr
  190. addr_end = addr + len(data)
  191. buf_end = buf_addr + len(buf_data)
  192. if addr_end > buf_end:
  193. buf_data += FILL_BYTE * (addr_end - buf_end)
  194. # replace new part
  195. rep_start = addr - buf_addr
  196. rep_end = rep_start + len(data)
  197. buf_data = buf_data[:rep_start] + data + buf_data[rep_end:]
  198. return (buf_addr, buf_data)
  199. def annotate_refs(grefs, addr, data, width=45, gaps=True):
  200. last_end = None
  201. for entry in grefs:
  202. if entry.loc < addr:
  203. continue
  204. if entry.loc + entry.size > addr + len(data):
  205. continue
  206. pos = entry.loc-addr
  207. end_pos = pos + entry.size
  208. buf = data[pos:end_pos]
  209. buf_repr = ''
  210. if len(buf) in [1, 2, 4]:
  211. # attempt to decode as integers
  212. buf_repr += ' I:' + str(int.from_bytes(buf, 'little')).rjust(10)
  213. if len(buf) in [4, 8]:
  214. # attempt to decode as floats
  215. typ = 'f' if len(buf) == 4 else 'd'
  216. buf_repr += ' F:' + '{:10.3f}'.format(unpack(typ, buf)[0])
  217. if gaps and last_end is not None and last_end < pos:
  218. # decode gaps
  219. gap_size = pos - last_end
  220. gap_buf = data[last_end:pos]
  221. print('{:04x} {} {:4} R:{}'.format(addr+last_end, "*UNKNOWN*".ljust(width),
  222. gap_size, gap_buf.hex()))
  223. print('{:04x} {} {:4}{} R:{}'.format(entry.loc, entry.name.ljust(width),
  224. entry.size, buf_repr, buf.hex()))
  225. last_end = end_pos
  226. def print_map(grefs):
  227. print('OFFSET\tSIZE\tNAME')
  228. for entry in grefs:
  229. print('{:x}\t{}\t{}'.format(entry.loc, entry.size, entry.name))
  230. def main():
  231. ap = argparse.ArgumentParser(description="""
  232. Generate a symbol table map starting directly from an ELF
  233. firmware with DWARF2 debugging information.
  234. When used along with a memory dump obtained from the D2 g-code,
  235. show the value of each symbol which is within the address range.
  236. """)
  237. ap.add_argument('elf', help='ELF file containing DWARF2 debugging information')
  238. ap.add_argument('--no-gaps', action='store_true',
  239. help='do not dump memory inbetween known symbols')
  240. ap.add_argument('--no-expand-structs', action='store_true',
  241. help='do not decode structure data')
  242. g = ap.add_mutually_exclusive_group(required=True)
  243. g.add_argument('dump', nargs='?', help='RAM dump obtained from D2 g-code')
  244. g.add_argument('--map', action='store_true', help='dump global memory map')
  245. args = ap.parse_args()
  246. grefs = get_elf_globals(args.elf, expand_structs=not args.no_expand_structs)
  247. grefs = list(sorted(grefs, key=lambda x: x.loc))
  248. if args.dump is None:
  249. print_map(grefs)
  250. else:
  251. addr, data = decode_dump(args.dump)
  252. annotate_refs(grefs, addr, data, gaps=not args.no_gaps)
  253. if __name__ == '__main__':
  254. exit(main())