espcoredump.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #!/usr/bin/env python
  2. #
  3. # ESP32 core dump Utility
  4. import argparse
  5. import logging
  6. import os
  7. import subprocess
  8. import sys
  9. from shutil import copyfile
  10. from construct import GreedyRange, Int32ul, Struct
  11. from corefile import __version__, xtensa
  12. from corefile.elf import TASK_STATUS_CORRECT, ElfFile, ElfSegment, ESPCoreDumpElfFile, EspTaskStatus
  13. from corefile.gdb import EspGDB
  14. from corefile.loader import ESPCoreDumpFileLoader, ESPCoreDumpFlashLoader
  15. from pygdbmi.gdbcontroller import DEFAULT_GDB_TIMEOUT_SEC
  16. IDF_PATH = os.getenv('IDF_PATH')
  17. if not IDF_PATH:
  18. sys.stderr.write('IDF_PATH is not found! Set proper IDF_PATH in environment.\n')
  19. sys.exit(2)
  20. sys.path.insert(0, os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool'))
  21. try:
  22. import esptool
  23. except ImportError:
  24. sys.stderr.write('esptool is not found!\n')
  25. sys.exit(2)
  26. if os.name == 'nt':
  27. CLOSE_FDS = False
  28. else:
  29. CLOSE_FDS = True
  30. def load_aux_elf(elf_path): # type: (str) -> (ElfFile, str)
  31. """
  32. Loads auxiliary ELF file and composes GDB command to read its symbols.
  33. """
  34. elf = None
  35. sym_cmd = ''
  36. if os.path.exists(elf_path):
  37. elf = ElfFile(elf_path)
  38. for s in elf.sections:
  39. if s.name == '.text':
  40. sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr)
  41. return elf, sym_cmd
  42. def core_prepare():
  43. loader = None
  44. core_filename = None
  45. if not args.core:
  46. # Core file not specified, try to read core dump from flash.
  47. loader = ESPCoreDumpFlashLoader(args.off, port=args.port, baud=args.baud)
  48. elif args.core_format != 'elf':
  49. # Core file specified, but not yet in ELF format. Convert it from raw or base64 into ELF.
  50. loader = ESPCoreDumpFileLoader(args.core, args.core_format == 'b64')
  51. else:
  52. # Core file is already in the ELF format
  53. core_filename = args.core
  54. # Load/convert the core file
  55. if loader:
  56. loader.create_corefile(exe_name=args.prog)
  57. core_filename = loader.core_elf_file.name
  58. if args.save_core:
  59. # We got asked to save the core file, make a copy
  60. copyfile(loader.core_elf_file.name, args.save_core)
  61. return core_filename, loader
  62. def dbg_corefile():
  63. """
  64. Command to load core dump from file or flash and run GDB debug session with it
  65. """
  66. rom_elf, rom_sym_cmd = load_aux_elf(args.rom_elf)
  67. core_filename, loader = core_prepare()
  68. p = subprocess.Popen(bufsize=0,
  69. args=[args.gdb,
  70. '--nw', # ignore .gdbinit
  71. '--core=%s' % core_filename, # core file,
  72. '-ex', rom_sym_cmd,
  73. args.prog],
  74. stdin=None, stdout=None, stderr=None,
  75. close_fds=CLOSE_FDS)
  76. p.wait()
  77. print('Done!')
  78. def info_corefile():
  79. """
  80. Command to load core dump from file or flash and print it's data in user friendly form
  81. """
  82. core_filename, loader = core_prepare()
  83. exe_elf = ElfFile(args.prog)
  84. core_elf = ESPCoreDumpElfFile(core_filename)
  85. if exe_elf.e_machine != core_elf.e_machine:
  86. raise ValueError('The arch should be the same between core elf and exe elf')
  87. if core_elf.e_machine == ESPCoreDumpElfFile.EM_XTENSA:
  88. exception_registers_info = xtensa.print_exc_regs_info
  89. else:
  90. raise NotImplementedError
  91. extra_note = None
  92. task_info = []
  93. for seg in core_elf.note_segments:
  94. for note_sec in seg.note_secs:
  95. if note_sec.type == ESPCoreDumpElfFile.PT_EXTRA_INFO and 'EXTRA_INFO' in note_sec.name.decode('ascii'):
  96. extra_note = note_sec
  97. if note_sec.type == ESPCoreDumpElfFile.PT_TASK_INFO and 'TASK_INFO' in note_sec.name.decode('ascii'):
  98. task_info_struct = EspTaskStatus.parse(note_sec.desc)
  99. task_info.append(task_info_struct)
  100. print('===============================================================')
  101. print('==================== ESP32 CORE DUMP START ====================')
  102. rom_elf, rom_sym_cmd = load_aux_elf(args.rom_elf)
  103. gdb = EspGDB(args.gdb, [rom_sym_cmd], core_filename, args.prog, timeout_sec=args.gdb_timeout_sec)
  104. extra_info = None
  105. if extra_note:
  106. extra_info = Struct('regs' / GreedyRange(Int32ul)).parse(extra_note.desc).regs
  107. marker = extra_info[0]
  108. if marker == ESPCoreDumpElfFile.CURR_TASK_MARKER:
  109. print('\nCrashed task has been skipped.')
  110. else:
  111. task_name = gdb.get_freertos_task_name(marker)
  112. print("\nCrashed task handle: 0x%x, name: '%s', GDB name: 'process %d'" % (marker, task_name, marker))
  113. print('\n================== CURRENT THREAD REGISTERS ===================')
  114. if extra_note and extra_info:
  115. exception_registers_info(extra_info)
  116. else:
  117. print('Exception registers have not been found!')
  118. print(gdb.run_cmd('info registers'))
  119. print('\n==================== CURRENT THREAD STACK =====================')
  120. print(gdb.run_cmd('bt'))
  121. if task_info and task_info[0].task_flags != TASK_STATUS_CORRECT:
  122. print('The current crashed task is corrupted.')
  123. print('Task #%d info: flags, tcb, stack (%x, %x, %x).' % (task_info[0].task_index,
  124. task_info[0].task_flags,
  125. task_info[0].task_tcb_addr,
  126. task_info[0].task_stack_start))
  127. print('\n======================== THREADS INFO =========================')
  128. print(gdb.run_cmd('info threads'))
  129. # THREADS STACKS
  130. threads, _ = gdb.get_thread_info()
  131. for thr in threads:
  132. thr_id = int(thr['id'])
  133. tcb_addr = gdb.gdb2freertos_thread_id(thr['target-id'])
  134. task_index = int(thr_id) - 1
  135. task_name = gdb.get_freertos_task_name(tcb_addr)
  136. gdb.switch_thread(thr_id)
  137. print('\n==================== THREAD {} (TCB: 0x{:x}, name: \'{}\') ====================='
  138. .format(thr_id, tcb_addr, task_name))
  139. print(gdb.run_cmd('bt'))
  140. if task_info and task_info[task_index].task_flags != TASK_STATUS_CORRECT:
  141. print("The task '%s' is corrupted." % thr_id)
  142. print('Task #%d info: flags, tcb, stack (%x, %x, %x).' % (task_info[task_index].task_index,
  143. task_info[task_index].task_flags,
  144. task_info[task_index].task_tcb_addr,
  145. task_info[task_index].task_stack_start))
  146. print('\n\n======================= ALL MEMORY REGIONS ========================')
  147. print('Name Address Size Attrs')
  148. merged_segs = []
  149. core_segs = core_elf.load_segments
  150. for sec in exe_elf.sections:
  151. merged = False
  152. for seg in core_segs:
  153. if seg.addr <= sec.addr <= seg.addr + len(seg.data):
  154. # sec: |XXXXXXXXXX|
  155. # seg: |...XXX.............|
  156. seg_addr = seg.addr
  157. if seg.addr + len(seg.data) <= sec.addr + len(sec.data):
  158. # sec: |XXXXXXXXXX|
  159. # seg: |XXXXXXXXXXX...|
  160. # merged: |XXXXXXXXXXXXXX|
  161. seg_len = len(sec.data) + (sec.addr - seg.addr)
  162. else:
  163. # sec: |XXXXXXXXXX|
  164. # seg: |XXXXXXXXXXXXXXXXX|
  165. # merged: |XXXXXXXXXXXXXXXXX|
  166. seg_len = len(seg.data)
  167. merged_segs.append((sec.name, seg_addr, seg_len, sec.attr_str(), True))
  168. core_segs.remove(seg)
  169. merged = True
  170. elif sec.addr <= seg.addr <= sec.addr + len(sec.data):
  171. # sec: |XXXXXXXXXX|
  172. # seg: |...XXX.............|
  173. seg_addr = sec.addr
  174. if (seg.addr + len(seg.data)) >= (sec.addr + len(sec.data)):
  175. # sec: |XXXXXXXXXX|
  176. # seg: |..XXXXXXXXXXX|
  177. # merged: |XXXXXXXXXXXXX|
  178. seg_len = len(sec.data) + (seg.addr + len(seg.data)) - (sec.addr + len(sec.data))
  179. else:
  180. # sec: |XXXXXXXXXX|
  181. # seg: |XXXXXX|
  182. # merged: |XXXXXXXXXX|
  183. seg_len = len(sec.data)
  184. merged_segs.append((sec.name, seg_addr, seg_len, sec.attr_str(), True))
  185. core_segs.remove(seg)
  186. merged = True
  187. if not merged:
  188. merged_segs.append((sec.name, sec.addr, len(sec.data), sec.attr_str(), False))
  189. for ms in merged_segs:
  190. print('%s 0x%x 0x%x %s' % (ms[0], ms[1], ms[2], ms[3]))
  191. for cs in core_segs:
  192. # core dump exec segments are from ROM, other are belong to tasks (TCB or stack)
  193. if cs.flags & ElfSegment.PF_X:
  194. seg_name = 'rom.text'
  195. else:
  196. seg_name = 'tasks.data'
  197. print('.coredump.%s 0x%x 0x%x %s' % (seg_name, cs.addr, len(cs.data), cs.attr_str()))
  198. if args.print_mem:
  199. print('\n====================== CORE DUMP MEMORY CONTENTS ========================')
  200. for cs in core_elf.load_segments:
  201. # core dump exec segments are from ROM, other are belong to tasks (TCB or stack)
  202. if cs.flags & ElfSegment.PF_X:
  203. seg_name = 'rom.text'
  204. else:
  205. seg_name = 'tasks.data'
  206. print('.coredump.%s 0x%x 0x%x %s' % (seg_name, cs.addr, len(cs.data), cs.attr_str()))
  207. print(gdb.run_cmd('x/%dx 0x%x' % (len(cs.data) // 4, cs.addr)))
  208. print('\n===================== ESP32 CORE DUMP END =====================')
  209. print('===============================================================')
  210. del gdb
  211. print('Done!')
  212. if __name__ == '__main__':
  213. parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__)
  214. parser.add_argument('--port', '-p', default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT),
  215. help='Serial port device')
  216. parser.add_argument('--baud', '-b', type=int,
  217. default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD),
  218. help='Serial port baud rate used when flashing/reading')
  219. parser.add_argument('--gdb-timeout-sec', type=int, default=DEFAULT_GDB_TIMEOUT_SEC,
  220. help='Overwrite the default internal delay for gdb responses')
  221. common_args = argparse.ArgumentParser(add_help=False)
  222. common_args.add_argument('--debug', '-d', type=int, default=3,
  223. help='Log level (0..3)')
  224. common_args.add_argument('--gdb', '-g', default='xtensa-esp32-elf-gdb',
  225. help='Path to gdb')
  226. common_args.add_argument('--core', '-c',
  227. help='Path to core dump file (if skipped core dump will be read from flash)')
  228. common_args.add_argument('--core-format', '-t', choices=['b64', 'elf', 'raw'], default='elf',
  229. help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), '
  230. 'raw (raw) or base64-encoded (b64) binary')
  231. common_args.add_argument('--off', '-o', type=int,
  232. help='Offset of coredump partition in flash (type "make partition_table" to see).')
  233. common_args.add_argument('--save-core', '-s',
  234. help='Save core to file. Otherwise temporary core file will be deleted. '
  235. 'Does not work with "-c"', )
  236. common_args.add_argument('--rom-elf', '-r', default='esp32_rom.elf',
  237. help='Path to ROM ELF file.')
  238. common_args.add_argument('prog', help='Path to program\'s ELF binary')
  239. operations = parser.add_subparsers(dest='operation')
  240. operations.add_parser('dbg_corefile', parents=[common_args],
  241. help='Starts GDB debugging session with specified corefile')
  242. info_coredump = operations.add_parser('info_corefile', parents=[common_args],
  243. help='Print core dump info from file')
  244. info_coredump.add_argument('--print-mem', '-m', action='store_true',
  245. help='Print memory dump')
  246. args = parser.parse_args()
  247. if args.debug == 0:
  248. log_level = logging.CRITICAL
  249. elif args.debug == 1:
  250. log_level = logging.ERROR
  251. elif args.debug == 2:
  252. log_level = logging.WARNING
  253. elif args.debug == 3:
  254. log_level = logging.INFO
  255. else:
  256. log_level = logging.DEBUG
  257. logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
  258. print('espcoredump.py v%s' % __version__)
  259. if args.operation == 'info_corefile':
  260. info_corefile()
  261. elif args.operation == 'dbg_corefile':
  262. dbg_corefile()
  263. else:
  264. raise ValueError('Please specify action, should be info_corefile or dbg_corefile')