mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-04 21:24:32 +02:00
Merge branch 'feature/espcoredump_py_riscv_support' into 'master'
feature: espcoredump py riscv support Closes IDF-2638 and IDF-2715 See merge request espressif/esp-idf!12259
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 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()
|
@@ -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,
|
||||
task_regs))
|
||||
self.target_methods.build_prstatus_data(task.task_header.tcb_addr,
|
||||
task_regs))
|
||||
|
||||
if extra_regs and len(core_dump_info_notes) == 0:
|
||||
# actually there will be only one such note - for crashed task
|
||||
if len(core_dump_info_notes) == 0: # the first task is the crashed task
|
||||
core_dump_info_notes += self._build_note_section('ESP_CORE_DUMP_INFO',
|
||||
ESPCoreDumpElfFile.PT_INFO,
|
||||
Int32ul.build(self.header.ver))
|
||||
Int32ul.build(self.header.ver)) # type: ignore
|
||||
_regs = [task.task_header.tcb_addr]
|
||||
|
||||
# For xtensa, we need to put the exception registers into the extra info as well
|
||||
if e_machine == ESPCoreDumpElfFile.EM_XTENSA and extra_regs:
|
||||
for reg_id in extra_regs:
|
||||
_regs.extend([reg_id, extra_regs[reg_id]])
|
||||
|
||||
exc_regs = []
|
||||
for reg_id in extra_regs:
|
||||
exc_regs.extend([reg_id, extra_regs[reg_id]])
|
||||
_regs = [task.task_header.tcb_addr] + exc_regs
|
||||
core_dump_info_notes += self._build_note_section(
|
||||
'EXTRA_INFO',
|
||||
ESPCoreDumpElfFile.PT_EXTRA_INFO,
|
||||
Int32ul[1 + len(exc_regs)].build(_regs)
|
||||
Int32ul[len(_regs)].build(_regs)
|
||||
)
|
||||
|
||||
if self.dump_ver == self.BIN_V2:
|
||||
@@ -409,30 +420,29 @@ class EspCoreDumpLoader(EspCoreDumpVersion):
|
||||
.format(len(task_info_notes), 0, e))
|
||||
# dump core ELF
|
||||
core_elf.e_type = ElfFile.ET_CORE
|
||||
core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA
|
||||
core_elf.dump(self.core_elf_file.name)
|
||||
core_elf.dump(self.core_elf_file) # type: ignore
|
||||
|
||||
|
||||
class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
ESP_COREDUMP_PART_TABLE_OFF = 0x8000
|
||||
|
||||
def __init__(self, offset, target='esp32', port=None, baud=None):
|
||||
def __init__(self, offset, target=None, port=None, baud=None):
|
||||
# type: (int, Optional[str], Optional[str], Optional[int]) -> None
|
||||
super(ESPCoreDumpFlashLoader, self).__init__()
|
||||
self.port = port
|
||||
self.baud = baud
|
||||
self.target = target
|
||||
|
||||
self._get_coredump(offset)
|
||||
self._reload_coredump()
|
||||
self._get_core_src(offset, target)
|
||||
self.target = self._load_core_src()
|
||||
|
||||
def _get_coredump(self, off):
|
||||
def _get_core_src(self, off, target=None): # type: (int, Optional[str]) -> None
|
||||
"""
|
||||
Loads core dump from flash using parttool or elftool (if offset is set)
|
||||
"""
|
||||
try:
|
||||
if off:
|
||||
logging.info('Invoke esptool to read image.')
|
||||
self._invoke_esptool(off=off)
|
||||
self._invoke_esptool(off=off, target=target)
|
||||
else:
|
||||
logging.info('Invoke parttool to read image.')
|
||||
self._invoke_parttool()
|
||||
@@ -440,15 +450,14 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
if e.output:
|
||||
logging.info(e.output)
|
||||
logging.error('Error during the subprocess execution')
|
||||
else:
|
||||
# Need to be closed before read. Otherwise the result will be wrong
|
||||
self.core_src_file.close()
|
||||
|
||||
def _invoke_esptool(self, off=None):
|
||||
def _invoke_esptool(self, off=None, target=None): # type: (Optional[int], Optional[str]) -> None
|
||||
"""
|
||||
Loads core dump from flash using elftool
|
||||
"""
|
||||
tool_args = [sys.executable, ESPTOOL_PY, '-c', self.target]
|
||||
if target is None:
|
||||
target = 'auto'
|
||||
tool_args = [sys.executable, ESPTOOL_PY, '-c', target]
|
||||
if self.port:
|
||||
tool_args.extend(['-p', self.port])
|
||||
if self.baud:
|
||||
@@ -466,14 +475,14 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
|
||||
# Here we use V1 format to locate the size
|
||||
tool_args.extend(['read_flash', str(off), str(EspCoreDumpV1Header.sizeof())])
|
||||
tool_args.append(self.core_src_file.name)
|
||||
tool_args.append(self.core_src_file) # type: ignore
|
||||
|
||||
# read core dump length
|
||||
et_out = subprocess.check_output(tool_args)
|
||||
if et_out:
|
||||
logging.info(et_out.decode('utf-8'))
|
||||
|
||||
header = EspCoreDumpV1Header.parse(open(self.core_src_file.name, 'rb').read())
|
||||
header = EspCoreDumpV1Header.parse(open(self.core_src_file, 'rb').read()) # type: ignore
|
||||
if not header or not 0 < header.tot_len <= part_size:
|
||||
logging.error('Incorrect size of core dump image: {}, use partition size instead: {}'
|
||||
.format(header.tot_len, part_size))
|
||||
@@ -492,7 +501,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
logging.debug(e.output)
|
||||
raise e
|
||||
|
||||
def _invoke_parttool(self):
|
||||
def _invoke_parttool(self): # type: () -> None
|
||||
"""
|
||||
Loads core dump from flash using parttool
|
||||
"""
|
||||
@@ -503,7 +512,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
|
||||
self.core_src_file = self._create_temp_file()
|
||||
try:
|
||||
tool_args.append(self.core_src_file.name)
|
||||
tool_args.append(self.core_src_file) # type: ignore
|
||||
# read core dump partition
|
||||
et_out = subprocess.check_output(tool_args)
|
||||
if et_out:
|
||||
@@ -515,7 +524,7 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
logging.debug(e.output)
|
||||
raise e
|
||||
|
||||
def _get_core_dump_partition_info(self, part_off=None):
|
||||
def _get_core_dump_partition_info(self, part_off=None): # type: (Optional[int]) -> Tuple[int, int]
|
||||
"""
|
||||
Get core dump partition info using parttool
|
||||
"""
|
||||
@@ -545,28 +554,27 @@ class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
|
||||
|
||||
|
||||
class ESPCoreDumpFileLoader(EspCoreDumpLoader):
|
||||
def __init__(self, path, is_b64=False):
|
||||
def __init__(self, path, is_b64=False): # type: (str, bool) -> None
|
||||
super(ESPCoreDumpFileLoader, self).__init__()
|
||||
self.is_b64 = is_b64
|
||||
|
||||
self._get_coredump(path)
|
||||
self._reload_coredump()
|
||||
self._get_core_src(path)
|
||||
self.target = self._load_core_src()
|
||||
|
||||
def _get_coredump(self, path):
|
||||
def _get_core_src(self, path): # type: (str) -> None
|
||||
"""
|
||||
Loads core dump from (raw binary or base64-encoded) file
|
||||
"""
|
||||
logging.debug('Load core dump from "%s", %s format', path, 'b64' if self.is_b64 else 'raw')
|
||||
if not self.is_b64:
|
||||
self.core_src_file = open(path, mode='rb')
|
||||
self.core_src_file = path
|
||||
else:
|
||||
self.core_src_file = self._create_temp_file()
|
||||
with open(path, 'rb') as fb64:
|
||||
while True:
|
||||
line = fb64.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
data = base64.standard_b64decode(line.rstrip(b'\r\n'))
|
||||
self.core_src_file.write(data)
|
||||
self.core_src_file.flush()
|
||||
self.core_src_file.seek(0)
|
||||
with open(self.core_src_file, 'wb') as fw:
|
||||
with open(path, 'rb') as fb64:
|
||||
while True:
|
||||
line = fb64.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
data = base64.standard_b64decode(line.rstrip(b'\r\n'))
|
||||
fw.write(data) # type: ignore
|
||||
|
62
components/espcoredump/corefile/riscv.py
Normal file
62
components/espcoredump/corefile/riscv.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#
|
||||
# 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'
|
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):
|
||||
EPC7_IDX = 14
|
||||
|
||||
@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,31 +130,31 @@ 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('epc1 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC1_IDX + 1])
|
||||
print('epc2 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC2_IDX + 1])
|
||||
print('epc3 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC3_IDX + 1])
|
||||
print('epc4 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC4_IDX + 1])
|
||||
print('epc5 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC5_IDX + 1])
|
||||
print('epc6 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC6_IDX + 1])
|
||||
print('epc7 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPC7_IDX + 1])
|
||||
print('eps2 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPS2_IDX + 1])
|
||||
print('eps3 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPS3_IDX + 1])
|
||||
print('eps4 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPS4_IDX + 1])
|
||||
print('eps5 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPS5_IDX + 1])
|
||||
print('eps6 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPS6_IDX + 1])
|
||||
print('eps7 0x%x' % extra_info[1 + 2 * XtensaRegisters.EPS7_IDX + 1])
|
||||
print('excvaddr 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EXCVADDR_IDX + 1])
|
||||
print('epc1 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC1_IDX + 1])
|
||||
print('epc2 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC2_IDX + 1])
|
||||
print('epc3 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC3_IDX + 1])
|
||||
print('epc4 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC4_IDX + 1])
|
||||
print('epc5 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC5_IDX + 1])
|
||||
print('epc6 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC6_IDX + 1])
|
||||
print('epc7 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPC7_IDX + 1])
|
||||
print('eps2 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPS2_IDX + 1])
|
||||
print('eps3 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPS3_IDX + 1])
|
||||
print('eps4 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPS4_IDX + 1])
|
||||
print('eps5 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPS5_IDX + 1])
|
||||
print('eps6 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPS6_IDX + 1])
|
||||
print('eps7 0x%x' % extra_info[1 + 2 * ExceptionRegisters.EPS7_IDX + 1])
|
||||
|
||||
|
||||
# from "gdb/xtensa-tdep.h"
|
||||
@@ -203,24 +207,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:
|
||||
@@ -248,10 +239,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]
|
||||
@@ -261,8 +252,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,
|
||||
@@ -279,3 +270,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
|
||||
@@ -40,26 +40,29 @@ else:
|
||||
CLOSE_FDS = True
|
||||
|
||||
|
||||
def load_aux_elf(elf_path): # type: (str) -> Tuple[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(): # type: () -> Tuple[Optional[str], ESPCoreDumpFlashLoader]
|
||||
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')
|
||||
@@ -69,51 +72,86 @@ def core_prepare(): # type: () -> Tuple[Optional[str], ESPCoreDumpFlashLoader]
|
||||
|
||||
# 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(): # type: () -> None
|
||||
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(): # type: () -> None
|
||||
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:
|
||||
@@ -125,8 +163,11 @@ def info_corefile(): # type: () -> None
|
||||
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:
|
||||
@@ -138,10 +179,12 @@ def info_corefile(): # type: () -> None
|
||||
task_name = gdb.get_freertos_task_name(marker)
|
||||
print("\nCrashed task handle: 0x%x, name: '%s', GDB name: 'process %d'" % (marker, task_name, marker))
|
||||
print('\n================== CURRENT THREAD REGISTERS ===================')
|
||||
if extra_note and extra_info:
|
||||
exception_registers_info(extra_info)
|
||||
else:
|
||||
print('Exception registers have not been found!')
|
||||
# Only xtensa have exception registers
|
||||
if exe_elf.e_machine == ESPCoreDumpElfFile.EM_XTENSA:
|
||||
if extra_note and extra_info:
|
||||
xtensa.print_exc_regs_info(extra_info)
|
||||
else:
|
||||
print('Exception registers have not been found!')
|
||||
print(gdb.run_cmd('info registers'))
|
||||
print('\n==================== CURRENT THREAD STACK =====================')
|
||||
print(gdb.run_cmd('bt'))
|
||||
@@ -241,10 +284,14 @@ def info_corefile(): # type: () -> None
|
||||
|
||||
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,
|
||||
@@ -256,20 +303,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')
|
||||
@@ -297,9 +344,18 @@ if __name__ == '__main__':
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
|
||||
|
||||
print('espcoredump.py v%s' % __version__)
|
||||
if args.operation == 'info_corefile':
|
||||
info_corefile()
|
||||
elif args.operation == 'dbg_corefile':
|
||||
dbg_corefile()
|
||||
else:
|
||||
raise ValueError('Please specify action, should be info_corefile or dbg_corefile')
|
||||
temp_core_files = None
|
||||
try:
|
||||
if args.operation == 'info_corefile':
|
||||
temp_core_files = info_corefile()
|
||||
elif args.operation == 'dbg_corefile':
|
||||
temp_core_files = dbg_corefile()
|
||||
else:
|
||||
raise ValueError('Please specify action, should be info_corefile or dbg_corefile')
|
||||
finally:
|
||||
if temp_core_files:
|
||||
for f in temp_core_files:
|
||||
try:
|
||||
os.remove(f)
|
||||
except OSError:
|
||||
pass
|
||||
|
@@ -115,7 +115,7 @@ typedef union {
|
||||
|
||||
/* We can determine the padding thank to the previous macros */
|
||||
#define PRSTATUS_SIG_PADDING (PRSTATUS_OFFSET_PR_CURSIG)
|
||||
#define PRSTATUS_PID_PADDING (PRSTATUS_OFFSET_PR_PID - PRSTATUS_OFFSET_PR_CURSIG - sizeof(uint32_t))
|
||||
#define PRSTATUS_PID_PADDING (PRSTATUS_OFFSET_PR_PID - PRSTATUS_OFFSET_PR_CURSIG - sizeof(uint16_t))
|
||||
#define PRSTATUS_REG_PADDING (PRSTATUS_OFFSET_PR_REG - PRSTATUS_OFFSET_PR_PID - sizeof(uint32_t))
|
||||
#define PRSTATUS_END_PADDING (PRSTATUS_SIZE - PRSTATUS_OFFSET_PR_REG - ELF_GREGSET_T_SIZE)
|
||||
|
||||
|
@@ -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 \
|
||||
|
@@ -4,12 +4,6 @@ Core Dump
|
||||
Overview
|
||||
--------
|
||||
|
||||
.. only:: not esp32
|
||||
|
||||
.. note::
|
||||
|
||||
The python utility does not fully support {IDF_TARGET_NAME}
|
||||
|
||||
ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analysis of software state at the moment of failure.
|
||||
Upon the crash system enters panic state, prints some information and halts or reboots depending configuration. User can choose to generate core dump in order to analyse
|
||||
the reason of failure on PC later on. Core dump contains snapshots of all tasks in the system at the moment of failure. Snapshots include tasks control blocks (TCB) and stacks.
|
||||
@@ -17,41 +11,66 @@ So it is possible to find out what task, at what instruction (line of code) and
|
||||
demand if previously attributed accordingly.
|
||||
ESP-IDF provides special script `espcoredump.py` to help users to retrieve and analyse core dumps. This tool provides two commands for core dumps analysis:
|
||||
|
||||
* info_corefile - prints crashed task's registers, callstack, list of available tasks in the system, memory regions and contents of memory stored in core dump (TCBs and stacks)
|
||||
* dbg_corefile - creates core dump ELF file and runs GDB debug session with this file. User can examine memory, variables and tasks states manually. Note that since not all memory is saved in core dump only values of variables allocated on stack will be meaningfull
|
||||
* ``info_corefile`` - prints crashed task's registers, callstack, list of available tasks in the system, memory regions and contents of memory stored in core dump (TCBs and stacks)
|
||||
* ``dbg_corefile`` - creates core dump ELF file and runs GDB debug session with this file. User can examine memory, variables and tasks states manually. Note that since not all memory is saved in core dump only values of variables allocated on stack will be meaningful
|
||||
|
||||
For more information about core dump internals see the - :doc:`Core dump internals <core_dump_internals>`
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
Configurations
|
||||
--------------
|
||||
|
||||
There are a number of core dump related configuration options which user can choose in project configuration menu (`idf.py menuconfig`).
|
||||
There are a number of core dump related configuration options which user can choose in project configuration menu (``idf.py menuconfig``).
|
||||
|
||||
1. Core dump data destination (`Components -> Core dump -> Data destination`):
|
||||
**Core dump data destination (Components -> Core dump -> Data destination)**
|
||||
|
||||
* Save core dump to Flash (Flash)
|
||||
* Print core dump to UART (UART)
|
||||
* Disable core dump generation (None)
|
||||
* Save core dump to Flash (Flash)
|
||||
* Print core dump to UART (UART)
|
||||
* Disable core dump generation (None)
|
||||
|
||||
2. Core dump data format (`Components -> Core dump -> Core dump data format`):
|
||||
**Core dump data format (Components -> Core dump -> Core dump data format)**
|
||||
|
||||
* ELF format (Executable and Linkable Format file for core dump)
|
||||
* Binary format (Basic binary format for core dump)
|
||||
* ELF format (Executable and Linkable Format file for core dump)
|
||||
* Binary format (Basic binary format for core dump)
|
||||
|
||||
The ELF format contains extended features and allow to save more information about broken tasks and crashed software but it requires more space in the flash memory.
|
||||
It also stores SHA256 of crashed application image. This format of core dump is recommended for new software designs and is flexible enough to extend saved information for future revisions.
|
||||
The Binary format is kept for compatibility standpoint, it uses less space in the memory to keep data and provides better performance.
|
||||
The ELF format contains extended features and allow to save more information about broken tasks and crashed software but it requires more space in the flash memory.
|
||||
This format of core dump is recommended for new software designs and is flexible enough to extend saved information for future revisions.
|
||||
|
||||
3. Maximum number of tasks snapshots in core dump (`Components -> Core dump -> Maximum number of tasks`).
|
||||
The Binary format is kept for compatibility standpoint, it uses less space in the memory to keep data and provides better performance.
|
||||
|
||||
4. Delay before core dump is printed to UART (`Components -> Core dump -> Delay before print to UART`). Value is in ms.
|
||||
**Core dump data integrity check (Components -> Core dump -> Core dump data integrity check)**
|
||||
|
||||
5. Type of data integrity check for core dump (`Components -> Core dump -> Core dump data integrity check`).
|
||||
.. only:: esp32
|
||||
|
||||
* Use CRC32 for core dump integrity verification
|
||||
* Use SHA256 for core dump integrity verification
|
||||
* Use CRC32 for core dump integrity verification
|
||||
* Use SHA256 for core dump integrity verification (only work in ELF format)
|
||||
|
||||
The SHA256 hash algorithm provides greater probability of detecting corruption than a CRC32 with multiple bit errors. The CRC32 option provides better calculation performance and consumes less memory for storage.
|
||||
The CRC32 option provides better calculation performance and consumes less memory for storage.
|
||||
|
||||
The SHA256 hash algorithm provides greater probability of detecting corruption than a CRC32 with multiple bit errors.
|
||||
|
||||
.. only:: not esp32
|
||||
|
||||
* Use CRC32 for core dump integrity verification
|
||||
|
||||
**Maximum number of tasks snapshots in core dump (Components -> Core dump -> Maximum number of tasks)**
|
||||
|
||||
**Delay before core dump is printed to UART (Components -> Core dump -> Delay before print to UART)**
|
||||
|
||||
The value is in ms.
|
||||
|
||||
**Handling of UART core dumps in IDF Monitor (Components -> Core dump -> Delay before print to UART)**
|
||||
|
||||
The value is base64 encoded.
|
||||
|
||||
* Decode and show summary (info_corefile)
|
||||
* Don't decode
|
||||
|
||||
.. only:: esp32c3
|
||||
|
||||
**Reserved stack size (Components -> Core dump -> Reserved stack size)**
|
||||
|
||||
Size of the memory to be reserved for core dump stack. If 0 core dump process will run on the stack of crashed task/ISR, otherwise special stack will be allocated.
|
||||
To ensure that core dump itself will not overflow task/ISR stack set this to the value above 800.
|
||||
|
||||
Save core dump to flash
|
||||
-----------------------
|
||||
@@ -60,40 +79,40 @@ When this option is selected core dumps are saved to special partition on flash.
|
||||
allocates necessary space on flash, But if user wants to use its own layout file together with core dump feature it should define separate partition for core dump
|
||||
as it is shown below::
|
||||
|
||||
# Name, Type, SubType, Offset, Size
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, app, factory, 0x10000, 1M
|
||||
coredump, data, coredump,, 64K
|
||||
# Name, Type, SubType, Offset, Size
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, app, factory, 0x10000, 1M
|
||||
coredump, data, coredump,, 64K
|
||||
|
||||
There are no special requrements for partition name. It can be choosen according to the user application needs, but partition type should be 'data' and
|
||||
There are no special requirements for partition name. It can be chosen according to the user application needs, but partition type should be 'data' and
|
||||
sub-type should be 'coredump'. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes.
|
||||
This overhead does not include size of TCB and stack for every task. So partirion size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes.
|
||||
This overhead does not include size of TCB and stack for every task. So partition size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes.
|
||||
|
||||
The example of generic command to analyze core dump from flash is: `espcoredump.py -p </path/to/serial/port> info_corefile </path/to/program/elf/file>`
|
||||
or `espcoredump.py -p </path/to/serial/port> dbg_corefile </path/to/program/elf/file>`
|
||||
The example of generic command to analyze core dump from flash is: ``espcoredump.py -p </path/to/serial/port> info_corefile </path/to/program/elf/file>``
|
||||
or ``espcoredump.py -p </path/to/serial/port> dbg_corefile </path/to/program/elf/file>``
|
||||
|
||||
Print core dump to UART
|
||||
-----------------------
|
||||
|
||||
When this option is selected base64-encoded core dumps are printed on UART upon system panic. In this case user should save core dump text body to some file manually and
|
||||
then run the following command: `espcoredump.py info_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>`
|
||||
or `espcoredump.py dbg_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>`
|
||||
then run the following command: ``espcoredump.py --chip <target_chip_type> info_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>``
|
||||
or ``espcoredump.py --chip <target_chip_type> dbg_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>``
|
||||
|
||||
Base64-encoded body of core dump will be between the following header and footer::
|
||||
|
||||
================= CORE DUMP START =================
|
||||
<body of base64-encoded core dump, save it to file on disk>
|
||||
================= CORE DUMP END ===================
|
||||
================= CORE DUMP START =================
|
||||
<body of base64-encoded core dump, save it to file on disk>
|
||||
================= CORE DUMP END ===================
|
||||
|
||||
The `CORE DUMP START` and `CORE DUMP END` lines must not be included in core dump text file.
|
||||
The ``CORE DUMP START`` and ``CORE DUMP END`` lines must not be included in core dump text file.
|
||||
|
||||
ROM Functions in Backtraces
|
||||
---------------------------
|
||||
|
||||
It is possible situation that at the moment of crash some tasks or/and crashed task itself have one or more ROM functions in their callstacks.
|
||||
Since ROM is not part of the program ELF it will be impossible for GDB to parse such callstacks, because it tries to analyse functions' prologues to acomplish that.
|
||||
Since ROM is not part of the program ELF it will be impossible for GDB to parse such callstacks, because it tries to analyse functions' prologues to accomplish that.
|
||||
In that case callstack printing will be broken with error message at the first ROM function.
|
||||
To overcome this issue you can use ROM ELF provided by Espressif (https://dl.espressif.com/dl/{IDF_TARGET_PATH_NAME}_rom.elf) and pass it to 'espcoredump.py'.
|
||||
|
||||
@@ -106,18 +125,9 @@ Core dump supports retrieving variable data over GDB by attributing special nota
|
||||
Supported notations and RAM regions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
- ``COREDUMP_DRAM_ATTR`` places variable into DRAM area which will be included into dump.
|
||||
- ``COREDUMP_RTC_ATTR`` places variable into RTC area which will be included into dump.
|
||||
- ``COREDUMP_RTC_FAST_ATTR`` places variable into RTC_FAST area which will be included into dump.
|
||||
- ``COREDUMP_IRAM_ATTR`` places variable into IRAM area which will be included into dump when :ref:`Enable IRAM as 8 bit accessible memory <CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY>` is set.
|
||||
|
||||
.. only:: esp32s2
|
||||
|
||||
- ``COREDUMP_DRAM_ATTR`` places variable into DRAM area which will be included into dump.
|
||||
- ``COREDUMP_RTC_ATTR`` places variable into RTC area which will be included into dump.
|
||||
- ``COREDUMP_RTC_FAST_ATTR`` places variable into RTC_FAST area which will be included into dump.
|
||||
* ``COREDUMP_DRAM_ATTR`` places variable into DRAM area which will be included into dump.
|
||||
* ``COREDUMP_RTC_ATTR`` places variable into RTC area which will be included into dump.
|
||||
* ``COREDUMP_RTC_FAST_ATTR`` places variable into RTC_FAST area which will be included into dump.
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
@@ -126,53 +136,76 @@ Example
|
||||
|
||||
2. In your project, create a global variable in DRAM area as such as:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
// uint8_t global_var;
|
||||
COREDUMP_DRAM_ATTR uint8_t global_var;
|
||||
// uint8_t global_var;
|
||||
COREDUMP_DRAM_ATTR uint8_t global_var;
|
||||
|
||||
3. In main application, set the variable to any value and `assert(0)` to cause a crash.
|
||||
3. In main application, set the variable to any value and ``assert(0)`` to cause a crash.
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
global_var = 25;
|
||||
assert(0);
|
||||
global_var = 25;
|
||||
assert(0);
|
||||
|
||||
4. Build, flash and run the application on a target device and wait for the dumping information.
|
||||
|
||||
5. Run the command below to start core dumping in GDB, where ``PORT`` is the device USB port:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
espcoredump.py -p PORT dbg_corefile <path/to/elf>
|
||||
espcoredump.py -p PORT dbg_corefile <path/to/elf>
|
||||
|
||||
6. In GDB shell, type ``p global_var`` to get the variable content:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
(gdb) p global_var
|
||||
$1 = 25 '\031'
|
||||
(gdb) p global_var
|
||||
$1 = 25 '\031'
|
||||
|
||||
Running 'espcoredump.py'
|
||||
------------------------
|
||||
Running ``espcoredump.py``
|
||||
--------------------------
|
||||
|
||||
Generic command syntax:
|
||||
|
||||
`espcoredump.py [options] command [args]`
|
||||
Generic command syntax: ``espcoredump.py [options] command [args]``
|
||||
|
||||
:Script Options:
|
||||
* --port,-p PORT. Serial port device.
|
||||
* --baud,-b BAUD. Serial port baud rate used when flashing/reading.
|
||||
|
||||
--chip {auto,esp32,esp32s2,esp32c3}
|
||||
Target chip type. Default value is "auto"
|
||||
|
||||
--port PORT, -p PORT Serial port device. Either "chip" or "port" need to be specified to determine the port when you have multi-target connected at the same time.
|
||||
|
||||
--baud BAUD, -b BAUD Serial port baud rate used when flashing/reading
|
||||
|
||||
--gdb-timeout-sec GDB_TIMEOUT_SEC
|
||||
Overwrite the default internal delay for gdb responses
|
||||
|
||||
:Commands:
|
||||
* info_corefile. Retrieve core dump and print useful info.
|
||||
* dbg_corefile. Retrieve core dump and start GDB session with it.
|
||||
|
||||
**dbg_corefile** Starts GDB debugging session with specified corefile
|
||||
|
||||
**info_corefile** Print core dump info from file
|
||||
|
||||
:Command Arguments:
|
||||
* --debug,-d DEBUG. Log level (0..3).
|
||||
* --gdb,-g GDB. Path to gdb to use for data retrieval.
|
||||
* --core,-c CORE. Path to core dump file to use (if skipped core dump will be read from flash).
|
||||
* --core-format,-t CORE_FORMAT. Specifies that file passed with "-c" is an ELF ("elf"), dumped raw binary ("raw") or base64-encoded ("b64") format.
|
||||
* --off,-o OFF. Offset of coredump partition in flash (type `idf.py partition_table` to see it).
|
||||
* --save-core,-s SAVE_CORE. Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c".
|
||||
* --rom-elf,-r ROM_ELF. Path to ROM ELF file to use (if skipped "esp32_rom.elf" is used).
|
||||
* --print-mem,-m Print memory dump. Used only with "info_corefile".
|
||||
* <prog> Path to program ELF file.
|
||||
|
||||
--debug DEBUG, -d DEBUG
|
||||
Log level (0..3)
|
||||
|
||||
--gdb GDB, -g GDB Path to gdb
|
||||
|
||||
--core CORE, -c CORE Path to core dump file (if skipped core dump will be read from flash)
|
||||
|
||||
--core-format {b64,elf,raw}, -t {b64,elf,raw}
|
||||
File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary
|
||||
|
||||
--off OFF, -o OFF Offset of coredump partition in flash (type "make partition_table" to see).
|
||||
|
||||
--save-core SAVE_CORE, -s SAVE_CORE
|
||||
Save core to file. Otherwise temporary core file will be deleted. Does not work with "-c"
|
||||
|
||||
--rom-elf ROM_ELF, -r ROM_ELF
|
||||
Path to ROM ELF file. Will use "<target>_rom.elf" if not specified
|
||||
|
||||
--print-mem, -m Print memory dump. Only valid when info_corefile.
|
||||
|
||||
**<prog>** Path to program ELF file.
|
||||
|
@@ -4,10 +4,7 @@ components/efuse/test_efuse_host/efuse_tests.py
|
||||
components/esp32s2/test/gen_digital_signature_tests.py
|
||||
components/esp_local_ctrl/python/esp_local_ctrl_pb2.py
|
||||
components/esp_netif/test_apps/component_ut_test.py
|
||||
components/espcoredump/corefile/elf.py
|
||||
components/espcoredump/corefile/gdb.py
|
||||
components/espcoredump/corefile/loader.py
|
||||
components/espcoredump/corefile/xtensa.py
|
||||
components/espcoredump/test/test_espcoredump.py
|
||||
components/lwip/weekend_test/net_suite_test.py
|
||||
components/mbedtls/esp_crt_bundle/gen_crt_bundle.py
|
||||
|
Reference in New Issue
Block a user