kopia lustrzana https://github.com/espressif/esp-idf
esp32: Adds documentation and comments to core dump feature files
rodzic
39ddc7b836
commit
50b3ce616f
|
@ -316,15 +316,6 @@ static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_
|
|||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* | MAGIC1 |
|
||||
* | TOTAL_LEN | TASKS_NUM | TCB_SIZE |
|
||||
* | TCB_ADDR_1 | STACK_TOP_1 | STACK_END_1 | TCB_1 | STACK_1 |
|
||||
* . . . .
|
||||
* . . . .
|
||||
* | TCB_ADDR_N | STACK_TOP_N | STACK_END_N | TCB_N | STACK_N |
|
||||
* | MAGIC2 |
|
||||
*/
|
||||
void esp_core_dump_to_flash(XtExcFrame *frame)
|
||||
{
|
||||
core_dump_write_config_t wr_cfg;
|
||||
|
@ -347,7 +338,6 @@ void esp_core_dump_to_flash(XtExcFrame *frame)
|
|||
|
||||
#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART
|
||||
static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8_t *dst) {
|
||||
// BASE64_ENCODE_BODY(src, src_len, dst);
|
||||
static const char *b64 =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, j, a, b, c;
|
||||
|
|
|
@ -14,8 +14,51 @@
|
|||
#ifndef ESP_CORE_DUMP_H_
|
||||
#define ESP_CORE_DUMP_H_
|
||||
|
||||
/**
|
||||
* @brief Initializes core dump module internal data.
|
||||
*
|
||||
* @note Should be called at system startup.
|
||||
*/
|
||||
void esp_core_dump_init();
|
||||
|
||||
/**
|
||||
* @brief Saves core dump to flash.
|
||||
*
|
||||
* The structure of data stored in flash is as follows:
|
||||
* | MAGIC1 |
|
||||
* | TOTAL_LEN | TASKS_NUM | TCB_SIZE |
|
||||
* | TCB_ADDR_1 | STACK_TOP_1 | STACK_END_1 | TCB_1 | STACK_1 |
|
||||
* . . . .
|
||||
* . . . .
|
||||
* | TCB_ADDR_N | STACK_TOP_N | STACK_END_N | TCB_N | STACK_N |
|
||||
* | MAGIC2 |
|
||||
* Core dump in flash consists of header and data for every task in the system at the moment of crash.
|
||||
* For flash data integrity control two magic numbers are used at the beginning and the end of core dump.
|
||||
* The structure of core dump data is described below in details.
|
||||
* 1) MAGIC1 and MAGIC2 are special numbers stored at the beginning and the end of core dump.
|
||||
* They are used to control core dump data integrity. Size of every number is 4 bytes.
|
||||
* 2) Core dump starts with header:
|
||||
* 2.1) TOTAL_LEN is total length of core dump data in flash including magic numbers. Size is 4 bytes.
|
||||
* 2.2) TASKS_NUM is the number of tasks for which data are stored. Size is 4 bytes.
|
||||
* 2.3) TCB_SIZE is the size of task's TCB structure. Size is 4 bytes.
|
||||
* 3) Core dump header is followed by the data for every task in the system.
|
||||
* Task data are started with task header:
|
||||
* 3.1) TCB_ADDR is the address of TCB in memory. Size is 4 bytes.
|
||||
* 3.2) STACK_TOP is the top of task's stack (address of the topmost stack item). Size is 4 bytes.
|
||||
* 3.2) STACK_END is the end of task's stack (address from which task's stack starts). Size is 4 bytes.
|
||||
* 4) Task header is followed by TCB data. Size is TCB_SIZE bytes.
|
||||
* 5) Task's stack is placed after TCB data. Size is (STACK_END - STACK_TOP) bytes.
|
||||
*/
|
||||
void esp_core_dump_to_flash();
|
||||
|
||||
/**
|
||||
* @brief Print base64-encoded core dump to UART.
|
||||
*
|
||||
* The structure of core dump data is the same as for data stored in flash (@see esp_core_dump_to_flash) with some notes:
|
||||
* 1) Magic numbers are not present in core dump printed to UART.
|
||||
* 2) Since magic numbers are omitted TOTAL_LEN does not include their size.
|
||||
* 3) Printed base64 data are surrounded with special messages to help user recognize the start and end of actual data.
|
||||
*/
|
||||
void esp_core_dump_to_uart();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,34 +30,63 @@ else:
|
|||
CLOSE_FDS = True
|
||||
|
||||
|
||||
class Struct(object):
|
||||
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 = "<HL"
|
||||
|
||||
Then subclass can be used to initialize fields of underlaying structure and convert it to binary representation:
|
||||
f = open('some_struct.bin', 'wb')
|
||||
s = SomeStruct()
|
||||
s.f1 = 1
|
||||
s.f2 = 10
|
||||
f.write(s.dump())
|
||||
f.close()
|
||||
"""
|
||||
|
||||
def __init__(self, buf=None):
|
||||
"""Base constructor for binary structure objects
|
||||
"""
|
||||
if buf is None:
|
||||
buf = b'\0' * self.sizeof()
|
||||
fields = struct.unpack(self.__class__.fmt, buf[:self.sizeof()])
|
||||
fields = struct.unpack(self.__class__.format, buf[:self.sizeof()])
|
||||
self.__dict__.update(zip(self.__class__.fields, fields))
|
||||
|
||||
def sizeof(self):
|
||||
return struct.calcsize(self.__class__.fmt)
|
||||
"""Returns the size of the structure represented by specific subclass
|
||||
"""
|
||||
return struct.calcsize(self.__class__.format)
|
||||
|
||||
def dump(self):
|
||||
"""Returns binary representation of structure
|
||||
"""
|
||||
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))
|
||||
return struct.pack(self.__class__.format, *(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) +
|
||||
"})")
|
||||
# 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"""
|
||||
class Elf32FileHeader(BinStruct):
|
||||
"""ELF32 file header
|
||||
"""
|
||||
fields = ("e_ident",
|
||||
"e_type",
|
||||
"e_machine",
|
||||
|
@ -72,9 +101,11 @@ class Elf32FileHeader(Struct):
|
|||
"e_shentsize",
|
||||
"e_shnum",
|
||||
"e_shstrndx")
|
||||
fmt = "<16sHHLLLLLHHHHHH"
|
||||
format = "<16sHHLLLLLHHHHHH"
|
||||
|
||||
def __init__(self, buf=None):
|
||||
"""Constructor for ELF32 file header structure
|
||||
"""
|
||||
super(Elf32FileHeader, self).__init__(buf)
|
||||
if buf is None:
|
||||
# Fill in sane ELF header for LSB32
|
||||
|
@ -83,8 +114,9 @@ class Elf32FileHeader(Struct):
|
|||
self.e_ehsize = self.sizeof()
|
||||
|
||||
|
||||
class Elf32ProgramHeader(Struct):
|
||||
"""ELF32 Program Header"""
|
||||
class Elf32ProgramHeader(BinStruct):
|
||||
"""ELF32 program header
|
||||
"""
|
||||
fields = ("p_type",
|
||||
"p_offset",
|
||||
"p_vaddr",
|
||||
|
@ -93,39 +125,37 @@ class Elf32ProgramHeader(Struct):
|
|||
"p_memsz",
|
||||
"p_flags",
|
||||
"p_align")
|
||||
fmt = "<LLLLLLLL"
|
||||
format = "<LLLLLLLL"
|
||||
|
||||
|
||||
class Elf32NoteDesc(object):
|
||||
"""ELF32 Note Descriptor"""
|
||||
def __init__(self, name, type, data):
|
||||
"""ELF32 note descriptor
|
||||
"""
|
||||
def __init__(self, name, type, desc):
|
||||
"""Constructor for ELF32 note descriptor
|
||||
"""
|
||||
self.name = bytearray(name, encoding='ascii') + b'\0'
|
||||
self.type = type
|
||||
self.data = data
|
||||
self.desc = desc
|
||||
|
||||
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
|
||||
"""Returns binary representation of ELF32 note descriptor
|
||||
"""
|
||||
hdr = struct.pack("<LLL", len(self.name), len(self.desc), self.type)
|
||||
# pad for 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
|
||||
desc = self.desc + ((4 - len(self.desc)) % 4) * b'\0'
|
||||
return hdr + 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
|
||||
class XtensaPrStatus(BinStruct):
|
||||
"""Xtensa program status structure"""
|
||||
fields = ("si_signo", "si_code", "si_errno",
|
||||
"pr_cursig", # Current signal
|
||||
"pr_cursig",
|
||||
"pr_pad0",
|
||||
"pr_sigpend",
|
||||
"pr_sighold",
|
||||
"pr_pid", # LWP ID
|
||||
"pr_pid",
|
||||
"pr_ppid",
|
||||
"pr_pgrp",
|
||||
"pr_sid",
|
||||
|
@ -133,26 +163,33 @@ class XtensaPrStatus(Struct):
|
|||
"pr_stime",
|
||||
"pr_cutime",
|
||||
"pr_cstime")
|
||||
fmt = "<3LHHLLLLLLQQQQ"
|
||||
format = "<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. """
|
||||
""" Wrapper class for a program segment in core ELF file, has a segment
|
||||
type and flags 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):
|
||||
"""Constructor for program segment
|
||||
"""
|
||||
super(ESPCoreDumpSegment, self).__init__(addr, data)
|
||||
self.flags = flags
|
||||
self.type = type
|
||||
|
||||
def __repr__(self):
|
||||
"""Returns string representation of program segment
|
||||
"""
|
||||
return "%s %s %s" % (self.type, self.attr_str(), super(ESPCoreDumpSegment, self).__repr__())
|
||||
|
||||
def attr_str(self):
|
||||
"""Returns string representation of program segment attributes
|
||||
"""
|
||||
str = ''
|
||||
if self.flags & self.PF_R:
|
||||
str += 'R'
|
||||
|
@ -170,8 +207,8 @@ class ESPCoreDumpSegment(esptool.ImageSegment):
|
|||
|
||||
|
||||
class ESPCoreDumpSection(esptool.ELFSection):
|
||||
"""
|
||||
TBD
|
||||
""" Wrapper class for a section in core ELF file, has a section
|
||||
flags as well as the common properties of an esptool.ELFSection.
|
||||
"""
|
||||
# section flags
|
||||
SHF_WRITE = 0x1
|
||||
|
@ -179,13 +216,19 @@ class ESPCoreDumpSection(esptool.ELFSection):
|
|||
SHF_EXECINSTR = 0x4
|
||||
|
||||
def __init__(self, name, addr, data, flags):
|
||||
"""Constructor for section
|
||||
"""
|
||||
super(ESPCoreDumpSection, self).__init__(name, addr, data)
|
||||
self.flags = flags
|
||||
|
||||
def __repr__(self):
|
||||
"""Returns string representation of section
|
||||
"""
|
||||
return "%s %s" % (super(ESPCoreDumpSection, self).__repr__(), self.attr_str())
|
||||
|
||||
def attr_str(self):
|
||||
"""Returns string representation of section attributes
|
||||
"""
|
||||
str = "R"
|
||||
if self.flags & self.SHF_WRITE:
|
||||
str += 'W'
|
||||
|
@ -203,6 +246,8 @@ class ESPCoreDumpSection(esptool.ELFSection):
|
|||
|
||||
|
||||
class ESPCoreDumpElfFile(esptool.ELFFile):
|
||||
""" Wrapper class for core dump ELF file
|
||||
"""
|
||||
# ELF file type
|
||||
ET_NONE = 0x0 # No file type
|
||||
ET_REL = 0x1 # Relocatable file
|
||||
|
@ -230,6 +275,8 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
PT_PHDR = 0x6
|
||||
|
||||
def __init__(self, name=None):
|
||||
"""Constructor for core dump ELF file
|
||||
"""
|
||||
if name:
|
||||
super(ESPCoreDumpElfFile, self).__init__(name)
|
||||
else:
|
||||
|
@ -239,6 +286,8 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
self.e_machine = self.EM_NONE
|
||||
|
||||
def _read_elf_file(self, f):
|
||||
"""Reads core dump from ELF file
|
||||
"""
|
||||
# read the ELF file header
|
||||
LEN_FILE_HEADER = 0x34
|
||||
try:
|
||||
|
@ -247,12 +296,12 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
_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))
|
||||
raise ESPCoreDumpError("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)
|
||||
raise ESPCoreDumpError("%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))
|
||||
raise ESPCoreDumpError("%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:
|
||||
|
@ -265,11 +314,13 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
self.program_segments = []
|
||||
|
||||
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 FatalError("No section header found at offset %04x in ELF file." % section_header_offs)
|
||||
raise ESPCoreDumpError("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)
|
||||
|
||||
|
@ -284,7 +335,7 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
|
||||
# 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)
|
||||
raise ESPCoreDumpError("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
|
||||
|
@ -306,11 +357,13 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
self.sections = prog_sections
|
||||
|
||||
def _read_program_segments(self, f, seg_table_offs, entsz, num):
|
||||
"""Reads core dump program segments from ELF file
|
||||
"""
|
||||
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)
|
||||
raise ESPCoreDumpError("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)
|
||||
|
||||
|
@ -318,8 +371,8 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
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)
|
||||
type,offset,vaddr,_paddr,filesz,_memsz,flags,_align = struct.unpack_from("<LLLLLLLL", seg_table[offs:])
|
||||
return (type,offset,vaddr,filesz,flags)
|
||||
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]
|
||||
|
||||
|
@ -328,32 +381,33 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
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
|
||||
prog_segments = [ESPCoreDumpSegment(vaddr, read_data(offset, filesz), type, flags) for (type, offset, vaddr, filesz,flags) 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):
|
||||
"""Adds new program segment
|
||||
"""
|
||||
# TODO: currently merging with existing segments is not supported
|
||||
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]." %
|
||||
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 FatalError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." %
|
||||
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))
|
||||
|
||||
# currently dumps only program segments.
|
||||
# dumping sections is not supported yet
|
||||
def dump(self, f):
|
||||
print "dump to '%s'" % 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
|
||||
|
@ -370,9 +424,7 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
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
|
||||
|
@ -382,57 +434,52 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
|
|||
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
|
||||
"""Core dump loader error class
|
||||
"""
|
||||
def __init__(self, message):
|
||||
"""Constructor for core dump loader error
|
||||
"""
|
||||
super(ESPCoreDumpLoaderError, self).__init__(message)
|
||||
|
||||
|
||||
class ESPCoreDumpLoader(object):
|
||||
"""
|
||||
TBD
|
||||
"""Core dump loader base class
|
||||
"""
|
||||
ESP32_COREDUMP_HDR_FMT = '<3L'
|
||||
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):
|
||||
# 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;
|
||||
"""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
|
||||
|
@ -446,7 +493,7 @@ class ESPCoreDumpLoader(object):
|
|||
# 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
|
||||
|
@ -454,7 +501,7 @@ class ESPCoreDumpLoader(object):
|
|||
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
|
||||
|
@ -467,25 +514,19 @@ class ESPCoreDumpLoader(object):
|
|||
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
|
||||
raise ESPCoreDumpLoaderError("Growing up stacks are not supported for now!")
|
||||
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
|
||||
|
||||
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:
|
||||
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):
|
||||
|
@ -494,23 +535,16 @@ class ESPCoreDumpLoader(object):
|
|||
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])
|
||||
# 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:
|
||||
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)
|
||||
|
@ -521,6 +555,8 @@ class ESPCoreDumpLoader(object):
|
|||
return regs
|
||||
|
||||
def remove_tmp_file(self, fname):
|
||||
"""Silently removes temporary file
|
||||
"""
|
||||
try:
|
||||
os.remove(fname)
|
||||
except OSError as e:
|
||||
|
@ -528,13 +564,15 @@ class ESPCoreDumpLoader(object):
|
|||
print "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):
|
||||
""" TBD
|
||||
"""Creates core dump ELF file
|
||||
"""
|
||||
core_off = off
|
||||
data = self.read_data(core_off, self.ESP32_COREDUMP_HDR_SZ)
|
||||
|
@ -542,7 +580,6 @@ class ESPCoreDumpLoader(object):
|
|||
tcbsz_aligned = tcbsz
|
||||
if tcbsz_aligned % 4:
|
||||
tcbsz_aligned = 4*(tcbsz_aligned/4 + 1)
|
||||
print "tot_len=%d, task_num=%d, tcbsz=%d" % (tot_len,task_num,tcbsz)
|
||||
core_off += self.ESP32_COREDUMP_HDR_SZ
|
||||
core_elf = ESPCoreDumpElfFile()
|
||||
notes = b''
|
||||
|
@ -555,7 +592,6 @@ class ESPCoreDumpLoader(object):
|
|||
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:
|
||||
|
@ -567,10 +603,8 @@ class ESPCoreDumpLoader(object):
|
|||
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)
|
||||
# print "tcb=%s" % data
|
||||
core_off += tcbsz_aligned
|
||||
data = self.read_data(core_off, stack_len_aligned)
|
||||
# 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, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
|
||||
|
@ -584,7 +618,6 @@ class ESPCoreDumpLoader(object):
|
|||
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
|
||||
|
||||
# add notes
|
||||
|
@ -602,20 +635,22 @@ class ESPCoreDumpLoader(object):
|
|||
return core_fname
|
||||
|
||||
def read_data(self, off, sz):
|
||||
# print "read_data: %x %d" % (off, sz)
|
||||
"""Reads data from raw core dump got from flash or UART
|
||||
"""
|
||||
self.fcore.seek(off)
|
||||
data = self.fcore.read(sz)
|
||||
# print "data1: %s" % data
|
||||
return data
|
||||
|
||||
|
||||
class ESPCoreDumpFileLoader(ESPCoreDumpLoader):
|
||||
""" TBD
|
||||
"""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
|
||||
"""
|
||||
|
@ -645,7 +680,7 @@ class ESPCoreDumpFileLoader(ESPCoreDumpLoader):
|
|||
|
||||
|
||||
class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
|
||||
""" TBD
|
||||
"""Core dump flash loader class
|
||||
"""
|
||||
ESP32_COREDUMP_FLASH_MAGIC_START = 0xE32C04ED
|
||||
ESP32_COREDUMP_FLASH_MAGIC_END = 0xE32C04ED
|
||||
|
@ -655,7 +690,8 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
|
|||
ESP32_COREDUMP_FLASH_HDR_SZ = struct.calcsize(ESP32_COREDUMP_FLASH_HDR_FMT)
|
||||
|
||||
def __init__(self, off, tool_path=None, chip='esp32', port=None, baud=None):
|
||||
# print "esptool.__file__ %s" % esptool.__file__
|
||||
"""Constructor for core dump flash loader
|
||||
"""
|
||||
super(ESPCoreDumpFlashLoader, self).__init__()
|
||||
if not tool_path:
|
||||
self.path = esptool.__file__
|
||||
|
@ -667,7 +703,7 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
|
|||
self.chip = chip
|
||||
self.dump_sz = 0
|
||||
self.fcore = self._load_coredump(off)
|
||||
|
||||
|
||||
def _load_coredump(self, off):
|
||||
"""Loads core dump from flash
|
||||
"""
|
||||
|
@ -702,7 +738,8 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
|
|||
return f
|
||||
|
||||
def _read_core_dump_length(self, f):
|
||||
print "Read core dump header from '%s'" % f.name
|
||||
"""Reads core dump length
|
||||
"""
|
||||
data = f.read(4*4)
|
||||
mag1,tot_len,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_FLASH_HDR_FMT, data)
|
||||
if mag1 != self.ESP32_COREDUMP_FLASH_MAGIC_START:
|
||||
|
@ -710,44 +747,50 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
|
|||
return tot_len
|
||||
|
||||
def create_corefile(self, core_fname=None):
|
||||
""" TBD
|
||||
"""Checks flash coredump data integrity and creates ELF file
|
||||
"""
|
||||
data = self.read_data(0, self.ESP32_COREDUMP_FLASH_MAGIC_SZ)
|
||||
mag1, = struct.unpack_from(self.ESP32_COREDUMP_FLASH_MAGIC_FMT, data)
|
||||
if mag1 != self.ESP32_COREDUMP_FLASH_MAGIC_START:
|
||||
print "Invalid start marker %x" % mag1
|
||||
return None
|
||||
raise ESPCoreDumpLoaderError("Invalid start marker %x" % mag1)
|
||||
|
||||
data = self.read_data(self.dump_sz-self.ESP32_COREDUMP_FLASH_MAGIC_SZ, self.ESP32_COREDUMP_FLASH_MAGIC_SZ)
|
||||
mag2, = struct.unpack_from(self.ESP32_COREDUMP_FLASH_MAGIC_FMT, data)
|
||||
if mag2 != self.ESP32_COREDUMP_FLASH_MAGIC_END:
|
||||
print "Invalid end marker %x" % mag2
|
||||
return None
|
||||
raise ESPCoreDumpLoaderError("Invalid end marker %x" % mag2)
|
||||
|
||||
return super(ESPCoreDumpFlashLoader, self).create_corefile(core_fname, off=self.ESP32_COREDUMP_FLASH_MAGIC_SZ)
|
||||
|
||||
|
||||
class GDBMIOutRecordHandler(object):
|
||||
""" TBD
|
||||
"""GDB/MI output record handler base class
|
||||
"""
|
||||
TAG = ''
|
||||
|
||||
def __init__(self, f, verbose=False):
|
||||
"""Base constructor for GDB/MI output record handler
|
||||
"""
|
||||
self.verbose = verbose
|
||||
|
||||
def execute(self, ln):
|
||||
"""Base method to execute GDB/MI output record handler function
|
||||
"""
|
||||
if self.verbose:
|
||||
print "%s.execute '%s'" % (self.__class__.__name__, ln)
|
||||
|
||||
|
||||
class GDBMIOutStreamHandler(GDBMIOutRecordHandler):
|
||||
""" TBD
|
||||
"""GDB/MI output stream handler class
|
||||
"""
|
||||
def __init__(self, f, verbose=False):
|
||||
"""Constructor for GDB/MI output stream handler
|
||||
"""
|
||||
super(GDBMIOutStreamHandler, self).__init__(None, verbose)
|
||||
self.func = f
|
||||
|
||||
def execute(self, ln):
|
||||
"""Executes GDB/MI output stream handler function
|
||||
"""
|
||||
GDBMIOutRecordHandler.execute(self, ln)
|
||||
if self.func:
|
||||
# remove TAG / quotes and replace c-string \n with actual NL
|
||||
|
@ -755,7 +798,7 @@ class GDBMIOutStreamHandler(GDBMIOutRecordHandler):
|
|||
|
||||
|
||||
class GDBMIResultHandler(GDBMIOutRecordHandler):
|
||||
""" TBD
|
||||
"""GDB/MI result handler class
|
||||
"""
|
||||
TAG = '^'
|
||||
RC_DONE = 'done'
|
||||
|
@ -765,11 +808,15 @@ class GDBMIResultHandler(GDBMIOutRecordHandler):
|
|||
RC_EXIT = 'exit'
|
||||
|
||||
def __init__(self, verbose=False):
|
||||
"""Constructor for GDB/MI result handler
|
||||
"""
|
||||
super(GDBMIResultHandler, self).__init__(None, verbose)
|
||||
self.result_class = None
|
||||
self.result_str = None
|
||||
|
||||
def _parse_rc(self, ln, rc):
|
||||
"""Parses result code
|
||||
"""
|
||||
rc_str = "{0}{1}".format(self.TAG, rc)
|
||||
if ln.startswith(rc_str):
|
||||
self.result_class = rc
|
||||
|
@ -786,6 +833,8 @@ class GDBMIResultHandler(GDBMIOutRecordHandler):
|
|||
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
|
||||
|
@ -797,17 +846,17 @@ class GDBMIResultHandler(GDBMIOutRecordHandler):
|
|||
return
|
||||
if self._parse_rc(ln, self.RC_EXIT):
|
||||
return
|
||||
print "Unknown result: '%s'" % ln
|
||||
print "Unknown GDB/MI result: '%s'" % ln
|
||||
|
||||
|
||||
class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler):
|
||||
""" TBD
|
||||
"""GDB/MI console stream handler class
|
||||
"""
|
||||
TAG = '~'
|
||||
|
||||
|
||||
def dbg_corefile(args):
|
||||
""" TBD
|
||||
""" Command to load core dump from file or flash and run GDB debug session with it
|
||||
"""
|
||||
global CLOSE_FDS
|
||||
loader = None
|
||||
|
@ -847,19 +896,14 @@ def dbg_corefile(args):
|
|||
|
||||
|
||||
def info_corefile(args):
|
||||
# def info_corefile(args):
|
||||
""" TBD
|
||||
""" 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):
|
||||
# print ln
|
||||
sys.stdout.write(ln)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def gdbmi_read2prompt(f, out_handlers=None):
|
||||
""" TBD
|
||||
"""
|
||||
while True:
|
||||
ln = f.readline().rstrip(' \r\n')
|
||||
if ln == '(gdb)':
|
||||
|
@ -908,10 +952,11 @@ def info_corefile(args):
|
|||
gdbmi_read2prompt(p.stdout, handlers)
|
||||
exe_elf = ESPCoreDumpElfFile(args.prog)
|
||||
core_elf = ESPCoreDumpElfFile(core_fname)
|
||||
merged_segs = []#[(s, 0) for s in exe_elf.sections if s.flags & (esptool.ELFSection.SHF_ALLOC | esptool.ELFSection.SHF_WRITE)]
|
||||
merged_segs = []
|
||||
core_segs = core_elf.program_segments
|
||||
for s in exe_elf.sections:
|
||||
merged = False
|
||||
for ps in core_elf.program_segments:
|
||||
for ps in core_segs:
|
||||
if ps.addr <= s.addr and ps.addr + len(ps.data) >= s.addr:
|
||||
# sec: |XXXXXXXXXX|
|
||||
# seg: |...XXX.............|
|
||||
|
@ -927,6 +972,7 @@ def info_corefile(args):
|
|||
# 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|
|
||||
|
@ -943,11 +989,11 @@ def info_corefile(args):
|
|||
# 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))
|
||||
# merged_segs.append(('None', ps.addr, len(ps.data), 'None'))
|
||||
|
||||
print "==============================================================="
|
||||
print "==================== ESP32 CORE DUMP START ===================="
|
||||
|
@ -969,7 +1015,7 @@ def info_corefile(args):
|
|||
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 "\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])
|
||||
|
@ -1018,11 +1064,6 @@ def main():
|
|||
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')
|
||||
|
@ -1054,10 +1095,7 @@ def main():
|
|||
|
||||
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.
|
||||
print 'espcoredump.py v%s' % __version__
|
||||
|
||||
operation_func = globals()[args.operation]
|
||||
operation_func(args)
|
||||
|
|
|
@ -2185,7 +2185,15 @@ eSleepModeStatus eTaskConfirmSleepModeStatus( void ) PRIVILEGED_FUNCTION;
|
|||
*/
|
||||
void *pvTaskIncrementMutexHeldCount( void );
|
||||
|
||||
/* Used by core dump facility to get list of task handles. */
|
||||
/*
|
||||
* This function fills array with TaskSnapshot_t structures for every task in the system.
|
||||
* Used by core dump facility to get snapshots of all tasks in the system.
|
||||
* Only available when configENABLE_TASK_SNAPSHOT is set to 1.
|
||||
* @param pxTaskSnapshotArray Pointer to array of TaskSnapshot_t structures to store tasks snapshot data.
|
||||
* @param uxArraySize Size of tasks snapshots array.
|
||||
* @param pxTcbSz Pointer to store size of TCB.
|
||||
* @return Number of elements stored in array.
|
||||
*/
|
||||
UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz );
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -41,13 +41,13 @@ void spi_flash_disable_interrupts_caches_and_other_cpu();
|
|||
void spi_flash_enable_interrupts_caches_and_other_cpu();
|
||||
|
||||
// Disables non-IRAM interrupt handlers on current CPU and caches on both CPUs.
|
||||
// This function is implied to be called from panic handler or when no OS is present
|
||||
// when non-current CPU is halted and can not execute code from flash.
|
||||
// This function is implied to be called from panic handler
|
||||
// (when non-current CPU is halted and can not execute code from flash) or when no OS is present.
|
||||
void spi_flash_disable_interrupts_caches_and_other_cpu_no_os();
|
||||
|
||||
// Enable cache, enable interrupts (to be added in future) on current CPU.
|
||||
// This function is implied to be called from panic handler or when no OS is present
|
||||
// when non-current CPU is halted and can not execute code from flash.
|
||||
// Enable cache, enable interrupts on current CPU.
|
||||
// This function is implied to be called from panic handler
|
||||
// (when non-current CPU is halted and can not execute code from flash) or when no OS is present.
|
||||
void spi_flash_enable_interrupts_caches_no_os();
|
||||
|
||||
#endif //ESP_SPI_FLASH_CACHE_UTILS_H
|
||||
|
|
|
@ -184,6 +184,9 @@ typedef void (*spi_flash_guard_end_func_t)(void);
|
|||
|
||||
/**
|
||||
* Structure holding SPI flash access critical section management functions
|
||||
*
|
||||
* @note Structure and corresponding guard functions should not reside in flash.
|
||||
* For example structure can be placed in DRAM and functions in IRAM sections.
|
||||
*/
|
||||
typedef struct {
|
||||
spi_flash_guard_start_func_t start; /**< critical section start func */
|
||||
|
@ -191,25 +194,25 @@ typedef struct {
|
|||
} spi_flash_guard_funcs_t;
|
||||
|
||||
/**
|
||||
* @brief Erase a range of flash sectors.
|
||||
* @brief Sets guard functions to access flash.
|
||||
*
|
||||
* @note Pointed structure and corresponding guard functions should not reside in flash.
|
||||
* For example structure can be placed in DRAM and functions in IRAM sections.
|
||||
*
|
||||
* @param start_address Address where erase operation has to start.
|
||||
* Must be 4kB-aligned
|
||||
* @param size Size of erased range, in bytes. Must be divisible by 4kB.
|
||||
*
|
||||
* @return esp_err_t
|
||||
* @param funcs pointer to structure holding flash access guard functions.
|
||||
*/
|
||||
void spi_flash_guard_set(const spi_flash_guard_funcs_t* funcs);
|
||||
|
||||
/** Default OS-aware flash access critical section functions */
|
||||
/**
|
||||
* @brief Default OS-aware flash access guard functions
|
||||
*/
|
||||
extern const spi_flash_guard_funcs_t g_flash_guard_default_ops;
|
||||
|
||||
/** Non-OS flash access critical section functions
|
||||
/**
|
||||
* @brief Non-OS flash access guard functions
|
||||
*
|
||||
* @note This version of functions is to be used when no OS is present or from panic handler.
|
||||
* It does not use any OS primitives and IPC and implies that
|
||||
* only calling CPU is active.
|
||||
* @note This version of flash guard functions is to be used when no OS is present or from panic handler.
|
||||
* It does not use any OS primitives and IPC and implies that only calling CPU is active.
|
||||
*/
|
||||
extern const spi_flash_guard_funcs_t g_flash_guard_no_os_ops;
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@ INPUT = ../components/esp32/include/esp_wifi.h \
|
|||
../components/esp32/include/esp_deep_sleep.h \
|
||||
../components/sdmmc/include/sdmmc_cmd.h \
|
||||
../components/fatfs/src/esp_vfs_fat.h \
|
||||
../components/fatfs/src/diskio.h
|
||||
../components/fatfs/src/diskio.h \
|
||||
../components/esp32/include/esp_core_dump.h
|
||||
|
||||
## Get warnings for functions that have no documentation for their parameters or return value
|
||||
##
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
ESP32 Core Dump
|
||||
================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analisys of software state at the moment of failure.
|
||||
Upon the crash system enters panic state, prints some information and halts or reboots depending configuration. User can choose to generate core dump in order to analyse
|
||||
the reason of failure on PC later on. Core dump contains snapshots of all tasks in the system at the moment of failure. Snapshots include tasks control blocks (TCB) and stacks.
|
||||
So it is possible to find out what task, at what instruction (line of code) and what callstack of that task lead to the crash.
|
||||
ESP-IDF provides special script `espcoredump.py` to help users to retrieve and analyse core dumps. This tool provides two commands for core dumps analysis:
|
||||
|
||||
* info_corefile - prints crashed task's registers, callstack, list of available tasks in the system, memory regions and contents of memory stored in core dump (TCBs and stacks)
|
||||
* dbg_corefile - creates core dump ELF file and runs GDB debug session with this file. User can examine memory, variables and tasks states manually. Note that since not all memory is saved in core dump only values of variables allocated on stack will be meaningfull
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Currently there are three options related to core dump generation which user can choose in configuration menu of the application (`make menuconfig`):
|
||||
|
||||
* Disable core dump generation
|
||||
* Save core dump to flash
|
||||
* Print core dump to UART
|
||||
|
||||
These options can be choosen in Components -> ESP32-specific config -> Core dump destination menu item.
|
||||
|
||||
Save core dump to flash
|
||||
-----------------------
|
||||
|
||||
When this option is selected core dumps are saved to special partition on flash. When using default partition table files which are provided with ESP-IDF it automatically
|
||||
allocates necessary space on flash, But if user wants to use its own layout file together with core dump feature it should define separate partition for core dump
|
||||
as it is shown below::
|
||||
|
||||
# Name, Type, SubType, Offset, Size
|
||||
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
|
||||
nvs, data, nvs, 0x9000, 0x6000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, app, factory, 0x10000, 1M
|
||||
coredump, data, 3, , 64K
|
||||
|
||||
There are no special requrements for partition name. It can be choosen according to the user application needs, but partition type should be 'data' and
|
||||
sub-type should be 3. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes.
|
||||
This overhead does not include size of TCB and stack for every task. So partirion size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes.
|
||||
|
||||
The example of generic command to analyze core dump from flash is: `espcoredump.py -p </path/to/serial/port> info_corefile </path/to/program/elf/file>`
|
||||
or `espcoredump.py -p </path/to/serial/port> dbg_corefile </path/to/program/elf/file>`
|
||||
|
||||
Print core dump to UART
|
||||
-----------------------
|
||||
|
||||
When this option is selected base64-encoded core dumps are printed on UART upon system panic. In this case user should save core dump text body to some file manually and
|
||||
then run the following command: `espcoredump.py -p </path/to/serial/port> info_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>`
|
||||
or `espcoredump.py -p </path/to/serial/port> dbg_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>`
|
||||
|
||||
Base64-encoded body of core dump will be between the following header and footer::
|
||||
|
||||
================= CORE DUMP START =================
|
||||
<body of base64-encoded core dump, copy it to file on disk>
|
||||
================= CORE DUMP END ===================
|
||||
|
||||
Command Options For 'espcoredump.py'
|
||||
--------------------------------------------
|
||||
|
||||
usage: coredumper [-h] [--chip {auto,esp32}] [--port PORT] [--baud BAUD]
|
||||
{dbg_corefile,info_corefile} ...
|
||||
|
||||
espcoredump.py v0.1-dev - ESP32 Core Dump Utility
|
||||
|
||||
positional arguments:
|
||||
{dbg_corefile,info_corefile}
|
||||
Run coredumper {command} -h for additional help
|
||||
dbg_corefile Starts GDB debugging session with specified corefile
|
||||
info_corefile Print core dump info from file
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--chip {auto,esp32}, -c {auto,esp32}
|
||||
Target chip type
|
||||
--port PORT, -p PORT Serial port device
|
||||
--baud BAUD, -b BAUD Serial port baud rate used when flashing/reading
|
||||
|
||||
|
||||
usage: coredumper info_corefile [-h] [--gdb GDB] [--core CORE]
|
||||
[--core-format CORE_FORMAT] [--off OFF]
|
||||
[--save-core SAVE_CORE] [--print-mem]
|
||||
prog
|
||||
|
||||
positional arguments:
|
||||
prog Path to program's ELF binary
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--gdb GDB, -g GDB Path to gdb
|
||||
--core CORE, -c CORE Path to core dump file (if skipped core dump will be
|
||||
read from flash)
|
||||
--core-format CORE_FORMAT, -t CORE_FORMAT
|
||||
(elf, raw or b64). File specified with "-c" is an ELF
|
||||
("elf"), raw (raw) or base64-encoded (b64) binary
|
||||
--off OFF, -o OFF Ofsset of coredump partition in flash (type "make
|
||||
partition_table" to see).
|
||||
--save-core SAVE_CORE, -s SAVE_CORE
|
||||
Save core to file. Othwerwise temporary core file will
|
||||
be deleted. Does not work with "-c"
|
||||
--print-mem, -m Print memory dump
|
||||
|
||||
|
||||
usage: coredumper dbg_corefile [-h] [--gdb GDB] [--core CORE]
|
||||
[--core-format CORE_FORMAT] [--off OFF]
|
||||
[--save-core SAVE_CORE]
|
||||
prog
|
||||
|
||||
positional arguments:
|
||||
prog Path to program's ELF binary
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--gdb GDB, -g GDB Path to gdb
|
||||
--core CORE, -c CORE Path to core dump file (if skipped core dump will be
|
||||
read from flash)
|
||||
--core-format CORE_FORMAT, -t CORE_FORMAT
|
||||
(elf, raw or b64). File specified with "-c" is an ELF
|
||||
("elf"), raw (raw) or base64-encoded (b64) binary
|
||||
--off OFF, -o OFF Ofsset of coredump partition in flash (type "make
|
||||
partition_table" to see).
|
||||
--save-core SAVE_CORE, -s SAVE_CORE
|
||||
Save core to file. Othwerwise temporary core file will
|
||||
be deleted. Ignored with "-c"
|
Ładowanie…
Reference in New Issue