#!/usr/bin/env python # # ESP32 core dump Utility from __future__ import print_function from __future__ import unicode_literals from __future__ import division import sys try: from builtins import zip from builtins import str from builtins import range from past.utils import old_div from builtins import object except ImportError: print('Import has failed probably because of the missing "future" package. Please install all the packages for ' 'interpreter {} from the $IDF_PATH/requirements.txt file.'.format(sys.executable)) sys.exit(1) import os import argparse import subprocess import tempfile import struct import errno import base64 import binascii import logging idf_path = os.getenv('IDF_PATH') if idf_path: sys.path.insert(0, os.path.join(idf_path, 'components', 'esptool_py', 'esptool')) try: import esptool except ImportError: print("Esptool is not found! Set proper $IDF_PATH in environment.") sys.exit(2) __version__ = "0.3-dev" if os.name == 'nt': CLOSE_FDS = False else: CLOSE_FDS = True class ESPCoreDumpError(RuntimeError): """Core dump runtime error class """ def __init__(self, message): """Constructor for core dump error """ super(ESPCoreDumpError, self).__init__(message) class BinStruct(object): """Binary structure representation Subclasses must specify actual structure layout using 'fields' and 'format' members. For example, the following subclass represents structure with two fields: f1 of size 2 bytes and 4 bytes f2. Little endian. class SomeStruct(BinStruct): fields = ("f1", "f2") format = " 0: self._read_sections(f, shoff, shstrndx) if phnum > 0: self._read_program_segments(f, phoff, phentsize, phnum) def _read_sections(self, f, section_header_offs, shstrndx): """Reads core dump sections from ELF file """ f.seek(section_header_offs) section_header = f.read() LEN_SEC_HEADER = 0x28 if len(section_header) == 0: raise ESPCoreDumpError("No section header found at offset %04x in ELF file." % section_header_offs) if len(section_header) % LEN_SEC_HEADER != 0: logging.warning('Unexpected ELF section header length %04x is not mod-%02x' % (len(section_header),LEN_SEC_HEADER)) # walk through the section header and extract all sections section_header_offsets = range(0, len(section_header), LEN_SEC_HEADER) def read_section_header(offs): name_offs,sec_type,flags,lma,sec_offs,size = struct.unpack_from("= ps.addr and addr < (ps.addr + seg_len): raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len): raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) # append self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags)) def dump(self, f): """Write core dump contents to file """ # TODO: currently dumps only program segments. # dumping sections is not supported yet # write ELF header ehdr = Elf32FileHeader() ehdr.e_type = self.e_type ehdr.e_machine = self.e_machine ehdr.e_entry = 0 ehdr.e_phoff = ehdr.sizeof() ehdr.e_shoff = 0 ehdr.e_flags = 0 ehdr.e_phentsize = Elf32ProgramHeader().sizeof() ehdr.e_phnum = len(self.program_segments) ehdr.e_shentsize = 0 ehdr.e_shnum = 0 ehdr.e_shstrndx = self.SHN_UNDEF f.write(ehdr.dump()) # write program header table cur_off = ehdr.e_ehsize + ehdr.e_phnum * ehdr.e_phentsize for i in range(len(self.program_segments)): phdr = Elf32ProgramHeader() phdr.p_type = self.program_segments[i].type phdr.p_offset = cur_off phdr.p_vaddr = self.program_segments[i].addr phdr.p_paddr = phdr.p_vaddr # TODO phdr.p_filesz = len(self.program_segments[i].data) phdr.p_memsz = phdr.p_filesz # TODO phdr.p_flags = self.program_segments[i].flags phdr.p_align = 0 # TODO f.write(phdr.dump()) cur_off += phdr.p_filesz # write program segments for i in range(len(self.program_segments)): f.write(self.program_segments[i].data) class ESPCoreDumpLoaderError(ESPCoreDumpError): """Core dump loader error class """ def __init__(self, message): """Constructor for core dump loader error """ super(ESPCoreDumpLoaderError, self).__init__(message) class ESPCoreDumpLoader(object): """Core dump loader base class """ ESP32_COREDUMP_VESION = 1 ESP32_COREDUMP_HDR_FMT = '<4L' ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) ESP32_COREDUMP_TSK_HDR_FMT = '<3L' ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT) def __init__(self): """Base constructor for core dump loader """ self.fcore = None def _get_registers_from_stack(self, data, grows_down): """Returns list of registers (in GDB format) from xtensa stack frame """ # from "gdb/xtensa-tdep.h" # typedef struct # { # 0 xtensa_elf_greg_t pc; # 1 xtensa_elf_greg_t ps; # 2 xtensa_elf_greg_t lbeg; # 3 xtensa_elf_greg_t lend; # 4 xtensa_elf_greg_t lcount; # 5 xtensa_elf_greg_t sar; # 6 xtensa_elf_greg_t windowstart; # 7 xtensa_elf_greg_t windowbase; # 8..63 xtensa_elf_greg_t reserved[8+48]; # 64 xtensa_elf_greg_t ar[64]; # } xtensa_elf_gregset_t; REG_PC_IDX = 0 REG_PS_IDX = 1 REG_LB_IDX = 2 REG_LE_IDX = 3 REG_LC_IDX = 4 REG_SAR_IDX = 5 # REG_WS_IDX = 6 # REG_WB_IDX = 7 REG_AR_START_IDX = 64 # REG_AR_NUM = 64 # FIXME: acc to xtensa_elf_gregset_t number of regs must be 128, # but gdb complanis when it less then 129 REG_NUM = 129 # XT_SOL_EXIT = 0 XT_SOL_PC = 1 XT_SOL_PS = 2 # XT_SOL_NEXT = 3 XT_SOL_AR_START = 4 XT_SOL_AR_NUM = 4 # XT_SOL_FRMSZ = 8 XT_STK_EXIT = 0 XT_STK_PC = 1 XT_STK_PS = 2 XT_STK_AR_START = 3 XT_STK_AR_NUM = 16 XT_STK_SAR = 19 # XT_STK_EXCCAUSE = 20 # XT_STK_EXCVADDR = 21 XT_STK_LBEG = 22 XT_STK_LEND = 23 XT_STK_LCOUNT = 24 XT_STK_FRMSZ = 25 regs = [0] * REG_NUM # TODO: support for growing up stacks if not grows_down: raise ESPCoreDumpLoaderError("Growing up stacks are not supported for now!") ex_struct = "<%dL" % XT_STK_FRMSZ if len(data) < struct.calcsize(ex_struct): raise ESPCoreDumpLoaderError("Too small stack to keep frame: %d bytes!" % len(data)) stack = struct.unpack(ex_struct, data[:struct.calcsize(ex_struct)]) # Stack frame type indicator is always the first item rc = stack[XT_STK_EXIT] if rc != 0: regs[REG_PC_IDX] = stack[XT_STK_PC] regs[REG_PS_IDX] = stack[XT_STK_PS] for i in range(XT_STK_AR_NUM): regs[REG_AR_START_IDX + i] = stack[XT_STK_AR_START + i] regs[REG_SAR_IDX] = stack[XT_STK_SAR] regs[REG_LB_IDX] = stack[XT_STK_LBEG] regs[REG_LE_IDX] = stack[XT_STK_LEND] regs[REG_LC_IDX] = stack[XT_STK_LCOUNT] # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set # and GDB can not unwind callstack properly (it implies not windowed call0) if regs[REG_PS_IDX] & (1 << 5): regs[REG_PS_IDX] &= ~(1 << 4) else: regs[REG_PC_IDX] = stack[XT_SOL_PC] regs[REG_PS_IDX] = stack[XT_SOL_PS] for i in range(XT_SOL_AR_NUM): regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i] # nxt = stack[XT_SOL_NEXT] # TODO: remove magic hack with saved PC to get proper value regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000) if regs[REG_PC_IDX] & 0x80000000: regs[REG_PC_IDX] = (regs[REG_PC_IDX] & 0x3fffffff) | 0x40000000 if regs[REG_AR_START_IDX + 0] & 0x80000000: regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000 return regs def remove_tmp_file(self, fname): """Silently removes temporary file """ try: os.remove(fname) except OSError as e: if e.errno != errno.ENOENT: logging.warning("Failed to remove temp file '%s' (%d)!" % (fname, e.errno)) def cleanup(self): """Cleans up loader resources """ if self.fcore: self.fcore.close() if self.fcore_name: self.remove_tmp_file(self.fcore_name) def create_corefile(self, core_fname=None, off=0, rom_elf=None): """Creates core dump ELF file """ core_off = off data = self.read_data(core_off, self.ESP32_COREDUMP_HDR_SZ) tot_len,coredump_ver,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_HDR_FMT, data) if coredump_ver > self.ESP32_COREDUMP_VESION: raise ESPCoreDumpLoaderError("Core dump version '%d' is not supported! Should be up to '%d'." % (coredump_ver, self.ESP32_COREDUMP_VESION)) tcbsz_aligned = tcbsz if tcbsz_aligned % 4: tcbsz_aligned = 4 * (old_div(tcbsz_aligned,4) + 1) core_off += self.ESP32_COREDUMP_HDR_SZ core_elf = ESPCoreDumpElfFile() notes = b'' for i in range(task_num): data = self.read_data(core_off, self.ESP32_COREDUMP_TSK_HDR_SZ) tcb_addr,stack_top,stack_end = struct.unpack_from(self.ESP32_COREDUMP_TSK_HDR_FMT, data) if stack_end > stack_top: stack_len = stack_end - stack_top stack_base = stack_top else: stack_len = stack_top - stack_end stack_base = stack_end stack_len_aligned = stack_len if stack_len_aligned % 4: stack_len_aligned = 4 * (old_div(stack_len_aligned,4) + 1) core_off += self.ESP32_COREDUMP_TSK_HDR_SZ logging.info("Read TCB %d bytes @ 0x%x" % (tcbsz_aligned, tcb_addr)) data = self.read_data(core_off, tcbsz_aligned) try: if tcbsz != tcbsz_aligned: core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) else: core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) except ESPCoreDumpError as e: logging.warning("Skip TCB %d bytes @ 0x%x. (Reason: %s)" % (tcbsz_aligned, tcb_addr, e)) core_off += tcbsz_aligned logging.info("Read stack %d bytes @ 0x%x" % (stack_len_aligned, stack_base)) data = self.read_data(core_off, stack_len_aligned) if stack_len != stack_len_aligned: data = data[:stack_len - stack_len_aligned] try: core_elf.add_program_segment(stack_base, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) except ESPCoreDumpError as e: logging.warning("Skip task's (%x) stack %d bytes @ 0x%x. (Reason: %s)" % (tcb_addr, stack_len_aligned, stack_base, e)) core_off += stack_len_aligned try: task_regs = self._get_registers_from_stack(data, stack_end > stack_top) except Exception as e: print(e) return None prstatus = XtensaPrStatus() prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task prstatus.pr_pid = i # TODO: use pid assigned by OS note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump() notes += note # add notes try: core_elf.add_program_segment(0, notes, ESPCoreDumpElfFile.PT_NOTE, 0) except ESPCoreDumpError as e: logging.warning("Skip NOTES segment %d bytes @ 0x%x. (Reason: %s)" % (len(notes), 0, e)) # add ROM text sections if rom_elf: for ps in rom_elf.program_segments: if ps.flags & ESPCoreDumpSegment.PF_X: try: core_elf.add_program_segment(ps.addr, ps.data, ESPCoreDumpElfFile.PT_LOAD, ps.flags) except ESPCoreDumpError as e: logging.warning("Skip ROM segment %d bytes @ 0x%x. (Reason: %s)" % (len(ps.data), ps.addr, e)) core_elf.e_type = ESPCoreDumpElfFile.ET_CORE core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA if core_fname: fce = open(core_fname, 'wb') else: fhnd,core_fname = tempfile.mkstemp() fce = os.fdopen(fhnd, 'wb') core_elf.dump(fce) fce.close() return core_fname def read_data(self, off, sz): """Reads data from raw core dump got from flash or UART """ self.fcore.seek(off) data = self.fcore.read(sz) return data class ESPCoreDumpFileLoader(ESPCoreDumpLoader): """Core dump file loader class """ def __init__(self, path, b64=False): """Constructor for core dump file loader """ super(ESPCoreDumpFileLoader, self).__init__() self.fcore = self._load_coredump(path, b64) def _load_coredump(self, path, b64): """Loads core dump from (raw binary or base64-encoded) file """ self.fcore_name = None if b64: fhnd,self.fcore_name = tempfile.mkstemp() fcore = os.fdopen(fhnd, 'wb') fb64 = open(path, 'rb') try: while True: line = fb64.readline() if len(line) == 0: break data = base64.standard_b64decode(line.rstrip(b'\r\n')) fcore.write(data) fcore.close() fcore = open(self.fcore_name, 'rb') except Exception as e: if self.fcore_name: self.remove_tmp_file(self.fcore_name) raise e finally: fb64.close() else: fcore = open(path, 'rb') return fcore class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): """Core dump flash loader class """ ESP32_COREDUMP_FLASH_CRC_FMT = ' sl: self.result_str = ln[sl:] if self.result_str.startswith(','): self.result_str = self.result_str[1:] else: logging.error("Invalid result format: '%s'" % ln) else: self.result_str = '' return True return False def execute(self, ln): """Executes GDB/MI result handler function """ GDBMIOutRecordHandler.execute(self, ln) if self._parse_rc(ln, self.RC_DONE): return if self._parse_rc(ln, self.RC_RUNNING): return if self._parse_rc(ln, self.RC_CONNECTED): return if self._parse_rc(ln, self.RC_ERROR): return if self._parse_rc(ln, self.RC_EXIT): return logging.error("Unknown GDB/MI result: '%s'" % ln) class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler): """GDB/MI console stream handler class """ TAG = '~' def load_aux_elf(elf_path): """ Loads auxilary ELF file and composes GDB command to read its symbols """ elf = None sym_cmd = '' if os.path.exists(elf_path): elf = ESPCoreDumpElfFile(elf_path) for s in elf.sections: if s.name == '.text': sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr) return (elf, sym_cmd) def dbg_corefile(args): """ Command to load core dump from file or flash and run GDB debug session with it """ global CLOSE_FDS loader = None rom_elf,rom_sym_cmd = load_aux_elf(args.rom_elf) if not args.core: loader = ESPCoreDumpFlashLoader(args.off, port=args.port) core_fname = loader.create_corefile(args.save_core, rom_elf=rom_elf) if not core_fname: logging.error("Failed to create corefile!") loader.cleanup() return else: core_fname = args.core if args.core_format and args.core_format != 'elf': loader = ESPCoreDumpFileLoader(core_fname, args.core_format == 'b64') core_fname = loader.create_corefile(args.save_core, rom_elf=rom_elf) if not core_fname: logging.error("Failed to create corefile!") loader.cleanup() return p = subprocess.Popen(bufsize=0, args=[args.gdb, '--nw', # ignore .gdbinit '--core=%s' % core_fname, # core file, '-ex', rom_sym_cmd, args.prog ], stdin=None, stdout=None, stderr=None, close_fds=CLOSE_FDS ) p.wait() if loader: if not args.core and not args.save_core: loader.remove_tmp_file(core_fname) loader.cleanup() print('Done!') def info_corefile(args): """ Command to load core dump from file or flash and print it's data in user friendly form """ global CLOSE_FDS def gdbmi_console_stream_handler(ln): sys.stdout.write(ln) sys.stdout.flush() def gdbmi_read2prompt(f, out_handlers=None): while True: ln = f.readline().decode('utf-8').rstrip(' \r\n') if ln == '(gdb)': break elif len(ln) == 0: break elif out_handlers: for h in out_handlers: if ln.startswith(out_handlers[h].TAG): out_handlers[h].execute(ln) break def gdbmi_start(handlers, gdb_cmds): gdb_args = [args.gdb, '--quiet', # inhibit dumping info at start-up '--nx', # inhibit window interface '--nw', # ignore .gdbinit '--interpreter=mi2', # use GDB/MI v2 '--core=%s' % core_fname] # core file for c in gdb_cmds: gdb_args += ['-ex', c] gdb_args.append(args.prog) p = subprocess.Popen(bufsize=0, args=gdb_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=CLOSE_FDS) gdbmi_read2prompt(p.stdout, handlers) return p def gdbmi_getinfo(p, handlers, gdb_cmd): for t in handlers: handlers[t].result_class = None p.stdin.write(bytearray("-interpreter-exec console \"%s\"\n" % gdb_cmd, encoding='utf-8')) gdbmi_read2prompt(p.stdout, handlers) if not handlers[GDBMIResultHandler.TAG].result_class or handlers[GDBMIResultHandler.TAG].result_class == GDBMIResultHandler.RC_EXIT: logging.error("GDB exited (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)) p.wait() logging.error("Problem occured! GDB exited, restart it.") p = gdbmi_start(handlers, []) elif handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: logging.error("GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)) return p loader = None rom_elf,rom_sym_cmd = load_aux_elf(args.rom_elf) if not args.core: loader = ESPCoreDumpFlashLoader(args.off, port=args.port) core_fname = loader.create_corefile(args.save_core, rom_elf=rom_elf) if not core_fname: logging.error("Failed to create corefile!") loader.cleanup() return else: core_fname = args.core if args.core_format and args.core_format != 'elf': loader = ESPCoreDumpFileLoader(core_fname, args.core_format == 'b64') core_fname = loader.create_corefile(args.save_core, rom_elf=rom_elf) if not core_fname: logging.error("Failed to create corefile!") loader.cleanup() return exe_elf = ESPCoreDumpElfFile(args.prog) core_elf = ESPCoreDumpElfFile(core_fname) merged_segs = [] core_segs = core_elf.program_segments for s in exe_elf.sections: merged = False for ps in core_segs: if ps.addr <= s.addr and ps.addr + len(ps.data) >= s.addr: # sec: |XXXXXXXXXX| # seg: |...XXX.............| seg_addr = ps.addr if ps.addr + len(ps.data) <= s.addr + len(s.data): # sec: |XXXXXXXXXX| # seg: |XXXXXXXXXXX...| # merged: |XXXXXXXXXXXXXX| seg_len = len(s.data) + (s.addr - ps.addr) else: # sec: |XXXXXXXXXX| # seg: |XXXXXXXXXXXXXXXXX| # merged: |XXXXXXXXXXXXXXXXX| seg_len = len(ps.data) merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) core_segs.remove(ps) merged = True elif ps.addr >= s.addr and ps.addr <= s.addr + len(s.data): # sec: |XXXXXXXXXX| # seg: |...XXX.............| seg_addr = s.addr if (ps.addr + len(ps.data)) >= (s.addr + len(s.data)): # sec: |XXXXXXXXXX| # seg: |..XXXXXXXXXXX| # merged: |XXXXXXXXXXXXX| seg_len = len(s.data) + (ps.addr + len(ps.data)) - (s.addr + len(s.data)) else: # sec: |XXXXXXXXXX| # seg: |XXXXXX| # merged: |XXXXXXXXXX| seg_len = len(s.data) merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) core_segs.remove(ps) merged = True if not merged: merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False)) handlers = {} handlers[GDBMIResultHandler.TAG] = GDBMIResultHandler(verbose=False) handlers[GDBMIStreamConsoleHandler.TAG] = GDBMIStreamConsoleHandler(None, verbose=False) p = gdbmi_start(handlers, [rom_sym_cmd]) print("===============================================================") print("==================== ESP32 CORE DUMP START ====================") handlers[GDBMIResultHandler.TAG].result_class = None handlers[GDBMIStreamConsoleHandler.TAG].func = gdbmi_console_stream_handler print("\n================== CURRENT THREAD REGISTERS ===================") p = gdbmi_getinfo(p, handlers, "info registers") print("\n==================== CURRENT THREAD STACK =====================") p = gdbmi_getinfo(p, handlers, "bt") print("\n======================== THREADS INFO =========================") p = gdbmi_getinfo(p, handlers, "info threads") print("\n======================= ALL MEMORY REGIONS ========================") print("Name Address Size Attrs") for ms in merged_segs: print("%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3])) for cs in core_segs: # core dump exec segments are from ROM, other are belong to tasks (TCB or stack) if cs.flags & ESPCoreDumpSegment.PF_X: seg_name = 'rom.text' else: seg_name = 'tasks.data' print(".coredump.%s 0x%x 0x%x %s" % (seg_name, cs.addr, len(cs.data), cs.attr_str())) if args.print_mem: print("\n====================== CORE DUMP MEMORY CONTENTS ========================") for cs in core_elf.program_segments: # core dump exec segments are from ROM, other are belong to tasks (TCB or stack) if cs.flags & ESPCoreDumpSegment.PF_X: seg_name = 'rom.text' else: seg_name = 'tasks.data' print(".coredump.%s 0x%x 0x%x %s" % (seg_name, cs.addr, len(cs.data), cs.attr_str())) p = gdbmi_getinfo(p, handlers, "x/%dx 0x%x" % (old_div(len(cs.data),4), cs.addr)) print("\n===================== ESP32 CORE DUMP END =====================") print("===============================================================") p.stdin.write(b'q\n') p.wait() p.stdin.close() p.stdout.close() if loader: if not args.core and not args.save_core: loader.remove_tmp_file(core_fname) loader.cleanup() print('Done!') def main(): parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__, prog='espcoredump') parser.add_argument('--chip', '-c', help='Target chip type', choices=['auto', 'esp32'], default=os.environ.get('ESPTOOL_CHIP', 'auto')) parser.add_argument( '--port', '-p', help='Serial port device', default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT)) parser.add_argument( '--baud', '-b', help='Serial port baud rate used when flashing/reading', type=int, default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD)) subparsers = parser.add_subparsers( dest='operation', help='Run coredumper {command} -h for additional help') parser_debug_coredump = subparsers.add_parser( 'dbg_corefile', help='Starts GDB debugging session with specified corefile') parser_debug_coredump.add_argument('--debug', '-d', help='Log level (0..3)', type=int, default=2) parser_debug_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') parser_debug_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) parser_debug_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), ' 'raw (raw) or base64-encoded (b64) binary', type=str, default='elf') parser_debug_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash ' '(type "make partition_table" to see).', type=int, default=0x110000) parser_debug_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. ' 'Ignored with "-c"', type=str) parser_debug_coredump.add_argument('--rom-elf', '-r', help='Path to ROM ELF file.', type=str, default='esp32_rom.elf') parser_debug_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) parser_info_coredump = subparsers.add_parser( 'info_corefile', help='Print core dump info from file') parser_info_coredump.add_argument('--debug', '-d', help='Log level (0..3)', type=int, default=0) parser_info_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') parser_info_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) parser_info_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), ' 'raw (raw) or base64-encoded (b64) binary', type=str, default='elf') parser_info_coredump.add_argument('--off', '-o', help='Offset of coredump partition in flash (type ' '"make partition_table" to see).', type=int, default=0x110000) parser_info_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. ' 'Does not work with "-c"', type=str) parser_info_coredump.add_argument('--rom-elf', '-r', help='Path to ROM ELF file.', type=str, default='esp32_rom.elf') parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true') parser_info_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) # internal sanity check - every operation matches a module function of the same name for operation in subparsers.choices: assert operation in globals(), "%s should be a module function" % operation args = parser.parse_args() log_level = logging.CRITICAL if args.debug == 0: log_level = logging.CRITICAL elif args.debug == 1: log_level = logging.ERROR elif args.debug == 2: log_level = logging.WARNING elif args.debug == 3: log_level = logging.INFO else: log_level = logging.DEBUG # logging.warning('Watch out!') # will print a message to the console # logging.info('I told you so') # will not print anything logging.basicConfig(format='%(levelname)s:%(message)s', level=log_level) print('espcoredump.py v%s' % __version__) operation_func = globals()[args.operation] operation_func(args) if __name__ == '__main__': try: main() except ESPCoreDumpError as e: print('\nA fatal error occurred: %s' % e) sys.exit(2)