mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-30 18:57:19 +02:00
feat(coredump): add esp32s2 and esp32c3 support
This commit is contained in:
@ -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)
|
||||
|
44
components/espcoredump/corefile/_parse_soc_header.py
Normal file
44
components/espcoredump/corefile/_parse_soc_header.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""
|
||||
This file is used to generate soc header constants into sub-package soc_headers
|
||||
"""
|
||||
import os
|
||||
from ast import literal_eval
|
||||
|
||||
from . import IDF_PATH, SUPPORTED_TARGETS
|
||||
|
||||
|
||||
def main(): # type: () -> None
|
||||
constants = [
|
||||
'SOC_DRAM_LOW',
|
||||
'SOC_DRAM_HIGH',
|
||||
'SOC_IRAM_LOW',
|
||||
'SOC_IRAM_HIGH',
|
||||
'SOC_RTC_DATA_LOW',
|
||||
'SOC_RTC_DATA_HIGH',
|
||||
'SOC_RTC_DRAM_LOW',
|
||||
'SOC_RTC_DRAM_HIGH',
|
||||
]
|
||||
|
||||
for target in SUPPORTED_TARGETS:
|
||||
target_constants = {}
|
||||
soc_header_fp = os.path.join(IDF_PATH, 'components/soc/{}/include/soc/soc.h'.format(target))
|
||||
module_fp = os.path.join(IDF_PATH, 'components', 'espcoredump', 'corefile', 'soc_headers',
|
||||
'{}.py'.format(target))
|
||||
|
||||
with open(soc_header_fp) as fr:
|
||||
for line in fr.readlines():
|
||||
for attr in constants:
|
||||
if '#define {}'.format(attr) in line:
|
||||
target_constants[attr] = literal_eval(line.strip().split()[-1])
|
||||
|
||||
for attr in constants:
|
||||
if attr not in target_constants:
|
||||
raise ValueError('ERROR: Attr {} is missing in {}'.format(attr, soc_header_fp))
|
||||
|
||||
with open(module_fp, 'w') as fw:
|
||||
for k, v in target_constants.items():
|
||||
fw.write('{} = {}\n'.format(k, hex(v)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
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]
|
||||
|
||||
exc_regs = []
|
||||
# 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:
|
||||
exc_regs.extend([reg_id, extra_regs[reg_id]])
|
||||
_regs = [task.task_header.tcb_addr] + exc_regs
|
||||
_regs.extend([reg_id, extra_regs[reg_id]])
|
||||
|
||||
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(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'))
|
||||
self.core_src_file.write(data)
|
||||
self.core_src_file.flush()
|
||||
self.core_src_file.seek(0)
|
||||
fw.write(data) # type: ignore
|
||||
|
63
components/espcoredump/corefile/riscv.py
Normal file
63
components/espcoredump/corefile/riscv.py
Normal file
@ -0,0 +1,63 @@
|
||||
#
|
||||
# Copyright 2021 Espressif Systems (Shanghai) CO., LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from construct import Int16ul, Int32ul, Padding, Struct
|
||||
|
||||
from . import BaseArchMethodsMixin, BaseTargetMethods, ESPCoreDumpLoaderError
|
||||
|
||||
try:
|
||||
from typing import Any, Optional, Tuple
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
RISCV_GP_REGS_COUNT = 32
|
||||
PRSTATUS_SIZE = 204
|
||||
PRSTATUS_OFFSET_PR_CURSIG = 12
|
||||
PRSTATUS_OFFSET_PR_PID = 24
|
||||
PRSTATUS_OFFSET_PR_REG = 72
|
||||
ELF_GREGSET_T_SIZE = 128
|
||||
|
||||
PrStruct = Struct(
|
||||
Padding(PRSTATUS_OFFSET_PR_CURSIG),
|
||||
'pr_cursig' / Int16ul,
|
||||
Padding(PRSTATUS_OFFSET_PR_PID - PRSTATUS_OFFSET_PR_CURSIG - Int16ul.sizeof()),
|
||||
'pr_pid' / Int32ul,
|
||||
Padding(PRSTATUS_OFFSET_PR_REG - PRSTATUS_OFFSET_PR_PID - Int32ul.sizeof()),
|
||||
'regs' / Int32ul[RISCV_GP_REGS_COUNT],
|
||||
Padding(PRSTATUS_SIZE - PRSTATUS_OFFSET_PR_REG - ELF_GREGSET_T_SIZE)
|
||||
)
|
||||
|
||||
|
||||
class RiscvMethodsMixin(BaseArchMethodsMixin):
|
||||
@staticmethod
|
||||
def get_registers_from_stack(data, grows_down):
|
||||
# type: (bytes, bool) -> Tuple[list[int], Optional[dict[int, int]]]
|
||||
regs = Int32ul[RISCV_GP_REGS_COUNT].parse(data)
|
||||
if not grows_down:
|
||||
raise ESPCoreDumpLoaderError('Growing up stacks are not supported for now!')
|
||||
return regs, None
|
||||
|
||||
@staticmethod
|
||||
def build_prstatus_data(tcb_addr, task_regs): # type: (int, list[int]) -> Any
|
||||
return PrStruct.build({
|
||||
'pr_cursig': 0,
|
||||
'pr_pid': tcb_addr,
|
||||
'regs': task_regs,
|
||||
})
|
||||
|
||||
|
||||
class Esp32c3Methods(BaseTargetMethods, RiscvMethodsMixin):
|
||||
TARGET = 'esp32c3'
|
8
components/espcoredump/corefile/soc_headers/esp32.py
Normal file
8
components/espcoredump/corefile/soc_headers/esp32.py
Normal file
@ -0,0 +1,8 @@
|
||||
SOC_DRAM_LOW = 0x3ffae000
|
||||
SOC_DRAM_HIGH = 0x40000000
|
||||
SOC_IRAM_LOW = 0x40080000
|
||||
SOC_IRAM_HIGH = 0x400a0000
|
||||
SOC_RTC_DRAM_LOW = 0x3ff80000
|
||||
SOC_RTC_DRAM_HIGH = 0x3ff82000
|
||||
SOC_RTC_DATA_LOW = 0x50000000
|
||||
SOC_RTC_DATA_HIGH = 0x50002000
|
8
components/espcoredump/corefile/soc_headers/esp32c3.py
Normal file
8
components/espcoredump/corefile/soc_headers/esp32c3.py
Normal file
@ -0,0 +1,8 @@
|
||||
SOC_IRAM_LOW = 0x4037c000
|
||||
SOC_IRAM_HIGH = 0x403e0000
|
||||
SOC_DRAM_LOW = 0x3fc80000
|
||||
SOC_DRAM_HIGH = 0x3fce0000
|
||||
SOC_RTC_DRAM_LOW = 0x50000000
|
||||
SOC_RTC_DRAM_HIGH = 0x50002000
|
||||
SOC_RTC_DATA_LOW = 0x50000000
|
||||
SOC_RTC_DATA_HIGH = 0x50002000
|
8
components/espcoredump/corefile/soc_headers/esp32s2.py
Normal file
8
components/espcoredump/corefile/soc_headers/esp32s2.py
Normal file
@ -0,0 +1,8 @@
|
||||
SOC_IRAM_LOW = 0x40020000
|
||||
SOC_IRAM_HIGH = 0x40070000
|
||||
SOC_DRAM_LOW = 0x3ffb0000
|
||||
SOC_DRAM_HIGH = 0x40000000
|
||||
SOC_RTC_DRAM_LOW = 0x3ff9e000
|
||||
SOC_RTC_DRAM_HIGH = 0x3ffa0000
|
||||
SOC_RTC_DATA_LOW = 0x50000000
|
||||
SOC_RTC_DATA_HIGH = 0x50002000
|
@ -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'
|
||||
|
@ -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,8 +178,10 @@ 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 ===================')
|
||||
# Only xtensa have exception registers
|
||||
if exe_elf.e_machine == ESPCoreDumpElfFile.EM_XTENSA:
|
||||
if extra_note and extra_info:
|
||||
exception_registers_info(extra_info)
|
||||
xtensa.print_exc_regs_info(extra_info)
|
||||
else:
|
||||
print('Exception registers have not been found!')
|
||||
print(gdb.run_cmd('info registers'))
|
||||
@ -235,10 +283,14 @@ def info_corefile():
|
||||
|
||||
del gdb
|
||||
print('Done!')
|
||||
return temp_files
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__)
|
||||
parser.add_argument('--chip', default=os.environ.get('ESPTOOL_CHIP', 'auto'),
|
||||
choices=['auto'] + SUPPORTED_TARGETS,
|
||||
help='Target chip type')
|
||||
parser.add_argument('--port', '-p', default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT),
|
||||
help='Serial port device')
|
||||
parser.add_argument('--baud', '-b', type=int,
|
||||
@ -250,20 +302,20 @@ if __name__ == '__main__':
|
||||
common_args = argparse.ArgumentParser(add_help=False)
|
||||
common_args.add_argument('--debug', '-d', type=int, default=3,
|
||||
help='Log level (0..3)')
|
||||
common_args.add_argument('--gdb', '-g', default='xtensa-esp32-elf-gdb',
|
||||
common_args.add_argument('--gdb', '-g',
|
||||
help='Path to gdb')
|
||||
common_args.add_argument('--core', '-c',
|
||||
help='Path to core dump file (if skipped core dump will be read from flash)')
|
||||
common_args.add_argument('--core-format', '-t', choices=['b64', 'elf', 'raw'], default='elf',
|
||||
help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), '
|
||||
help='File specified with "-c" is an ELF ("elf"), '
|
||||
'raw (raw) or base64-encoded (b64) binary')
|
||||
common_args.add_argument('--off', '-o', type=int,
|
||||
help='Offset of coredump partition in flash (type "make partition_table" to see).')
|
||||
common_args.add_argument('--save-core', '-s',
|
||||
help='Save core to file. Otherwise temporary core file will be deleted. '
|
||||
'Does not work with "-c"', )
|
||||
common_args.add_argument('--rom-elf', '-r', default='esp32_rom.elf',
|
||||
help='Path to ROM ELF file.')
|
||||
common_args.add_argument('--rom-elf', '-r',
|
||||
help='Path to ROM ELF file. Will use "<target>_rom.elf" if not specified')
|
||||
common_args.add_argument('prog', help='Path to program\'s ELF binary')
|
||||
|
||||
operations = parser.add_subparsers(dest='operation')
|
||||
@ -291,9 +343,18 @@ if __name__ == '__main__':
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
|
||||
|
||||
print('espcoredump.py v%s' % __version__)
|
||||
temp_core_files = None
|
||||
try:
|
||||
if args.operation == 'info_corefile':
|
||||
info_corefile()
|
||||
temp_core_files = info_corefile()
|
||||
elif args.operation == 'dbg_corefile':
|
||||
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
|
||||
|
@ -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 \
|
||||
|
0
tools/ci/mypy_ignore_list.txt
Normal file
0
tools/ci/mypy_ignore_list.txt
Normal file
Reference in New Issue
Block a user