diff --git a/components/espcoredump/corefile/__init__.py b/components/espcoredump/corefile/__init__.py index 5eb0a3313a..ae523c796c 100644 --- a/components/espcoredump/corefile/__init__.py +++ b/components/espcoredump/corefile/__init__.py @@ -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) diff --git a/components/espcoredump/corefile/_parse_soc_header.py b/components/espcoredump/corefile/_parse_soc_header.py new file mode 100644 index 0000000000..1e0fe309c9 --- /dev/null +++ b/components/espcoredump/corefile/_parse_soc_header.py @@ -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() diff --git a/components/espcoredump/corefile/elf.py b/components/espcoredump/corefile/elf.py index f0e381b2c2..17e3189eb1 100644 --- a/components/espcoredump/corefile/elf.py +++ b/components/espcoredump/corefile/elf.py @@ -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, diff --git a/components/espcoredump/corefile/gdb.py b/components/espcoredump/corefile/gdb.py index df93eea2b0..d0c0467c77 100644 --- a/components/espcoredump/corefile/gdb.py +++ b/components/espcoredump/corefile/gdb.py @@ -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) diff --git a/components/espcoredump/corefile/loader.py b/components/espcoredump/corefile/loader.py index 64349f92ab..2cd3e47408 100644 --- a/components/espcoredump/corefile/loader.py +++ b/components/espcoredump/corefile/loader.py @@ -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 diff --git a/components/espcoredump/corefile/riscv.py b/components/espcoredump/corefile/riscv.py new file mode 100644 index 0000000000..bbc83a1c84 --- /dev/null +++ b/components/espcoredump/corefile/riscv.py @@ -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' diff --git a/components/espcoredump/corefile/soc_headers/__init__.py b/components/espcoredump/corefile/soc_headers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/espcoredump/corefile/soc_headers/esp32.py b/components/espcoredump/corefile/soc_headers/esp32.py new file mode 100644 index 0000000000..88953e136b --- /dev/null +++ b/components/espcoredump/corefile/soc_headers/esp32.py @@ -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 diff --git a/components/espcoredump/corefile/soc_headers/esp32c3.py b/components/espcoredump/corefile/soc_headers/esp32c3.py new file mode 100644 index 0000000000..9f828e2962 --- /dev/null +++ b/components/espcoredump/corefile/soc_headers/esp32c3.py @@ -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 diff --git a/components/espcoredump/corefile/soc_headers/esp32s2.py b/components/espcoredump/corefile/soc_headers/esp32s2.py new file mode 100644 index 0000000000..521faf2d7c --- /dev/null +++ b/components/espcoredump/corefile/soc_headers/esp32s2.py @@ -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 diff --git a/components/espcoredump/corefile/xtensa.py b/components/espcoredump/corefile/xtensa.py index 10c5fbaed9..1c038e3819 100644 --- a/components/espcoredump/corefile/xtensa.py +++ b/components/espcoredump/corefile/xtensa.py @@ -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' diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py index 0019265040..fecb8370aa 100755 --- a/components/espcoredump/espcoredump.py +++ b/components/espcoredump/espcoredump.py @@ -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 "_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 diff --git a/components/espcoredump/test/test_espcoredump.sh b/components/espcoredump/test/test_espcoredump.sh index 7c6d083f4c..bbbbbd42b0 100755 --- a/components/espcoredump/test/test_espcoredump.sh +++ b/components/espcoredump/test/test_espcoredump.sh @@ -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 \ diff --git a/tools/ci/mypy_ignore_list.txt b/tools/ci/mypy_ignore_list.txt new file mode 100644 index 0000000000..e69de29bb2