elf_mem_map 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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. import os
  8. import re
  9. from lib.dump import decode_dump
  10. from lib.avr import *
  11. Entry = namedtuple('Entry', ['name', 'loc', 'size', 'declpos'])
  12. Member = namedtuple('Member', ['name', 'off', 'size'])
  13. def array_inc(loc, dim, idx=0):
  14. if idx == len(dim):
  15. return True
  16. loc[idx] += 1
  17. if loc[idx] == dim[idx]:
  18. loc[idx] = 0
  19. return array_inc(loc, dim, idx+1)
  20. return False
  21. def get_type_size(type_DIE):
  22. while True:
  23. if 'DW_AT_byte_size' in type_DIE.attributes:
  24. return type_DIE, type_DIE.attributes.get('DW_AT_byte_size').value
  25. if 'DW_AT_type' not in type_DIE.attributes:
  26. return None
  27. type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
  28. def get_type_arrsize(type_DIE):
  29. size = get_type_size(type_DIE)
  30. if size is None:
  31. return None
  32. byte_size = size[1]
  33. if size[0].tag != 'DW_TAG_pointer_type':
  34. array_DIE = get_type_def(type_DIE, 'DW_TAG_array_type')
  35. if array_DIE is not None:
  36. for range_DIE in array_DIE.iter_children():
  37. if range_DIE.tag == 'DW_TAG_subrange_type' and \
  38. 'DW_AT_upper_bound' in range_DIE.attributes:
  39. dim = range_DIE.attributes['DW_AT_upper_bound'].value + 1
  40. byte_size *= dim
  41. return byte_size
  42. def get_type_def(type_DIE, type_tag):
  43. while True:
  44. if type_DIE.tag == type_tag:
  45. return type_DIE
  46. if 'DW_AT_type' not in type_DIE.attributes:
  47. return None
  48. type_DIE = type_DIE.get_DIE_from_attribute('DW_AT_type')
  49. def get_FORM_block1(attr):
  50. if attr.form != 'DW_FORM_block1':
  51. return None
  52. if attr.value[0] == 3: # OP_addr
  53. return int.from_bytes(attr.value[1:], 'little')
  54. if attr.value[0] == 35: # OP_plus_uconst (ULEB128)
  55. v = 0
  56. s = 0
  57. for b in attr.value[1:]:
  58. v |= (b & 0x7f) << s
  59. if b & 0x80 == 0:
  60. break
  61. s += 7
  62. return v
  63. return None
  64. def get_array_dims(DIE):
  65. array_DIE = get_type_def(DIE, 'DW_TAG_array_type')
  66. if array_DIE is None:
  67. return []
  68. array_dim = []
  69. for range_DIE in array_DIE.iter_children():
  70. if range_DIE.tag == 'DW_TAG_subrange_type' and \
  71. 'DW_AT_upper_bound' in range_DIE.attributes:
  72. array_dim.append(range_DIE.attributes['DW_AT_upper_bound'].value + 1)
  73. return array_dim
  74. def get_struct_members(DIE, entry, expand_structs, struct_gaps):
  75. struct_DIE = get_type_def(DIE, 'DW_TAG_structure_type')
  76. if struct_DIE is None:
  77. return []
  78. members = []
  79. for member_DIE in struct_DIE.iter_children():
  80. if member_DIE.tag == 'DW_TAG_member' and 'DW_AT_name' in member_DIE.attributes:
  81. m_name = member_DIE.attributes['DW_AT_name'].value.decode('ascii')
  82. m_off = get_FORM_block1(member_DIE.attributes['DW_AT_data_member_location'])
  83. m_byte_size = get_type_size(member_DIE)[1]
  84. # still expand member arrays
  85. m_array_dim = get_array_dims(member_DIE)
  86. if m_byte_size == 1 and len(m_array_dim) > 1:
  87. # likely string, remove one dimension
  88. m_byte_size *= m_array_dim.pop()
  89. if len(m_array_dim) == 0 or (len(m_array_dim) == 1 and m_array_dim[0] == 1):
  90. # plain entry
  91. members.append(Member(m_name, m_off, m_byte_size))
  92. elif len(m_array_dim) == 1 and m_byte_size == 1:
  93. # likely string, avoid expansion
  94. members.append(Member(m_name + '[]', m_off, m_array_dim[0]))
  95. else:
  96. # expand array entries
  97. m_array_pos = m_off
  98. m_array_loc = [0] * len(m_array_dim)
  99. while True:
  100. # location index
  101. sfx = ''
  102. for d in range(len(m_array_dim)):
  103. sfx += '[{}]'.format(str(m_array_loc[d]).rjust(len(str(m_array_dim[d]-1)), '0'))
  104. members.append(Member(m_name + sfx, m_array_pos, m_byte_size))
  105. # advance
  106. if array_inc(m_array_loc, m_array_dim):
  107. break
  108. m_array_pos += m_byte_size
  109. if struct_gaps and len(members):
  110. # fill gaps in the middle
  111. members = list(sorted(members, key=lambda x: x.off))
  112. last_end = 0
  113. for n in range(len(members)):
  114. member = members[n]
  115. if member.off > last_end:
  116. members.append(Member('*UNKNOWN*', last_end, member.off - last_end))
  117. last_end = member.off + member.size
  118. if struct_gaps and len(members):
  119. # fill gap at the end
  120. members = list(sorted(members, key=lambda x: x.off))
  121. last = members[-1]
  122. last_end = last.off + last.size
  123. if entry.size > last_end:
  124. members.append(Member('*UNKNOWN*', last_end, entry.size - last_end))
  125. return members
  126. def get_elf_globals(path, expand_structs, struct_gaps=True):
  127. fd = open(path, "rb")
  128. if fd is None:
  129. return
  130. elffile = elftools.elf.elffile.ELFFile(fd)
  131. if elffile is None or not elffile.has_dwarf_info():
  132. return
  133. # probably not needed, since we're decoding expressions manually
  134. elftools.dwarf.descriptions.set_global_machine_arch(elffile.get_machine_arch())
  135. dwarfinfo = elffile.get_dwarf_info()
  136. grefs = []
  137. for CU in dwarfinfo.iter_CUs():
  138. file_entries = dwarfinfo.line_program_for_CU(CU).header["file_entry"]
  139. for DIE in CU.iter_DIEs():
  140. # handle only variable types
  141. if DIE.tag != 'DW_TAG_variable':
  142. continue
  143. if 'DW_AT_location' not in DIE.attributes:
  144. continue
  145. if 'DW_AT_name' not in DIE.attributes and \
  146. 'DW_AT_abstract_origin' not in DIE.attributes:
  147. continue
  148. # handle locations encoded directly as DW_OP_addr (leaf globals)
  149. loc = get_FORM_block1(DIE.attributes['DW_AT_location'])
  150. if loc is None or loc < SRAM_OFFSET or loc >= EEPROM_OFFSET:
  151. continue
  152. loc -= SRAM_OFFSET
  153. # variable name/type
  154. if 'DW_AT_name' not in DIE.attributes and \
  155. 'DW_AT_abstract_origin' in DIE.attributes:
  156. DIE = DIE.get_DIE_from_attribute('DW_AT_abstract_origin')
  157. if 'DW_AT_location' in DIE.attributes:
  158. # duplicate reference (handled directly), skip
  159. continue
  160. if 'DW_AT_name' not in DIE.attributes:
  161. continue
  162. if 'DW_AT_type' not in DIE.attributes:
  163. continue
  164. name = DIE.attributes['DW_AT_name'].value.decode('ascii')
  165. # get final storage size
  166. size = get_type_size(DIE)
  167. if size is None:
  168. continue
  169. byte_size = size[1]
  170. # location of main definition
  171. declpos = ''
  172. if 'DW_AT_decl_file' in DIE.attributes and \
  173. 'DW_AT_decl_line' in DIE.attributes:
  174. line = DIE.attributes['DW_AT_decl_line'].value
  175. fname = DIE.attributes['DW_AT_decl_file'].value
  176. if fname and fname - 1 < len(file_entries):
  177. fname = file_entries[fname-1].name.decode('ascii')
  178. declpos = '{}:{}'.format(fname, line)
  179. # fetch array dimensions (if known)
  180. array_dim = get_array_dims(DIE)
  181. # fetch structure members (one level only)
  182. entry = Entry(name, loc, byte_size, declpos)
  183. if not expand_structs or size[0].tag == 'DW_TAG_pointer_type':
  184. members = []
  185. else:
  186. members = get_struct_members(DIE, entry, expand_structs, struct_gaps)
  187. def expand_members(entry, members):
  188. if len(members) == 0:
  189. grefs.append(entry)
  190. else:
  191. for member in members:
  192. grefs.append(Entry(entry.name + '.' + member.name,
  193. entry.loc + member.off, member.size,
  194. entry.declpos))
  195. if byte_size == 1 and len(array_dim) > 1:
  196. # likely string, remove one dimension
  197. byte_size *= array_dim.pop()
  198. if len(array_dim) == 0 or (len(array_dim) == 1 and array_dim[0] == 1):
  199. # plain entry
  200. expand_members(entry, members)
  201. elif len(array_dim) == 1 and byte_size == 1:
  202. # likely string, avoid expansion
  203. grefs.append(Entry(entry.name + '[]', entry.loc,
  204. array_dim[0], entry.declpos))
  205. else:
  206. # expand array entries
  207. array_pos = loc
  208. array_loc = [0] * len(array_dim)
  209. while True:
  210. # location index
  211. sfx = ''
  212. for d in range(len(array_dim)):
  213. sfx += '[{}]'.format(str(array_loc[d]).rjust(len(str(array_dim[d]-1)), '0'))
  214. expand_members(Entry(entry.name + sfx, array_pos,
  215. byte_size, entry.declpos), members)
  216. # advance
  217. if array_inc(array_loc, array_dim):
  218. break
  219. array_pos += byte_size
  220. return grefs
  221. def annotate_refs(grefs, addr, data, width, gaps=True, overlaps=True):
  222. last_end = None
  223. for entry in grefs:
  224. if entry.loc < addr:
  225. continue
  226. if entry.loc + entry.size > addr + len(data):
  227. continue
  228. pos = entry.loc-addr
  229. end_pos = pos + entry.size
  230. buf = data[pos:end_pos]
  231. buf_repr = ''
  232. if len(buf) in [1, 2, 4]:
  233. # attempt to decode as integers
  234. buf_repr += ' I:' + str(int.from_bytes(buf, 'little')).rjust(10)
  235. if len(buf) in [4, 8]:
  236. # attempt to decode as floats
  237. typ = 'f' if len(buf) == 4 else 'd'
  238. buf_repr += ' F:' + '{:10.3f}'.format(unpack(typ, buf)[0])
  239. if last_end is not None:
  240. if gaps and last_end < pos:
  241. # decode gaps
  242. gap_size = pos - last_end
  243. gap_buf = data[last_end:pos]
  244. print('{:04x} {} {:4} R:{}'.format(addr+last_end, "*UNKNOWN*".ljust(width),
  245. gap_size, gap_buf.hex()))
  246. if overlaps and last_end > pos + 1:
  247. gap_size = pos - last_end
  248. print('{:04x} {} {:4}'.format(addr+last_end, "*OVERLAP*".ljust(width), gap_size))
  249. print('{:04x} {} {:4}{} R:{}'.format(entry.loc, entry.name.ljust(width),
  250. entry.size, buf_repr, buf.hex()))
  251. last_end = end_pos
  252. def print_map(grefs):
  253. print('OFFSET\tSIZE\tNAME\tDECLPOS')
  254. for entry in grefs:
  255. print('{:x}\t{}\t{}\t{}'.format(entry.loc, entry.size, entry.name, entry.declpos))
  256. def print_qdirstat(grefs):
  257. print('[qdirstat 1.0 cache file]')
  258. entries = {}
  259. for entry in grefs:
  260. # do not output registers when looking at space usage
  261. if entry.loc < SRAM_START:
  262. continue
  263. paths = list(filter(None, re.split(r'[\[\].]', entry.name)))
  264. base = entries
  265. for i in range(len(paths) - 1):
  266. name = paths[i]
  267. if name not in base:
  268. base[name] = {}
  269. base = base[name]
  270. name = paths[-1]
  271. if name in base:
  272. name = '{}_{:x}'.format(entry.name, entry.loc)
  273. base[name] = entry.size
  274. def walker(root, prefix):
  275. files = []
  276. dirs = []
  277. for name, entries in root.items():
  278. if type(entries) == int:
  279. files.append([name, entries])
  280. else:
  281. dirs.append([name, entries])
  282. # print files
  283. print('D\t{}\t{}\t0x0'.format(prefix, 0))
  284. for name, size in files:
  285. print('F\t{}\t{}\t0x0'.format(name, size))
  286. # recurse directories
  287. for name, entries in dirs:
  288. walker(entries, prefix + '/' + name)
  289. walker(entries, '/')
  290. def main():
  291. ap = argparse.ArgumentParser(description="""
  292. Generate a symbol table map starting directly from an ELF
  293. firmware with DWARF3 debugging information.
  294. When used along with a memory dump obtained from the D2/D21/D23 g-code,
  295. show the value of each symbol which is within the address range.
  296. """)
  297. ap.add_argument('elf', help='ELF file containing DWARF debugging information')
  298. ap.add_argument('--no-gaps', action='store_true',
  299. help='do not dump memory inbetween known symbols')
  300. ap.add_argument('--no-expand-structs', action='store_true',
  301. help='do not decode structure data')
  302. ap.add_argument('--overlaps', action='store_true',
  303. help='annotate overlaps greater than 1 byte')
  304. ap.add_argument('--name-width', type=int, default=50,
  305. help='set name column width')
  306. g = ap.add_mutually_exclusive_group(required=True)
  307. g.add_argument('dump', nargs='?', help='RAM dump obtained from D2 g-code')
  308. g.add_argument('--map', action='store_true', help='dump global memory map')
  309. g.add_argument('--qdirstat', action='store_true',
  310. help='dump qdirstat-compatible size usage map')
  311. args = ap.parse_args()
  312. grefs = get_elf_globals(args.elf, expand_structs=not args.no_expand_structs)
  313. grefs = list(sorted(grefs, key=lambda x: x.loc))
  314. if args.map:
  315. print_map(grefs)
  316. elif args.qdirstat:
  317. print_qdirstat(grefs)
  318. else:
  319. # fetch the memory data
  320. dump = decode_dump(args.dump)
  321. if dump is None:
  322. return os.EX_DATAERR
  323. # strip padding, if present
  324. addr_start = dump.ranges[0][0]
  325. addr_end = dump.ranges[-1][0]+dump.ranges[-1][1]
  326. data = dump.data[addr_start:addr_end]
  327. annotate_refs(grefs, addr_start, data,
  328. width=args.name_width,
  329. gaps=not args.no_gaps,
  330. overlaps=args.overlaps)
  331. if __name__ == '__main__':
  332. exit(main())