Complimentary changes: 1) Partition table definitions files with core dump partition 2) Special sub-type for core dump partition 3) Special version of spi_flash_xxx 4) espcoredump.py is script to get core dump from flash and print useful info 5) FreeRTOS API was extended to get tasks snapshots
1014 lines
38 KiB
Python
Executable file
1014 lines
38 KiB
Python
Executable file
#!/usr/bin/env python
|
|
#
|
|
# ESP32 core dump Utility
|
|
|
|
import sys
|
|
import os
|
|
import argparse
|
|
import subprocess
|
|
import tempfile
|
|
import struct
|
|
import array
|
|
import errno
|
|
|
|
try:
|
|
import esptool
|
|
except ImportError:
|
|
idf_path = os.getenv('IDF_PATH')
|
|
if idf_path is None:
|
|
print "Esptool is not found! Install it or set proper $IDF_PATH in environment."
|
|
sys.exit(2)
|
|
sys.path.append('%s/components/esptool_py/esptool' % idf_path)
|
|
import esptool
|
|
|
|
__version__ = "0.1-dev"
|
|
|
|
ESP32_COREDUMP_HDR_FMT = '<4L'
|
|
ESP32_COREDUMP_FLASH_MAGIC_START = 0xDEADBEEF
|
|
ESP32_COREDUMP_FLASH_MAGIC_END = 0xACDCFEED
|
|
|
|
|
|
class Struct(object):
|
|
def __init__(self, buf=None):
|
|
if buf is None:
|
|
buf = b'\0' * self.sizeof()
|
|
fields = struct.unpack(self.__class__.fmt, buf[:self.sizeof()])
|
|
self.__dict__.update(zip(self.__class__.fields, fields))
|
|
|
|
def sizeof(self):
|
|
return struct.calcsize(self.__class__.fmt)
|
|
|
|
def dump(self):
|
|
keys = self.__class__.fields
|
|
if sys.version_info > (3, 0):
|
|
# Convert strings into bytearrays if this is Python 3
|
|
for k in keys:
|
|
if type(self.__dict__[k]) is str:
|
|
self.__dict__[k] = bytearray(self.__dict__[k], encoding='ascii')
|
|
return struct.pack(self.__class__.fmt, *(self.__dict__[k] for k in keys))
|
|
|
|
def __str__(self):
|
|
keys = self.__class__.fields
|
|
return (self.__class__.__name__ + "({" +
|
|
", ".join("%s:%r" % (k, self.__dict__[k]) for k in keys) +
|
|
"})")
|
|
|
|
|
|
class Elf32FileHeader(Struct):
|
|
"""ELF32 File header"""
|
|
fields = ("e_ident",
|
|
"e_type",
|
|
"e_machine",
|
|
"e_version",
|
|
"e_entry",
|
|
"e_phoff",
|
|
"e_shoff",
|
|
"e_flags",
|
|
"e_ehsize",
|
|
"e_phentsize",
|
|
"e_phnum",
|
|
"e_shentsize",
|
|
"e_shnum",
|
|
"e_shstrndx")
|
|
fmt = "<16sHHLLLLLHHHHHH"
|
|
|
|
def __init__(self, buf=None):
|
|
super(Elf32FileHeader, self).__init__(buf)
|
|
if buf is None:
|
|
# Fill in sane ELF header for LSB32
|
|
self.e_ident = "\x7fELF\1\1\1\0\0\0\0\0\0\0\0\0"
|
|
self.e_version = ESPCoreDumpFile.EV_CURRENT
|
|
self.e_ehsize = self.sizeof()
|
|
|
|
|
|
class Elf32ProgramHeader(Struct):
|
|
"""ELF32 Program Header"""
|
|
fields = ("p_type",
|
|
"p_offset",
|
|
"p_vaddr",
|
|
"p_paddr",
|
|
"p_filesz",
|
|
"p_memsz",
|
|
"p_flags",
|
|
"p_align")
|
|
fmt = "<LLLLLLLL"
|
|
|
|
|
|
class Elf32NoteDesc(object):
|
|
"""ELF32 Note Descriptor"""
|
|
def __init__(self, name, type, data):
|
|
self.name = bytearray(name, encoding='ascii') + b'\0'
|
|
self.type = type
|
|
self.data = data
|
|
|
|
def dump(self):
|
|
"""Conveninece function to format a note descriptor.
|
|
All note descriptors must be concatenated and added to a
|
|
PT_NOTE segment."""
|
|
header = struct.pack("<LLL", len(self.name), len(self.data), self.type)
|
|
# pad up to 4 byte alignment
|
|
name = self.name + ((4 - len(self.name)) % 4) * b'\0'
|
|
desc = self.data + ((4 - len(self.data)) % 4) * b'\0'
|
|
print "dump %d %d %d %d %d" % (len(header), len(name), len(self.name), len(desc), len(self.data))
|
|
return header + name + desc
|
|
|
|
|
|
class XtensaPrStatus(Struct):
|
|
"""Xtensa Program Status structure"""
|
|
# Only pr_cursig and pr_pid are read by bfd
|
|
# Structure followed by 72 bytes representing general-purpose registers
|
|
# check elf32-xtensa.c in libbfd for details
|
|
fields = ("si_signo", "si_code", "si_errno",
|
|
"pr_cursig", # Current signal
|
|
"pr_pad0",
|
|
"pr_sigpend",
|
|
"pr_sighold",
|
|
"pr_pid", # LWP ID
|
|
"pr_ppid",
|
|
"pr_pgrp",
|
|
"pr_sid",
|
|
"pr_utime",
|
|
"pr_stime",
|
|
"pr_cutime",
|
|
"pr_cstime")
|
|
fmt = "<3LHHLLLLLLQQQQ"
|
|
|
|
|
|
class ESPCoreDumpSegment(esptool.ImageSegment):
|
|
""" Wrapper class for a program segment in an ELF image, has a section
|
|
name as well as the common properties of an ImageSegment. """
|
|
# segment flags
|
|
PF_X = 0x1 # Execute
|
|
PF_W = 0x2 # Write
|
|
PF_R = 0x4 # Read
|
|
|
|
def __init__(self, addr, data, type, flags):
|
|
super(ESPCoreDumpSegment, self).__init__(addr, data)
|
|
self.flags = flags
|
|
self.type = type
|
|
|
|
def __repr__(self):
|
|
return "%s %s %s" % (self.type, self.attr_str(), super(ESPCoreDumpSegment, self).__repr__())
|
|
|
|
def attr_str(self):
|
|
str = ''
|
|
if self.flags & self.PF_R:
|
|
str += 'R'
|
|
else:
|
|
str += ' '
|
|
if self.flags & self.PF_W:
|
|
str += 'W'
|
|
else:
|
|
str += ' '
|
|
if self.flags & self.PF_X:
|
|
str += 'X'
|
|
else:
|
|
str += ' '
|
|
return str
|
|
|
|
|
|
class ESPCoreDumpSection(esptool.ELFSection):
|
|
"""
|
|
TBD
|
|
"""
|
|
# section flags
|
|
SHF_WRITE = 0x1
|
|
SHF_ALLOC = 0x2
|
|
SHF_EXECINSTR = 0x4
|
|
|
|
def __init__(self, name, addr, data, flags):
|
|
super(ESPCoreDumpSection, self).__init__(name, addr, data)
|
|
self.flags = flags
|
|
|
|
def __repr__(self):
|
|
return "%s %s" % (super(ESPCoreDumpSection, self).__repr__(), self.attr_str())
|
|
|
|
def attr_str(self):
|
|
str = "R"
|
|
if self.flags & self.SHF_WRITE:
|
|
str += 'W'
|
|
else:
|
|
str += ' '
|
|
if self.flags & self.SHF_EXECINSTR:
|
|
str += 'X'
|
|
else:
|
|
str += ' '
|
|
if self.flags & self.SHF_ALLOC:
|
|
str += 'A'
|
|
else:
|
|
str += ' '
|
|
return str
|
|
|
|
|
|
class ESPCoreDumpFile(esptool.ELFFile):
|
|
# ELF file type
|
|
ET_NONE = 0x0 # No file type
|
|
ET_REL = 0x1 # Relocatable file
|
|
ET_EXEC = 0x2 # Executable file
|
|
ET_DYN = 0x3 # Shared object file
|
|
ET_CORE = 0x4 # Core file
|
|
# ELF file version
|
|
EV_NONE = 0x0
|
|
EV_CURRENT = 0x1
|
|
# ELF file machine type
|
|
EM_NONE = 0x0
|
|
EM_XTENSA = 0x5E
|
|
# section types
|
|
SEC_TYPE_PROGBITS = 0x01
|
|
SEC_TYPE_STRTAB = 0x03
|
|
# special section index
|
|
SHN_UNDEF = 0x0
|
|
# program segment types
|
|
PT_NULL = 0x0
|
|
PT_LOAD = 0x1
|
|
PT_DYNAMIC = 0x2
|
|
PT_INTERP = 0x3
|
|
PT_NOTE = 0x4
|
|
PT_SHLIB = 0x5
|
|
PT_PHDR = 0x6
|
|
|
|
def __init__(self, name=None):
|
|
if name:
|
|
super(ESPCoreDumpFile, self).__init__(name)
|
|
else:
|
|
self.sections = []
|
|
self.program_segments = []
|
|
self.e_type = self.ET_NONE
|
|
self.e_machine = self.EM_NONE
|
|
|
|
def _read_elf_file(self, f):
|
|
# read the ELF file header
|
|
LEN_FILE_HEADER = 0x34
|
|
try:
|
|
(ident,type,machine,_version,
|
|
self.entrypoint,phoff,shoff,_flags,
|
|
_ehsize, phentsize,phnum,_shentsize,
|
|
shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER))
|
|
except struct.error as e:
|
|
raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e))
|
|
|
|
if ident[0] != '\x7f' or ident[1:4] != 'ELF':
|
|
raise FatalError("%s has invalid ELF magic header" % self.name)
|
|
if machine != self.EM_XTENSA:
|
|
raise FatalError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine))
|
|
self.e_type = type
|
|
self.e_machine = machine
|
|
if shnum > 0:
|
|
self._read_sections(f, shoff, shstrndx)
|
|
else:
|
|
self.sections = []
|
|
if phnum > 0:
|
|
self._read_program_segments(f, phoff, phentsize, phnum)
|
|
else:
|
|
self.program_segments = []
|
|
|
|
def _read_sections(self, f, section_header_offs, shstrndx):
|
|
f.seek(section_header_offs)
|
|
section_header = f.read()
|
|
LEN_SEC_HEADER = 0x28
|
|
if len(section_header) == 0:
|
|
raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs)
|
|
if len(section_header) % LEN_SEC_HEADER != 0:
|
|
print '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("<LLLLLL", section_header[offs:])
|
|
return (name_offs, sec_type, flags, lma, size, sec_offs)
|
|
all_sections = [read_section_header(offs) for offs in section_header_offsets]
|
|
prog_sections = [s for s in all_sections if s[1] == esptool.ELFFile.SEC_TYPE_PROGBITS]
|
|
|
|
# search for the string table section
|
|
if not shstrndx * LEN_SEC_HEADER in section_header_offsets:
|
|
raise FatalError("ELF file has no STRTAB section at shstrndx %d" % shstrndx)
|
|
_,sec_type,_,_,sec_size,sec_offs = read_section_header(shstrndx * LEN_SEC_HEADER)
|
|
if sec_type != esptool.ELFFile.SEC_TYPE_STRTAB:
|
|
print 'WARNING: ELF file has incorrect STRTAB section type 0x%02x' % sec_type
|
|
f.seek(sec_offs)
|
|
string_table = f.read(sec_size)
|
|
|
|
# build the real list of ELFSections by reading the actual section names from the
|
|
# string table section, and actual data for each section from the ELF file itself
|
|
def lookup_string(offs):
|
|
raw = string_table[offs:]
|
|
return raw[:raw.index('\x00')]
|
|
|
|
def read_data(offs,size):
|
|
f.seek(offs)
|
|
return f.read(size)
|
|
|
|
prog_sections = [ESPCoreDumpSection(lookup_string(n_offs), lma, read_data(offs, size), flags) for (n_offs, _type, flags, lma, size, offs) in prog_sections
|
|
if lma != 0]
|
|
self.sections = prog_sections
|
|
|
|
def _read_program_segments(self, f, seg_table_offs, entsz, num):
|
|
f.seek(seg_table_offs)
|
|
seg_table = f.read(entsz*num)
|
|
LEN_SEG_HEADER = 0x20
|
|
if len(seg_table) == 0:
|
|
raise FatalError("No program header table found at offset %04x in ELF file." % seg_table_offs)
|
|
if len(seg_table) % LEN_SEG_HEADER != 0:
|
|
print 'WARNING: Unexpected ELF program header table length %04x is not mod-%02x' % (len(seg_table),LEN_SEG_HEADER)
|
|
|
|
# walk through the program segment table and extract all segments
|
|
seg_table_offs = range(0, len(seg_table), LEN_SEG_HEADER)
|
|
|
|
def read_program_header(offs):
|
|
type,offset,vaddr,_paddr,filesz,_memsz,_flags,_align = struct.unpack_from("<LLLLLLLL", seg_table[offs:])
|
|
return (type,offset,vaddr,filesz)
|
|
all_segments = [read_program_header(offs) for offs in seg_table_offs]
|
|
prog_segments = [s for s in all_segments if s[0] == self.PT_LOAD]
|
|
|
|
# build the real list of ImageSegment by reading actual data for each segment from the ELF file itself
|
|
def read_data(offs,size):
|
|
f.seek(offs)
|
|
return f.read(size)
|
|
|
|
prog_segments = [esptool.ImageSegment(vaddr, read_data(offset, filesz), offset) for (_type, offset, vaddr, filesz) in prog_segments
|
|
if vaddr != 0]
|
|
self.program_segments = prog_segments
|
|
# print "prog_segments=%s" % (self.program_segments)
|
|
|
|
# currently merging is not supported
|
|
def add_program_segment(self, addr, data, type, flags):
|
|
data_sz = len(data)
|
|
print "add_program_segment: %x %d" % (addr, data_sz)
|
|
# check for overlapping and merge if needed
|
|
if addr != 0 and data_sz != 0:
|
|
for ps in self.program_segments:
|
|
seg_len = len(ps.data)
|
|
if addr >= ps.addr and addr < (ps.addr + seg_len):
|
|
raise FatalError("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 FatalError("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))
|
|
|
|
# currently dumps only program segments.
|
|
# dumping sections is not supported yet
|
|
def dump(self, f):
|
|
print "dump to '%s'" % f
|
|
# 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
|
|
# print "" % (ehdr.e_ehsize, ehdr.e_phnum, ehdr.e_phentsize)
|
|
for i in range(len(self.program_segments)):
|
|
print "dump header for seg '%s'" % self.program_segments[i]
|
|
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
|
|
# print "header '%s'" % phdr
|
|
f.write(phdr.dump())
|
|
cur_off += phdr.p_filesz
|
|
# write program segments
|
|
for i in range(len(self.program_segments)):
|
|
print "dump seg '%s'" % self.program_segments[i]
|
|
f.write(self.program_segments[i].data)
|
|
|
|
|
|
class ESPCoreDumpError(RuntimeError):
|
|
"""
|
|
TBD
|
|
"""
|
|
def __init__(self, message):
|
|
super(ESPCoreDumpError, self).__init__(message)
|
|
|
|
|
|
class ESPCoreDumpLoaderError(ESPCoreDumpError):
|
|
"""
|
|
TBD
|
|
"""
|
|
def __init__(self, message):
|
|
super(ESPCoreDumpLoaderError, self).__init__(message)
|
|
|
|
|
|
class ESPCoreDumpLoader(object):
|
|
"""
|
|
TBD
|
|
"""
|
|
FLASH_READ_BLOCK_SZ = 0x2000
|
|
def __init__(self, off, path=None, chip='esp32', port=None, baud=None):
|
|
# print "esptool.__file__ %s" % esptool.__file__
|
|
if not path:
|
|
self.path = esptool.__file__
|
|
self.path = self.path[:-1]
|
|
else:
|
|
self.path = path
|
|
self.port = port
|
|
self.baud = baud
|
|
self.chip = chip
|
|
self.fcores = []
|
|
self.fgdbcore = None
|
|
self._load_coredump(off)
|
|
|
|
def _load_coredump(self, off):
|
|
args = [self.path, '-c', self.chip]
|
|
if self.port:
|
|
args.extend(['-p', self.port])
|
|
if self.baud:
|
|
args.extend(['-b', str(self.baud)])
|
|
read_sz = self.FLASH_READ_BLOCK_SZ
|
|
read_off = off
|
|
args.extend(['read_flash', str(read_off), str(read_sz), ''])
|
|
try:
|
|
dump_sz = 0
|
|
tot_len = 0
|
|
while True:
|
|
fhnd,fname = tempfile.mkstemp()
|
|
# print "tmpname %s" % fname
|
|
# os.close(fhnd)
|
|
args[-1] = fname
|
|
et_out = subprocess.check_output(args)
|
|
print et_out
|
|
# data = os.fdopen(fhnd, 'r').read(sz)
|
|
self.fcores.append(os.fdopen(fhnd, 'r'))
|
|
if dump_sz == 0:
|
|
# read dump length from the first block
|
|
dump_sz = self._read_core_dump_length(self.fcores[0])
|
|
tot_len += read_sz
|
|
if tot_len >= dump_sz:
|
|
break
|
|
read_off += read_sz
|
|
if dump_sz - tot_len >= self.FLASH_READ_BLOCK_SZ:
|
|
read_sz = self.FLASH_READ_BLOCK_SZ
|
|
else:
|
|
read_sz = dump_sz - tot_len
|
|
args[-3] = str(read_off)
|
|
args[-2] = str(read_sz)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print "esptool script execution failed with err %d" % e.returncode
|
|
print "Command ran: '%s'" % e.cmd
|
|
print "Command out:"
|
|
print e.output
|
|
self.cleanup()
|
|
return []
|
|
|
|
def _read_core_dump_length(self, f):
|
|
global ESP32_COREDUMP_HDR_FMT
|
|
global ESP32_COREDUMP_FLASH_MAGIC_START
|
|
print "Read core dump header from '%s'" % f.name
|
|
data = f.read(4*4)
|
|
mag1,tot_len,task_num,tcbsz = struct.unpack_from(ESP32_COREDUMP_HDR_FMT, data)
|
|
if mag1 != ESP32_COREDUMP_FLASH_MAGIC_START:
|
|
raise ESPCoreDumpLoaderError("Invalid start magic number!")
|
|
return tot_len
|
|
|
|
def remove_tmp_file(self, fname):
|
|
try:
|
|
os.remove(fname)
|
|
except OSError as e:
|
|
if e.errno != errno.ENOENT:
|
|
print "Warning failed to remove temp file '%s'!" % fname
|
|
|
|
def _get_registers_from_stack(self, data, grows_down):
|
|
# 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:
|
|
print "Growing up stacks are not supported for now!"
|
|
return regs
|
|
# for i in range(REG_NUM):
|
|
# regs[i] = i
|
|
# return regs
|
|
ex_struct = "<%dL" % XT_STK_FRMSZ
|
|
if len(data) < struct.calcsize(ex_struct):
|
|
print "Too small stack to keep frame: %d bytes!" % len(data)
|
|
return regs
|
|
|
|
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:
|
|
print "EXCSTACKFRAME %d" % rc
|
|
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]
|
|
print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % (
|
|
regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0],
|
|
regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3])
|
|
else:
|
|
print "SOLSTACKFRAME %d" % rc
|
|
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]
|
|
print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % (
|
|
regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0],
|
|
regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3])
|
|
|
|
# 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 cleanup(self):
|
|
# if self.fgdbcore:
|
|
# self.fgdbcore.close()
|
|
# self.remove_tmp_file(self.fgdbcore.name)
|
|
for f in self.fcores:
|
|
if f:
|
|
f.close()
|
|
self.remove_tmp_file(f.name)
|
|
|
|
def get_corefile_from_flash(self):
|
|
""" TBD
|
|
"""
|
|
global ESP32_COREDUMP_HDR_FMT
|
|
ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT)
|
|
ESP32_COREDUMP_TSK_HDR_FMT = '<LLL'
|
|
ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT)
|
|
ESP32_COREDUMP_MAGIC_FMT = '<L'
|
|
ESP32_COREDUMP_MAGIC_SZ = struct.calcsize(ESP32_COREDUMP_MAGIC_FMT)
|
|
no_progress = True #False
|
|
if no_progress:
|
|
flash_progress = None
|
|
else:
|
|
def flash_progress(progress, length):
|
|
msg = '%d (%d %%)' % (progress, progress * 100.0 / length)
|
|
padding = '\b' * len(msg)
|
|
if progress == length:
|
|
padding = '\n'
|
|
sys.stdout.write(msg + padding)
|
|
sys.stdout.flush()
|
|
|
|
core_off = 0
|
|
print "Read core dump header"
|
|
data = self.read_flash(core_off, ESP32_COREDUMP_HDR_SZ, flash_progress)
|
|
mag1,tot_len,task_num,tcbsz = struct.unpack_from(ESP32_COREDUMP_HDR_FMT, data)
|
|
tcbsz_aligned = tcbsz
|
|
if tcbsz_aligned % 4:
|
|
tcbsz_aligned = 4*(tcbsz_aligned/4 + 1)
|
|
print "mag1=%x, tot_len=%d, task_num=%d, tcbsz=%d" % (mag1,tot_len,task_num,tcbsz)
|
|
core_off += ESP32_COREDUMP_HDR_SZ
|
|
core_elf = ESPCoreDumpFile()
|
|
notes = b''
|
|
for i in range(task_num):
|
|
print "Read task[%d] header" % i
|
|
data = self.read_flash(core_off, ESP32_COREDUMP_TSK_HDR_SZ, flash_progress)
|
|
tcb_addr,stack_top,stack_end = struct.unpack_from(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
|
|
print "tcb_addr=%x, stack_top=%x, stack_end=%x, stack_len=%d" % (tcb_addr,stack_top,stack_end,stack_len)
|
|
|
|
stack_len_aligned = stack_len
|
|
if stack_len_aligned % 4:
|
|
stack_len_aligned = 4*(stack_len_aligned/4 + 1)
|
|
|
|
core_off += ESP32_COREDUMP_TSK_HDR_SZ
|
|
print "Read task[%d] TCB" % i
|
|
data = self.read_flash(core_off, tcbsz_aligned, flash_progress)
|
|
if tcbsz != tcbsz_aligned:
|
|
core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
|
|
else:
|
|
core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
|
|
# print "tcb=%s" % data
|
|
core_off += tcbsz_aligned
|
|
print "Read task[%d] stack %d bytes" % (i,stack_len)
|
|
data = self.read_flash(core_off, stack_len_aligned, flash_progress)
|
|
# print "stk=%s" % data
|
|
if stack_len != stack_len_aligned:
|
|
data = data[:stack_len - stack_len_aligned]
|
|
core_elf.add_program_segment(stack_base, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
|
|
core_off += stack_len_aligned
|
|
|
|
task_regs = self._get_registers_from_stack(data, stack_end > stack_top)
|
|
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()
|
|
print "NOTE_LEN %d" % len(note)
|
|
notes += note
|
|
|
|
print "Read core dump endmarker"
|
|
data = self.read_flash(core_off, ESP32_COREDUMP_MAGIC_SZ, flash_progress)
|
|
mag = struct.unpack_from(ESP32_COREDUMP_MAGIC_FMT, data)
|
|
print "mag2=%x" % (mag)
|
|
|
|
# add notes
|
|
core_elf.add_program_segment(0, notes, ESPCoreDumpFile.PT_NOTE, 0)
|
|
|
|
core_elf.e_type = ESPCoreDumpFile.ET_CORE
|
|
core_elf.e_machine = ESPCoreDumpFile.EM_XTENSA
|
|
fhnd,fname = tempfile.mkstemp()
|
|
self.fgdbcore = os.fdopen(fhnd, 'wb')
|
|
core_elf.dump(self.fgdbcore)
|
|
return fname
|
|
######################### END ###########################
|
|
|
|
def read_flash(self, off, sz, progress=None):
|
|
# print "read_flash: %x %d" % (off, sz)
|
|
id = off / self.FLASH_READ_BLOCK_SZ
|
|
if id >= len(self.fcores):
|
|
return ''
|
|
self.fcores[id].seek(off % self.FLASH_READ_BLOCK_SZ)
|
|
data = self.fcores[id].read(sz)
|
|
# print "data1: %s" % data
|
|
return data
|
|
|
|
class GDBMIOutRecordHandler(object):
|
|
""" TBD
|
|
"""
|
|
TAG = ''
|
|
|
|
def __init__(self, f, verbose=False):
|
|
self.verbose = verbose
|
|
|
|
def execute(self, ln):
|
|
if self.verbose:
|
|
print "%s.execute '%s'" % (self.__class__.__name__, ln)
|
|
|
|
|
|
class GDBMIOutStreamHandler(GDBMIOutRecordHandler):
|
|
""" TBD
|
|
"""
|
|
def __init__(self, f, verbose=False):
|
|
super(GDBMIOutStreamHandler, self).__init__(None, verbose)
|
|
self.func = f
|
|
|
|
def execute(self, ln):
|
|
GDBMIOutRecordHandler.execute(self, ln)
|
|
if self.func:
|
|
# remove TAG / quotes and replace c-string \n with actual NL
|
|
self.func(ln[1:].strip('"').replace('\\n', '\n').replace('\\t', '\t'))
|
|
|
|
|
|
class GDBMIResultHandler(GDBMIOutRecordHandler):
|
|
""" TBD
|
|
"""
|
|
TAG = '^'
|
|
RC_DONE = 'done'
|
|
RC_RUNNING = 'running'
|
|
RC_CONNECTED = 'connected'
|
|
RC_ERROR = 'error'
|
|
RC_EXIT = 'exit'
|
|
|
|
def __init__(self, verbose=False):
|
|
super(GDBMIResultHandler, self).__init__(None, verbose)
|
|
self.result_class = None
|
|
self.result_str = None
|
|
|
|
def _parse_rc(self, ln, rc):
|
|
rc_str = "{0}{1}".format(self.TAG, rc)
|
|
if ln.startswith(rc_str):
|
|
self.result_class = rc
|
|
sl = len(rc_str)
|
|
if len(ln) > sl:
|
|
self.result_str = ln[sl:]
|
|
if self.result_str.startswith(','):
|
|
self.result_str = self.result_str[1:]
|
|
else:
|
|
print "Invalid result format: '%s'" % ln
|
|
else:
|
|
self.result_str = ''
|
|
return True
|
|
return False
|
|
|
|
def execute(self, ln):
|
|
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
|
|
print "Unknown result: '%s'" % ln
|
|
|
|
|
|
class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler):
|
|
""" TBD
|
|
"""
|
|
TAG = '~'
|
|
|
|
|
|
def dbg_corefile(args):
|
|
""" TBD
|
|
"""
|
|
print "dbg_corefile %s %s %s" % (args.gdb, args.prog, args.core)
|
|
loader = None
|
|
if not args.core:
|
|
loader = ESPCoreDumpLoader(args.off, port=args.port)
|
|
core_fname = loader.get_corefile_from_flash()
|
|
loader.fgdbcore.close()
|
|
# core_fname = 'esp_core.elf'
|
|
else:
|
|
core_fname = args.core
|
|
# print core_fname
|
|
# return
|
|
|
|
p = subprocess.Popen(
|
|
bufsize = 0,
|
|
args = [args.gdb,
|
|
'--nw', # ignore .gdbinit
|
|
'--core=%s' % core_fname, # core file
|
|
args.prog],
|
|
stdin = None, stdout = None, stderr = None,
|
|
close_fds = True
|
|
)
|
|
p.wait()
|
|
if loader:
|
|
loader.remove_tmp_file(loader.fgdbcore.name)
|
|
loader.cleanup()
|
|
print 'Done!'
|
|
|
|
|
|
def info_corefile(args):
|
|
# def info_corefile(args):
|
|
""" TBD
|
|
"""
|
|
print "info_corefile %s %s %s" % (args.gdb, args.prog, args.core)
|
|
|
|
|
|
def gdbmi_console_stream_handler(ln):
|
|
# print ln
|
|
sys.stdout.write(ln)
|
|
sys.stdout.flush()
|
|
|
|
|
|
def gdbmi_read2prompt(f, out_handlers=None):
|
|
""" TBD
|
|
"""
|
|
while True:
|
|
ln = f.readline().rstrip(' \n')
|
|
# print "LINE='{0}'".format(ln)
|
|
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
|
|
|
|
loader = None
|
|
if not args.core:
|
|
loader = ESPCoreDumpLoader(args.off, port=args.port)
|
|
core_fname = loader.get_corefile_from_flash()
|
|
loader.fgdbcore.close()
|
|
else:
|
|
core_fname = args.core
|
|
|
|
handlers = {}
|
|
handlers[GDBMIResultHandler.TAG] = GDBMIResultHandler(verbose=False)
|
|
handlers[GDBMIStreamConsoleHandler.TAG] = GDBMIStreamConsoleHandler(None, verbose=False)
|
|
p = subprocess.Popen(
|
|
bufsize = 0,
|
|
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
|
|
args.prog],
|
|
# ],
|
|
stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT,
|
|
close_fds = True
|
|
)
|
|
|
|
gdbmi_read2prompt(p.stdout, handlers)
|
|
exe_elf = ESPCoreDumpFile(args.prog)
|
|
core_elf = ESPCoreDumpFile(core_fname)
|
|
merged_segs = []#[(s, 0) for s in exe_elf.sections if s.flags & (esptool.ELFSection.SHF_ALLOC | esptool.ELFSection.SHF_WRITE)]
|
|
for s in exe_elf.sections:
|
|
merged = False
|
|
for ps in core_elf.program_segments:
|
|
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))
|
|
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))
|
|
merged = True
|
|
|
|
if not merged:
|
|
merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False))
|
|
# merged_segs.append(('None', ps.addr, len(ps.data), 'None'))
|
|
|
|
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.stdin.write("-interpreter-exec console \"info registers\"\n")
|
|
gdbmi_read2prompt(p.stdout, handlers)
|
|
if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE:
|
|
print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)
|
|
print "\n==================== CURRENT THREAD STACK ====================="
|
|
p.stdin.write("-interpreter-exec console \"bt\"\n")
|
|
gdbmi_read2prompt(p.stdout, handlers)
|
|
if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE:
|
|
print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)
|
|
print "\n======================== THREADS INFO ========================="
|
|
p.stdin.write("-interpreter-exec console \"info threads\"\n")
|
|
gdbmi_read2prompt(p.stdout, handlers)
|
|
if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE:
|
|
print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)
|
|
print "\n======================= 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])
|
|
if args.print_mem:
|
|
print "\n====================== MEMORY CONTENTS ========================"
|
|
for ms in merged_segs:
|
|
# if ms[3].find('W') == -1:
|
|
if not ms[4]:
|
|
continue
|
|
print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3])
|
|
p.stdin.write("-interpreter-exec console \"x/%dx 0x%x\"\n" % (ms[2]/4, ms[1]))
|
|
gdbmi_read2prompt(p.stdout, handlers)
|
|
if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE:
|
|
print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)
|
|
|
|
print "\n===================== ESP32 CORE DUMP END ====================="
|
|
print "==============================================================="
|
|
|
|
p.terminate()
|
|
p.stdin.close()
|
|
p.stdout.close()
|
|
if loader:
|
|
loader.remove_tmp_file(loader.fgdbcore.name)
|
|
loader.cleanup()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='coredumper.py v%s - ESP32 Core Dump Utility' % __version__, prog='coredumper')
|
|
|
|
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))
|
|
|
|
# parser.add_argument(
|
|
# '--no-stub',
|
|
# help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.",
|
|
# action='store_true')
|
|
|
|
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('--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('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000)
|
|
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('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb')
|
|
parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true')
|
|
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('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000)
|
|
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.keys():
|
|
assert operation in globals(), "%s should be a module function" % operation
|
|
|
|
args = parser.parse_args()
|
|
|
|
print 'coredumper.py v%s' % __version__
|
|
|
|
# operation function can take 1 arg (args), 2 args (esp, arg)
|
|
# or be a member function of the ESPLoader class.
|
|
|
|
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)
|