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");
# you may not use this file except in compliance with the License.
@ -16,7 +16,22 @@
__version__ = '0.4-dev'
import abc
import os
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):
@ -27,42 +42,84 @@ class ESPCoreDumpLoaderError(ESPCoreDumpError):
pass
class _TargetMethodsBase(object):
@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):
class BaseArchMethodsMixin(with_metaclass(abc.ABCMeta)): # type: ignore
@staticmethod
@abstractmethod
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
@abstractmethod
def build_prstatus_data(tcb_addr, task_regs):
return b''
def build_prstatus_data(tcb_addr, task_regs): # type: (int, list[int]) -> str
"""
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");
# you may not use this file except in compliance with the License.
@ -17,8 +17,13 @@
import hashlib
import os
from construct import (AlignedStruct, Bytes, Const, GreedyRange, Int16ul, Int32ul, Padding, Pointer, Sequence, Struct,
this)
from construct import (AlignedStruct, Bytes, Const, Container, GreedyRange, Int16ul, Int32ul, Padding, Pointer,
Sequence, Struct, this)
try:
from typing import Optional
except ImportError:
pass
# Following structs are based on spec
# https://refspecs.linuxfoundation.org/elf/elf.pdf
@ -110,12 +115,13 @@ class ElfFile(object):
EV_CURRENT = 0x01
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_machine = e_machine
self._struct = None # construct Struct
self._model = None # construct Container
self._section_names = [] # type: list[str]
self._struct = None # type: Optional[Struct]
self._model = None # type: Optional[Container]
self._section_names = {} # type: dict[int, str]
self.sections = [] # type: list[ElfSection]
self.load_segments = [] # type: list[ElfSegment]
@ -171,7 +177,7 @@ class ElfFile(object):
name += c
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
:param header_tables: contains elf_header, program_headers, section_headers
@ -219,12 +225,12 @@ class ElfFile(object):
return Struct(*args)
@property
def sha256(self):
def sha256(self): # type: () -> bytes
"""
:return: SHA256 hash of the input ELF file
"""
sha256 = hashlib.sha256()
sha256.update(self._struct.build(self._model))
sha256.update(self._struct.build(self._model)) # type: ignore
return sha256.digest()
@ -234,13 +240,13 @@ class ElfSection(object):
SHF_EXECINSTR = 0x04
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.addr = addr
self.data = data
self.flags = flags
def attr_str(self):
def attr_str(self): # type: () -> str
if self.flags & self.SHF_MASKPROC:
return 'MS'
@ -250,7 +256,7 @@ class ElfSection(object):
res += 'A' if self.flags & self.SHF_ALLOC else ' '
return res
def __repr__(self):
def __repr__(self): # type: () -> str
return '{:>32} [Addr] 0x{:>08X}, [Size] 0x{:>08X} {:>4}' \
.format(self.name, self.addr, len(self.data), self.attr_str())
@ -260,13 +266,13 @@ class ElfSegment(object):
PF_W = 0x02
PF_R = 0x04
def __init__(self, addr, data, flags):
def __init__(self, addr, data, flags): # type: (int, bytes, int) -> None
self.addr = addr
self.data = data
self.flags = flags
self.type = ElfFile.PT_LOAD
def attr_str(self):
def attr_str(self): # type: () -> str
res = ''
res += 'R' if self.flags & self.PF_R else ' '
res += 'W' if self.flags & self.PF_W else ' '
@ -274,22 +280,22 @@ class ElfSegment(object):
return res
@staticmethod
def _type_str():
def _type_str(): # type: () -> str
return 'LOAD'
def __repr__(self):
def __repr__(self): # type: () -> str
return '{:>8} Addr 0x{:>08X}, Size 0x{:>08X} Flags {:4}' \
.format(self._type_str(), self.addr, len(self.data), self.attr_str())
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)
self.type = ElfFile.PT_NOTE
self.note_secs = NoteSections.parse(self.data)
@staticmethod
def _type_str():
def _type_str(): # type: () -> str
return 'NOTE'
@ -316,13 +322,15 @@ class ESPCoreDumpElfFile(ElfFile):
# ELF file machine type
EM_XTENSA = 0x5E
EM_RISCV = 0xF3
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_machine = e_machine or self.EM_XTENSA
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:
self.load_segments.append(ElfSegment(addr, data, flags))
else:
@ -352,7 +360,7 @@ class ESPCoreDumpElfFile(ElfFile):
})
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:
res += ProgramHeader.build({
'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");
# you may not use this file except in compliance with the License.
@ -18,19 +18,14 @@ import logging
import re
import time
from . import ESPCoreDumpError
try:
import typing
except ImportError:
pass
from pygdbmi.gdbcontroller import DEFAULT_GDB_TIMEOUT_SEC, GdbController
from . import ESPCoreDumpError
class EspGDB(object):
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
"""
@ -59,7 +54,7 @@ class EspGDB(object):
def _gdbmi_run_cmd_get_responses(self, cmd, resp_message, resp_type, multiple=True,
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)
t_end = time.time() + self.timeout
filtered_response_list = []
@ -80,15 +75,15 @@ class EspGDB(object):
return filtered_response_list
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]
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 """
return self._gdbmi_run_cmd_get_one_response("-data-evaluate-expression \"%s\"" % expr,
'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 """
try:
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 ''
def run_cmd(self, gdb_cmd): # type: (str) -> str
def run_cmd(self, gdb_cmd):
""" Execute a generic GDB console command via MI2
"""
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') \
.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 """
result = self._gdbmi_run_cmd_get_one_response('-thread-info', 'done', 'result')['payload']
current_thread_id = result['current-thread-id']
threads = result['threads']
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 """
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))
@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 """
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");
# 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 . import ESPCoreDumpLoaderError, _ArchMethodsBase, _TargetMethodsBase
from . import ESPCoreDumpLoaderError
from .elf import (TASK_STATUS_CORRECT, TASK_STATUS_TCB_CORRUPTED, ElfFile, ElfSegment, ESPCoreDumpElfFile,
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')
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
ESP32 = 0
ESP32S2 = 2
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
"""
super(EspCoreDumpVersion, self).__init__()
@ -89,26 +97,26 @@ class EspCoreDumpVersion(object):
self.set_version(version)
@staticmethod
def make_dump_ver(major, minor):
def make_dump_ver(major, minor): # type: (int, int) -> int
return ((major & 0xFF) << 8) | ((minor & 0xFF) << 0)
def set_version(self, version):
def set_version(self, version): # type: (int) -> None
self.version = version
@property
def chip_ver(self):
def chip_ver(self): # type: () -> int
return (self.version & 0xFFFF0000) >> 16
@property
def dump_ver(self):
def dump_ver(self): # type: () -> int
return self.version & 0x0000FFFF
@property
def major(self):
def major(self): # type: () -> int
return (self.version & 0x0000FF00) >> 8
@property
def minor(self):
def minor(self): # type: () -> int
return self.version & 0x000000FF
@ -119,42 +127,37 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
ELF_CRC32 = EspCoreDumpVersion.make_dump_ver(1, 0)
ELF_SHA256 = EspCoreDumpVersion.make_dump_ver(1, 1)
def __init__(self):
def __init__(self): # type: () -> None
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 = None
self.core_elf_file = None
self.core_elf_file = None # type: Optional[str]
self.header = None
self.header_struct = EspCoreDumpV1Header
self.checksum_struct = CRC
# These two method classes will be assigned in ``reload_coredump``
self.target_method_cls = _TargetMethodsBase
self.arch_method_cls = _ArchMethodsBase
# target classes will be assigned in ``_reload_coredump``
self.target_methods = Esp32Methods()
self._temp_files = []
self.temp_files = [] # type: list[str]
def __del__(self):
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):
def _create_temp_file(self): # type: () -> str
t = tempfile.NamedTemporaryFile('wb', delete=False)
self._temp_files.append(t.name)
return t
# Here we close this at first to make sure the read/write is wrapped in context manager
# 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):
with open(self.core_src_file.name, 'rb') as fr:
def _load_core_src(self): # type: () -> str
"""
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()
_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()),
'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
if self.header_struct != EspCoreDumpV1Header:
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:
self.target_method_cls = _TargetMethodsESP32
if self.chip_ver in self.XTENSA_CHIPS:
self.arch_method_cls = _ArchMethodsXtensa
self.target_methods = Esp32Methods() # type: ignore
elif self.chip_ver == self.ESP32S2:
self.target_methods = Esp32S2Methods() # type: ignore
elif self.chip_ver == self.ESP32C3:
self.target_methods = Esp32c3Methods() # type: ignore
else:
raise NotImplementedError
else:
raise ESPCoreDumpLoaderError('Core dump chip "0x%x" is not supported!' % self.chip_ver)
def _validate_dump_file(self):
if self.chip_ver not in self.ESP_COREDUMP_TARGETS:
return self.target_methods.TARGET # type: ignore
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}"'
.format(self.chip_ver, self.ESP32S2))
@ -204,20 +212,24 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
elif self.checksum_struct == SHA256:
self._sha256_validate()
def _crc_validate(self):
data_crc = binascii.crc32(EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) & 0xffffffff
if data_crc != self.core_src.checksum:
raise ESPCoreDumpLoaderError('Invalid core dump CRC %x, should be %x' % (data_crc, self.core_src.crc))
def _crc_validate(self): # type: () -> None
data_crc = binascii.crc32(
EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) & 0xffffffff # type: ignore
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):
data_sha256 = hashlib.sha256(EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data)
def _sha256_validate(self): # type: () -> None
data_sha256 = hashlib.sha256(
EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) # type: ignore
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:
raise ESPCoreDumpLoaderError('Invalid core dump SHA256 "{}", should be "{}"'
.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
"""
@ -226,22 +238,21 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
if self.dump_ver in [self.ELF_CRC32,
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,
self.BIN_V2]:
self._extract_bin_corefile()
self._extract_bin_corefile(e_machine)
else:
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
"""
self.core_elf_file.write(self.core_src.data)
# Need to be closed before read. Otherwise the result will be wrong
self.core_elf_file.close()
with open(self.core_elf_file, 'wb') as fw: # type: ignore
fw.write(self.core_src.data) # type: ignore
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)
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()])
if coredump_sha256.sha256 != app_sha256:
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))
if coredump_sha256.ver != self.version:
raise ESPCoreDumpLoaderError(
@ -267,46 +278,43 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
.format(coredump_sha256.ver, self.version))
@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:
return align_with * (size // align_with + 1)
return size
@staticmethod
def _build_note_section(name, sec_type, desc):
name = bytearray(name, encoding='ascii') + b'\0'
return NoteSection.build({
'namesz': len(name),
def _build_note_section(name, sec_type, desc): # type: (str, int, str) -> bytes
b_name = bytearray(name, encoding='ascii') + b'\0'
return NoteSection.build({ # type: ignore
'namesz': len(b_name),
'descsz': len(desc),
'type': sec_type,
'name': name,
'name': b_name,
'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
"""
tcbsz_aligned = self._get_aligned_size(self.header.tcbsz)
coredump_data_struct = Struct(
'tasks' / GreedyRange(
AlignedStruct(
4,
'task_header' / TaskHeader,
'tcb' / Bytes(self.header.tcbsz),
'stack' / Bytes(abs_(this.task_header.stack_top - this.task_header.stack_end)),
'tcb' / Bytes(self.header.tcbsz), # type: ignore
'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()
core_elf = ESPCoreDumpElfFile(e_machine=e_machine)
notes = b''
core_dump_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):
stack_len_aligned = self._get_aligned_size(abs(task.task_header.stack_top - task.task_header.stack_end))
task_status_kwargs = {
@ -314,32 +322,34 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
'task_flags': TASK_STATUS_CORRECT,
'task_tcb_addr': task.task_header.tcb_addr,
'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_name': Padding(16).build({}) # currently we don't have task_name, keep it as padding
}
# Write TCB
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,
task.tcb,
ElfFile.PT_LOAD,
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
except ESPCoreDumpLoaderError as e:
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
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'],
task.stack,
ElfFile.PT_LOAD,
ElfSegment.PF_R | ElfSegment.PF_W)
elif task_status_kwargs['task_stack_start'] \
and self.target_method_cls.addr_is_fake(task_status_kwargs['task_stack_start']):
elif (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
core_elf.add_segment(task_status_kwargs['task_stack_start'],
task.stack,
@ -355,7 +365,7 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
try:
logging.debug('Stack start_end: 0x{:x} @ 0x{:x}'
.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.task_header.stack_end > task.task_header.stack_top
)
@ -367,23 +377,24 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
EspTaskStatus.build(task_status_kwargs))
notes += self._build_note_section('CORE',
ElfFile.PT_LOAD,
self.arch_method_cls.build_prstatus_data(task.task_header.tcb_addr,
task_regs))
self.target_methods.build_prstatus_data(task.task_header.tcb_addr,
task_regs))
if extra_regs and len(core_dump_info_notes) == 0:
# actually there will be only one such note - for crashed task
if len(core_dump_info_notes) == 0: # the first task is the crashed task
core_dump_info_notes += self._build_note_section('ESP_CORE_DUMP_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(
'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:
@ -409,30 +420,29 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
.format(len(task_info_notes), 0, e))
# dump core ELF
core_elf.e_type = ElfFile.ET_CORE
core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA
core_elf.dump(self.core_elf_file.name)
core_elf.dump(self.core_elf_file) # type: ignore
class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
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__()
self.port = port
self.baud = baud
self.target = target
self._get_coredump(offset)
self._reload_coredump()
self._get_core_src(offset, target)
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)
"""
try:
if off:
logging.info('Invoke esptool to read image.')
self._invoke_esptool(off=off)
self._invoke_esptool(off=off, target=target)
else:
logging.info('Invoke parttool to read image.')
self._invoke_parttool()
@ -440,15 +450,14 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
if e.output:
logging.info(e.output)
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
"""
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:
tool_args.extend(['-p', self.port])
if self.baud:
@ -466,14 +475,14 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
# Here we use V1 format to locate the size
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
et_out = subprocess.check_output(tool_args)
if et_out:
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:
logging.error('Incorrect size of core dump image: {}, use partition size instead: {}'
.format(header.tot_len, part_size))
@ -492,7 +501,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
logging.debug(e.output)
raise e
def _invoke_parttool(self):
def _invoke_parttool(self): # type: () -> None
"""
Loads core dump from flash using parttool
"""
@ -503,7 +512,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
self.core_src_file = self._create_temp_file()
try:
tool_args.append(self.core_src_file.name)
tool_args.append(self.core_src_file) # type: ignore
# read core dump partition
et_out = subprocess.check_output(tool_args)
if et_out:
@ -515,7 +524,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
logging.debug(e.output)
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
"""
@ -545,28 +554,27 @@ class ESPCoreDumpFlashLoader(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__()
self.is_b64 = is_b64
self._get_coredump(path)
self._reload_coredump()
self._get_core_src(path)
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
"""
logging.debug('Load core dump from "%s", %s format', path, 'b64' if self.is_b64 else 'raw')
if not self.is_b64:
self.core_src_file = open(path, mode='rb')
self.core_src_file = path
else:
self.core_src_file = self._create_temp_file()
with open(path, 'rb') as fb64:
while True:
line = fb64.readline()
if len(line) == 0:
break
data = base64.standard_b64decode(line.rstrip(b'\r\n'))
self.core_src_file.write(data)
self.core_src_file.flush()
self.core_src_file.seek(0)
with open(self.core_src_file, 'wb') as fw:
with open(path, 'rb') as fb64:
while True:
line = fb64.readline()
if len(line) == 0:
break
data = base64.standard_b64decode(line.rstrip(b'\r\n'))
fw.write(data) # type: ignore

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");
# you may not use this file except in compliance with the License.
@ -16,12 +16,16 @@
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
XCHAL_EXCCAUSE_NUM = 64
# Exception cause dictionary to get translation of exccause register
# From 4.4.1.5 table 4-64 Exception Causes of Xtensa
# 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
EXCCAUSE_IDX = 0
EXCVADDR_IDX = 1
@ -100,14 +104,14 @@ class XtensaRegisters(object):
EPS7_IDX = 199
@property
def registers(self):
def registers(self): # type: () -> dict[str, int]
return {k: v for k, v in self.__class__.__dict__.items()
if not k.startswith('__') and isinstance(v, int)}
# Following structs are based on source code
# IDF_PATH/components/espcoredump/src/core_dump_port.c
XtensaPrStatus = Struct(
PrStatus = Struct(
'si_signo' / Int32ul,
'si_code' / 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
:param extra_info: extra info data str
: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)
if not exccause_str:
exccause_str = ('Invalid EXCCAUSE code', 'Invalid EXCAUSE description or not found.')
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
for i in range(5, len(extra_info), 2):
if (extra_info[i] >= XtensaRegisters.EPC1_IDX and extra_info[i] <= XtensaRegisters.EPC7_IDX):
print('epc%d 0x%x' % ((extra_info[i] - XtensaRegisters.EPC1_IDX + 1), extra_info[i + 1]))
if (extra_info[i] >= ExceptionRegisters.EPC1_IDX and extra_info[i] <= ExceptionRegisters.EPC7_IDX):
print('epc%d 0x%x' % ((extra_info[i] - ExceptionRegisters.EPC1_IDX + 1), extra_info[i + 1]))
# skip crashed_task_tcb, exccause, and excvaddr
for i in range(5, len(extra_info), 2):
if (extra_info[i] >= XtensaRegisters.EPS2_IDX and extra_info[i] <= XtensaRegisters.EPS7_IDX):
print('eps%d 0x%x' % ((extra_info[i] - XtensaRegisters.EPS2_IDX + 2), extra_info[i + 1]))
if (extra_info[i] >= ExceptionRegisters.EPS2_IDX and extra_info[i] <= ExceptionRegisters.EPS7_IDX):
print('eps%d 0x%x' % ((extra_info[i] - ExceptionRegisters.EPS2_IDX + 2), extra_info[i + 1]))
# from "gdb/xtensa-tdep.h"
@ -200,24 +204,11 @@ XT_STK_LCOUNT = 24
XT_STK_FRMSZ = 25
class _TargetMethodsESP32(_TargetMethodsBase):
@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):
class XtensaMethodsMixin(BaseArchMethodsMixin):
@staticmethod
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
# TODO: support for growing up stacks
if not grows_down:
@ -245,10 +236,10 @@ class _ArchMethodsXtensa(_ArchMethodsBase):
if regs[REG_PS_IDX] & (1 << 5):
regs[REG_PS_IDX] &= ~(1 << 4)
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:
extra_regs[XtensaRegisters.EXCCAUSE_IDX] = INVALID_CAUSE_VALUE
extra_regs[XtensaRegisters.EXCVADDR_IDX] = stack[XT_STK_EXCVADDR]
extra_regs[ExceptionRegisters.EXCCAUSE_IDX] = INVALID_CAUSE_VALUE
extra_regs[ExceptionRegisters.EXCVADDR_IDX] = stack[XT_STK_EXCVADDR]
else:
regs[REG_PC_IDX] = stack[XT_SOL_PC]
regs[REG_PS_IDX] = stack[XT_SOL_PS]
@ -258,8 +249,8 @@ class _ArchMethodsXtensa(_ArchMethodsBase):
return regs, extra_regs
@staticmethod
def build_prstatus_data(tcb_addr, task_regs):
return XtensaPrStatus.build({
def build_prstatus_data(tcb_addr, task_regs): # type: (int, list[int]) -> Any
return PrStatus.build({
'si_signo': 0,
'si_code': 0,
'si_errno': 0,
@ -276,3 +267,11 @@ class _ArchMethodsXtensa(_ArchMethodsBase):
'pr_cutime': 0,
'pr_cstime': 0,
}) + 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
#
# ESP32 core dump Utility
# ESP-IDF Core Dump Utility
import argparse
import logging
@ -10,7 +10,7 @@ import sys
from shutil import copyfile
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.gdb import EspGDB
from corefile.loader import ESPCoreDumpFileLoader, ESPCoreDumpFlashLoader
@ -28,32 +28,40 @@ except ImportError:
sys.stderr.write('esptool is not found!\n')
sys.exit(2)
try:
from typing import Optional, Tuple
except ImportError:
pass
if os.name == 'nt':
CLOSE_FDS = False
else:
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.
"""
elf = None
sym_cmd = ''
if os.path.exists(elf_path):
elf = ElfFile(elf_path)
for s in elf.sections:
if s.name == '.text':
sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr)
return elf, sym_cmd
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
core_filename = None
target = None
temp_files = None
if not args.core:
# 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':
# 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')
@ -63,51 +71,86 @@ def core_prepare():
# Load/convert the core file
if loader:
loader.create_corefile(exe_name=args.prog)
core_filename = loader.core_elf_file.name
loader.create_corefile(exe_name=args.prog, e_machine=e_machine)
core_filename = loader.core_elf_file
if args.save_core:
# 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
"""
rom_elf, rom_sym_cmd = load_aux_elf(args.rom_elf)
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)
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,
args=[args.gdb,
args=[gdb_tool,
'--nw', # ignore .gdbinit
'--core=%s' % core_filename, # core file,
'--core=%s' % core_elf_path, # core file,
'-ex', rom_sym_cmd,
args.prog],
stdin=None, stdout=None, stderr=None,
close_fds=CLOSE_FDS)
p.wait()
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
"""
core_filename, loader = core_prepare()
exe_elf = ElfFile(args.prog)
core_elf = ESPCoreDumpElfFile(core_filename)
exe_elf = ESPCoreDumpElfFile(args.prog)
core_elf_path, target, temp_files = get_core_dump_elf(e_machine=exe_elf.e_machine)
core_elf = ESPCoreDumpElfFile(core_elf_path)
if exe_elf.e_machine != core_elf.e_machine:
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
task_info = []
for seg in core_elf.note_segments:
@ -119,8 +162,11 @@ def info_corefile():
task_info.append(task_info_struct)
print('===============================================================')
print('==================== ESP32 CORE DUMP START ====================')
rom_elf, rom_sym_cmd = load_aux_elf(args.rom_elf)
gdb = EspGDB(args.gdb, [rom_sym_cmd], core_filename, args.prog, timeout_sec=args.gdb_timeout_sec)
rom_elf_path = get_rom_elf_path(target)
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
if extra_note:
@ -132,10 +178,12 @@ def info_corefile():
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('\n================== CURRENT THREAD REGISTERS ===================')
if extra_note and extra_info:
exception_registers_info(extra_info)
else:
print('Exception registers have not been found!')
# Only xtensa have exception registers
if exe_elf.e_machine == ESPCoreDumpElfFile.EM_XTENSA:
if extra_note and extra_info:
xtensa.print_exc_regs_info(extra_info)
else:
print('Exception registers have not been found!')
print(gdb.run_cmd('info registers'))
print('\n==================== CURRENT THREAD STACK =====================')
print(gdb.run_cmd('bt'))
@ -235,10 +283,14 @@ def info_corefile():
del gdb
print('Done!')
return temp_files
if __name__ == '__main__':
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),
help='Serial port device')
parser.add_argument('--baud', '-b', type=int,
@ -250,20 +302,20 @@ if __name__ == '__main__':
common_args = argparse.ArgumentParser(add_help=False)
common_args.add_argument('--debug', '-d', type=int, default=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')
common_args.add_argument('--core', '-c',
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',
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')
common_args.add_argument('--off', '-o', type=int,
help='Offset of coredump partition in flash (type "make partition_table" to see).')
common_args.add_argument('--save-core', '-s',
help='Save core to file. Otherwise temporary core file will be deleted. '
'Does not work with "-c"', )
common_args.add_argument('--rom-elf', '-r', default='esp32_rom.elf',
help='Path to ROM ELF file.')
common_args.add_argument('--rom-elf', '-r',
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')
operations = parser.add_subparsers(dest='operation')
@ -291,9 +343,18 @@ if __name__ == '__main__':
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
print('espcoredump.py v%s' % __version__)
if args.operation == 'info_corefile':
info_corefile()
elif args.operation == 'dbg_corefile':
dbg_corefile()
else:
raise ValueError('Please specify action, should be info_corefile or dbg_corefile')
temp_core_files = None
try:
if args.operation == 'info_corefile':
temp_core_files = info_corefile()
elif args.operation == '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 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 \
&& 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 \
&& coverage run -a --source=corefile ./test_espcoredump.py \
&& coverage report ../corefile/elf.py ../corefile/gdb.py ../corefile/loader.py ../corefile/xtensa.py ../espcoredump.py \

View File