feat(coredump): add esp32s2 and esp32c3 support

This commit is contained in:
Fu Hanxi
2021-04-09 11:39:37 +08:00
parent f9cf648afd
commit fbfef19982
14 changed files with 530 additions and 271 deletions

View File

@ -1,5 +1,5 @@
# #
# Copyright 2021 Espressif Systems (Shanghai) PTE LTD # Copyright 2021 Espressif Systems (Shanghai) CO., LTD
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,7 +16,22 @@
__version__ = '0.4-dev' __version__ = '0.4-dev'
import abc
import os
from abc import abstractmethod from abc import abstractmethod
from importlib import import_module
from future.utils import with_metaclass
try:
from typing import Optional, Tuple
except ImportError:
pass
IDF_PATH = os.path.normpath(os.getenv('IDF_PATH', '.'))
XTENSA_TARGETS = ['esp32', 'esp32s2']
RISCV_TARGETS = ['esp32c3']
SUPPORTED_TARGETS = XTENSA_TARGETS + RISCV_TARGETS
class ESPCoreDumpError(RuntimeError): class ESPCoreDumpError(RuntimeError):
@ -27,42 +42,84 @@ class ESPCoreDumpLoaderError(ESPCoreDumpError):
pass pass
class _TargetMethodsBase(object): class BaseArchMethodsMixin(with_metaclass(abc.ABCMeta)): # type: ignore
@staticmethod
@abstractmethod
def tcb_is_sane(tcb_addr, tcb_size):
"""
Check tcb address if it is correct
"""
return False
@staticmethod
@abstractmethod
def stack_is_sane(sp):
"""
Check stack address if it is correct
"""
return False
@staticmethod
@abstractmethod
def addr_is_fake(addr):
"""
Check if address is in fake area
"""
return False
class _ArchMethodsBase(object):
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def get_registers_from_stack(data, grows_down): def get_registers_from_stack(data, grows_down):
# type: (bytes, bool) -> Tuple[list[int], Optional[dict[int, int]]]
""" """
Returns list of registers (in GDB format) from stack frame Parse stack data, growing up stacks are not supported for now.
:param data: stack data
:param grows_down: stack grow direction
:return: return tuple (regs, exception_regs)
""" """
return [], {} pass
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def build_prstatus_data(tcb_addr, task_regs): def build_prstatus_data(tcb_addr, task_regs): # type: (int, list[int]) -> str
return b'' """
Build PrStatus note section
:param tcb_addr: tcb addr
:param task_regs: registers
:return: str
"""
pass
class BaseTargetMethods(with_metaclass(abc.ABCMeta, BaseArchMethodsMixin)): # type: ignore
UNKNOWN = 'unknown'
TARGET = UNKNOWN
COREDUMP_FAKE_STACK_START = 0x20000000
COREDUMP_FAKE_STACK_LIMIT = 0x30000000
COREDUMP_MAX_TASK_STACK_SIZE = 64 * 1024
def __init__(self): # type: () -> None
if self.TARGET == self.UNKNOWN:
raise ValueError('Please use the derived child-class with valid TARGET')
self._set_attr_from_soc_header()
def _set_attr_from_soc_header(self): # type: () -> None
module = import_module('corefile.soc_headers.{}'.format(self.TARGET))
for k, v in module.__dict__.items():
if k.startswith('SOC_'):
setattr(self, k, v)
def _esp_ptr_in_dram(self, addr): # type: (int) -> bool
return self.SOC_DRAM_LOW <= addr < self.SOC_DRAM_HIGH # type: ignore
def _esp_ptr_in_iram(self, addr): # type: (int) -> bool
return self.SOC_IRAM_LOW <= addr < self.SOC_IRAM_HIGH # type: ignore
def _esp_ptr_in_rtc_slow(self, addr): # type: (int) -> bool
return self.SOC_RTC_DATA_LOW <= addr < self.SOC_RTC_DATA_HIGH # type: ignore
def _esp_ptr_in_rtc_dram_fast(self, addr): # type: (int) -> bool
return self.SOC_RTC_DRAM_LOW <= addr < self.SOC_RTC_DRAM_HIGH # type: ignore
def tcb_is_sane(self, tcb_addr, tcb_size): # type: (int, int) -> bool
for func in [self._esp_ptr_in_dram,
self._esp_ptr_in_iram,
self._esp_ptr_in_rtc_slow,
self._esp_ptr_in_rtc_dram_fast]:
res = func(tcb_addr) and func(tcb_addr + tcb_size - 1)
if res:
return True
return False
def _esp_stack_ptr_in_dram(self, addr): # type: (int) -> bool
return not (addr < self.SOC_DRAM_LOW + 0x10
or addr > self.SOC_DRAM_HIGH - 0x10
or (addr & 0xF) != 0)
def stack_is_sane(self, stack_start, stack_end): # type: (int, int) -> bool
return (self._esp_stack_ptr_in_dram(stack_start)
and self._esp_ptr_in_dram(stack_end)
and stack_start < stack_end
and (stack_end - stack_start) < self.COREDUMP_MAX_TASK_STACK_SIZE)
def addr_is_fake(self, addr): # type: (int) -> bool
return (self.COREDUMP_FAKE_STACK_START <= addr < self.COREDUMP_FAKE_STACK_LIMIT
or addr > 2 ** 31 - 1)

View File

@ -0,0 +1,44 @@
"""
This file is used to generate soc header constants into sub-package soc_headers
"""
import os
from ast import literal_eval
from . import IDF_PATH, SUPPORTED_TARGETS
def main(): # type: () -> None
constants = [
'SOC_DRAM_LOW',
'SOC_DRAM_HIGH',
'SOC_IRAM_LOW',
'SOC_IRAM_HIGH',
'SOC_RTC_DATA_LOW',
'SOC_RTC_DATA_HIGH',
'SOC_RTC_DRAM_LOW',
'SOC_RTC_DRAM_HIGH',
]
for target in SUPPORTED_TARGETS:
target_constants = {}
soc_header_fp = os.path.join(IDF_PATH, 'components/soc/{}/include/soc/soc.h'.format(target))
module_fp = os.path.join(IDF_PATH, 'components', 'espcoredump', 'corefile', 'soc_headers',
'{}.py'.format(target))
with open(soc_header_fp) as fr:
for line in fr.readlines():
for attr in constants:
if '#define {}'.format(attr) in line:
target_constants[attr] = literal_eval(line.strip().split()[-1])
for attr in constants:
if attr not in target_constants:
raise ValueError('ERROR: Attr {} is missing in {}'.format(attr, soc_header_fp))
with open(module_fp, 'w') as fw:
for k, v in target_constants.items():
fw.write('{} = {}\n'.format(k, hex(v)))
if __name__ == '__main__':
main()

View File

@ -1,5 +1,5 @@
# #
# Copyright 2021 Espressif Systems (Shanghai) PTE LTD # Copyright 2021 Espressif Systems (Shanghai) CO., LTD
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,8 +17,13 @@
import hashlib import hashlib
import os import os
from construct import (AlignedStruct, Bytes, Const, GreedyRange, Int16ul, Int32ul, Padding, Pointer, Sequence, Struct, from construct import (AlignedStruct, Bytes, Const, Container, GreedyRange, Int16ul, Int32ul, Padding, Pointer,
this) Sequence, Struct, this)
try:
from typing import Optional
except ImportError:
pass
# Following structs are based on spec # Following structs are based on spec
# https://refspecs.linuxfoundation.org/elf/elf.pdf # https://refspecs.linuxfoundation.org/elf/elf.pdf
@ -110,12 +115,13 @@ class ElfFile(object):
EV_CURRENT = 0x01 EV_CURRENT = 0x01
def __init__(self, elf_path=None, e_type=None, e_machine=None): def __init__(self, elf_path=None, e_type=None, e_machine=None):
# type: (Optional[str], Optional[int], Optional[int]) -> None
self.e_type = e_type self.e_type = e_type
self.e_machine = e_machine self.e_machine = e_machine
self._struct = None # construct Struct self._struct = None # type: Optional[Struct]
self._model = None # construct Container self._model = None # type: Optional[Container]
self._section_names = [] # type: list[str] self._section_names = {} # type: dict[int, str]
self.sections = [] # type: list[ElfSection] self.sections = [] # type: list[ElfSection]
self.load_segments = [] # type: list[ElfSegment] self.load_segments = [] # type: list[ElfSegment]
@ -171,7 +177,7 @@ class ElfFile(object):
name += c name += c
return res return res
def _generate_struct_from_headers(self, header_tables): def _generate_struct_from_headers(self, header_tables): # type: (Container) -> Struct
""" """
Generate ``construct`` Struct for this file Generate ``construct`` Struct for this file
:param header_tables: contains elf_header, program_headers, section_headers :param header_tables: contains elf_header, program_headers, section_headers
@ -219,12 +225,12 @@ class ElfFile(object):
return Struct(*args) return Struct(*args)
@property @property
def sha256(self): def sha256(self): # type: () -> bytes
""" """
:return: SHA256 hash of the input ELF file :return: SHA256 hash of the input ELF file
""" """
sha256 = hashlib.sha256() sha256 = hashlib.sha256()
sha256.update(self._struct.build(self._model)) sha256.update(self._struct.build(self._model)) # type: ignore
return sha256.digest() return sha256.digest()
@ -234,13 +240,13 @@ class ElfSection(object):
SHF_EXECINSTR = 0x04 SHF_EXECINSTR = 0x04
SHF_MASKPROC = 0xf0000000 SHF_MASKPROC = 0xf0000000
def __init__(self, name, addr, data, flags): def __init__(self, name, addr, data, flags): # type: (str, int, bytes, int) -> None
self.name = name self.name = name
self.addr = addr self.addr = addr
self.data = data self.data = data
self.flags = flags self.flags = flags
def attr_str(self): def attr_str(self): # type: () -> str
if self.flags & self.SHF_MASKPROC: if self.flags & self.SHF_MASKPROC:
return 'MS' return 'MS'
@ -250,7 +256,7 @@ class ElfSection(object):
res += 'A' if self.flags & self.SHF_ALLOC else ' ' res += 'A' if self.flags & self.SHF_ALLOC else ' '
return res return res
def __repr__(self): def __repr__(self): # type: () -> str
return '{:>32} [Addr] 0x{:>08X}, [Size] 0x{:>08X} {:>4}' \ return '{:>32} [Addr] 0x{:>08X}, [Size] 0x{:>08X} {:>4}' \
.format(self.name, self.addr, len(self.data), self.attr_str()) .format(self.name, self.addr, len(self.data), self.attr_str())
@ -260,13 +266,13 @@ class ElfSegment(object):
PF_W = 0x02 PF_W = 0x02
PF_R = 0x04 PF_R = 0x04
def __init__(self, addr, data, flags): def __init__(self, addr, data, flags): # type: (int, bytes, int) -> None
self.addr = addr self.addr = addr
self.data = data self.data = data
self.flags = flags self.flags = flags
self.type = ElfFile.PT_LOAD self.type = ElfFile.PT_LOAD
def attr_str(self): def attr_str(self): # type: () -> str
res = '' res = ''
res += 'R' if self.flags & self.PF_R else ' ' res += 'R' if self.flags & self.PF_R else ' '
res += 'W' if self.flags & self.PF_W else ' ' res += 'W' if self.flags & self.PF_W else ' '
@ -274,22 +280,22 @@ class ElfSegment(object):
return res return res
@staticmethod @staticmethod
def _type_str(): def _type_str(): # type: () -> str
return 'LOAD' return 'LOAD'
def __repr__(self): def __repr__(self): # type: () -> str
return '{:>8} Addr 0x{:>08X}, Size 0x{:>08X} Flags {:4}' \ return '{:>8} Addr 0x{:>08X}, Size 0x{:>08X} Flags {:4}' \
.format(self._type_str(), self.addr, len(self.data), self.attr_str()) .format(self._type_str(), self.addr, len(self.data), self.attr_str())
class ElfNoteSegment(ElfSegment): class ElfNoteSegment(ElfSegment):
def __init__(self, addr, data, flags): def __init__(self, addr, data, flags): # type: (int, bytes, int) -> None
super(ElfNoteSegment, self).__init__(addr, data, flags) super(ElfNoteSegment, self).__init__(addr, data, flags)
self.type = ElfFile.PT_NOTE self.type = ElfFile.PT_NOTE
self.note_secs = NoteSections.parse(self.data) self.note_secs = NoteSections.parse(self.data)
@staticmethod @staticmethod
def _type_str(): def _type_str(): # type: () -> str
return 'NOTE' return 'NOTE'
@ -316,13 +322,15 @@ class ESPCoreDumpElfFile(ElfFile):
# ELF file machine type # ELF file machine type
EM_XTENSA = 0x5E EM_XTENSA = 0x5E
EM_RISCV = 0xF3
def __init__(self, elf_path=None, e_type=None, e_machine=None): def __init__(self, elf_path=None, e_type=None, e_machine=None):
# type: (Optional[str], Optional[int], Optional[int]) -> None
_e_type = e_type or self.ET_CORE _e_type = e_type or self.ET_CORE
_e_machine = e_machine or self.EM_XTENSA _e_machine = e_machine or self.EM_XTENSA
super(ESPCoreDumpElfFile, self).__init__(elf_path, _e_type, _e_machine) super(ESPCoreDumpElfFile, self).__init__(elf_path, _e_type, _e_machine)
def add_segment(self, addr, data, seg_type, flags): def add_segment(self, addr, data, seg_type, flags): # type: (int, bytes, int, int) -> None
if seg_type != self.PT_NOTE: if seg_type != self.PT_NOTE:
self.load_segments.append(ElfSegment(addr, data, flags)) self.load_segments.append(ElfSegment(addr, data, flags))
else: else:
@ -352,7 +360,7 @@ class ESPCoreDumpElfFile(ElfFile):
}) })
offset = ElfHeader.sizeof() + (len(self.load_segments) + len(self.note_segments)) * ProgramHeader.sizeof() offset = ElfHeader.sizeof() + (len(self.load_segments) + len(self.note_segments)) * ProgramHeader.sizeof()
_segments = self.load_segments + self.note_segments _segments = self.load_segments + self.note_segments # type: ignore
for seg in _segments: for seg in _segments:
res += ProgramHeader.build({ res += ProgramHeader.build({
'p_type': seg.type, 'p_type': seg.type,

View File

@ -1,5 +1,5 @@
# #
# Copyright 2021 Espressif Systems (Shanghai) PTE LTD # Copyright 2021 Espressif Systems (Shanghai) CO., LTD
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -18,19 +18,14 @@ import logging
import re import re
import time import time
from . import ESPCoreDumpError
try:
import typing
except ImportError:
pass
from pygdbmi.gdbcontroller import DEFAULT_GDB_TIMEOUT_SEC, GdbController from pygdbmi.gdbcontroller import DEFAULT_GDB_TIMEOUT_SEC, GdbController
from . import ESPCoreDumpError
class EspGDB(object): class EspGDB(object):
def __init__(self, gdb_path, gdb_cmds, core_filename, prog_filename, timeout_sec=DEFAULT_GDB_TIMEOUT_SEC): def __init__(self, gdb_path, gdb_cmds, core_filename, prog_filename, timeout_sec=DEFAULT_GDB_TIMEOUT_SEC):
# type: (str, typing.List[str], str, str, int) -> None
""" """
Start GDB and initialize a GdbController instance Start GDB and initialize a GdbController instance
""" """
@ -59,7 +54,7 @@ class EspGDB(object):
def _gdbmi_run_cmd_get_responses(self, cmd, resp_message, resp_type, multiple=True, def _gdbmi_run_cmd_get_responses(self, cmd, resp_message, resp_type, multiple=True,
done_message=None, done_type=None): done_message=None, done_type=None):
# type: (str, typing.Optional[str], str, bool, typing.Optional[str], typing.Optional[str]) -> list
self.p.write(cmd, read_response=False) self.p.write(cmd, read_response=False)
t_end = time.time() + self.timeout t_end = time.time() + self.timeout
filtered_response_list = [] filtered_response_list = []
@ -80,15 +75,15 @@ class EspGDB(object):
return filtered_response_list return filtered_response_list
def _gdbmi_run_cmd_get_one_response(self, cmd, resp_message, resp_type): def _gdbmi_run_cmd_get_one_response(self, cmd, resp_message, resp_type):
# type: ( str, typing.Optional[str], str) -> dict
return self._gdbmi_run_cmd_get_responses(cmd, resp_message, resp_type, multiple=False)[0] return self._gdbmi_run_cmd_get_responses(cmd, resp_message, resp_type, multiple=False)[0]
def _gdbmi_data_evaluate_expression(self, expr): # type: (str) -> str def _gdbmi_data_evaluate_expression(self, expr):
""" Get the value of an expression, similar to the 'print' command """ """ Get the value of an expression, similar to the 'print' command """
return self._gdbmi_run_cmd_get_one_response("-data-evaluate-expression \"%s\"" % expr, return self._gdbmi_run_cmd_get_one_response("-data-evaluate-expression \"%s\"" % expr,
'done', 'result')['payload']['value'] 'done', 'result')['payload']['value']
def get_freertos_task_name(self, tcb_addr): # type: (int) -> str def get_freertos_task_name(self, tcb_addr):
""" Get FreeRTOS task name given the TCB address """ """ Get FreeRTOS task name given the TCB address """
try: try:
val = self._gdbmi_data_evaluate_expression('(char*)((TCB_t *)0x%x)->pcTaskName' % tcb_addr) val = self._gdbmi_data_evaluate_expression('(char*)((TCB_t *)0x%x)->pcTaskName' % tcb_addr)
@ -102,7 +97,7 @@ class EspGDB(object):
return result.group(1) return result.group(1)
return '' return ''
def run_cmd(self, gdb_cmd): # type: (str) -> str def run_cmd(self, gdb_cmd):
""" Execute a generic GDB console command via MI2 """ Execute a generic GDB console command via MI2
""" """
filtered_responses = self._gdbmi_run_cmd_get_responses(cmd="-interpreter-exec console \"%s\"" % gdb_cmd, filtered_responses = self._gdbmi_run_cmd_get_responses(cmd="-interpreter-exec console \"%s\"" % gdb_cmd,
@ -113,14 +108,14 @@ class EspGDB(object):
.replace('\\t', '\t') \ .replace('\\t', '\t') \
.rstrip('\n') .rstrip('\n')
def get_thread_info(self): # type: () -> (typing.List[dict], str) def get_thread_info(self):
""" Get information about all threads known to GDB, and the current thread ID """ """ Get information about all threads known to GDB, and the current thread ID """
result = self._gdbmi_run_cmd_get_one_response('-thread-info', 'done', 'result')['payload'] result = self._gdbmi_run_cmd_get_one_response('-thread-info', 'done', 'result')['payload']
current_thread_id = result['current-thread-id'] current_thread_id = result['current-thread-id']
threads = result['threads'] threads = result['threads']
return threads, current_thread_id return threads, current_thread_id
def switch_thread(self, thr_id): # type: (int) -> None def switch_thread(self, thr_id):
""" Tell GDB to switch to a specific thread, given its ID """ """ Tell GDB to switch to a specific thread, given its ID """
self._gdbmi_run_cmd_get_one_response('-thread-select %s' % thr_id, 'done', 'result') self._gdbmi_run_cmd_get_one_response('-thread-select %s' % thr_id, 'done', 'result')
@ -129,6 +124,6 @@ class EspGDB(object):
return list(filter(lambda rsp: rsp['message'] == resp_message and rsp['type'] == resp_type, responses)) return list(filter(lambda rsp: rsp['message'] == resp_message and rsp['type'] == resp_type, responses))
@staticmethod @staticmethod
def gdb2freertos_thread_id(gdb_target_id): # type: (str) -> int def gdb2freertos_thread_id(gdb_target_id):
""" Convert GDB 'target ID' to the FreeRTOS TCB address """ """ Convert GDB 'target ID' to the FreeRTOS TCB address """
return int(gdb_target_id.replace('process ', ''), 0) return int(gdb_target_id.replace('process ', ''), 0)

View File

@ -1,5 +1,5 @@
# #
# Copyright 2021 Espressif Systems (Shanghai) PTE LTD # Copyright 2021 Espressif Systems (Shanghai) CO., LTD
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -25,12 +25,18 @@ import tempfile
from construct import AlignedStruct, Bytes, GreedyRange, Int32ul, Padding, Struct, abs_, this from construct import AlignedStruct, Bytes, GreedyRange, Int32ul, Padding, Struct, abs_, this
from . import ESPCoreDumpLoaderError, _ArchMethodsBase, _TargetMethodsBase from . import ESPCoreDumpLoaderError
from .elf import (TASK_STATUS_CORRECT, TASK_STATUS_TCB_CORRUPTED, ElfFile, ElfSegment, ESPCoreDumpElfFile, from .elf import (TASK_STATUS_CORRECT, TASK_STATUS_TCB_CORRUPTED, ElfFile, ElfSegment, ESPCoreDumpElfFile,
EspTaskStatus, NoteSection) EspTaskStatus, NoteSection)
from .xtensa import _ArchMethodsXtensa, _TargetMethodsESP32 from .riscv import Esp32c3Methods
from .xtensa import Esp32Methods, Esp32S2Methods
IDF_PATH = os.getenv('IDF_PATH') try:
from typing import Optional, Tuple
except ImportError:
pass
IDF_PATH = os.getenv('IDF_PATH', '')
PARTTOOL_PY = os.path.join(IDF_PATH, 'components', 'partition_table', 'parttool.py') PARTTOOL_PY = os.path.join(IDF_PATH, 'components', 'partition_table', 'parttool.py')
ESPTOOL_PY = os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool', 'esptool.py') ESPTOOL_PY = os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool', 'esptool.py')
@ -74,12 +80,14 @@ class EspCoreDumpVersion(object):
# This class contains all version-dependent params # This class contains all version-dependent params
ESP32 = 0 ESP32 = 0
ESP32S2 = 2 ESP32S2 = 2
XTENSA_CHIPS = [ESP32, ESP32S2] XTENSA_CHIPS = [ESP32, ESP32S2]
ESP_COREDUMP_TARGETS = XTENSA_CHIPS ESP32C3 = 5
RISCV_CHIPS = [ESP32C3]
def __init__(self, version=None): COREDUMP_SUPPORTED_TARGETS = XTENSA_CHIPS + RISCV_CHIPS
def __init__(self, version=None): # type: (int) -> None
"""Constructor for core dump version """Constructor for core dump version
""" """
super(EspCoreDumpVersion, self).__init__() super(EspCoreDumpVersion, self).__init__()
@ -89,26 +97,26 @@ class EspCoreDumpVersion(object):
self.set_version(version) self.set_version(version)
@staticmethod @staticmethod
def make_dump_ver(major, minor): def make_dump_ver(major, minor): # type: (int, int) -> int
return ((major & 0xFF) << 8) | ((minor & 0xFF) << 0) return ((major & 0xFF) << 8) | ((minor & 0xFF) << 0)
def set_version(self, version): def set_version(self, version): # type: (int) -> None
self.version = version self.version = version
@property @property
def chip_ver(self): def chip_ver(self): # type: () -> int
return (self.version & 0xFFFF0000) >> 16 return (self.version & 0xFFFF0000) >> 16
@property @property
def dump_ver(self): def dump_ver(self): # type: () -> int
return self.version & 0x0000FFFF return self.version & 0x0000FFFF
@property @property
def major(self): def major(self): # type: () -> int
return (self.version & 0x0000FF00) >> 8 return (self.version & 0x0000FF00) >> 8
@property @property
def minor(self): def minor(self): # type: () -> int
return self.version & 0x000000FF return self.version & 0x000000FF
@ -119,42 +127,37 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
ELF_CRC32 = EspCoreDumpVersion.make_dump_ver(1, 0) ELF_CRC32 = EspCoreDumpVersion.make_dump_ver(1, 0)
ELF_SHA256 = EspCoreDumpVersion.make_dump_ver(1, 1) ELF_SHA256 = EspCoreDumpVersion.make_dump_ver(1, 1)
def __init__(self): def __init__(self): # type: () -> None
super(EspCoreDumpLoader, self).__init__() super(EspCoreDumpLoader, self).__init__()
self.core_src_file = None self.core_src_file = None # type: Optional[str]
self.core_src_struct = None self.core_src_struct = None
self.core_src = None self.core_src = None
self.core_elf_file = None self.core_elf_file = None # type: Optional[str]
self.header = None self.header = None
self.header_struct = EspCoreDumpV1Header self.header_struct = EspCoreDumpV1Header
self.checksum_struct = CRC self.checksum_struct = CRC
# These two method classes will be assigned in ``reload_coredump`` # target classes will be assigned in ``_reload_coredump``
self.target_method_cls = _TargetMethodsBase self.target_methods = Esp32Methods()
self.arch_method_cls = _ArchMethodsBase
self._temp_files = [] self.temp_files = [] # type: list[str]
def __del__(self): def _create_temp_file(self): # type: () -> str
if self.core_src_file:
self.core_src_file.close()
if self.core_elf_file:
self.core_elf_file.close()
for f in self._temp_files:
try:
os.remove(f)
except OSError:
pass
def _create_temp_file(self):
t = tempfile.NamedTemporaryFile('wb', delete=False) t = tempfile.NamedTemporaryFile('wb', delete=False)
self._temp_files.append(t.name) # Here we close this at first to make sure the read/write is wrapped in context manager
return t # Otherwise the result will be wrong if you read while open in another session
t.close()
self.temp_files.append(t.name)
return t.name
def _reload_coredump(self): def _load_core_src(self): # type: () -> str
with open(self.core_src_file.name, 'rb') as fr: """
Write core elf into ``self.core_src``,
Return the target str by reading core elf
"""
with open(self.core_src_file, 'rb') as fr: # type: ignore
coredump_bytes = fr.read() coredump_bytes = fr.read()
_header = EspCoreDumpV1Header.parse(coredump_bytes) # first we use V1 format to get version _header = EspCoreDumpV1Header.parse(coredump_bytes) # first we use V1 format to get version
@ -179,23 +182,28 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
'data' / Bytes(this.header.tot_len - self.header_struct.sizeof() - self.checksum_struct.sizeof()), 'data' / Bytes(this.header.tot_len - self.header_struct.sizeof() - self.checksum_struct.sizeof()),
'checksum' / self.checksum_struct, 'checksum' / self.checksum_struct,
) )
self.core_src = self.core_src_struct.parse(coredump_bytes) self.core_src = self.core_src_struct.parse(coredump_bytes) # type: ignore
# Reload header if header struct changes after parsing # Reload header if header struct changes after parsing
if self.header_struct != EspCoreDumpV1Header: if self.header_struct != EspCoreDumpV1Header:
self.header = EspCoreDumpV2Header.parse(coredump_bytes) self.header = EspCoreDumpV2Header.parse(coredump_bytes)
if self.chip_ver in self.ESP_COREDUMP_TARGETS: if self.chip_ver in self.COREDUMP_SUPPORTED_TARGETS:
if self.chip_ver == self.ESP32: if self.chip_ver == self.ESP32:
self.target_method_cls = _TargetMethodsESP32 self.target_methods = Esp32Methods() # type: ignore
elif self.chip_ver == self.ESP32S2:
if self.chip_ver in self.XTENSA_CHIPS: self.target_methods = Esp32S2Methods() # type: ignore
self.arch_method_cls = _ArchMethodsXtensa elif self.chip_ver == self.ESP32C3:
self.target_methods = Esp32c3Methods() # type: ignore
else:
raise NotImplementedError
else: else:
raise ESPCoreDumpLoaderError('Core dump chip "0x%x" is not supported!' % self.chip_ver) raise ESPCoreDumpLoaderError('Core dump chip "0x%x" is not supported!' % self.chip_ver)
def _validate_dump_file(self): return self.target_methods.TARGET # type: ignore
if self.chip_ver not in self.ESP_COREDUMP_TARGETS:
def _validate_dump_file(self): # type: () -> None
if self.chip_ver not in self.COREDUMP_SUPPORTED_TARGETS:
raise ESPCoreDumpLoaderError('Invalid core dump chip version: "{}", should be <= "0x{:X}"' raise ESPCoreDumpLoaderError('Invalid core dump chip version: "{}", should be <= "0x{:X}"'
.format(self.chip_ver, self.ESP32S2)) .format(self.chip_ver, self.ESP32S2))
@ -204,20 +212,24 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
elif self.checksum_struct == SHA256: elif self.checksum_struct == SHA256:
self._sha256_validate() self._sha256_validate()
def _crc_validate(self): def _crc_validate(self): # type: () -> None
data_crc = binascii.crc32(EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) & 0xffffffff data_crc = binascii.crc32(
if data_crc != self.core_src.checksum: EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) & 0xffffffff # type: ignore
raise ESPCoreDumpLoaderError('Invalid core dump CRC %x, should be %x' % (data_crc, self.core_src.crc)) if data_crc != self.core_src.checksum: # type: ignore
raise ESPCoreDumpLoaderError(
'Invalid core dump CRC %x, should be %x' % (data_crc, self.core_src.crc)) # type: ignore
def _sha256_validate(self): def _sha256_validate(self): # type: () -> None
data_sha256 = hashlib.sha256(EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) data_sha256 = hashlib.sha256(
EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) # type: ignore
data_sha256_str = data_sha256.hexdigest() data_sha256_str = data_sha256.hexdigest()
sha256_str = binascii.hexlify(self.core_src.checksum).decode('ascii') sha256_str = binascii.hexlify(self.core_src.checksum).decode('ascii') # type: ignore
if data_sha256_str != sha256_str: if data_sha256_str != sha256_str:
raise ESPCoreDumpLoaderError('Invalid core dump SHA256 "{}", should be "{}"' raise ESPCoreDumpLoaderError('Invalid core dump SHA256 "{}", should be "{}"'
.format(data_sha256_str, sha256_str)) .format(data_sha256_str, sha256_str))
def create_corefile(self, exe_name=None): # type: (str) -> None def create_corefile(self, exe_name=None, e_machine=ESPCoreDumpElfFile.EM_XTENSA):
# type: (Optional[str], int) -> None
""" """
Creates core dump ELF file Creates core dump ELF file
""" """
@ -226,22 +238,21 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
if self.dump_ver in [self.ELF_CRC32, if self.dump_ver in [self.ELF_CRC32,
self.ELF_SHA256]: self.ELF_SHA256]:
self._extract_elf_corefile(exe_name) self._extract_elf_corefile(exe_name, e_machine)
elif self.dump_ver in [self.BIN_V1, elif self.dump_ver in [self.BIN_V1,
self.BIN_V2]: self.BIN_V2]:
self._extract_bin_corefile() self._extract_bin_corefile(e_machine)
else: else:
raise NotImplementedError raise NotImplementedError
def _extract_elf_corefile(self, exe_name=None): def _extract_elf_corefile(self, exe_name=None, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (str, int) -> None
""" """
Reads the ELF formatted core dump image and parse it Reads the ELF formatted core dump image and parse it
""" """
self.core_elf_file.write(self.core_src.data) with open(self.core_elf_file, 'wb') as fw: # type: ignore
# Need to be closed before read. Otherwise the result will be wrong fw.write(self.core_src.data) # type: ignore
self.core_elf_file.close()
core_elf = ESPCoreDumpElfFile(self.core_elf_file.name) core_elf = ESPCoreDumpElfFile(self.core_elf_file, e_machine=e_machine) # type: ignore
# Read note segments from core file which are belong to tasks (TCB or stack) # Read note segments from core file which are belong to tasks (TCB or stack)
for seg in core_elf.note_segments: for seg in core_elf.note_segments:
@ -259,7 +270,7 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
coredump_sha256 = coredump_sha256_struct.parse(note_sec.desc[:coredump_sha256_struct.sizeof()]) coredump_sha256 = coredump_sha256_struct.parse(note_sec.desc[:coredump_sha256_struct.sizeof()])
if coredump_sha256.sha256 != app_sha256: if coredump_sha256.sha256 != app_sha256:
raise ESPCoreDumpLoaderError( raise ESPCoreDumpLoaderError(
'Invalid application image for coredump: coredump SHA256({}) != app SHA256({}).' 'Invalid application image for coredump: coredump SHA256({!r}) != app SHA256({!r}).'
.format(coredump_sha256, app_sha256)) .format(coredump_sha256, app_sha256))
if coredump_sha256.ver != self.version: if coredump_sha256.ver != self.version:
raise ESPCoreDumpLoaderError( raise ESPCoreDumpLoaderError(
@ -267,46 +278,43 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
.format(coredump_sha256.ver, self.version)) .format(coredump_sha256.ver, self.version))
@staticmethod @staticmethod
def _get_aligned_size(size, align_with=4): def _get_aligned_size(size, align_with=4): # type: (int, int) -> int
if size % align_with: if size % align_with:
return align_with * (size // align_with + 1) return align_with * (size // align_with + 1)
return size return size
@staticmethod @staticmethod
def _build_note_section(name, sec_type, desc): def _build_note_section(name, sec_type, desc): # type: (str, int, str) -> bytes
name = bytearray(name, encoding='ascii') + b'\0' b_name = bytearray(name, encoding='ascii') + b'\0'
return NoteSection.build({ return NoteSection.build({ # type: ignore
'namesz': len(name), 'namesz': len(b_name),
'descsz': len(desc), 'descsz': len(desc),
'type': sec_type, 'type': sec_type,
'name': name, 'name': b_name,
'desc': desc, 'desc': desc,
}) })
def _extract_bin_corefile(self): def _extract_bin_corefile(self, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (int) -> None
""" """
Creates core dump ELF file Creates core dump ELF file
""" """
tcbsz_aligned = self._get_aligned_size(self.header.tcbsz)
coredump_data_struct = Struct( coredump_data_struct = Struct(
'tasks' / GreedyRange( 'tasks' / GreedyRange(
AlignedStruct( AlignedStruct(
4, 4,
'task_header' / TaskHeader, 'task_header' / TaskHeader,
'tcb' / Bytes(self.header.tcbsz), 'tcb' / Bytes(self.header.tcbsz), # type: ignore
'stack' / Bytes(abs_(this.task_header.stack_top - this.task_header.stack_end)), 'stack' / Bytes(abs_(this.task_header.stack_top - this.task_header.stack_end)), # type: ignore
) )
), ),
'mem_seg_headers' / MemSegmentHeader[self.core_src.header.segs_num] 'mem_seg_headers' / MemSegmentHeader[self.core_src.header.segs_num] # type: ignore
) )
core_elf = ESPCoreDumpElfFile(e_machine=e_machine)
core_elf = ESPCoreDumpElfFile()
notes = b'' notes = b''
core_dump_info_notes = b'' core_dump_info_notes = b''
task_info_notes = b'' task_info_notes = b''
coredump_data = coredump_data_struct.parse(self.core_src.data) coredump_data = coredump_data_struct.parse(self.core_src.data) # type: ignore
for i, task in enumerate(coredump_data.tasks): for i, task in enumerate(coredump_data.tasks):
stack_len_aligned = self._get_aligned_size(abs(task.task_header.stack_top - task.task_header.stack_end)) stack_len_aligned = self._get_aligned_size(abs(task.task_header.stack_top - task.task_header.stack_end))
task_status_kwargs = { task_status_kwargs = {
@ -314,32 +322,34 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
'task_flags': TASK_STATUS_CORRECT, 'task_flags': TASK_STATUS_CORRECT,
'task_tcb_addr': task.task_header.tcb_addr, 'task_tcb_addr': task.task_header.tcb_addr,
'task_stack_start': min(task.task_header.stack_top, task.task_header.stack_end), 'task_stack_start': min(task.task_header.stack_top, task.task_header.stack_end),
'task_stack_end': max(task.task_header.stack_top, task.task_header.stack_end),
'task_stack_len': stack_len_aligned, 'task_stack_len': stack_len_aligned,
'task_name': Padding(16).build({}) # currently we don't have task_name, keep it as padding 'task_name': Padding(16).build({}) # currently we don't have task_name, keep it as padding
} }
# Write TCB # Write TCB
try: try:
if self.target_method_cls.tcb_is_sane(task.task_header.tcb_addr, tcbsz_aligned): if self.target_methods.tcb_is_sane(task.task_header.tcb_addr, self.header.tcbsz): # type: ignore
core_elf.add_segment(task.task_header.tcb_addr, core_elf.add_segment(task.task_header.tcb_addr,
task.tcb, task.tcb,
ElfFile.PT_LOAD, ElfFile.PT_LOAD,
ElfSegment.PF_R | ElfSegment.PF_W) ElfSegment.PF_R | ElfSegment.PF_W)
elif task.task_header.tcb_addr and self.target_method_cls.addr_is_fake(task.task_header.tcb_addr): elif task.task_header.tcb_addr and self.target_methods.addr_is_fake(task.task_header.tcb_addr):
task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED
except ESPCoreDumpLoaderError as e: except ESPCoreDumpLoaderError as e:
logging.warning('Skip TCB {} bytes @ 0x{:x}. (Reason: {})' logging.warning('Skip TCB {} bytes @ 0x{:x}. (Reason: {})'
.format(tcbsz_aligned, task.task_header.tcb_addr, e)) .format(self.header.tcbsz, task.task_header.tcb_addr, e)) # type: ignore
# Write stack # Write stack
try: try:
if self.target_method_cls.stack_is_sane(task_status_kwargs['task_stack_start']): if self.target_methods.stack_is_sane(task_status_kwargs['task_stack_start'],
task_status_kwargs['task_stack_end']):
core_elf.add_segment(task_status_kwargs['task_stack_start'], core_elf.add_segment(task_status_kwargs['task_stack_start'],
task.stack, task.stack,
ElfFile.PT_LOAD, ElfFile.PT_LOAD,
ElfSegment.PF_R | ElfSegment.PF_W) ElfSegment.PF_R | ElfSegment.PF_W)
elif task_status_kwargs['task_stack_start'] \ elif (task_status_kwargs['task_stack_start']
and self.target_method_cls.addr_is_fake(task_status_kwargs['task_stack_start']): and self.target_methods.addr_is_fake(task_status_kwargs['task_stack_start'])):
task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED
core_elf.add_segment(task_status_kwargs['task_stack_start'], core_elf.add_segment(task_status_kwargs['task_stack_start'],
task.stack, task.stack,
@ -355,7 +365,7 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
try: try:
logging.debug('Stack start_end: 0x{:x} @ 0x{:x}' logging.debug('Stack start_end: 0x{:x} @ 0x{:x}'
.format(task.task_header.stack_top, task.task_header.stack_end)) .format(task.task_header.stack_top, task.task_header.stack_end))
task_regs, extra_regs = self.arch_method_cls.get_registers_from_stack( task_regs, extra_regs = self.target_methods.get_registers_from_stack(
task.stack, task.stack,
task.task_header.stack_end > task.task_header.stack_top task.task_header.stack_end > task.task_header.stack_top
) )
@ -367,23 +377,24 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
EspTaskStatus.build(task_status_kwargs)) EspTaskStatus.build(task_status_kwargs))
notes += self._build_note_section('CORE', notes += self._build_note_section('CORE',
ElfFile.PT_LOAD, ElfFile.PT_LOAD,
self.arch_method_cls.build_prstatus_data(task.task_header.tcb_addr, self.target_methods.build_prstatus_data(task.task_header.tcb_addr,
task_regs)) task_regs))
if extra_regs and len(core_dump_info_notes) == 0: if len(core_dump_info_notes) == 0: # the first task is the crashed task
# actually there will be only one such note - for crashed task
core_dump_info_notes += self._build_note_section('ESP_CORE_DUMP_INFO', core_dump_info_notes += self._build_note_section('ESP_CORE_DUMP_INFO',
ESPCoreDumpElfFile.PT_INFO, ESPCoreDumpElfFile.PT_INFO,
Int32ul.build(self.header.ver)) Int32ul.build(self.header.ver)) # type: ignore
_regs = [task.task_header.tcb_addr]
# For xtensa, we need to put the exception registers into the extra info as well
if e_machine == ESPCoreDumpElfFile.EM_XTENSA and extra_regs:
for reg_id in extra_regs:
_regs.extend([reg_id, extra_regs[reg_id]])
exc_regs = []
for reg_id in extra_regs:
exc_regs.extend([reg_id, extra_regs[reg_id]])
_regs = [task.task_header.tcb_addr] + exc_regs
core_dump_info_notes += self._build_note_section( core_dump_info_notes += self._build_note_section(
'EXTRA_INFO', 'EXTRA_INFO',
ESPCoreDumpElfFile.PT_EXTRA_INFO, ESPCoreDumpElfFile.PT_EXTRA_INFO,
Int32ul[1 + len(exc_regs)].build(_regs) Int32ul[len(_regs)].build(_regs)
) )
if self.dump_ver == self.BIN_V2: if self.dump_ver == self.BIN_V2:
@ -409,30 +420,29 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
.format(len(task_info_notes), 0, e)) .format(len(task_info_notes), 0, e))
# dump core ELF # dump core ELF
core_elf.e_type = ElfFile.ET_CORE core_elf.e_type = ElfFile.ET_CORE
core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA core_elf.dump(self.core_elf_file) # type: ignore
core_elf.dump(self.core_elf_file.name)
class ESPCoreDumpFlashLoader(EspCoreDumpLoader): class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
ESP_COREDUMP_PART_TABLE_OFF = 0x8000 ESP_COREDUMP_PART_TABLE_OFF = 0x8000
def __init__(self, offset, target='esp32', port=None, baud=None): def __init__(self, offset, target=None, port=None, baud=None):
# type: (int, Optional[str], Optional[str], Optional[int]) -> None
super(ESPCoreDumpFlashLoader, self).__init__() super(ESPCoreDumpFlashLoader, self).__init__()
self.port = port self.port = port
self.baud = baud self.baud = baud
self.target = target
self._get_coredump(offset) self._get_core_src(offset, target)
self._reload_coredump() self.target = self._load_core_src()
def _get_coredump(self, off): def _get_core_src(self, off, target=None): # type: (int, Optional[str]) -> None
""" """
Loads core dump from flash using parttool or elftool (if offset is set) Loads core dump from flash using parttool or elftool (if offset is set)
""" """
try: try:
if off: if off:
logging.info('Invoke esptool to read image.') logging.info('Invoke esptool to read image.')
self._invoke_esptool(off=off) self._invoke_esptool(off=off, target=target)
else: else:
logging.info('Invoke parttool to read image.') logging.info('Invoke parttool to read image.')
self._invoke_parttool() self._invoke_parttool()
@ -440,15 +450,14 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
if e.output: if e.output:
logging.info(e.output) logging.info(e.output)
logging.error('Error during the subprocess execution') logging.error('Error during the subprocess execution')
else:
# Need to be closed before read. Otherwise the result will be wrong
self.core_src_file.close()
def _invoke_esptool(self, off=None): def _invoke_esptool(self, off=None, target=None): # type: (Optional[int], Optional[str]) -> None
""" """
Loads core dump from flash using elftool Loads core dump from flash using elftool
""" """
tool_args = [sys.executable, ESPTOOL_PY, '-c', self.target] if target is None:
target = 'auto'
tool_args = [sys.executable, ESPTOOL_PY, '-c', target]
if self.port: if self.port:
tool_args.extend(['-p', self.port]) tool_args.extend(['-p', self.port])
if self.baud: if self.baud:
@ -466,14 +475,14 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
# Here we use V1 format to locate the size # Here we use V1 format to locate the size
tool_args.extend(['read_flash', str(off), str(EspCoreDumpV1Header.sizeof())]) tool_args.extend(['read_flash', str(off), str(EspCoreDumpV1Header.sizeof())])
tool_args.append(self.core_src_file.name) tool_args.append(self.core_src_file) # type: ignore
# read core dump length # read core dump length
et_out = subprocess.check_output(tool_args) et_out = subprocess.check_output(tool_args)
if et_out: if et_out:
logging.info(et_out.decode('utf-8')) logging.info(et_out.decode('utf-8'))
header = EspCoreDumpV1Header.parse(open(self.core_src_file.name, 'rb').read()) header = EspCoreDumpV1Header.parse(open(self.core_src_file, 'rb').read()) # type: ignore
if not header or not 0 < header.tot_len <= part_size: if not header or not 0 < header.tot_len <= part_size:
logging.error('Incorrect size of core dump image: {}, use partition size instead: {}' logging.error('Incorrect size of core dump image: {}, use partition size instead: {}'
.format(header.tot_len, part_size)) .format(header.tot_len, part_size))
@ -492,7 +501,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
logging.debug(e.output) logging.debug(e.output)
raise e raise e
def _invoke_parttool(self): def _invoke_parttool(self): # type: () -> None
""" """
Loads core dump from flash using parttool Loads core dump from flash using parttool
""" """
@ -503,7 +512,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
self.core_src_file = self._create_temp_file() self.core_src_file = self._create_temp_file()
try: try:
tool_args.append(self.core_src_file.name) tool_args.append(self.core_src_file) # type: ignore
# read core dump partition # read core dump partition
et_out = subprocess.check_output(tool_args) et_out = subprocess.check_output(tool_args)
if et_out: if et_out:
@ -515,7 +524,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
logging.debug(e.output) logging.debug(e.output)
raise e raise e
def _get_core_dump_partition_info(self, part_off=None): def _get_core_dump_partition_info(self, part_off=None): # type: (Optional[int]) -> Tuple[int, int]
""" """
Get core dump partition info using parttool Get core dump partition info using parttool
""" """
@ -545,28 +554,27 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
class ESPCoreDumpFileLoader(EspCoreDumpLoader): class ESPCoreDumpFileLoader(EspCoreDumpLoader):
def __init__(self, path, is_b64=False): def __init__(self, path, is_b64=False): # type: (str, bool) -> None
super(ESPCoreDumpFileLoader, self).__init__() super(ESPCoreDumpFileLoader, self).__init__()
self.is_b64 = is_b64 self.is_b64 = is_b64
self._get_coredump(path) self._get_core_src(path)
self._reload_coredump() self.target = self._load_core_src()
def _get_coredump(self, path): def _get_core_src(self, path): # type: (str) -> None
""" """
Loads core dump from (raw binary or base64-encoded) file Loads core dump from (raw binary or base64-encoded) file
""" """
logging.debug('Load core dump from "%s", %s format', path, 'b64' if self.is_b64 else 'raw') logging.debug('Load core dump from "%s", %s format', path, 'b64' if self.is_b64 else 'raw')
if not self.is_b64: if not self.is_b64:
self.core_src_file = open(path, mode='rb') self.core_src_file = path
else: else:
self.core_src_file = self._create_temp_file() self.core_src_file = self._create_temp_file()
with open(path, 'rb') as fb64: with open(self.core_src_file, 'wb') as fw:
while True: with open(path, 'rb') as fb64:
line = fb64.readline() while True:
if len(line) == 0: line = fb64.readline()
break if len(line) == 0:
data = base64.standard_b64decode(line.rstrip(b'\r\n')) break
self.core_src_file.write(data) data = base64.standard_b64decode(line.rstrip(b'\r\n'))
self.core_src_file.flush() fw.write(data) # type: ignore
self.core_src_file.seek(0)

View File

@ -0,0 +1,63 @@
#
# Copyright 2021 Espressif Systems (Shanghai) CO., LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from construct import Int16ul, Int32ul, Padding, Struct
from . import BaseArchMethodsMixin, BaseTargetMethods, ESPCoreDumpLoaderError
try:
from typing import Any, Optional, Tuple
except ImportError:
pass
RISCV_GP_REGS_COUNT = 32
PRSTATUS_SIZE = 204
PRSTATUS_OFFSET_PR_CURSIG = 12
PRSTATUS_OFFSET_PR_PID = 24
PRSTATUS_OFFSET_PR_REG = 72
ELF_GREGSET_T_SIZE = 128
PrStruct = Struct(
Padding(PRSTATUS_OFFSET_PR_CURSIG),
'pr_cursig' / Int16ul,
Padding(PRSTATUS_OFFSET_PR_PID - PRSTATUS_OFFSET_PR_CURSIG - Int16ul.sizeof()),
'pr_pid' / Int32ul,
Padding(PRSTATUS_OFFSET_PR_REG - PRSTATUS_OFFSET_PR_PID - Int32ul.sizeof()),
'regs' / Int32ul[RISCV_GP_REGS_COUNT],
Padding(PRSTATUS_SIZE - PRSTATUS_OFFSET_PR_REG - ELF_GREGSET_T_SIZE)
)
class RiscvMethodsMixin(BaseArchMethodsMixin):
@staticmethod
def get_registers_from_stack(data, grows_down):
# type: (bytes, bool) -> Tuple[list[int], Optional[dict[int, int]]]
regs = Int32ul[RISCV_GP_REGS_COUNT].parse(data)
if not grows_down:
raise ESPCoreDumpLoaderError('Growing up stacks are not supported for now!')
return regs, None
@staticmethod
def build_prstatus_data(tcb_addr, task_regs): # type: (int, list[int]) -> Any
return PrStruct.build({
'pr_cursig': 0,
'pr_pid': tcb_addr,
'regs': task_regs,
})
class Esp32c3Methods(BaseTargetMethods, RiscvMethodsMixin):
TARGET = 'esp32c3'

View File

@ -0,0 +1,8 @@
SOC_DRAM_LOW = 0x3ffae000
SOC_DRAM_HIGH = 0x40000000
SOC_IRAM_LOW = 0x40080000
SOC_IRAM_HIGH = 0x400a0000
SOC_RTC_DRAM_LOW = 0x3ff80000
SOC_RTC_DRAM_HIGH = 0x3ff82000
SOC_RTC_DATA_LOW = 0x50000000
SOC_RTC_DATA_HIGH = 0x50002000

View File

@ -0,0 +1,8 @@
SOC_IRAM_LOW = 0x4037c000
SOC_IRAM_HIGH = 0x403e0000
SOC_DRAM_LOW = 0x3fc80000
SOC_DRAM_HIGH = 0x3fce0000
SOC_RTC_DRAM_LOW = 0x50000000
SOC_RTC_DRAM_HIGH = 0x50002000
SOC_RTC_DATA_LOW = 0x50000000
SOC_RTC_DATA_HIGH = 0x50002000

View File

@ -0,0 +1,8 @@
SOC_IRAM_LOW = 0x40020000
SOC_IRAM_HIGH = 0x40070000
SOC_DRAM_LOW = 0x3ffb0000
SOC_DRAM_HIGH = 0x40000000
SOC_RTC_DRAM_LOW = 0x3ff9e000
SOC_RTC_DRAM_HIGH = 0x3ffa0000
SOC_RTC_DATA_LOW = 0x50000000
SOC_RTC_DATA_HIGH = 0x50002000

View File

@ -1,5 +1,5 @@
# #
# Copyright 2021 Espressif Systems (Shanghai) PTE LTD # Copyright 2021 Espressif Systems (Shanghai) CO., LTD
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,12 +16,16 @@
from construct import Int16ul, Int32ul, Int64ul, Struct from construct import Int16ul, Int32ul, Int64ul, Struct
from . import ESPCoreDumpLoaderError, _ArchMethodsBase, _TargetMethodsBase from . import BaseArchMethodsMixin, BaseTargetMethods, ESPCoreDumpLoaderError
try:
from typing import Any, Optional, Tuple
except ImportError:
pass
INVALID_CAUSE_VALUE = 0xFFFF INVALID_CAUSE_VALUE = 0xFFFF
XCHAL_EXCCAUSE_NUM = 64 XCHAL_EXCCAUSE_NUM = 64
# Exception cause dictionary to get translation of exccause register # Exception cause dictionary to get translation of exccause register
# From 4.4.1.5 table 4-64 Exception Causes of Xtensa # From 4.4.1.5 table 4-64 Exception Causes of Xtensa
# Instruction Set Architecture (ISA) Reference Manual # Instruction Set Architecture (ISA) Reference Manual
@ -81,7 +85,7 @@ XTENSA_EXCEPTION_CAUSE_DICT = {
} }
class XtensaRegisters(object): class ExceptionRegisters(object):
# extra regs IDs used in EXTRA_INFO note # extra regs IDs used in EXTRA_INFO note
EXCCAUSE_IDX = 0 EXCCAUSE_IDX = 0
EXCVADDR_IDX = 1 EXCVADDR_IDX = 1
@ -100,14 +104,14 @@ class XtensaRegisters(object):
EPS7_IDX = 199 EPS7_IDX = 199
@property @property
def registers(self): def registers(self): # type: () -> dict[str, int]
return {k: v for k, v in self.__class__.__dict__.items() return {k: v for k, v in self.__class__.__dict__.items()
if not k.startswith('__') and isinstance(v, int)} if not k.startswith('__') and isinstance(v, int)}
# Following structs are based on source code # Following structs are based on source code
# IDF_PATH/components/espcoredump/src/core_dump_port.c # IDF_PATH/components/espcoredump/src/core_dump_port.c
XtensaPrStatus = Struct( PrStatus = Struct(
'si_signo' / Int32ul, 'si_signo' / Int32ul,
'si_code' / Int32ul, 'si_code' / Int32ul,
'si_errno' / Int32ul, 'si_errno' / Int32ul,
@ -126,28 +130,28 @@ XtensaPrStatus = Struct(
) )
def print_exc_regs_info(extra_info): def print_exc_regs_info(extra_info): # type: (list[int]) -> None
""" """
Print the register info by parsing extra_info Print the register info by parsing extra_info
:param extra_info: extra info data str :param extra_info: extra info data str
:return: None :return: None
""" """
exccause = extra_info[1 + 2 * XtensaRegisters.EXCCAUSE_IDX + 1] exccause = extra_info[1 + 2 * ExceptionRegisters.EXCCAUSE_IDX + 1]
exccause_str = XTENSA_EXCEPTION_CAUSE_DICT.get(exccause) exccause_str = XTENSA_EXCEPTION_CAUSE_DICT.get(exccause)
if not exccause_str: if not exccause_str:
exccause_str = ('Invalid EXCCAUSE code', 'Invalid EXCAUSE description or not found.') exccause_str = ('Invalid EXCCAUSE code', 'Invalid EXCAUSE description or not found.')
print('exccause 0x%x (%s)' % (exccause, exccause_str[0])) print('exccause 0x%x (%s)' % (exccause, exccause_str[0]))
print('excvaddr 0x%x' % extra_info[1 + 2 * XtensaRegisters.EXCVADDR_IDX + 1]) print('excvaddr 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EXCVADDR_IDX + 1])
# skip crashed_task_tcb, exccause, and excvaddr # skip crashed_task_tcb, exccause, and excvaddr
for i in range(5, len(extra_info), 2): for i in range(5, len(extra_info), 2):
if (extra_info[i] >= XtensaRegisters.EPC1_IDX and extra_info[i] <= XtensaRegisters.EPC7_IDX): if (extra_info[i] >= ExceptionRegisters.EPC1_IDX and extra_info[i] <= ExceptionRegisters.EPC7_IDX):
print('epc%d 0x%x' % ((extra_info[i] - XtensaRegisters.EPC1_IDX + 1), extra_info[i + 1])) print('epc%d 0x%x' % ((extra_info[i] - ExceptionRegisters.EPC1_IDX + 1), extra_info[i + 1]))
# skip crashed_task_tcb, exccause, and excvaddr # skip crashed_task_tcb, exccause, and excvaddr
for i in range(5, len(extra_info), 2): for i in range(5, len(extra_info), 2):
if (extra_info[i] >= XtensaRegisters.EPS2_IDX and extra_info[i] <= XtensaRegisters.EPS7_IDX): if (extra_info[i] >= ExceptionRegisters.EPS2_IDX and extra_info[i] <= ExceptionRegisters.EPS7_IDX):
print('eps%d 0x%x' % ((extra_info[i] - XtensaRegisters.EPS2_IDX + 2), extra_info[i + 1])) print('eps%d 0x%x' % ((extra_info[i] - ExceptionRegisters.EPS2_IDX + 2), extra_info[i + 1]))
# from "gdb/xtensa-tdep.h" # from "gdb/xtensa-tdep.h"
@ -200,24 +204,11 @@ XT_STK_LCOUNT = 24
XT_STK_FRMSZ = 25 XT_STK_FRMSZ = 25
class _TargetMethodsESP32(_TargetMethodsBase): class XtensaMethodsMixin(BaseArchMethodsMixin):
@staticmethod
def tcb_is_sane(tcb_addr, tcb_size):
return not (tcb_addr < 0x3ffae000 or (tcb_addr + tcb_size) > 0x40000000)
@staticmethod
def stack_is_sane(sp):
return not (sp < 0x3ffae010 or sp > 0x3fffffff)
@staticmethod
def addr_is_fake(addr):
return (0x20000000 <= addr < 0x3f3fffff) or addr >= 0x80000000
class _ArchMethodsXtensa(_ArchMethodsBase):
@staticmethod @staticmethod
def get_registers_from_stack(data, grows_down): def get_registers_from_stack(data, grows_down):
extra_regs = {v: 0 for v in XtensaRegisters().registers.values()} # type: (bytes, bool) -> Tuple[list[int], Optional[dict[int, int]]]
extra_regs = {v: 0 for v in ExceptionRegisters().registers.values()}
regs = [0] * REG_NUM regs = [0] * REG_NUM
# TODO: support for growing up stacks # TODO: support for growing up stacks
if not grows_down: if not grows_down:
@ -245,10 +236,10 @@ class _ArchMethodsXtensa(_ArchMethodsBase):
if regs[REG_PS_IDX] & (1 << 5): if regs[REG_PS_IDX] & (1 << 5):
regs[REG_PS_IDX] &= ~(1 << 4) regs[REG_PS_IDX] &= ~(1 << 4)
if stack[XT_STK_EXCCAUSE] in XTENSA_EXCEPTION_CAUSE_DICT: if stack[XT_STK_EXCCAUSE] in XTENSA_EXCEPTION_CAUSE_DICT:
extra_regs[XtensaRegisters.EXCCAUSE_IDX] = stack[XT_STK_EXCCAUSE] extra_regs[ExceptionRegisters.EXCCAUSE_IDX] = stack[XT_STK_EXCCAUSE]
else: else:
extra_regs[XtensaRegisters.EXCCAUSE_IDX] = INVALID_CAUSE_VALUE extra_regs[ExceptionRegisters.EXCCAUSE_IDX] = INVALID_CAUSE_VALUE
extra_regs[XtensaRegisters.EXCVADDR_IDX] = stack[XT_STK_EXCVADDR] extra_regs[ExceptionRegisters.EXCVADDR_IDX] = stack[XT_STK_EXCVADDR]
else: else:
regs[REG_PC_IDX] = stack[XT_SOL_PC] regs[REG_PC_IDX] = stack[XT_SOL_PC]
regs[REG_PS_IDX] = stack[XT_SOL_PS] regs[REG_PS_IDX] = stack[XT_SOL_PS]
@ -258,8 +249,8 @@ class _ArchMethodsXtensa(_ArchMethodsBase):
return regs, extra_regs return regs, extra_regs
@staticmethod @staticmethod
def build_prstatus_data(tcb_addr, task_regs): def build_prstatus_data(tcb_addr, task_regs): # type: (int, list[int]) -> Any
return XtensaPrStatus.build({ return PrStatus.build({
'si_signo': 0, 'si_signo': 0,
'si_code': 0, 'si_code': 0,
'si_errno': 0, 'si_errno': 0,
@ -276,3 +267,11 @@ class _ArchMethodsXtensa(_ArchMethodsBase):
'pr_cutime': 0, 'pr_cutime': 0,
'pr_cstime': 0, 'pr_cstime': 0,
}) + Int32ul[len(task_regs)].build(task_regs) }) + Int32ul[len(task_regs)].build(task_regs)
class Esp32Methods(BaseTargetMethods, XtensaMethodsMixin):
TARGET = 'esp32'
class Esp32S2Methods(BaseTargetMethods, XtensaMethodsMixin):
TARGET = 'esp32s2'

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# ESP32 core dump Utility # ESP-IDF Core Dump Utility
import argparse import argparse
import logging import logging
@ -10,7 +10,7 @@ import sys
from shutil import copyfile from shutil import copyfile
from construct import GreedyRange, Int32ul, Struct from construct import GreedyRange, Int32ul, Struct
from corefile import __version__, xtensa from corefile import RISCV_TARGETS, SUPPORTED_TARGETS, XTENSA_TARGETS, __version__, xtensa
from corefile.elf import TASK_STATUS_CORRECT, ElfFile, ElfSegment, ESPCoreDumpElfFile, EspTaskStatus from corefile.elf import TASK_STATUS_CORRECT, ElfFile, ElfSegment, ESPCoreDumpElfFile, EspTaskStatus
from corefile.gdb import EspGDB from corefile.gdb import EspGDB
from corefile.loader import ESPCoreDumpFileLoader, ESPCoreDumpFlashLoader from corefile.loader import ESPCoreDumpFileLoader, ESPCoreDumpFlashLoader
@ -28,32 +28,40 @@ except ImportError:
sys.stderr.write('esptool is not found!\n') sys.stderr.write('esptool is not found!\n')
sys.exit(2) sys.exit(2)
try:
from typing import Optional, Tuple
except ImportError:
pass
if os.name == 'nt': if os.name == 'nt':
CLOSE_FDS = False CLOSE_FDS = False
else: else:
CLOSE_FDS = True CLOSE_FDS = True
def load_aux_elf(elf_path): # type: (str) -> (ElfFile, str) def load_aux_elf(elf_path): # type: (str) -> str
""" """
Loads auxiliary ELF file and composes GDB command to read its symbols. Loads auxiliary ELF file and composes GDB command to read its symbols.
""" """
elf = None
sym_cmd = '' sym_cmd = ''
if os.path.exists(elf_path): if os.path.exists(elf_path):
elf = ElfFile(elf_path) elf = ElfFile(elf_path)
for s in elf.sections: for s in elf.sections:
if s.name == '.text': if s.name == '.text':
sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr) sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr)
return elf, sym_cmd return sym_cmd
def core_prepare(): def get_core_dump_elf(e_machine=ESPCoreDumpFileLoader.ESP32):
# type: (int) -> Tuple[str, Optional[str], Optional[list[str]]]
loader = None loader = None
core_filename = None core_filename = None
target = None
temp_files = None
if not args.core: if not args.core:
# Core file not specified, try to read core dump from flash. # Core file not specified, try to read core dump from flash.
loader = ESPCoreDumpFlashLoader(args.off, port=args.port, baud=args.baud) loader = ESPCoreDumpFlashLoader(args.off, args.chip, port=args.port, baud=args.baud)
elif args.core_format != 'elf': elif args.core_format != 'elf':
# Core file specified, but not yet in ELF format. Convert it from raw or base64 into ELF. # Core file specified, but not yet in ELF format. Convert it from raw or base64 into ELF.
loader = ESPCoreDumpFileLoader(args.core, args.core_format == 'b64') loader = ESPCoreDumpFileLoader(args.core, args.core_format == 'b64')
@ -63,51 +71,86 @@ def core_prepare():
# Load/convert the core file # Load/convert the core file
if loader: if loader:
loader.create_corefile(exe_name=args.prog) loader.create_corefile(exe_name=args.prog, e_machine=e_machine)
core_filename = loader.core_elf_file.name core_filename = loader.core_elf_file
if args.save_core: if args.save_core:
# We got asked to save the core file, make a copy # We got asked to save the core file, make a copy
copyfile(loader.core_elf_file.name, args.save_core) copyfile(loader.core_elf_file, args.save_core)
target = loader.target
temp_files = loader.temp_files
return core_filename, loader return core_filename, target, temp_files # type: ignore
def dbg_corefile(): def get_target(): # type: () -> str
if args.chip != 'auto':
return args.chip # type: ignore
inst = esptool.ESPLoader.detect_chip(args.port, args.baud)
return inst.CHIP_NAME.lower().replace('-', '') # type: ignore
def get_gdb_path(target=None): # type: (Optional[str]) -> str
if args.gdb:
return args.gdb # type: ignore
if target is None:
target = get_target()
if target in XTENSA_TARGETS:
# For some reason, xtensa-esp32s2-elf-gdb will report some issue.
# Use xtensa-esp32-elf-gdb instead.
return 'xtensa-esp32-elf-gdb'
if target in RISCV_TARGETS:
return 'riscv32-esp-elf-gdb'
raise ValueError('Invalid value: {}. For now we only support {}'.format(target, SUPPORTED_TARGETS))
def get_rom_elf_path(target=None): # type: (Optional[str]) -> str
if args.rom_elf:
return args.rom_elf # type: ignore
if target is None:
target = get_target()
return '{}_rom.elf'.format(target)
def dbg_corefile(): # type: () -> Optional[list[str]]
""" """
Command to load core dump from file or flash and run GDB debug session with it Command to load core dump from file or flash and run GDB debug session with it
""" """
rom_elf, rom_sym_cmd = load_aux_elf(args.rom_elf) exe_elf = ESPCoreDumpElfFile(args.prog)
core_filename, loader = core_prepare() core_elf_path, target, temp_files = get_core_dump_elf(e_machine=exe_elf.e_machine)
rom_elf_path = get_rom_elf_path(target)
rom_sym_cmd = load_aux_elf(rom_elf_path)
gdb_tool = get_gdb_path(target)
p = subprocess.Popen(bufsize=0, p = subprocess.Popen(bufsize=0,
args=[args.gdb, args=[gdb_tool,
'--nw', # ignore .gdbinit '--nw', # ignore .gdbinit
'--core=%s' % core_filename, # core file, '--core=%s' % core_elf_path, # core file,
'-ex', rom_sym_cmd, '-ex', rom_sym_cmd,
args.prog], args.prog],
stdin=None, stdout=None, stderr=None, stdin=None, stdout=None, stderr=None,
close_fds=CLOSE_FDS) close_fds=CLOSE_FDS)
p.wait() p.wait()
print('Done!') print('Done!')
return temp_files
def info_corefile(): def info_corefile(): # type: () -> Optional[list[str]]
""" """
Command to load core dump from file or flash and print it's data in user friendly form Command to load core dump from file or flash and print it's data in user friendly form
""" """
core_filename, loader = core_prepare() exe_elf = ESPCoreDumpElfFile(args.prog)
core_elf_path, target, temp_files = get_core_dump_elf(e_machine=exe_elf.e_machine)
exe_elf = ElfFile(args.prog) core_elf = ESPCoreDumpElfFile(core_elf_path)
core_elf = ESPCoreDumpElfFile(core_filename)
if exe_elf.e_machine != core_elf.e_machine: if exe_elf.e_machine != core_elf.e_machine:
raise ValueError('The arch should be the same between core elf and exe elf') raise ValueError('The arch should be the same between core elf and exe elf')
if core_elf.e_machine == ESPCoreDumpElfFile.EM_XTENSA:
exception_registers_info = xtensa.print_exc_regs_info
else:
raise NotImplementedError
extra_note = None extra_note = None
task_info = [] task_info = []
for seg in core_elf.note_segments: for seg in core_elf.note_segments:
@ -119,8 +162,11 @@ def info_corefile():
task_info.append(task_info_struct) task_info.append(task_info_struct)
print('===============================================================') print('===============================================================')
print('==================== ESP32 CORE DUMP START ====================') print('==================== ESP32 CORE DUMP START ====================')
rom_elf, rom_sym_cmd = load_aux_elf(args.rom_elf) rom_elf_path = get_rom_elf_path(target)
gdb = EspGDB(args.gdb, [rom_sym_cmd], core_filename, args.prog, timeout_sec=args.gdb_timeout_sec) rom_sym_cmd = load_aux_elf(rom_elf_path)
gdb_tool = get_gdb_path(target)
gdb = EspGDB(gdb_tool, [rom_sym_cmd], core_elf_path, args.prog, timeout_sec=args.gdb_timeout_sec)
extra_info = None extra_info = None
if extra_note: if extra_note:
@ -132,10 +178,12 @@ def info_corefile():
task_name = gdb.get_freertos_task_name(marker) task_name = gdb.get_freertos_task_name(marker)
print("\nCrashed task handle: 0x%x, name: '%s', GDB name: 'process %d'" % (marker, task_name, marker)) print("\nCrashed task handle: 0x%x, name: '%s', GDB name: 'process %d'" % (marker, task_name, marker))
print('\n================== CURRENT THREAD REGISTERS ===================') print('\n================== CURRENT THREAD REGISTERS ===================')
if extra_note and extra_info: # Only xtensa have exception registers
exception_registers_info(extra_info) if exe_elf.e_machine == ESPCoreDumpElfFile.EM_XTENSA:
else: if extra_note and extra_info:
print('Exception registers have not been found!') xtensa.print_exc_regs_info(extra_info)
else:
print('Exception registers have not been found!')
print(gdb.run_cmd('info registers')) print(gdb.run_cmd('info registers'))
print('\n==================== CURRENT THREAD STACK =====================') print('\n==================== CURRENT THREAD STACK =====================')
print(gdb.run_cmd('bt')) print(gdb.run_cmd('bt'))
@ -235,10 +283,14 @@ def info_corefile():
del gdb del gdb
print('Done!') print('Done!')
return temp_files
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__) parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__)
parser.add_argument('--chip', default=os.environ.get('ESPTOOL_CHIP', 'auto'),
choices=['auto'] + SUPPORTED_TARGETS,
help='Target chip type')
parser.add_argument('--port', '-p', default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT), parser.add_argument('--port', '-p', default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT),
help='Serial port device') help='Serial port device')
parser.add_argument('--baud', '-b', type=int, parser.add_argument('--baud', '-b', type=int,
@ -250,20 +302,20 @@ if __name__ == '__main__':
common_args = argparse.ArgumentParser(add_help=False) common_args = argparse.ArgumentParser(add_help=False)
common_args.add_argument('--debug', '-d', type=int, default=3, common_args.add_argument('--debug', '-d', type=int, default=3,
help='Log level (0..3)') help='Log level (0..3)')
common_args.add_argument('--gdb', '-g', default='xtensa-esp32-elf-gdb', common_args.add_argument('--gdb', '-g',
help='Path to gdb') help='Path to gdb')
common_args.add_argument('--core', '-c', common_args.add_argument('--core', '-c',
help='Path to core dump file (if skipped core dump will be read from flash)') help='Path to core dump file (if skipped core dump will be read from flash)')
common_args.add_argument('--core-format', '-t', choices=['b64', 'elf', 'raw'], default='elf', common_args.add_argument('--core-format', '-t', choices=['b64', 'elf', 'raw'], default='elf',
help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), ' help='File specified with "-c" is an ELF ("elf"), '
'raw (raw) or base64-encoded (b64) binary') 'raw (raw) or base64-encoded (b64) binary')
common_args.add_argument('--off', '-o', type=int, common_args.add_argument('--off', '-o', type=int,
help='Offset of coredump partition in flash (type "make partition_table" to see).') help='Offset of coredump partition in flash (type "make partition_table" to see).')
common_args.add_argument('--save-core', '-s', common_args.add_argument('--save-core', '-s',
help='Save core to file. Otherwise temporary core file will be deleted. ' help='Save core to file. Otherwise temporary core file will be deleted. '
'Does not work with "-c"', ) 'Does not work with "-c"', )
common_args.add_argument('--rom-elf', '-r', default='esp32_rom.elf', common_args.add_argument('--rom-elf', '-r',
help='Path to ROM ELF file.') help='Path to ROM ELF file. Will use "<target>_rom.elf" if not specified')
common_args.add_argument('prog', help='Path to program\'s ELF binary') common_args.add_argument('prog', help='Path to program\'s ELF binary')
operations = parser.add_subparsers(dest='operation') operations = parser.add_subparsers(dest='operation')
@ -291,9 +343,18 @@ if __name__ == '__main__':
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
print('espcoredump.py v%s' % __version__) print('espcoredump.py v%s' % __version__)
if args.operation == 'info_corefile': temp_core_files = None
info_corefile() try:
elif args.operation == 'dbg_corefile': if args.operation == 'info_corefile':
dbg_corefile() temp_core_files = info_corefile()
else: elif args.operation == 'dbg_corefile':
raise ValueError('Please specify action, should be info_corefile or dbg_corefile') temp_core_files = dbg_corefile()
else:
raise ValueError('Please specify action, should be info_corefile or dbg_corefile')
finally:
if temp_core_files:
for f in temp_core_files:
try:
os.remove(f)
except OSError:
pass

View File

@ -2,9 +2,9 @@
{ coverage debug sys \ { coverage debug sys \
&& coverage erase \ && coverage erase \
&& coverage run -a --source=corefile ../espcoredump.py --gdb-timeout-sec 5 info_corefile -m -t b64 -c coredump.b64 -s core.elf test.elf &> output \ && coverage run -a --source=corefile ../espcoredump.py --chip esp32 --gdb-timeout-sec 5 info_corefile -m -t b64 -c coredump.b64 -s core.elf test.elf &> output \
&& diff expected_output output \ && diff expected_output output \
&& coverage run -a --source=corefile ../espcoredump.py --gdb-timeout-sec 5 info_corefile -m -t elf -c core.elf test.elf &> output2 \ && coverage run -a --source=corefile ../espcoredump.py --chip esp32 --gdb-timeout-sec 5 info_corefile -m -t elf -c core.elf test.elf &> output2 \
&& diff expected_output output2 \ && diff expected_output output2 \
&& coverage run -a --source=corefile ./test_espcoredump.py \ && coverage run -a --source=corefile ./test_espcoredump.py \
&& coverage report ../corefile/elf.py ../corefile/gdb.py ../corefile/loader.py ../corefile/xtensa.py ../espcoredump.py \ && coverage report ../corefile/elf.py ../corefile/gdb.py ../corefile/loader.py ../corefile/xtensa.py ../espcoredump.py \

View File