espcoredump.py 15 KB

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