diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index ec2770f5d3..e11876a807 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -185,6 +185,7 @@ test_idf_monitor: - tools/test_idf_monitor/outputs/* expire_in: 1 week script: + - eval $($IDF_PATH/tools/idf_tools.py export) - cd ${IDF_PATH}/tools/test_idf_monitor - ./run_test_idf_monitor.py @@ -276,6 +277,7 @@ test_espcoredump: # install CMake version specified in tools.json SETUP_TOOLS_LIST: "all" script: + - eval $($IDF_PATH/tools/idf_tools.py export) - retry_failed git clone ${IDF_COREDUMP_ELF_REPO} -b $IDF_COREDUMP_ELF_TAG - cd ${IDF_PATH}/components/espcoredump/test/ - ./test_espcoredump.sh ${CI_PROJECT_DIR}/idf-coredump-elf diff --git a/components/espcoredump/corefile/__init__.py b/components/espcoredump/corefile/__init__.py deleted file mode 100644 index 710cf81688..0000000000 --- a/components/espcoredump/corefile/__init__.py +++ /dev/null @@ -1,125 +0,0 @@ -# -# 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. -# - -__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', 'esp32s3'] -RISCV_TARGETS = ['esp32c3'] -SUPPORTED_TARGETS = XTENSA_TARGETS + RISCV_TARGETS - - -class ESPCoreDumpError(RuntimeError): - pass - - -class ESPCoreDumpLoaderError(ESPCoreDumpError): - pass - - -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]]] - """ - 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) - """ - pass - - @staticmethod - @abstractmethod - 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 deleted file mode 100644 index a57886f214..0000000000 --- a/components/espcoredump/corefile/_parse_soc_header.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -This file is used to generate soc header constants into sub-package soc_headers -""" -import os -from ast import literal_eval - -from corefile 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 deleted file mode 100644 index 8ffa12b6cd..0000000000 --- a/components/espcoredump/corefile/elf.py +++ /dev/null @@ -1,372 +0,0 @@ -# -# 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. -# - -import hashlib -import os - -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 -# and source code -# IDF_PATH/components/espcoredump/include_core_dump/elf.h - -ElfIdentification = Struct( - 'EI_MAG' / Const(b'\x7fELF'), - 'EI_CLASS' / Const(b'\x01'), # ELFCLASS32 - 'EI_DATA' / Const(b'\x01'), # ELFDATA2LSB - 'EI_VERSION' / Const(b'\x01'), # EV_CURRENT - Padding(9), -) - -ElfHeader = Struct( - 'e_ident' / ElfIdentification, - 'e_type' / Int16ul, - 'e_machine' / Int16ul, - 'e_version' / Int32ul, - 'e_entry' / Int32ul, - 'e_phoff' / Int32ul, - 'e_shoff' / Int32ul, - 'e_flags' / Int32ul, - 'e_ehsize' / Int16ul, - 'e_phentsize' / Int16ul, - 'e_phnum' / Int16ul, - 'e_shentsize' / Int16ul, - 'e_shnum' / Int16ul, - 'e_shstrndx' / Int16ul, -) - -SectionHeader = Struct( - 'sh_name' / Int32ul, - 'sh_type' / Int32ul, - 'sh_flags' / Int32ul, - 'sh_addr' / Int32ul, - 'sh_offset' / Int32ul, - 'sh_size' / Int32ul, - 'sh_link' / Int32ul, - 'sh_info' / Int32ul, - 'sh_addralign' / Int32ul, - 'sh_entsize' / Int32ul, -) - -ProgramHeader = Struct( - 'p_type' / Int32ul, - 'p_offset' / Int32ul, - 'p_vaddr' / Int32ul, - 'p_paddr' / Int32ul, - 'p_filesz' / Int32ul, - 'p_memsz' / Int32ul, - 'p_flags' / Int32ul, - 'p_align' / Int32ul, -) - -ElfHeaderTables = Struct( - 'elf_header' / ElfHeader, - 'program_headers' / Pointer(this.elf_header.e_phoff, ProgramHeader[this.elf_header.e_phnum]), - 'section_headers' / Pointer(this.elf_header.e_shoff, SectionHeader[this.elf_header.e_shnum]), -) - -NoteSection = AlignedStruct( - 4, - 'namesz' / Int32ul, - 'descsz' / Int32ul, - 'type' / Int32ul, - 'name' / Bytes(this.namesz), - 'desc' / Bytes(this.descsz), -) - -NoteSections = GreedyRange(NoteSection) - - -class ElfFile(object): - """ - Elf class to a single elf file - """ - - SHN_UNDEF = 0x00 - SHT_PROGBITS = 0x01 - SHT_STRTAB = 0x03 - SHT_NOBITS = 0x08 - - PT_LOAD = 0x01 - PT_NOTE = 0x04 - - ET_CORE = 0x04 - - 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 # type: Optional[Struct] - self._model = None # type: Optional[Container] - - self.sections = [] # type: list[ElfSection] - self.load_segments = [] # type: list[ElfSegment] - self.note_segments = [] # type: list[ElfNoteSegment] - - if elf_path and os.path.isfile(elf_path): - self.read_elf(elf_path) - - def read_elf(self, elf_path): # type: (str) -> None - """ - Read elf file, also write to ``self.model``, ``self.program_headers``, - ``self.section_headers`` - :param elf_path: elf file path - :return: None - """ - with open(elf_path, 'rb') as fr: - elf_bytes = fr.read() - header_tables = ElfHeaderTables.parse(elf_bytes) - self.e_type = header_tables.elf_header.e_type - self.e_machine = header_tables.elf_header.e_machine - - self._struct = self._generate_struct_from_headers(header_tables) - self._model = self._struct.parse(elf_bytes) - - self.load_segments = [ElfSegment(seg.ph.p_vaddr, - seg.data, - seg.ph.p_flags) for seg in self._model.load_segments] - self.note_segments = [ElfNoteSegment(seg.ph.p_vaddr, - seg.data, - seg.ph.p_flags) for seg in self._model.note_segments] - self.sections = [ElfSection(self._parse_string_table(self._model.string_table, sec.sh.sh_name), - sec.sh.sh_addr, - sec.data, - sec.sh.sh_flags) for sec in self._model.sections] - - @staticmethod - def _parse_string_table(byte_str, offset): # type: (bytes, int) -> str - section_name_str = byte_str[offset:] - string_end = section_name_str.find(0x00) - - if (string_end == -1): - raise ValueError('Unable to get section name from section header string table') - - name = section_name_str[:string_end].decode('utf-8') - - return name - - 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 - :return: Struct of the whole file - """ - elf_header = header_tables.elf_header - program_headers = header_tables.program_headers - section_headers = header_tables.section_headers - assert program_headers or section_headers - - string_table_sh = None - load_segment_subcons = [] - note_segment_subcons = [] - # Here we point back to make segments know their program headers - for i, ph in enumerate(program_headers): - args = [ - 'ph' / Pointer(elf_header.e_phoff + i * ProgramHeader.sizeof(), ProgramHeader), - 'data' / Pointer(ph.p_offset, Bytes(ph.p_filesz)), - ] - if ph.p_vaddr == 0 and ph.p_type == self.PT_NOTE: - args.append('note_secs' / Pointer(ph.p_offset, NoteSections)) - note_segment_subcons.append(Struct(*args)) - elif ph.p_vaddr != 0: - load_segment_subcons.append(Struct(*args)) - - section_subcons = [] - for i, sh in enumerate(section_headers): - if sh.sh_type == self.SHT_STRTAB and i == elf_header.e_shstrndx: - string_table_sh = sh - elif sh.sh_addr != 0 and sh.sh_type == self.SHT_PROGBITS: - section_subcons.append(Struct( - 'sh' / Pointer(elf_header.e_shoff + i * SectionHeader.sizeof(), SectionHeader), - 'data' / Pointer(sh.sh_offset, Bytes(sh.sh_size)), - )) - - args = [ - 'elf_header' / ElfHeader, - 'load_segments' / Sequence(*load_segment_subcons), - 'note_segments' / Sequence(*note_segment_subcons), - 'sections' / Sequence(*section_subcons), - ] - if string_table_sh is not None: - args.append('string_table' / Pointer(string_table_sh.sh_offset, Bytes(string_table_sh.sh_size))) - - return Struct(*args) - - @property - def sha256(self): # type: () -> bytes - """ - :return: SHA256 hash of the input ELF file - """ - sha256 = hashlib.sha256() - sha256.update(self._struct.build(self._model)) # type: ignore - return sha256.digest() - - -class ElfSection(object): - SHF_WRITE = 0x01 - SHF_ALLOC = 0x02 - SHF_EXECINSTR = 0x04 - SHF_MASKPROC = 0xf0000000 - - 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): # type: () -> str - if self.flags & self.SHF_MASKPROC: - return 'MS' - - res = 'R' - res += 'W' if self.flags & self.SHF_WRITE else ' ' - res += 'X' if self.flags & self.SHF_EXECINSTR else ' ' - res += 'A' if self.flags & self.SHF_ALLOC else ' ' - return res - - 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()) - - -class ElfSegment(object): - PF_X = 0x01 - PF_W = 0x02 - PF_R = 0x04 - - 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): # type: () -> str - res = '' - res += 'R' if self.flags & self.PF_R else ' ' - res += 'W' if self.flags & self.PF_W else ' ' - res += 'E' if self.flags & self.PF_X else ' ' - return res - - @staticmethod - def _type_str(): # type: () -> str - return 'LOAD' - - 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): # 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(): # type: () -> str - return 'NOTE' - - -TASK_STATUS_CORRECT = 0x00 -TASK_STATUS_TCB_CORRUPTED = 0x01 -TASK_STATUS_STACK_CORRUPTED = 0x02 - -EspTaskStatus = Struct( - 'task_index' / Int32ul, - 'task_flags' / Int32ul, - 'task_tcb_addr' / Int32ul, - 'task_stack_start' / Int32ul, - 'task_stack_len' / Int32ul, - 'task_name' / Bytes(16), -) - - -class ESPCoreDumpElfFile(ElfFile): - PT_INFO = 8266 - PT_TASK_INFO = 678 - PT_EXTRA_INFO = 677 - - CURR_TASK_MARKER = 0xdeadbeef - - # 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): # type: (int, bytes, int, int) -> None - if seg_type != self.PT_NOTE: - self.load_segments.append(ElfSegment(addr, data, flags)) - else: - self.note_segments.append(ElfNoteSegment(addr, data, flags)) - - def dump(self, output_path): # type: (str) -> None - """ - Dump self.model into file - :param output_path: output file path - :return: None - """ - res = b'' - res += ElfHeader.build({ - 'e_type': self.e_type, - 'e_machine': self.e_machine, - 'e_version': self.EV_CURRENT, - 'e_entry': 0, - 'e_phoff': ElfHeader.sizeof(), - 'e_shoff': 0, - 'e_flags': 0, - 'e_ehsize': ElfHeader.sizeof(), - 'e_phentsize': ProgramHeader.sizeof(), - 'e_phnum': len(self.load_segments) + len(self.note_segments), - 'e_shentsize': 0, - 'e_shnum': 0, - 'e_shstrndx': self.SHN_UNDEF, - }) - - offset = ElfHeader.sizeof() + (len(self.load_segments) + len(self.note_segments)) * ProgramHeader.sizeof() - _segments = self.load_segments + self.note_segments # type: ignore - for seg in _segments: - res += ProgramHeader.build({ - 'p_type': seg.type, - 'p_offset': offset, - 'p_vaddr': seg.addr, - 'p_paddr': seg.addr, - 'p_filesz': len(seg.data), - 'p_memsz': len(seg.data), - 'p_flags': seg.flags, - 'p_align': 0, - }) - offset += len(seg.data) - - for seg in _segments: - res += seg.data - - with open(output_path, 'wb') as fw: - fw.write(res) diff --git a/components/espcoredump/corefile/gdb.py b/components/espcoredump/corefile/gdb.py deleted file mode 100644 index d0c0467c77..0000000000 --- a/components/espcoredump/corefile/gdb.py +++ /dev/null @@ -1,129 +0,0 @@ -# -# 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. -# - -import logging -import re -import time - -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): - - """ - Start GDB and initialize a GdbController instance - """ - gdb_args = ['--quiet', # inhibit dumping info at start-up - '--nx', # inhibit window interface - '--nw', # ignore .gdbinit - '--interpreter=mi2', # use GDB/MI v2 - '--core=%s' % core_filename] # core file - for c in gdb_cmds: - if c: - gdb_args += ['-ex', c] - gdb_args.append(prog_filename) - self.p = GdbController(gdb_path=gdb_path, gdb_args=gdb_args) - self.timeout = timeout_sec - - # Consume initial output by issuing a dummy command - self._gdbmi_run_cmd_get_responses(cmd='-data-list-register-values x pc', - resp_message=None, resp_type='console', multiple=True, - done_message='done', done_type='result') - - def __del__(self): - try: - self.p.exit() - except IndexError: - logging.warning('Attempt to terminate the GDB process failed, because it is already terminated. Skip') - - def _gdbmi_run_cmd_get_responses(self, cmd, resp_message, resp_type, multiple=True, - done_message=None, done_type=None): - - self.p.write(cmd, read_response=False) - t_end = time.time() + self.timeout - filtered_response_list = [] - all_responses = [] - while time.time() < t_end: - more_responses = self.p.get_gdb_response(timeout_sec=0, raise_error_on_timeout=False) - filtered_response_list += filter(lambda rsp: rsp['message'] == resp_message and rsp['type'] == resp_type, - more_responses) - all_responses += more_responses - if filtered_response_list and not multiple: - break - if done_message and done_type and self._gdbmi_filter_responses(more_responses, done_message, done_type): - break - if not filtered_response_list and not multiple: - raise ESPCoreDumpError("Couldn't find response with message '{}', type '{}' in responses '{}'".format( - resp_message, resp_type, str(all_responses) - )) - return filtered_response_list - - def _gdbmi_run_cmd_get_one_response(self, cmd, resp_message, resp_type): - - return self._gdbmi_run_cmd_get_responses(cmd, resp_message, resp_type, multiple=False)[0] - - 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): - """ Get FreeRTOS task name given the TCB address """ - try: - val = self._gdbmi_data_evaluate_expression('(char*)((TCB_t *)0x%x)->pcTaskName' % tcb_addr) - except (ESPCoreDumpError, KeyError): - # KeyError is raised when "value" is not in "payload" - return '' - - # Value is of form '0x12345678 "task_name"', extract the actual name - result = re.search(r"\"([^']*)\"$", val) - if result: - return result.group(1) - return '' - - 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, - resp_message=None, resp_type='console', multiple=True, - done_message='done', done_type='result') - return ''.join([x['payload'] for x in filtered_responses]) \ - .replace('\\n', '\n') \ - .replace('\\t', '\t') \ - .rstrip('\n') - - 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): - """ 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') - - @staticmethod - def _gdbmi_filter_responses(responses, resp_message, resp_type): - return list(filter(lambda rsp: rsp['message'] == resp_message and rsp['type'] == resp_type, responses)) - - @staticmethod - 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 deleted file mode 100644 index 144a350826..0000000000 --- a/components/espcoredump/corefile/loader.py +++ /dev/null @@ -1,583 +0,0 @@ -# -# 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. -# - -import base64 -import binascii -import hashlib -import logging -import os -import subprocess -import sys -import tempfile - -from construct import AlignedStruct, Bytes, GreedyRange, Int32ul, Padding, Struct, abs_, this - -from . import ESPCoreDumpLoaderError -from .elf import (TASK_STATUS_CORRECT, TASK_STATUS_TCB_CORRUPTED, ElfFile, ElfSegment, ESPCoreDumpElfFile, - EspTaskStatus, NoteSection) -from .riscv import Esp32c3Methods -from .xtensa import Esp32Methods, Esp32S2Methods, Esp32S3Methods - -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') - -# Following structs are based on source code -# components/espcoredump/include_core_dump/esp_core_dump_priv.h - -EspCoreDumpV1Header = Struct( - 'tot_len' / Int32ul, - 'ver' / Int32ul, - 'task_num' / Int32ul, - 'tcbsz' / Int32ul, -) - -EspCoreDumpV2Header = Struct( - 'tot_len' / Int32ul, - 'ver' / Int32ul, - 'task_num' / Int32ul, - 'tcbsz' / Int32ul, - 'segs_num' / Int32ul, -) - -CRC = Int32ul -SHA256 = Bytes(32) - -TaskHeader = Struct( - 'tcb_addr' / Int32ul, - 'stack_top' / Int32ul, - 'stack_end' / Int32ul, -) - -MemSegmentHeader = Struct( - 'mem_start' / Int32ul, - 'mem_sz' / Int32ul, - 'data' / Bytes(this.mem_sz), -) - - -class EspCoreDumpVersion(object): - """Core dump version class, it contains all version-dependent params - """ - # Chip IDs should be in sync with components/esp_hw_support/include/esp_chip_info.h - ESP32 = 0 - ESP32S2 = 2 - ESP32S3 = 9 - XTENSA_CHIPS = [ESP32, ESP32S2, ESP32S3] - - ESP32C3 = 5 - RISCV_CHIPS = [ESP32C3] - - COREDUMP_SUPPORTED_TARGETS = XTENSA_CHIPS + RISCV_CHIPS - - def __init__(self, version=None): # type: (int) -> None - """Constructor for core dump version - """ - super(EspCoreDumpVersion, self).__init__() - if version is None: - self.version = 0 - else: - self.set_version(version) - - @staticmethod - def make_dump_ver(major, minor): # type: (int, int) -> int - return ((major & 0xFF) << 8) | ((minor & 0xFF) << 0) - - def set_version(self, version): # type: (int) -> None - self.version = version - - @property - def chip_ver(self): # type: () -> int - return (self.version & 0xFFFF0000) >> 16 - - @property - def dump_ver(self): # type: () -> int - return self.version & 0x0000FFFF - - @property - def major(self): # type: () -> int - return (self.version & 0x0000FF00) >> 8 - - @property - def minor(self): # type: () -> int - return self.version & 0x000000FF - - -class EspCoreDumpLoader(EspCoreDumpVersion): - # "legacy" stands for core dumps v0.1 (before IDF v4.1) - BIN_V1 = EspCoreDumpVersion.make_dump_ver(0, 1) - BIN_V2 = EspCoreDumpVersion.make_dump_ver(0, 2) - ELF_CRC32 = EspCoreDumpVersion.make_dump_ver(1, 0) - ELF_SHA256 = EspCoreDumpVersion.make_dump_ver(1, 1) - - def __init__(self): # type: () -> None - super(EspCoreDumpLoader, self).__init__() - self.core_src_file = None # type: Optional[str] - self.core_src_struct = None - self.core_src = None - - self.core_elf_file = None # type: Optional[str] - - self.header = None - self.header_struct = EspCoreDumpV1Header - self.checksum_struct = CRC - - # target classes will be assigned in ``_reload_coredump`` - self.target_methods = Esp32Methods() - - self.temp_files = [] # type: list[str] - - def _create_temp_file(self): # type: () -> str - t = tempfile.NamedTemporaryFile('wb', delete=False) - # 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 _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 - self.set_version(_header.ver) - if self.dump_ver == self.ELF_CRC32: - self.checksum_struct = CRC - self.header_struct = EspCoreDumpV2Header - elif self.dump_ver == self.ELF_SHA256: - self.checksum_struct = SHA256 - self.header_struct = EspCoreDumpV2Header - elif self.dump_ver == self.BIN_V1: - self.checksum_struct = CRC - self.header_struct = EspCoreDumpV1Header - elif self.dump_ver == self.BIN_V2: - self.checksum_struct = CRC - self.header_struct = EspCoreDumpV2Header - else: - raise ESPCoreDumpLoaderError('Core dump version "0x%x" is not supported!' % self.dump_ver) - - self.core_src_struct = Struct( - 'header' / self.header_struct, - '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) # 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.COREDUMP_SUPPORTED_TARGETS: - if self.chip_ver == self.ESP32: - 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 - elif self.chip_ver == self.ESP32S3: - self.target_methods = Esp32S3Methods() # type: ignore - else: - raise NotImplementedError - else: - raise ESPCoreDumpLoaderError('Core dump chip "0x%x" is not supported!' % self.chip_ver) - - 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)) - - if self.checksum_struct == CRC: - self._crc_validate() - elif self.checksum_struct == SHA256: - self._sha256_validate() - - 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): # 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') # 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, e_machine=ESPCoreDumpElfFile.EM_XTENSA): - # type: (Optional[str], int) -> None - """ - Creates core dump ELF file - """ - self._validate_dump_file() - self.core_elf_file = self._create_temp_file() - - if self.dump_ver in [self.ELF_CRC32, - self.ELF_SHA256]: - self._extract_elf_corefile(exe_name, e_machine) - elif self.dump_ver in [self.BIN_V1, - self.BIN_V2]: - self._extract_bin_corefile(e_machine) - else: - raise NotImplementedError - - 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 - """ - 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, 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: - for note_sec in seg.note_secs: - # Check for version info note - if note_sec.name == 'ESP_CORE_DUMP_INFO' \ - and note_sec.type == ESPCoreDumpElfFile.PT_INFO \ - and exe_name: - exe_elf = ElfFile(exe_name) - app_sha256 = binascii.hexlify(exe_elf.sha256) - coredump_sha256_struct = Struct( - 'ver' / Int32ul, - 'sha256' / Bytes(64) # SHA256 as hex string - ) - 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({!r}) != app SHA256({!r}).' - .format(coredump_sha256, app_sha256)) - if coredump_sha256.ver != self.version: - raise ESPCoreDumpLoaderError( - 'Invalid application image for coredump: coredump SHA256 version({}) != app SHA256 version({}).' - .format(coredump_sha256.ver, self.version)) - - @staticmethod - 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): # 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': b_name, - 'desc': desc, - }) - - def _extract_bin_corefile(self, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (int) -> None - """ - Creates core dump ELF file - """ - coredump_data_struct = Struct( - 'tasks' / GreedyRange( - AlignedStruct( - 4, - 'task_header' / TaskHeader, - '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] # type: ignore - ) - 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) # 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 = { - 'task_index': i, - '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_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_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(self.header.tcbsz, task.task_header.tcb_addr, e)) # type: ignore - - # Write stack - try: - 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_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, - ElfFile.PT_LOAD, - ElfSegment.PF_R | ElfSegment.PF_W) - except ESPCoreDumpLoaderError as e: - logging.warning('Skip task\'s ({:x}) stack {} bytes @ 0x{:x}. (Reason: {})' - .format(task_status_kwargs['tcb_addr'], - task_status_kwargs['stack_len_aligned'], - task_status_kwargs['stack_base'], - e)) - - 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.target_methods.get_registers_from_stack( - task.stack, - task.task_header.stack_end > task.task_header.stack_top - ) - except Exception as e: - raise ESPCoreDumpLoaderError(str(e)) - - task_info_notes += self._build_note_section('TASK_INFO', - ESPCoreDumpElfFile.PT_TASK_INFO, - EspTaskStatus.build(task_status_kwargs)) - notes += self._build_note_section('CORE', - ElfFile.PT_LOAD, - self.target_methods.build_prstatus_data(task.task_header.tcb_addr, - task_regs)) - - 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)) # 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]]) - - core_dump_info_notes += self._build_note_section( - 'EXTRA_INFO', - ESPCoreDumpElfFile.PT_EXTRA_INFO, - Int32ul[len(_regs)].build(_regs) - ) - - if self.dump_ver == self.BIN_V2: - for header in coredump_data.mem_seg_headers: - logging.debug('Read memory segment {} bytes @ 0x{:x}'.format(header.mem_sz, header.mem_start)) - core_elf.add_segment(header.mem_start, header.data, ElfFile.PT_LOAD, ElfSegment.PF_R | ElfSegment.PF_W) - - # add notes - try: - core_elf.add_segment(0, notes, ElfFile.PT_NOTE, 0) - except ESPCoreDumpLoaderError as e: - logging.warning('Skip NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})'.format(len(notes), 0, e)) - # add core dump info notes - try: - core_elf.add_segment(0, core_dump_info_notes, ElfFile.PT_NOTE, 0) - except ESPCoreDumpLoaderError as e: - logging.warning('Skip core dump info NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})' - .format(len(core_dump_info_notes), 0, e)) - try: - core_elf.add_segment(0, task_info_notes, ElfFile.PT_NOTE, 0) - except ESPCoreDumpLoaderError as e: - logging.warning('Skip failed tasks info NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})' - .format(len(task_info_notes), 0, e)) - # dump core ELF - core_elf.e_type = ElfFile.ET_CORE - core_elf.dump(self.core_elf_file) # type: ignore - - -class ESPCoreDumpFlashLoader(EspCoreDumpLoader): - ESP_COREDUMP_PART_TABLE_OFF = 0x8000 - - 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._get_core_src(offset, target) - self.target = self._load_core_src() - - 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, target=target) - else: - logging.info('Invoke parttool to read image.') - self._invoke_parttool() - except subprocess.CalledProcessError as e: - if e.output: - logging.info(e.output) - logging.error('Error during the subprocess execution') - - def _invoke_esptool(self, off=None, target=None): # type: (Optional[int], Optional[str]) -> None - """ - Loads core dump from flash using elftool - """ - 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: - tool_args.extend(['-b', str(self.baud)]) - - self.core_src_file = self._create_temp_file() - try: - (part_offset, part_size) = self._get_core_dump_partition_info() - if not off: - off = part_offset # set default offset if not specified - logging.warning('The core dump image offset is not specified. Use partition offset: %d.', part_offset) - if part_offset != off: - logging.warning('Predefined image offset: %d does not match core dump partition offset: %d', off, - part_offset) - - # 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) # 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, '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)) - coredump_len = part_size - else: - coredump_len = header.tot_len - # set actual size of core dump image and read it from flash - tool_args[-2] = str(coredump_len) - et_out = subprocess.check_output(tool_args) - if et_out: - logging.info(et_out.decode('utf-8')) - except subprocess.CalledProcessError as e: - logging.error('esptool script execution failed with err %d', e.returncode) - logging.debug("Command ran: '%s'", e.cmd) - logging.debug('Command out:') - logging.debug(e.output) - raise e - - def _invoke_parttool(self): # type: () -> None - """ - Loads core dump from flash using parttool - """ - tool_args = [sys.executable, PARTTOOL_PY] - if self.port: - tool_args.extend(['--port', self.port]) - tool_args.extend(['read_partition', '--partition-type', 'data', '--partition-subtype', 'coredump', '--output']) - - self.core_src_file = self._create_temp_file() - try: - tool_args.append(self.core_src_file) # type: ignore - # read core dump partition - et_out = subprocess.check_output(tool_args) - if et_out: - logging.info(et_out.decode('utf-8')) - except subprocess.CalledProcessError as e: - logging.error('parttool script execution failed with err %d', e.returncode) - logging.debug("Command ran: '%s'", e.cmd) - logging.debug('Command out:') - logging.debug(e.output) - raise e - - def _get_core_dump_partition_info(self, part_off=None): # type: (Optional[int]) -> Tuple[int, int] - """ - Get core dump partition info using parttool - """ - logging.info('Retrieving core dump partition offset and size...') - if not part_off: - part_off = self.ESP_COREDUMP_PART_TABLE_OFF - try: - tool_args = [sys.executable, PARTTOOL_PY, '-q', '--partition-table-offset', str(part_off)] - if self.port: - tool_args.extend(['--port', self.port]) - invoke_args = tool_args + ['get_partition_info', '--partition-type', 'data', - '--partition-subtype', 'coredump', - '--info', 'offset', 'size'] - res = subprocess.check_output(invoke_args).strip() - (offset_str, size_str) = res.rsplit(b'\n')[-1].split(b' ') - size = int(size_str, 16) - offset = int(offset_str, 16) - logging.info('Core dump partition offset=%d, size=%d', offset, size) - except subprocess.CalledProcessError as e: - logging.error('parttool get partition info failed with err %d', e.returncode) - logging.debug("Command ran: '%s'", e.cmd) - logging.debug('Command out:') - logging.debug(e.output) - logging.error('Check if the coredump partition exists in partition table.') - raise e - return offset, size - - -class ESPCoreDumpFileLoader(EspCoreDumpLoader): - def __init__(self, path, is_b64=False): # type: (str, bool) -> None - super(ESPCoreDumpFileLoader, self).__init__() - self.is_b64 = is_b64 - - self._get_core_src(path) - self.target = self._load_core_src() - - 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 = path - else: - self.core_src_file = self._create_temp_file() - 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 deleted file mode 100644 index c9b3587e24..0000000000 --- a/components/espcoredump/corefile/riscv.py +++ /dev/null @@ -1,62 +0,0 @@ -# -# 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 corefile 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 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/espcoredump/corefile/soc_headers/esp32.py b/components/espcoredump/corefile/soc_headers/esp32.py deleted file mode 100644 index 88953e136b..0000000000 --- a/components/espcoredump/corefile/soc_headers/esp32.py +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 9f828e2962..0000000000 --- a/components/espcoredump/corefile/soc_headers/esp32c3.py +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 521faf2d7c..0000000000 --- a/components/espcoredump/corefile/soc_headers/esp32s2.py +++ /dev/null @@ -1,8 +0,0 @@ -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/soc_headers/esp32s3.py b/components/espcoredump/corefile/soc_headers/esp32s3.py deleted file mode 100644 index d0d5679cda..0000000000 --- a/components/espcoredump/corefile/soc_headers/esp32s3.py +++ /dev/null @@ -1,8 +0,0 @@ -SOC_DRAM_LOW = 0x3FC88000 -SOC_DRAM_HIGH = 0x3FD00000 -SOC_IRAM_LOW = 0x40370000 -SOC_IRAM_HIGH = 0x403E0000 -SOC_RTC_DRAM_LOW = 0x600FE000 -SOC_RTC_DRAM_HIGH = 0x60100000 -SOC_RTC_DATA_LOW = 0x50000000 -SOC_RTC_DATA_HIGH = 0x50002000 diff --git a/components/espcoredump/corefile/xtensa.py b/components/espcoredump/corefile/xtensa.py deleted file mode 100644 index d2fa810f91..0000000000 --- a/components/espcoredump/corefile/xtensa.py +++ /dev/null @@ -1,281 +0,0 @@ -# -# 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, Int64ul, Struct - -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 - -XTENSA_EXCEPTION_CAUSE_DICT = { - 0: ('IllegalInstructionCause', 'Illegal instruction'), - 1: ('SyscallCause', 'SYSCALL instruction'), - 2: ('InstructionFetchErrorCause', - 'Processor internal physical address or data error during instruction fetch. (See EXCVADDR for more information)'), - 3: ('LoadStoreErrorCause', - 'Processor internal physical address or data error during load or store. (See EXCVADDR for more information)'), - 4: ('Level1InterruptCause', 'Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register'), - 5: ('AllocaCause', 'MOVSP instruction, if caller`s registers are not in the register file'), - 6: ('IntegerDivideByZeroCause', 'QUOS: QUOU, REMS: or REMU divisor operand is zero'), - 8: ('PrivilegedCause', 'Attempt to execute a privileged operation when CRING ? 0'), - 9: ('LoadStoreAlignmentCause', 'Load or store to an unaligned address. (See EXCVADDR for more information)'), - 12: ('InstrPIFDataErrorCause', 'PIF data error during instruction fetch. (See EXCVADDR for more information)'), - 13: ('LoadStorePIFDataErrorCause', - 'Synchronous PIF data error during LoadStore access. (See EXCVADDR for more information)'), - 14: ('InstrPIFAddrErrorCause', 'PIF address error during instruction fetch. (See EXCVADDR for more information)'), - 15: ('LoadStorePIFAddrErrorCause', - 'Synchronous PIF address error during LoadStore access. (See EXCVADDR for more information)'), - 16: ('InstTLBMissCause', 'Error during Instruction TLB refill. (See EXCVADDR for more information)'), - 17: ('InstTLBMultiHitCause', 'Multiple instruction TLB entries matched. (See EXCVADDR for more information)'), - 18: ('InstFetchPrivilegeCause', - 'An instruction fetch referenced a virtual address at a ring level less than CRING. (See EXCVADDR for more information)'), - 20: ('InstFetchProhibitedCause', - 'An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch (EXCVADDR).'), - 24: ('LoadStoreTLBMissCause', 'Error during TLB refill for a load or store. (See EXCVADDR for more information)'), - 25: ('LoadStoreTLBMultiHitCause', - 'Multiple TLB entries matched for a load or store. (See EXCVADDR for more information)'), - 26: ('LoadStorePrivilegeCause', - 'A load or store referenced a virtual address at a ring level less than CRING. (See EXCVADDR for more information)'), - 28: ('LoadProhibitedCause', - 'A load referenced a page mapped with an attribute that does not permit loads. (See EXCVADDR for more information)'), - 29: ('StoreProhibitedCause', - 'A store referenced a page mapped with an attribute that does not permit stores [Region Protection Option or MMU Option].'), - 32: ('Coprocessor0Disabled', 'Coprocessor 0 instruction when cp0 disabled'), - 33: ('Coprocessor1Disabled', 'Coprocessor 1 instruction when cp1 disabled'), - 34: ('Coprocessor2Disabled', 'Coprocessor 2 instruction when cp2 disabled'), - 35: ('Coprocessor3Disabled', 'Coprocessor 3 instruction when cp3 disabled'), - 36: ('Coprocessor4Disabled', 'Coprocessor 4 instruction when cp4 disabled'), - 37: ('Coprocessor5Disabled', 'Coprocessor 5 instruction when cp5 disabled'), - 38: ('Coprocessor6Disabled', 'Coprocessor 6 instruction when cp6 disabled'), - 39: ('Coprocessor7Disabled', 'Coprocessor 7 instruction when cp7 disabled'), - INVALID_CAUSE_VALUE: ( - 'InvalidCauseRegister', 'Invalid EXCCAUSE register value or current task is broken and was skipped'), - # ESP panic pseudo reasons - XCHAL_EXCCAUSE_NUM + 0: ('UnknownException', 'Unknown exception'), - XCHAL_EXCCAUSE_NUM + 1: ('DebugException', 'Unhandled debug exception'), - XCHAL_EXCCAUSE_NUM + 2: ('DoubleException', 'Double exception'), - XCHAL_EXCCAUSE_NUM + 3: ('KernelException', 'Unhandled kernel exception'), - XCHAL_EXCCAUSE_NUM + 4: ('CoprocessorException', 'Coprocessor exception'), - XCHAL_EXCCAUSE_NUM + 5: ('InterruptWDTTimoutCPU0', 'Interrupt wdt timeout on CPU0'), - XCHAL_EXCCAUSE_NUM + 6: ('InterruptWDTTimoutCPU1', 'Interrupt wdt timeout on CPU1'), - XCHAL_EXCCAUSE_NUM + 7: ('CacheError', 'Cache disabled but cached memory region accessed'), -} - - -class ExceptionRegisters(object): - # extra regs IDs used in EXTRA_INFO note - EXCCAUSE_IDX = 0 - EXCVADDR_IDX = 1 - EPC1_IDX = 177 - EPC2_IDX = 178 - EPC3_IDX = 179 - EPC4_IDX = 180 - EPC5_IDX = 181 - EPC6_IDX = 182 - EPC7_IDX = 183 - EPS2_IDX = 194 - EPS3_IDX = 195 - EPS4_IDX = 196 - EPS5_IDX = 197 - EPS6_IDX = 198 - EPS7_IDX = 199 - - @property - 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 -PrStatus = Struct( - 'si_signo' / Int32ul, - 'si_code' / Int32ul, - 'si_errno' / Int32ul, - 'pr_cursig' / Int16ul, - 'pr_pad0' / Int16ul, - 'pr_sigpend' / Int32ul, - 'pr_sighold' / Int32ul, - 'pr_pid' / Int32ul, - 'pr_ppid' / Int32ul, - 'pr_pgrp' / Int32ul, - 'pr_sid' / Int32ul, - 'pr_utime' / Int64ul, - 'pr_stime' / Int64ul, - 'pr_cutime' / Int64ul, - 'pr_cstime' / Int64ul, -) - - -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 * 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 * ExceptionRegisters.EXCVADDR_IDX + 1]) - - # skip crashed_task_tcb, exccause, and excvaddr - for i in range(5, len(extra_info), 2): - 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] >= 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" -# typedef struct -# { -# 0 xtensa_elf_greg_t pc; -# 1 xtensa_elf_greg_t ps; -# 2 xtensa_elf_greg_t lbeg; -# 3 xtensa_elf_greg_t lend; -# 4 xtensa_elf_greg_t lcount; -# 5 xtensa_elf_greg_t sar; -# 6 xtensa_elf_greg_t windowstart; -# 7 xtensa_elf_greg_t windowbase; -# 8..63 xtensa_elf_greg_t reserved[8+48]; -# 64 xtensa_elf_greg_t ar[64]; -# } xtensa_elf_gregset_t; -REG_PC_IDX = 0 -REG_PS_IDX = 1 -REG_LB_IDX = 2 -REG_LE_IDX = 3 -REG_LC_IDX = 4 -REG_SAR_IDX = 5 -# REG_WS_IDX = 6 -# REG_WB_IDX = 7 -REG_AR_START_IDX = 64 -# REG_AR_NUM = 64 -# FIXME: acc to xtensa_elf_gregset_t number of regs must be 128, -# but gdb complains when it less then 129 -REG_NUM = 129 - -# XT_SOL_EXIT = 0 -XT_SOL_PC = 1 -XT_SOL_PS = 2 -# XT_SOL_NEXT = 3 -XT_SOL_AR_START = 4 -XT_SOL_AR_NUM = 4 -# XT_SOL_FRMSZ = 8 - -XT_STK_EXIT = 0 -XT_STK_PC = 1 -XT_STK_PS = 2 -XT_STK_AR_START = 3 -XT_STK_AR_NUM = 16 -XT_STK_SAR = 19 -XT_STK_EXCCAUSE = 20 -XT_STK_EXCVADDR = 21 -XT_STK_LBEG = 22 -XT_STK_LEND = 23 -XT_STK_LCOUNT = 24 -XT_STK_FRMSZ = 25 - - -class XtensaMethodsMixin(BaseArchMethodsMixin): - @staticmethod - def get_registers_from_stack(data, grows_down): - # 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: - raise ESPCoreDumpLoaderError('Growing up stacks are not supported for now!') - ex_struct = Struct( - 'stack' / Int32ul[XT_STK_FRMSZ] - ) - if len(data) < ex_struct.sizeof(): - raise ESPCoreDumpLoaderError('Too small stack to keep frame: %d bytes!' % len(data)) - - stack = ex_struct.parse(data).stack - # Stack frame type indicator is always the first item - rc = stack[XT_STK_EXIT] - if rc != 0: - regs[REG_PC_IDX] = stack[XT_STK_PC] - regs[REG_PS_IDX] = stack[XT_STK_PS] - for i in range(XT_STK_AR_NUM): - regs[REG_AR_START_IDX + i] = stack[XT_STK_AR_START + i] - regs[REG_SAR_IDX] = stack[XT_STK_SAR] - regs[REG_LB_IDX] = stack[XT_STK_LBEG] - regs[REG_LE_IDX] = stack[XT_STK_LEND] - regs[REG_LC_IDX] = stack[XT_STK_LCOUNT] - # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set - # and GDB can not unwind callstack properly (it implies not windowed call0) - if regs[REG_PS_IDX] & (1 << 5): - regs[REG_PS_IDX] &= ~(1 << 4) - if stack[XT_STK_EXCCAUSE] in XTENSA_EXCEPTION_CAUSE_DICT: - extra_regs[ExceptionRegisters.EXCCAUSE_IDX] = stack[XT_STK_EXCCAUSE] - else: - 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] - for i in range(XT_SOL_AR_NUM): - regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i] - # nxt = stack[XT_SOL_NEXT] - return regs, extra_regs - - @staticmethod - 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, - 'pr_cursig': 0, # TODO: set sig only for current/failed task - 'pr_pad0': 0, - 'pr_sigpend': 0, - 'pr_sighold': 0, - 'pr_pid': tcb_addr, - 'pr_ppid': 0, - 'pr_pgrp': 0, - 'pr_sid': 0, - 'pr_utime': 0, - 'pr_stime': 0, - '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' - - -class Esp32S3Methods(BaseTargetMethods, XtensaMethodsMixin): - TARGET = 'esp32s3' diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py old mode 100755 new mode 100644 index 14bddbbe1c..bf667e5333 --- a/components/espcoredump/espcoredump.py +++ b/components/espcoredump/espcoredump.py @@ -1,334 +1,22 @@ -#!/usr/bin/env python # -# ESP-IDF Core Dump Utility +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +# -import argparse import logging -import os -import subprocess -import sys -from shutil import copyfile - -from construct import GreedyRange, Int32ul, Struct -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 -from pygdbmi.gdbcontroller import DEFAULT_GDB_TIMEOUT_SEC +import os.path try: - from typing import Optional, Tuple + from esp_coredump import CoreDump except ImportError: - # Only used for type annotations - pass + raise ModuleNotFoundError('No module named "esp_coredump" please install esp_coredump by running ' + '"python -m pip install esp-coredump"') -IDF_PATH = os.getenv('IDF_PATH') -if not IDF_PATH: - sys.stderr.write('IDF_PATH is not found! Set proper IDF_PATH in environment.\n') - sys.exit(2) - -sys.path.insert(0, os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool')) -try: - import esptool -except ImportError: - sys.stderr.write('esptool is not found!\n') - sys.exit(2) - -if os.name == 'nt': - CLOSE_FDS = False -else: - CLOSE_FDS = True +from esp_coredump.cli_ext import parser -def load_aux_elf(elf_path): # type: (str) -> str - """ - Loads auxiliary ELF file and composes GDB command to read its symbols. - """ - 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 sym_cmd - - -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, 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') - else: - # Core file is already in the ELF format - core_filename = args.core - - # Load/convert the core file - if loader: - 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, args.save_core) - target = loader.target - temp_files = loader.temp_files - - return core_filename, target, temp_files # type: ignore - - -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 - """ - 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=[gdb_tool, - '--nw', # ignore .gdbinit - '--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(): # type: () -> Optional[list[str]] - """ - Command to load core dump from file or flash and print it's data in user friendly form - """ - 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') - - extra_note = None - task_info = [] - for seg in core_elf.note_segments: - for note_sec in seg.note_secs: - if note_sec.type == ESPCoreDumpElfFile.PT_EXTRA_INFO and 'EXTRA_INFO' in note_sec.name.decode('ascii'): - extra_note = note_sec - if note_sec.type == ESPCoreDumpElfFile.PT_TASK_INFO and 'TASK_INFO' in note_sec.name.decode('ascii'): - task_info_struct = EspTaskStatus.parse(note_sec.desc) - task_info.append(task_info_struct) - print('===============================================================') - print('==================== ESP32 CORE DUMP START ====================') - 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: - extra_info = Struct('regs' / GreedyRange(Int32ul)).parse(extra_note.desc).regs - marker = extra_info[0] - if marker == ESPCoreDumpElfFile.CURR_TASK_MARKER: - print('\nCrashed task has been skipped.') - else: - 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 ===================') - # 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')) - if task_info and task_info[0].task_flags != TASK_STATUS_CORRECT: - print('The current crashed task is corrupted.') - print('Task #%d info: flags, tcb, stack (%x, %x, %x).' % (task_info[0].task_index, - task_info[0].task_flags, - task_info[0].task_tcb_addr, - task_info[0].task_stack_start)) - print('\n======================== THREADS INFO =========================') - print(gdb.run_cmd('info threads')) - # THREADS STACKS - threads, _ = gdb.get_thread_info() - for thr in threads: - thr_id = int(thr['id']) - tcb_addr = gdb.gdb2freertos_thread_id(thr['target-id']) - task_index = int(thr_id) - 1 - task_name = gdb.get_freertos_task_name(tcb_addr) - gdb.switch_thread(thr_id) - print('\n==================== THREAD {} (TCB: 0x{:x}, name: \'{}\') =====================' - .format(thr_id, tcb_addr, task_name)) - print(gdb.run_cmd('bt')) - if task_info and task_info[task_index].task_flags != TASK_STATUS_CORRECT: - print("The task '%s' is corrupted." % thr_id) - print('Task #%d info: flags, tcb, stack (%x, %x, %x).' % (task_info[task_index].task_index, - task_info[task_index].task_flags, - task_info[task_index].task_tcb_addr, - task_info[task_index].task_stack_start)) - print('\n\n======================= ALL MEMORY REGIONS ========================') - print('Name Address Size Attrs') - merged_segs = [] - core_segs = core_elf.load_segments - for sec in exe_elf.sections: - merged = False - for seg in core_segs: - if seg.addr <= sec.addr <= seg.addr + len(seg.data): - # sec: |XXXXXXXXXX| - # seg: |...XXX.............| - seg_addr = seg.addr - if seg.addr + len(seg.data) <= sec.addr + len(sec.data): - # sec: |XXXXXXXXXX| - # seg: |XXXXXXXXXXX...| - # merged: |XXXXXXXXXXXXXX| - seg_len = len(sec.data) + (sec.addr - seg.addr) - else: - # sec: |XXXXXXXXXX| - # seg: |XXXXXXXXXXXXXXXXX| - # merged: |XXXXXXXXXXXXXXXXX| - seg_len = len(seg.data) - merged_segs.append((sec.name, seg_addr, seg_len, sec.attr_str(), True)) - core_segs.remove(seg) - merged = True - elif sec.addr <= seg.addr <= sec.addr + len(sec.data): - # sec: |XXXXXXXXXX| - # seg: |...XXX.............| - seg_addr = sec.addr - if (seg.addr + len(seg.data)) >= (sec.addr + len(sec.data)): - # sec: |XXXXXXXXXX| - # seg: |..XXXXXXXXXXX| - # merged: |XXXXXXXXXXXXX| - seg_len = len(sec.data) + (seg.addr + len(seg.data)) - (sec.addr + len(sec.data)) - else: - # sec: |XXXXXXXXXX| - # seg: |XXXXXX| - # merged: |XXXXXXXXXX| - seg_len = len(sec.data) - merged_segs.append((sec.name, seg_addr, seg_len, sec.attr_str(), True)) - core_segs.remove(seg) - merged = True - - if not merged: - merged_segs.append((sec.name, sec.addr, len(sec.data), sec.attr_str(), False)) - - for ms in merged_segs: - print('%s 0x%x 0x%x %s' % (ms[0], ms[1], ms[2], ms[3])) - - for cs in core_segs: - # core dump exec segments are from ROM, other are belong to tasks (TCB or stack) - if cs.flags & ElfSegment.PF_X: - seg_name = 'rom.text' - else: - seg_name = 'tasks.data' - print('.coredump.%s 0x%x 0x%x %s' % (seg_name, cs.addr, len(cs.data), cs.attr_str())) - if args.print_mem: - print('\n====================== CORE DUMP MEMORY CONTENTS ========================') - for cs in core_elf.load_segments: - # core dump exec segments are from ROM, other are belong to tasks (TCB or stack) - if cs.flags & ElfSegment.PF_X: - seg_name = 'rom.text' - else: - seg_name = 'tasks.data' - print('.coredump.%s 0x%x 0x%x %s' % (seg_name, cs.addr, len(cs.data), cs.attr_str())) - print(gdb.run_cmd('x/%dx 0x%x' % (len(cs.data) // 4, cs.addr))) - - print('\n===================== ESP32 CORE DUMP END =====================') - print('===============================================================') - - 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, - default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD), - help='Serial port baud rate used when flashing/reading') - parser.add_argument('--gdb-timeout-sec', type=int, default=DEFAULT_GDB_TIMEOUT_SEC, - help='Overwrite the default internal delay for gdb responses') - - 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', - 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='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', - 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') - - operations.add_parser('dbg_corefile', parents=[common_args], - help='Starts GDB debugging session with specified corefile') - - info_coredump = operations.add_parser('info_corefile', parents=[common_args], - help='Print core dump info from file') - info_coredump.add_argument('--print-mem', '-m', action='store_true', - help='Print memory dump') - +def main(): # type: () -> None args = parser.parse_args() if args.debug == 0: @@ -343,13 +31,19 @@ if __name__ == '__main__': log_level = logging.DEBUG logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) - print('espcoredump.py v%s' % __version__) + kwargs = {k: v for k, v in vars(args).items() if v is not None} + + del(kwargs['debug']) + del(kwargs['operation']) + + espcoredump = CoreDump(**kwargs) temp_core_files = None + try: if args.operation == 'info_corefile': - temp_core_files = info_corefile() + temp_core_files = espcoredump.info_corefile() elif args.operation == 'dbg_corefile': - temp_core_files = dbg_corefile() + temp_core_files = espcoredump.dbg_corefile() else: raise ValueError('Please specify action, should be info_corefile or dbg_corefile') finally: @@ -359,3 +53,7 @@ if __name__ == '__main__': os.remove(f) except OSError: pass + + +if __name__ == '__main__': + main() diff --git a/components/espcoredump/test/test_espcoredump.py b/components/espcoredump/test/test_espcoredump.py index fa352f1abd..819586bc85 100755 --- a/components/espcoredump/test/test_espcoredump.py +++ b/components/espcoredump/test/test_espcoredump.py @@ -1,36 +1,20 @@ #!/usr/bin/env python # -# Copyright 2018 Espressif Systems (Shanghai) PTE 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. +# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 import os -import sys import unittest try: - from corefile.elf import ESPCoreDumpElfFile - from corefile.loader import ESPCoreDumpFileLoader, ESPCoreDumpLoaderError + from esp_coredump.corefile import ESPCoreDumpLoaderError + from esp_coredump.corefile.elf import ESPCoreDumpElfFile + from esp_coredump.corefile.loader import ESPCoreDumpFileLoader except ImportError: - idf_path = os.getenv('IDF_PATH') - if idf_path: - sys.path.insert(0, os.path.join(idf_path, 'components', 'espcoredump')) - else: - sys.path.insert(0, '..') - from corefile.elf import ESPCoreDumpElfFile - from corefile.loader import ESPCoreDumpFileLoader, ESPCoreDumpLoaderError + raise ModuleNotFoundError('No module named "esp_coredump" please install esp_coredump by running ' + '"python -m pip install esp-coredump"') -SUPPORTED_TARGET = ['esp32', 'esp32s2', 'esp32c3'] +SUPPORTED_TARGET = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3'] class TestESPCoreDumpElfFile(unittest.TestCase): diff --git a/components/espcoredump/test/test_espcoredump.sh b/components/espcoredump/test/test_espcoredump.sh index 5732ae40b2..fa3045a2a1 100755 --- a/components/espcoredump/test/test_espcoredump.sh +++ b/components/espcoredump/test/test_espcoredump.sh @@ -11,21 +11,22 @@ else elf_dir=$1 fi +COREDUMP_VERSION="espcoredump.py v$(python -c "import pkg_resources; print(pkg_resources.get_distribution('esp-coredump').version)")" +COREDUMP_VERSION_REGEX="espcoredump.py v([0-9])+.([0-9a-z-])+(.[0-9a-z-])?" SUPPORTED_TARGETS=("esp32" "esp32s2" "esp32c3" "esp32s3" ) res=0 -python -m coverage erase for chip in "${SUPPORTED_TARGETS[@]}"; do { echo "run b64 decoding tests on $chip" - python -m coverage run -a --source=corefile ../espcoredump.py --chip="$chip" --gdb-timeout-sec 5 info_corefile -m -t b64 -c "${chip}/coredump.b64" -s "${chip}/core.elf" "${elf_dir}/${chip}.elf" &>"${chip}/output" && + esp-coredump --chip="$chip" --gdb-timeout-sec 5 info_corefile -m -t b64 -c "${chip}/coredump.b64" -s "${chip}/core.elf" "${elf_dir}/${chip}.elf" &>"${chip}/output" && + sed -i -E "s/$COREDUMP_VERSION_REGEX/$COREDUMP_VERSION/" "${chip}/expected_output" diff "${chip}/expected_output" "${chip}/output" && - python -m coverage run -a --source=corefile ../espcoredump.py --chip="$chip" --gdb-timeout-sec 5 info_corefile -m -t elf -c "${chip}/core.elf" "${elf_dir}/${chip}.elf" &>"${chip}/output2" && + esp-coredump --chip="$chip" --gdb-timeout-sec 5 info_corefile -m -t elf -c "${chip}/core.elf" "${elf_dir}/${chip}.elf" &>"${chip}/output2" && + sed -E "s/$COREDUMP_VERSION_REGEX/$COREDUMP_VERSION/" "${chip}/expected_output" diff "${chip}/expected_output" "${chip}/output2" } || { echo 'The test for espcoredump has failed!' res=1 } done -python -m coverage run -a --source=corefile ./test_espcoredump.py -python -m coverage report ../corefile/*.py ../espcoredump.py exit $res diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index ab9d7426bc..eff8ea1a83 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -728,18 +728,6 @@ components/esp_wifi/src/lib_printf.c components/esp_wifi/src/mesh_event.c components/esp_wifi/src/smartconfig.c components/esp_wifi/test/test_wifi_init.c -components/espcoredump/corefile/__init__.py -components/espcoredump/corefile/_parse_soc_header.py -components/espcoredump/corefile/elf.py -components/espcoredump/corefile/gdb.py -components/espcoredump/corefile/loader.py -components/espcoredump/corefile/riscv.py -components/espcoredump/corefile/soc_headers/esp32.py -components/espcoredump/corefile/soc_headers/esp32c3.py -components/espcoredump/corefile/soc_headers/esp32s2.py -components/espcoredump/corefile/soc_headers/esp32s3.py -components/espcoredump/corefile/xtensa.py -components/espcoredump/espcoredump.py components/espcoredump/include/esp_core_dump.h components/espcoredump/include/port/riscv/esp_core_dump_summary_port.h components/espcoredump/include/port/xtensa/esp_core_dump_summary_port.h @@ -754,7 +742,6 @@ components/espcoredump/src/core_dump_binary.c components/espcoredump/src/core_dump_flash.c components/espcoredump/src/core_dump_uart.c components/espcoredump/src/port/riscv/core_dump_port.c -components/espcoredump/test/test_espcoredump.py components/espcoredump/test_apps/main/test_core_dump.c components/fatfs/diskio/diskio.c components/fatfs/diskio/diskio_impl.h @@ -2582,7 +2569,6 @@ tools/test_apps/system/panic/test_panic_util/test_panic_util.py tools/test_apps/system/startup/app_test.py tools/test_apps/system/startup/main/test_startup_main.c tools/test_idf_monitor/dummy.c -tools/test_idf_monitor/run_test_idf_monitor.py tools/test_idf_py/extra_path/some_ext.py tools/test_idf_py/idf_ext.py tools/test_idf_py/test_idf_extensions/test_ext/__init__.py diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index e8e3bf4751..0c3c4f443e 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -3,7 +3,6 @@ components/app_update/otatool.py components/efuse/efuse_table_gen.py components/efuse/test_efuse_host/efuse_tests.py components/esp_wifi/test_md5/test_md5.sh -components/espcoredump/espcoredump.py components/espcoredump/test/test_espcoredump.py components/espcoredump/test/test_espcoredump.sh components/espcoredump/test_apps/build_espcoredump.sh diff --git a/tools/idf_monitor_base/constants.py b/tools/idf_monitor_base/constants.py index a3783882d9..139c390d94 100644 --- a/tools/idf_monitor_base/constants.py +++ b/tools/idf_monitor_base/constants.py @@ -39,7 +39,6 @@ __version__ = '1.1' # paths to scripts PANIC_OUTPUT_DECODE_SCRIPT = os.path.join(os.path.dirname(__file__), '..', 'gdb_panic_server.py') -COREDUMP_SCRIPT = os.path.join(os.path.dirname(__file__), '..', '..', 'components', 'espcoredump', 'espcoredump.py') # regex matches an potential PC value (0x4xxxxxxx) MATCH_PCADDR = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE) diff --git a/tools/idf_monitor_base/coredump.py b/tools/idf_monitor_base/coredump.py index c8ab6b4d73..0a2a9119f4 100644 --- a/tools/idf_monitor_base/coredump.py +++ b/tools/idf_monitor_base/coredump.py @@ -1,12 +1,11 @@ +import io import os import queue -import subprocess -import sys import tempfile -from contextlib import contextmanager +from contextlib import contextmanager, redirect_stdout from typing import Generator -from .constants import COREDUMP_SCRIPT, TAG_KEY +from .constants import TAG_KEY from .logger import Logger from .output_helpers import yellow_print from .web_socket_client import WebSocketClient @@ -46,46 +45,45 @@ class CoreDump: if self._decode_coredumps != COREDUMP_DECODE_INFO: raise NotImplementedError('process_coredump: %s not implemented' % self._decode_coredumps) coredump_file = None - try: - # On Windows, the temporary file can't be read unless it is closed. - # Set delete=False and delete the file manually later. - with tempfile.NamedTemporaryFile(mode='wb', delete=False) as coredump_file: - coredump_file.write(self._coredump_buffer) - coredump_file.flush() + # On Windows, the temporary file can't be read unless it is closed. + # Set delete=False and delete the file manually later. + with tempfile.NamedTemporaryFile(mode='wb', delete=False) as coredump_file: + coredump_file.write(self._coredump_buffer) + coredump_file.flush() - if self.websocket_client: - self.logger.output_enabled = True - yellow_print('Communicating through WebSocket') - self.websocket_client.send({'event': 'coredump', - 'file': coredump_file.name, - 'prog': self.elf_file}) - yellow_print('Waiting for debug finished event') - self.websocket_client.wait([('event', 'debug_finished')]) - yellow_print('Communications through WebSocket is finished') - else: - cmd = [sys.executable, - COREDUMP_SCRIPT, - 'info_corefile', - '--core', coredump_file.name, - '--core-format', 'b64', - self.elf_file - ] - output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) - self.logger.output_enabled = True - self.logger.print(output) # noqa: E999 - self.logger.output_enabled = False # Will be reenabled in check_coredump_trigger_after_print - except subprocess.CalledProcessError as e: - yellow_print('Failed to run espcoredump script: {}\n{}\n\n'.format(e, e.output)) + if self.websocket_client: self.logger.output_enabled = True - self.logger.print(COREDUMP_UART_START + b'\n') - self.logger.print(self._coredump_buffer) - # end line will be printed in handle_serial_input - finally: - if coredump_file is not None: - try: - os.unlink(coredump_file.name) - except OSError as e: - yellow_print('Couldn\'t remote temporary core dump file ({})'.format(e)) + yellow_print('Communicating through WebSocket') + self.websocket_client.send({'event': 'coredump', + 'file': coredump_file.name, + 'prog': self.elf_file}) + yellow_print('Waiting for debug finished event') + self.websocket_client.wait([('event', 'debug_finished')]) + yellow_print('Communications through WebSocket is finished') + else: + try: + import esp_coredump + except ImportError as e: + yellow_print('Failed to parse core dump info: ' + 'Module {} is not installed \n\n'.format(e.name)) + self.logger.output_enabled = True + self.logger.print(COREDUMP_UART_START + b'\n') + self.logger.print(self._coredump_buffer) + # end line will be printed in handle_serial_input + else: + coredump = esp_coredump.CoreDump(core=coredump_file.name, core_format='b64', prog=self.elf_file) + f = io.StringIO() + with redirect_stdout(f): + coredump.info_corefile() + output = f.getvalue() + self.logger.output_enabled = True + self.logger.print(output.encode('utf-8')) + self.logger.output_enabled = False # Will be reenabled in check_coredump_trigger_after_print + if coredump_file is not None: + try: + os.unlink(coredump_file.name) + except OSError as e: + yellow_print('Couldn\'t remote temporary core dump file ({})'.format(e)) def _check_coredump_trigger_before_print(self, line): # type: (bytes) -> None if self._decode_coredumps == COREDUMP_DECODE_DISABLE: diff --git a/tools/requirements/requirements.core.txt b/tools/requirements/requirements.core.txt index 5554dd3b9e..3619b19980 100644 --- a/tools/requirements/requirements.core.txt +++ b/tools/requirements/requirements.core.txt @@ -8,16 +8,13 @@ cryptography pyparsing pyelftools idf-component-manager +esp-coredump # esptool dependencies (see components/esptool_py/esptool/setup.py) reedsolo bitstring ecdsa -# espcoredump dependencies -construct -pygdbmi - # kconfig and menuconfig dependencies kconfiglib windows-curses; sys_platform == 'win32' diff --git a/tools/test_idf_monitor/run_test_idf_monitor.py b/tools/test_idf_monitor/run_test_idf_monitor.py index 20f312f7c7..dc151a16e7 100755 --- a/tools/test_idf_monitor/run_test_idf_monitor.py +++ b/tools/test_idf_monitor/run_test_idf_monitor.py @@ -1,18 +1,7 @@ #!/usr/bin/env python # -# Copyright 2018-2019 Espressif Systems (Shanghai) PTE 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. +# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 from __future__ import print_function, unicode_literals @@ -20,6 +9,7 @@ import errno import filecmp import os import pty +import re import socket import subprocess import sys @@ -58,6 +48,22 @@ SOCKET_TIMEOUT = 30 # the test is restarted after failure (idf_monitor has to be killed): RETRIES_PER_TEST = 2 +COREDUMP_VERSION_REGEX = r'espcoredump\.py v\d+\.[\d\w-]+(\.[\d\w-]+)?' + + +def remove_coredump_version_string(file_path): + with open(file_path, 'r') as file: + init_text = file.read() + modified_text = re.sub(COREDUMP_VERSION_REGEX, '', init_text, re.MULTILINE) + + if not init_text != modified_text: + return None + + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file.write(modified_text.encode()) + + return temp_file.name + def monitor_timeout(process): if process.poll() is None: @@ -167,7 +173,10 @@ def test_iteration(runner, test): print('\tThe client was closed successfully') f1 = IN_DIR + test[2] f2 = OUT_DIR + test[2] + temp_f1, temp_f2 = remove_coredump_version_string(f1), remove_coredump_version_string(f2) print('\tdiff {} {}'.format(f1, f2)) + if temp_f1 and temp_f2: + f1, f2 = temp_f1, temp_f2 if filecmp.cmp(f1, f2, shallow=False): print('\tTest has passed') else: