mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'fix/tools-gdbinit-with-elf-symbols' into 'master'
tools: fixed elf symbols load if gdbinit specified Closes IDF-4991 and GCC-246 See merge request espressif/esp-idf!18089
This commit is contained in:
commit
d08898aabc
@ -235,8 +235,10 @@ build_non_test_components_apps:
|
||||
- "**/build*/flasher_args.json"
|
||||
- "**/build*/flash_project_args"
|
||||
- "**/build*/config/sdkconfig.json"
|
||||
- "**/build*/bootloader/*.elf"
|
||||
- "**/build*/bootloader/*.bin"
|
||||
- "**/build*/partition_table/*.bin"
|
||||
- "**/build*/project_description.json"
|
||||
- list_job_*.json
|
||||
- size_info.txt
|
||||
when: always
|
||||
|
@ -11,8 +11,9 @@ endif()
|
||||
#
|
||||
idf_build_get_property(build_dir BUILD_DIR)
|
||||
set(BOOTLOADER_BUILD_DIR "${build_dir}/bootloader")
|
||||
set(BOOTLOADER_ELF_FILE "${BOOTLOADER_BUILD_DIR}/bootloader.elf")
|
||||
set(bootloader_binary_files
|
||||
"${BOOTLOADER_BUILD_DIR}/bootloader.elf"
|
||||
"${BOOTLOADER_ELF_FILE}"
|
||||
"${BOOTLOADER_BUILD_DIR}/bootloader.bin"
|
||||
"${BOOTLOADER_BUILD_DIR}/bootloader.map"
|
||||
)
|
||||
|
@ -72,6 +72,11 @@ On Linux and macOS, it is recommended to install ninja using the OS-specific pac
|
||||
.. tool-dfu-util-notes
|
||||
|
||||
|
||||
---
|
||||
|
||||
.. tool-esp-rom-elfs-notes
|
||||
|
||||
|
||||
---
|
||||
|
||||
.. tool-idf-python-notes
|
||||
|
@ -74,6 +74,11 @@ On Linux and macOS, it is recommended to install ninja using the OS package mana
|
||||
.. tool-dfu-util-notes
|
||||
|
||||
|
||||
---
|
||||
|
||||
.. tool-esp-rom-elfs-notes
|
||||
|
||||
|
||||
---
|
||||
|
||||
.. tool-idf-python-notes
|
||||
|
@ -4,6 +4,7 @@
|
||||
"build_dir": "${BUILD_DIR}",
|
||||
"config_file": "${SDKCONFIG}",
|
||||
"config_defaults": "${SDKCONFIG_DEFAULTS}",
|
||||
"bootloader_elf": "${BOOTLOADER_ELF_FILE}",
|
||||
"app_elf": "${PROJECT_EXECUTABLE}",
|
||||
"app_bin": "${PROJECT_BIN}",
|
||||
"git_revision": "${IDF_VER}",
|
||||
|
35
tools/idf.py
35
tools/idf.py
@ -27,7 +27,7 @@ from collections import Counter, OrderedDict, _OrderedDictKeysView
|
||||
from importlib import import_module
|
||||
from pkgutil import iter_modules
|
||||
from types import FrameType
|
||||
from typing import Any, Callable, Dict, List, Optional, TextIO, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
# pyc files remain in the filesystem when switching between branches which might raise errors for incompatible
|
||||
# idf.py extensions. Therefore, pyc file generation is turned off:
|
||||
@ -37,8 +37,8 @@ import python_version_checker # noqa: E402
|
||||
|
||||
try:
|
||||
from idf_py_actions.errors import FatalError # noqa: E402
|
||||
from idf_py_actions.tools import (PropertyDict, executable_exists, get_target, idf_version, # noqa: E402
|
||||
merge_action_lists, realpath)
|
||||
from idf_py_actions.tools import (PROG, SHELL_COMPLETE_RUN, SHELL_COMPLETE_VAR, PropertyDict, # noqa: E402
|
||||
debug_print_idf_version, get_target, merge_action_lists, print_warning, realpath)
|
||||
if os.getenv('IDF_COMPONENT_MANAGER') != '0':
|
||||
from idf_component_manager import idf_extensions
|
||||
except ImportError:
|
||||
@ -53,23 +53,6 @@ PYTHON = sys.executable
|
||||
# you have to pass env=os.environ explicitly anywhere that we create a process
|
||||
os.environ['PYTHON'] = sys.executable
|
||||
|
||||
# Name of the program, normally 'idf.py'.
|
||||
# Can be overridden from idf.bat using IDF_PY_PROGRAM_NAME
|
||||
PROG = os.getenv('IDF_PY_PROGRAM_NAME', 'idf.py')
|
||||
|
||||
# environment variable used during click shell completion run
|
||||
SHELL_COMPLETE_VAR = '_IDF.PY_COMPLETE'
|
||||
|
||||
# was shell completion invoked?
|
||||
SHELL_COMPLETE_RUN = SHELL_COMPLETE_VAR in os.environ
|
||||
|
||||
|
||||
# function prints warning when autocompletion is not being performed
|
||||
# set argument stream to sys.stderr for errors and exceptions
|
||||
def print_warning(message: str, stream: TextIO=None) -> None:
|
||||
if not SHELL_COMPLETE_RUN:
|
||||
print(message, file=stream or sys.stderr)
|
||||
|
||||
|
||||
def check_environment() -> List:
|
||||
"""
|
||||
@ -79,10 +62,6 @@ def check_environment() -> List:
|
||||
"""
|
||||
checks_output = []
|
||||
|
||||
if not executable_exists(['cmake', '--version']):
|
||||
debug_print_idf_version()
|
||||
raise FatalError("'cmake' must be available on the PATH to use %s" % PROG)
|
||||
|
||||
# verify that IDF_PATH env variable is set
|
||||
# find the directory idf.py is in, then the parent directory of this, and assume this is IDF_PATH
|
||||
detected_idf_path = realpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
@ -137,14 +116,6 @@ def _safe_relpath(path: str, start: Optional[str]=None) -> str:
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
def debug_print_idf_version() -> None:
|
||||
version = idf_version()
|
||||
if version:
|
||||
print_warning('ESP-IDF %s' % version)
|
||||
else:
|
||||
print_warning('ESP-IDF version unknown')
|
||||
|
||||
|
||||
def init_cli(verbose_output: List=None) -> Any:
|
||||
# Click is imported here to run it after check_environment()
|
||||
import click
|
||||
|
@ -34,3 +34,11 @@ URL_TO_DOC = 'https://docs.espressif.com/projects/esp-idf'
|
||||
|
||||
SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2']
|
||||
PREVIEW_TARGETS = ['linux', 'esp32h2', 'esp32c6']
|
||||
|
||||
OPENOCD_TAGET_CONFIG_DEFAULT = '-f interface/ftdi/esp32_devkitj_v1.cfg -f target/{target}.cfg'
|
||||
OPENOCD_TAGET_CONFIG: Dict[str, str] = {
|
||||
'esp32': '-f board/esp32-wrover-kit-3.3v.cfg',
|
||||
'esp32s2': '-f board/esp32s2-kaluga-1.cfg',
|
||||
'esp32c3': '-f board/esp32c3-builtin.cfg',
|
||||
'esp32s3': '-f board/esp32s3-builtin.cfg',
|
||||
}
|
||||
|
@ -4,18 +4,65 @@ import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from textwrap import indent
|
||||
from threading import Thread
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from click.core import Context
|
||||
from idf_py_actions.constants import OPENOCD_TAGET_CONFIG, OPENOCD_TAGET_CONFIG_DEFAULT
|
||||
from idf_py_actions.errors import FatalError
|
||||
from idf_py_actions.tools import PropertyDict, ensure_build_directory
|
||||
|
||||
PYTHON = sys.executable
|
||||
ESP_ROM_INFO_FILE = 'roms.json'
|
||||
GDBINIT_PYTHON_TEMPLATE = '''
|
||||
# Add Python GDB extensions
|
||||
python
|
||||
import sys
|
||||
sys.path = {sys_path}
|
||||
import freertos_gdb
|
||||
end
|
||||
'''
|
||||
GDBINIT_PYTHON_NOT_SUPPORTED = '''
|
||||
# Python scripting is not supported in this copy of GDB.
|
||||
# Please make sure that your Python distribution contains Python shared library.
|
||||
'''
|
||||
GDBINIT_BOOTLOADER_ADD_SYMBOLS = '''
|
||||
# Load bootloader symbols
|
||||
set confirm off
|
||||
add-symbol-file {boot_elf}
|
||||
set confirm on
|
||||
'''
|
||||
GDBINIT_BOOTLOADER_NOT_FOUND = '''
|
||||
# Bootloader elf was not found
|
||||
'''
|
||||
GDBINIT_APP_ADD_SYMBOLS = '''
|
||||
# Load application file
|
||||
file {app_elf}
|
||||
'''
|
||||
GDBINIT_CONNECT = '''
|
||||
# Connect to the default openocd-esp port and break on app_main()
|
||||
target remote :3333
|
||||
monitor reset halt
|
||||
flushregs
|
||||
thbreak app_main
|
||||
continue
|
||||
'''
|
||||
GDBINIT_MAIN = '''
|
||||
source {py_extensions}
|
||||
source {symbols}
|
||||
source {connect}
|
||||
'''
|
||||
|
||||
|
||||
def get_openocd_arguments(target: str) -> str:
|
||||
default_args = OPENOCD_TAGET_CONFIG_DEFAULT.format(target=target)
|
||||
return str(OPENOCD_TAGET_CONFIG.get(target, default_args))
|
||||
|
||||
|
||||
def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
@ -89,22 +136,111 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
# execute simple python command to check is it supported
|
||||
return subprocess.run([gdb, '--batch-silent', '--ex', 'python import os'], stderr=subprocess.DEVNULL).returncode == 0
|
||||
|
||||
def create_local_gdbinit(gdb: str, gdbinit: str, elf_file: str) -> None:
|
||||
with open(gdbinit, 'w') as f:
|
||||
def get_normalized_path(path: str) -> str:
|
||||
if os.name == 'nt':
|
||||
return os.path.normpath(path).replace('\\','\\\\')
|
||||
return path
|
||||
|
||||
def get_rom_if_condition_str(date_addr: int, date_str: str) -> str:
|
||||
r = []
|
||||
for i in range(0, len(date_str), 4):
|
||||
value = hex(int.from_bytes(bytes(date_str[i:i + 4], 'utf-8'), 'little'))
|
||||
r.append(f'(*(int*) {hex(date_addr + i)}) == {value}')
|
||||
return 'if ' + ' && '.join(r)
|
||||
|
||||
def generate_gdbinit_rom_add_symbols(target: str) -> str:
|
||||
base_ident = ' '
|
||||
rom_elfs_dir = os.getenv('ESP_ROM_ELF_DIR')
|
||||
if not rom_elfs_dir:
|
||||
raise FatalError('ESP_ROM_ELF_DIR environment variable is not defined. Please try to run IDF "install" and "export" scripts.')
|
||||
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), ESP_ROM_INFO_FILE), 'r') as f:
|
||||
roms = json.load(f)
|
||||
if target not in roms:
|
||||
msg_body = f'Target "{target}" was not found in "{ESP_ROM_INFO_FILE}". Please check IDF integrity.'
|
||||
if os.getenv('ESP_IDF_GDB_TESTING'):
|
||||
raise FatalError(msg_body)
|
||||
print(f'Warning: {msg_body}')
|
||||
return f'# {msg_body}'
|
||||
r = ['', f'# Load {target} ROM ELF symbols']
|
||||
is_one_revision = len(roms[target]) == 1
|
||||
if not is_one_revision:
|
||||
r.append('define target hookpost-remote')
|
||||
r.append('set confirm off')
|
||||
# Workaround for reading ROM data on xtensa chips
|
||||
# This should be deleted after the new openocd-esp release (newer than v0.11.0-esp32-20220706)
|
||||
xtensa_chips = ['esp32', 'esp32s2', 'esp32s3']
|
||||
if target in xtensa_chips:
|
||||
r.append('monitor xtensa set_permissive 1')
|
||||
# Since GDB does not have 'else if' statement than we use nested 'if..else' instead.
|
||||
for i, k in enumerate(roms[target], 1):
|
||||
indent_str = base_ident * i
|
||||
rom_file = get_normalized_path(os.path.join(rom_elfs_dir, f'{target}_rev{k["rev"]}_rom.elf'))
|
||||
build_date_addr = int(k['build_date_str_addr'], base=16)
|
||||
r.append(indent(f'# if $_streq((char *) {hex(build_date_addr)}, "{k["build_date_str"]}")', indent_str))
|
||||
r.append(indent(get_rom_if_condition_str(build_date_addr, k['build_date_str']), indent_str))
|
||||
r.append(indent(f'add-symbol-file {rom_file}', indent_str + base_ident))
|
||||
r.append(indent('else', indent_str))
|
||||
if i == len(roms[target]):
|
||||
# In case no one known ROM ELF fits - print error and exit with error code 1
|
||||
indent_str += base_ident
|
||||
msg_body = f'unknown {target} ROM revision.'
|
||||
if os.getenv('ESP_IDF_GDB_TESTING'):
|
||||
r.append(indent(f'echo Error: {msg_body}\\n', indent_str))
|
||||
r.append(indent('quit 1', indent_str))
|
||||
else:
|
||||
r.append(indent(f'echo Warning: {msg_body}\\n', indent_str))
|
||||
# Close 'else' operators
|
||||
for i in range(len(roms[target]), 0, -1):
|
||||
r.append(indent('end', base_ident * i))
|
||||
if target in xtensa_chips:
|
||||
r.append('monitor xtensa set_permissive 0')
|
||||
r.append('set confirm on')
|
||||
if not is_one_revision:
|
||||
r.append('end')
|
||||
r.append('')
|
||||
return os.linesep.join(r)
|
||||
raise FatalError(f'{ESP_ROM_INFO_FILE} file not found. Please check IDF integrity.')
|
||||
|
||||
def generate_gdbinit_files(gdb: str, gdbinit: Optional[str], project_desc: Dict[str, Any]) -> None:
|
||||
app_elf = get_normalized_path(os.path.join(project_desc['build_dir'], project_desc['app_elf']))
|
||||
if not os.path.exists(app_elf):
|
||||
raise FatalError('ELF file not found. You need to build & flash the project before running debug targets')
|
||||
|
||||
# Recreate empty 'gdbinit' directory
|
||||
gdbinit_dir = os.path.join(project_desc['build_dir'], 'gdbinit')
|
||||
if os.path.isfile(gdbinit_dir):
|
||||
os.remove(gdbinit_dir)
|
||||
elif os.path.isdir(gdbinit_dir):
|
||||
shutil.rmtree(gdbinit_dir)
|
||||
os.mkdir(gdbinit_dir)
|
||||
|
||||
# Prepare gdbinit for Python GDB extensions import
|
||||
py_extensions = os.path.join(gdbinit_dir, 'py_extensions')
|
||||
with open(py_extensions, 'w') as f:
|
||||
if is_gdb_with_python(gdb):
|
||||
f.write('python\n')
|
||||
f.write('import sys\n')
|
||||
f.write(f'sys.path = {sys.path}\n')
|
||||
f.write('import freertos_gdb\n')
|
||||
f.write('end\n')
|
||||
if os.name == 'nt':
|
||||
elf_file = elf_file.replace('\\','\\\\')
|
||||
f.write('file {}\n'.format(elf_file))
|
||||
f.write('target remote :3333\n')
|
||||
f.write('mon reset halt\n')
|
||||
f.write('flushregs\n')
|
||||
f.write('thb app_main\n')
|
||||
f.write('c\n')
|
||||
f.write(GDBINIT_PYTHON_TEMPLATE.format(sys_path=sys.path))
|
||||
else:
|
||||
f.write(GDBINIT_PYTHON_NOT_SUPPORTED)
|
||||
|
||||
# Prepare gdbinit for related ELFs symbols load
|
||||
symbols = os.path.join(gdbinit_dir, 'symbols')
|
||||
with open(symbols, 'w') as f:
|
||||
boot_elf = get_normalized_path(project_desc['bootloader_elf']) if 'bootloader_elf' in project_desc else None
|
||||
if boot_elf and os.path.exists(boot_elf):
|
||||
f.write(GDBINIT_BOOTLOADER_ADD_SYMBOLS.format(boot_elf=boot_elf))
|
||||
else:
|
||||
f.write(GDBINIT_BOOTLOADER_NOT_FOUND)
|
||||
f.write(generate_gdbinit_rom_add_symbols(project_desc['target']))
|
||||
f.write(GDBINIT_APP_ADD_SYMBOLS.format(app_elf=app_elf))
|
||||
|
||||
# Generate the gdbinit for target connect if no custom gdbinit is present
|
||||
if not gdbinit:
|
||||
gdbinit = os.path.join(gdbinit_dir, 'connect')
|
||||
with open(gdbinit, 'w') as f:
|
||||
f.write(GDBINIT_CONNECT)
|
||||
|
||||
with open(os.path.join(gdbinit_dir, 'gdbinit'), 'w') as f:
|
||||
f.write(GDBINIT_MAIN.format(py_extensions=py_extensions, symbols=symbols, connect=gdbinit))
|
||||
|
||||
def debug_cleanup() -> None:
|
||||
print('cleaning up debug targets')
|
||||
@ -154,12 +290,6 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
"""
|
||||
Execute openocd as external tool
|
||||
"""
|
||||
OPENOCD_TAGET_CONFIG = {
|
||||
'esp32': '-f board/esp32-wrover-kit-3.3v.cfg',
|
||||
'esp32s2': '-f board/esp32s2-kaluga-1.cfg',
|
||||
'esp32c3': '-f board/esp32c3-builtin.cfg',
|
||||
'esp32s3': '-f board/esp32s3-builtin.cfg',
|
||||
}
|
||||
if os.getenv('OPENOCD_SCRIPTS') is None:
|
||||
raise FatalError('OPENOCD_SCRIPTS not found in the environment: Please run export.sh/export.bat', ctx)
|
||||
openocd_arguments = os.getenv('OPENOCD_COMMANDS') if openocd_commands is None else openocd_commands
|
||||
@ -167,8 +297,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
if openocd_arguments is None:
|
||||
# use default value if commands not defined in the environment nor command line
|
||||
target = project_desc['target']
|
||||
default_args = '-f interface/ftdi/esp32_devkitj_v1.cfg -f target/{}.cfg'.format(target)
|
||||
openocd_arguments = OPENOCD_TAGET_CONFIG.get(target, default_args)
|
||||
openocd_arguments = get_openocd_arguments(target)
|
||||
print('Note: OpenOCD cfg not found (via env variable OPENOCD_COMMANDS nor as a --openocd-commands argument)\n'
|
||||
'OpenOCD arguments default to: "{}"'.format(openocd_arguments))
|
||||
# script directory is taken from the environment by OpenOCD, update only if command line arguments to override
|
||||
@ -189,7 +318,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
processes['openocd_outfile_name'] = openocd_out_name
|
||||
print('OpenOCD started as a background task {}'.format(process.pid))
|
||||
|
||||
def get_gdb_args(gdbinit: str, project_desc: Dict[str, Any]) -> List:
|
||||
def get_gdb_args(project_desc: Dict[str, Any]) -> List:
|
||||
gdbinit = os.path.join(project_desc['build_dir'], 'gdbinit', 'gdbinit')
|
||||
args = ['-x={}'.format(gdbinit)]
|
||||
debug_prefix_gdbinit = project_desc.get('debug_prefix_map_gdbinit')
|
||||
if debug_prefix_gdbinit:
|
||||
@ -203,16 +333,14 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
project_desc = get_project_desc(args, ctx)
|
||||
local_dir = project_desc['build_dir']
|
||||
gdb = project_desc['monitor_toolprefix'] + 'gdb'
|
||||
if gdbinit is None:
|
||||
gdbinit = os.path.join(local_dir, 'gdbinit')
|
||||
create_local_gdbinit(gdb, gdbinit, os.path.join(args.build_dir, project_desc['app_elf']))
|
||||
generate_gdbinit_files(gdb, gdbinit, project_desc)
|
||||
|
||||
# this is a workaround for gdbgui
|
||||
# gdbgui is using shlex.split for the --gdb-args option. When the input is:
|
||||
# - '"-x=foo -x=bar"', would return ['foo bar']
|
||||
# - '-x=foo', would return ['-x', 'foo'] and mess up the former option '--gdb-args'
|
||||
# so for one item, use extra double quotes. for more items, use no extra double quotes.
|
||||
gdb_args_list = get_gdb_args(gdbinit, project_desc)
|
||||
gdb_args_list = get_gdb_args(project_desc)
|
||||
gdb_args = '"{}"'.format(' '.join(gdb_args_list)) if len(gdb_args_list) == 1 else ' '.join(gdb_args_list)
|
||||
args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args]
|
||||
print(args)
|
||||
@ -274,9 +402,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
"""
|
||||
Synchronous GDB target with text ui mode
|
||||
"""
|
||||
gdb(action, ctx, args, 1, gdbinit, require_openocd)
|
||||
gdb(action, ctx, args, False, 1, gdbinit, require_openocd)
|
||||
|
||||
def gdb(action: str, ctx: Context, args: PropertyDict, gdb_tui: Optional[int], gdbinit: Optional[str], require_openocd: bool) -> None:
|
||||
def gdb(action: str, ctx: Context, args: PropertyDict, batch: bool, gdb_tui: Optional[int], gdbinit: Optional[str], require_openocd: bool) -> None:
|
||||
"""
|
||||
Synchronous GDB target
|
||||
"""
|
||||
@ -284,18 +412,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
watch_openocd.start()
|
||||
processes['threads_to_join'].append(watch_openocd)
|
||||
project_desc = get_project_desc(args, ctx)
|
||||
|
||||
elf_file = os.path.join(args.build_dir, project_desc['app_elf'])
|
||||
if not os.path.exists(elf_file):
|
||||
raise FatalError('ELF file not found. You need to build & flash the project before running debug targets', ctx)
|
||||
gdb = project_desc['monitor_toolprefix'] + 'gdb'
|
||||
local_dir = project_desc['build_dir']
|
||||
if gdbinit is None:
|
||||
gdbinit = os.path.join(local_dir, 'gdbinit')
|
||||
create_local_gdbinit(gdb, gdbinit, elf_file)
|
||||
args = [gdb, *get_gdb_args(gdbinit, project_desc)]
|
||||
generate_gdbinit_files(gdb, gdbinit, project_desc)
|
||||
args = [gdb, *get_gdb_args(project_desc)]
|
||||
if gdb_tui is not None:
|
||||
args += ['-tui']
|
||||
if batch:
|
||||
args += ['--batch']
|
||||
t = Thread(target=run_gdb, args=(args,))
|
||||
t.start()
|
||||
while True:
|
||||
@ -352,12 +475,17 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
|
||||
'callback': gdb,
|
||||
'help': 'Run the GDB.',
|
||||
'options': [
|
||||
{
|
||||
'names': ['--batch'],
|
||||
'help': ('exit after processing gdbinit.\n'),
|
||||
'hidden': True,
|
||||
'is_flag': True,
|
||||
'default': False,
|
||||
},
|
||||
{
|
||||
'names': ['--gdb-tui', '--gdb_tui'],
|
||||
'help':
|
||||
('run gdb in TUI mode\n'),
|
||||
'default':
|
||||
None,
|
||||
'help': ('run gdb in TUI mode\n'),
|
||||
'default': None,
|
||||
}, gdbinit, fail_if_openocd_failed
|
||||
],
|
||||
'order_dependencies': ['all', 'flash'],
|
||||
|
47
tools/idf_py_actions/roms.json
Normal file
47
tools/idf_py_actions/roms.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"esp32": [
|
||||
{
|
||||
"rev": 0,
|
||||
"build_date_str_addr": "0x3ff9ea80",
|
||||
"build_date_str": "Jun 8 2016"
|
||||
},
|
||||
{
|
||||
"rev": 3,
|
||||
"build_date_str_addr": "0x3ff9e986",
|
||||
"build_date_str": "Jul 29 2019"
|
||||
}
|
||||
],
|
||||
"esp32s2": [
|
||||
{
|
||||
"rev": 0,
|
||||
"build_date_str_addr": "0x3ffaf34b",
|
||||
"build_date_str": "Oct 25 2019"
|
||||
}
|
||||
],
|
||||
"esp32s3": [
|
||||
{
|
||||
"rev": 0,
|
||||
"build_date_str_addr": "0x3ff194ad",
|
||||
"build_date_str": "Mar 1 2021"
|
||||
}
|
||||
],
|
||||
"esp32c2": [
|
||||
{
|
||||
"rev": 0,
|
||||
"build_date_str_addr": "0x3ff47874",
|
||||
"build_date_str": "Jan 27 2022"
|
||||
}
|
||||
],
|
||||
"esp32c3": [
|
||||
{
|
||||
"rev": 0,
|
||||
"build_date_str_addr": "0x3ff1b878",
|
||||
"build_date_str": "Sep 18 2020"
|
||||
},
|
||||
{
|
||||
"rev": 3,
|
||||
"build_date_str_addr": "0x3ff1a374",
|
||||
"build_date_str": "Feb 7 2021"
|
||||
}
|
||||
]
|
||||
}
|
29
tools/idf_py_actions/roms_schema.json
Normal file
29
tools/idf_py_actions/roms_schema.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^esp32.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rev": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Chip revision/ROM revision number"
|
||||
},
|
||||
"build_date_str_addr": {
|
||||
"type": "string",
|
||||
"description": "The ROM build date string address to compare between ROM elf file and chip ROM memory",
|
||||
"pattern": "^0x[0-9a-fA-F]{8}$"
|
||||
}
|
||||
},
|
||||
"required": ["rev", "build_date_str_addr"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,16 @@ import yaml
|
||||
from .constants import GENERATORS
|
||||
from .errors import FatalError
|
||||
|
||||
# Name of the program, normally 'idf.py'.
|
||||
# Can be overridden from idf.bat using IDF_PY_PROGRAM_NAME
|
||||
PROG = os.getenv('IDF_PY_PROGRAM_NAME', 'idf.py')
|
||||
|
||||
# environment variable used during click shell completion run
|
||||
SHELL_COMPLETE_VAR = '_IDF.PY_COMPLETE'
|
||||
|
||||
# was shell completion invoked?
|
||||
SHELL_COMPLETE_RUN = SHELL_COMPLETE_VAR in os.environ
|
||||
|
||||
|
||||
def executable_exists(args: List) -> bool:
|
||||
try:
|
||||
@ -78,6 +88,13 @@ def idf_version() -> Optional[str]:
|
||||
return version
|
||||
|
||||
|
||||
# function prints warning when autocompletion is not being performed
|
||||
# set argument stream to sys.stderr for errors and exceptions
|
||||
def print_warning(message: str, stream: TextIO=None) -> None:
|
||||
if not SHELL_COMPLETE_RUN:
|
||||
print(message, file=stream or sys.stderr)
|
||||
|
||||
|
||||
def color_print(message: str, color: str, newline: Optional[str]='\n') -> None:
|
||||
""" Print a message to stderr with colored highlighting """
|
||||
ansi_normal = '\033[0m'
|
||||
@ -95,6 +112,10 @@ def red_print(message: str, newline: Optional[str]='\n') -> None:
|
||||
color_print(message, ansi_red, newline)
|
||||
|
||||
|
||||
def debug_print_idf_version() -> None:
|
||||
print_warning(f'ESP-IDF {idf_version() or "version unknown"}')
|
||||
|
||||
|
||||
def generate_hints(*filenames: str) -> Generator:
|
||||
"""Getting output files and printing hints on how to resolve errors based on the output."""
|
||||
with open(os.path.join(os.path.dirname(__file__), 'hints.yml'), 'r') as file:
|
||||
@ -383,6 +404,11 @@ def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmak
|
||||
the build directory, an error is raised. If the parameter is None, this function will set it to
|
||||
an auto-detected default generator or to the value already configured in the build directory.
|
||||
"""
|
||||
|
||||
if not executable_exists(['cmake', '--version']):
|
||||
debug_print_idf_version()
|
||||
raise FatalError(f'"cmake" must be available on the PATH to use {PROG}')
|
||||
|
||||
project_dir = args.project_dir
|
||||
# Verify the project directory
|
||||
if not os.path.isdir(project_dir):
|
||||
|
@ -163,6 +163,9 @@ class Platforms:
|
||||
if platform_alias is None:
|
||||
return None
|
||||
|
||||
if platform_alias == 'any' and CURRENT_PLATFORM:
|
||||
platform_alias = CURRENT_PLATFORM
|
||||
|
||||
platform_name = Platforms.PLATFORM_FROM_NAME.get(platform_alias, None)
|
||||
|
||||
# ARM platform may run on armhf hardware but having armel installed packages.
|
||||
@ -448,7 +451,7 @@ def rename_with_retry(path_from, path_to): # type: (str, str) -> None
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
def strip_container_dirs(path, levels): # type: (str, int) -> None
|
||||
def do_strip_container_dirs(path, levels): # type: (str, int) -> None
|
||||
assert levels > 0
|
||||
# move the original directory out of the way (add a .tmp suffix)
|
||||
tmp_path = path + '.tmp'
|
||||
@ -545,6 +548,7 @@ IDFToolOptions = namedtuple('IDFToolOptions', [
|
||||
'version_cmd',
|
||||
'version_regex',
|
||||
'version_regex_replace',
|
||||
'is_executable',
|
||||
'export_paths',
|
||||
'export_vars',
|
||||
'install',
|
||||
@ -561,8 +565,8 @@ class IDFTool(object):
|
||||
INSTALL_NEVER = 'never'
|
||||
|
||||
def __init__(self, name, description, install, info_url, license, version_cmd, version_regex, supported_targets, version_regex_replace=None,
|
||||
strip_container_dirs=0):
|
||||
# type: (str, str, str, str, str, List[str], str, List[str], Optional[str], int) -> None
|
||||
strip_container_dirs=0, is_executable=True):
|
||||
# type: (str, str, str, str, str, List[str], str, List[str], Optional[str], int, bool) -> None
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.drop_versions()
|
||||
@ -570,11 +574,12 @@ class IDFTool(object):
|
||||
self.versions_installed = [] # type: List[str]
|
||||
if version_regex_replace is None:
|
||||
version_regex_replace = VERSION_REGEX_REPLACE_DEFAULT
|
||||
self.options = IDFToolOptions(version_cmd, version_regex, version_regex_replace,
|
||||
self.options = IDFToolOptions(version_cmd, version_regex, version_regex_replace, is_executable,
|
||||
[], OrderedDict(), install, info_url, license, strip_container_dirs, supported_targets) # type: ignore
|
||||
self.platform_overrides = [] # type: List[Dict[str, str]]
|
||||
self._platform = CURRENT_PLATFORM
|
||||
self._update_current_options()
|
||||
self.is_executable = is_executable
|
||||
|
||||
def copy_for_platform(self, platform): # type: (str) -> IDFTool
|
||||
result = copy.deepcopy(self)
|
||||
@ -621,7 +626,9 @@ class IDFTool(object):
|
||||
v_repl = re.sub(SUBST_TOOL_PATH_REGEX, replace_path, v)
|
||||
if v_repl != v:
|
||||
v_repl = to_shell_specific_paths([v_repl])[0]
|
||||
result[k] = v_repl
|
||||
old_v = os.environ.get(k)
|
||||
if old_v is None or old_v != v_repl:
|
||||
result[k] = v_repl
|
||||
return result
|
||||
|
||||
def check_version(self, extra_paths=None): # type: (Optional[List[str]]) -> str
|
||||
@ -712,6 +719,9 @@ class IDFTool(object):
|
||||
if not os.path.exists(tool_path):
|
||||
# version not installed
|
||||
continue
|
||||
if not self.is_executable:
|
||||
self.versions_installed.append(version)
|
||||
continue
|
||||
try:
|
||||
ver_str = self.check_version(self.get_export_paths(version))
|
||||
except ToolNotFound:
|
||||
@ -777,7 +787,7 @@ class IDFTool(object):
|
||||
mkdir_p(dest_dir)
|
||||
unpack(archive_path, dest_dir)
|
||||
if self._current_options.strip_container_dirs: # type: ignore
|
||||
strip_container_dirs(dest_dir, self._current_options.strip_container_dirs) # type: ignore
|
||||
do_strip_container_dirs(dest_dir, self._current_options.strip_container_dirs) # type: ignore
|
||||
|
||||
@staticmethod
|
||||
def check_download_file(download_obj, local_path): # type: (IDFToolDownload, str) -> bool
|
||||
@ -794,28 +804,29 @@ class IDFTool(object):
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, tool_dict): # type: (Dict[str, Union[str, List[str], Dict[str, str]]]) -> IDFTool
|
||||
# json.load will return 'str' types in Python 3 and 'unicode' in Python 2
|
||||
expected_str_type = type(u'')
|
||||
|
||||
# Validate json fields
|
||||
tool_name = tool_dict.get('name') # type: ignore
|
||||
if type(tool_name) is not expected_str_type:
|
||||
if not isinstance(tool_name, str):
|
||||
raise RuntimeError('tool_name is not a string')
|
||||
|
||||
description = tool_dict.get('description') # type: ignore
|
||||
if type(description) is not expected_str_type:
|
||||
if not isinstance(description, str):
|
||||
raise RuntimeError('description is not a string')
|
||||
|
||||
is_executable = tool_dict.get('is_executable', True) # type: ignore
|
||||
if not isinstance(is_executable, bool):
|
||||
raise RuntimeError('is_executable for tool %s is not a bool' % tool_name)
|
||||
|
||||
version_cmd = tool_dict.get('version_cmd')
|
||||
if type(version_cmd) is not list:
|
||||
raise RuntimeError('version_cmd for tool %s is not a list of strings' % tool_name)
|
||||
|
||||
version_regex = tool_dict.get('version_regex')
|
||||
if type(version_regex) is not expected_str_type or not version_regex:
|
||||
if not isinstance(version_regex, str) or (not version_regex and is_executable):
|
||||
raise RuntimeError('version_regex for tool %s is not a non-empty string' % tool_name)
|
||||
|
||||
version_regex_replace = tool_dict.get('version_regex_replace')
|
||||
if version_regex_replace and type(version_regex_replace) is not expected_str_type:
|
||||
if version_regex_replace and not isinstance(version_regex_replace, str):
|
||||
raise RuntimeError('version_regex_replace for tool %s is not a string' % tool_name)
|
||||
|
||||
export_paths = tool_dict.get('export_paths')
|
||||
@ -831,15 +842,15 @@ class IDFTool(object):
|
||||
raise RuntimeError('versions for tool %s is not an array' % tool_name)
|
||||
|
||||
install = tool_dict.get('install', False) # type: ignore
|
||||
if type(install) is not expected_str_type:
|
||||
if not isinstance(install, str):
|
||||
raise RuntimeError('install for tool %s is not a string' % tool_name)
|
||||
|
||||
info_url = tool_dict.get('info_url', False) # type: ignore
|
||||
if type(info_url) is not expected_str_type:
|
||||
if not isinstance(info_url, str):
|
||||
raise RuntimeError('info_url for tool %s is not a string' % tool_name)
|
||||
|
||||
license = tool_dict.get('license', False) # type: ignore
|
||||
if type(license) is not expected_str_type:
|
||||
if not isinstance(license, str):
|
||||
raise RuntimeError('license for tool %s is not a string' % tool_name)
|
||||
|
||||
strip_container_dirs = tool_dict.get('strip_container_dirs', 0)
|
||||
@ -857,7 +868,7 @@ class IDFTool(object):
|
||||
# Create the object
|
||||
tool_obj = cls(tool_name, description, install, info_url, license, # type: ignore
|
||||
version_cmd, version_regex, supported_targets, version_regex_replace, # type: ignore
|
||||
strip_container_dirs) # type: ignore
|
||||
strip_container_dirs, is_executable) # type: ignore
|
||||
|
||||
for path in export_paths: # type: ignore
|
||||
tool_obj.options.export_paths.append(path) # type: ignore
|
||||
@ -871,7 +882,7 @@ class IDFTool(object):
|
||||
raise RuntimeError('platforms for override %d of tool %s is not a list' % (index, tool_name))
|
||||
|
||||
install = override.get('install') # type: ignore
|
||||
if install is not None and type(install) is not expected_str_type:
|
||||
if install is not None and not isinstance(install, str):
|
||||
raise RuntimeError('install for override %d of tool %s is not a string' % (index, tool_name))
|
||||
|
||||
version_cmd = override.get('version_cmd') # type: ignore
|
||||
@ -880,12 +891,12 @@ class IDFTool(object):
|
||||
(index, tool_name))
|
||||
|
||||
version_regex = override.get('version_regex') # type: ignore
|
||||
if version_regex is not None and (type(version_regex) is not expected_str_type or not version_regex):
|
||||
if version_regex is not None and (not isinstance(version_regex, str) or not version_regex):
|
||||
raise RuntimeError('version_regex for override %d of tool %s is not a non-empty string' %
|
||||
(index, tool_name))
|
||||
|
||||
version_regex_replace = override.get('version_regex_replace') # type: ignore
|
||||
if version_regex_replace is not None and type(version_regex_replace) is not expected_str_type:
|
||||
if version_regex_replace is not None and not isinstance(version_regex_replace, str):
|
||||
raise RuntimeError('version_regex_replace for override %d of tool %s is not a string' %
|
||||
(index, tool_name))
|
||||
|
||||
@ -901,11 +912,11 @@ class IDFTool(object):
|
||||
recommended_versions = {} # type: dict[str, list[str]]
|
||||
for version_dict in versions: # type: ignore
|
||||
version = version_dict.get('name') # type: ignore
|
||||
if type(version) is not expected_str_type:
|
||||
if not isinstance(version, str):
|
||||
raise RuntimeError('version name for tool {} is not a string'.format(tool_name))
|
||||
|
||||
version_status = version_dict.get('status') # type: ignore
|
||||
if type(version_status) is not expected_str_type and version_status not in IDFToolVersion.STATUS_VALUES:
|
||||
if not isinstance(version_status, str) and version_status not in IDFToolVersion.STATUS_VALUES:
|
||||
raise RuntimeError('tool {} version {} status is not one of {}', tool_name, version,
|
||||
IDFToolVersion.STATUS_VALUES)
|
||||
|
||||
@ -972,6 +983,8 @@ class IDFTool(object):
|
||||
tool_json['platform_overrides'] = overrides_array
|
||||
if self.options.strip_container_dirs:
|
||||
tool_json['strip_container_dirs'] = self.options.strip_container_dirs
|
||||
if self.options.is_executable is False:
|
||||
tool_json['is_executable'] = self.options.is_executable
|
||||
return tool_json
|
||||
|
||||
|
||||
@ -1514,10 +1527,22 @@ def action_export(args): # type: ignore
|
||||
all_tools_found = True
|
||||
export_vars = {}
|
||||
paths_to_export = []
|
||||
|
||||
self_restart_cmd = f'{sys.executable} {__file__}{(" --tools-json " + args.tools_json) if args.tools_json else ""}'
|
||||
self_restart_cmd = to_shell_specific_paths([self_restart_cmd])[0]
|
||||
prefer_system_hint = '' if IDF_TOOLS_EXPORT_CMD else f' To use it, run \'{self_restart_cmd} export --prefer-system\''
|
||||
install_cmd = to_shell_specific_paths([IDF_TOOLS_INSTALL_CMD])[0] if IDF_TOOLS_INSTALL_CMD else self_restart_cmd + ' install'
|
||||
|
||||
for name, tool in tools_info.items():
|
||||
if tool.get_install_type() == IDFTool.INSTALL_NEVER:
|
||||
continue
|
||||
tool.find_installed_versions()
|
||||
version_to_use = tool.get_preferred_installed_version()
|
||||
|
||||
if not tool.is_executable and version_to_use:
|
||||
tool_export_vars = tool.get_export_vars(version_to_use)
|
||||
export_vars = {**export_vars, **tool_export_vars}
|
||||
continue
|
||||
|
||||
if tool.version_in_path:
|
||||
if tool.version_in_path not in tool.versions:
|
||||
@ -1541,20 +1566,6 @@ def action_export(args): # type: ignore
|
||||
warn('using a deprecated version of tool {} found in PATH: {}'.format(name, tool.version_in_path))
|
||||
continue
|
||||
|
||||
self_restart_cmd = '{} {}{}'.format(sys.executable, __file__,
|
||||
(' --tools-json ' + args.tools_json) if args.tools_json else '')
|
||||
self_restart_cmd = to_shell_specific_paths([self_restart_cmd])[0]
|
||||
|
||||
if IDF_TOOLS_EXPORT_CMD:
|
||||
prefer_system_hint = ''
|
||||
else:
|
||||
prefer_system_hint = ' To use it, run \'{} export --prefer-system\''.format(self_restart_cmd)
|
||||
|
||||
if IDF_TOOLS_INSTALL_CMD:
|
||||
install_cmd = to_shell_specific_paths([IDF_TOOLS_INSTALL_CMD])[0]
|
||||
else:
|
||||
install_cmd = self_restart_cmd + ' install'
|
||||
|
||||
if not tool.versions_installed:
|
||||
if tool.get_install_type() == IDFTool.INSTALL_ALWAYS:
|
||||
all_tools_found = False
|
||||
@ -1573,15 +1584,11 @@ def action_export(args): # type: ignore
|
||||
info('Not using an unsupported version of tool {} found in PATH: {}.'.format(
|
||||
tool.name, tool.version_in_path) + prefer_system_hint, f=sys.stderr)
|
||||
|
||||
version_to_use = tool.get_preferred_installed_version()
|
||||
export_paths = tool.get_export_paths(version_to_use)
|
||||
if export_paths:
|
||||
paths_to_export += export_paths
|
||||
tool_export_vars = tool.get_export_vars(version_to_use)
|
||||
for k, v in tool_export_vars.items():
|
||||
old_v = os.environ.get(k)
|
||||
if old_v is None or old_v != v:
|
||||
export_vars[k] = v
|
||||
export_vars = {**export_vars, **tool_export_vars}
|
||||
|
||||
current_path = os.getenv('PATH')
|
||||
idf_python_env_path, idf_python_export_path, virtualenv_python, _ = get_python_env_path()
|
||||
|
6
tools/test_apps/system/gdb/CMakeLists.txt
Normal file
6
tools/test_apps/system/gdb/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(gdb)
|
6
tools/test_apps/system/gdb/README.md
Normal file
6
tools/test_apps/system/gdb/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# IDF GDB test application
|
||||
|
||||
This project tests if `idf.py gdb` works correct
|
3
tools/test_apps/system/gdb/main/CMakeLists.txt
Normal file
3
tools/test_apps/system/gdb/main/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "hello_world_main.c"
|
||||
INCLUDE_DIRS "")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
46
tools/test_apps/system/gdb/main/hello_world_main.c
Normal file
46
tools/test_apps/system/gdb/main/hello_world_main.c
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_flash.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
printf("Hello world!\n");
|
||||
|
||||
/* Print chip information */
|
||||
esp_chip_info_t chip_info;
|
||||
uint32_t flash_size;
|
||||
esp_chip_info(&chip_info);
|
||||
printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
|
||||
CONFIG_IDF_TARGET,
|
||||
chip_info.cores,
|
||||
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
|
||||
|
||||
printf("silicon revision %d, ", chip_info.revision);
|
||||
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||
printf("Get flash size failed");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%uMB %s flash\n", flash_size / (1024 * 1024),
|
||||
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
|
||||
|
||||
printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
|
||||
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
printf("Restarting in %d seconds...\n", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
printf("Restarting now.\n");
|
||||
fflush(stdout);
|
||||
esp_restart();
|
||||
}
|
53
tools/test_apps/system/gdb/pytest_gdb.py
Normal file
53
tools/test_apps/system/gdb/pytest_gdb.py
Normal file
@ -0,0 +1,53 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
try:
|
||||
from idf_py_actions.debug_ext import get_openocd_arguments
|
||||
except ModuleNotFoundError:
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
||||
from idf_py_actions.debug_ext import get_openocd_arguments
|
||||
|
||||
|
||||
@pytest.mark.supported_targets
|
||||
@pytest.mark.test_jtag_arm
|
||||
def test_idf_gdb(dut: Dut) -> None:
|
||||
# Need to wait a moment to connect via OpenOCD after the hard reset happened.
|
||||
# Along with this check that app runs ok
|
||||
dut.expect('Hello world!')
|
||||
|
||||
# Don't need to have output from UART any more
|
||||
dut.serial.stop_redirect_thread()
|
||||
|
||||
with open(os.path.join(dut.logdir, 'ocd.log'), 'w') as ocd_log:
|
||||
ocd = subprocess.Popen(f'openocd {get_openocd_arguments(dut.target)}', stdout=ocd_log, stderr=ocd_log, shell=True)
|
||||
|
||||
try:
|
||||
gdb_env = os.environ.copy()
|
||||
gdb_env['ESP_IDF_GDB_TESTING'] = '1'
|
||||
|
||||
with open(os.path.join(dut.logdir, 'gdb.log'), 'w') as gdb_log, \
|
||||
pexpect.spawn(f'idf.py -B {dut.app.binary_path} gdb --batch',
|
||||
env=gdb_env,
|
||||
timeout=60,
|
||||
logfile=gdb_log,
|
||||
encoding='utf-8',
|
||||
codec_errors='ignore') as p:
|
||||
p.expect(re.compile(r'add symbol table from file.*bootloader.elf'))
|
||||
p.expect(re.compile(r'add symbol table from file.*rom.elf'))
|
||||
p.expect_exact('hit Temporary breakpoint 1, app_main ()')
|
||||
finally:
|
||||
try:
|
||||
ocd.send_signal(signal.SIGINT)
|
||||
ocd.communicate(timeout=15)
|
||||
except subprocess.TimeoutExpired:
|
||||
ocd.kill()
|
||||
ocd.communicate()
|
@ -67,26 +67,27 @@ class WebSocketServer(object):
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.test_jtag_arm
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize('config', ['gdb_stub', 'coredump'], indirect=True)
|
||||
def test_monitor_ide_integration(config: str, dut: Dut) -> None:
|
||||
# The port needs to be closed because idf_monitor.py will connect to it
|
||||
dut.serial.stop_redirect_thread()
|
||||
|
||||
monitor_py = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf_monitor.py')
|
||||
with open(f'monitor_{config}.log', 'w') as log:
|
||||
monitor_cmd = ' '.join([sys.executable, monitor_py, os.path.join(dut.app.binary_path, 'panic.elf'),
|
||||
'--port', str(dut.serial.port),
|
||||
'--ws', f'ws://{WebSocketServer.HOST}:{WebSocketServer.PORT}'])
|
||||
with WebSocketServer(), pexpect.spawn(monitor_cmd,
|
||||
logfile=log,
|
||||
timeout=60,
|
||||
encoding='utf-8',
|
||||
codec_errors='ignore') as p:
|
||||
p.expect(re.compile(r'Guru Meditation Error'), timeout=10)
|
||||
p.expect_exact('Communicating through WebSocket', timeout=5)
|
||||
# The elements of dictionary can be printed in different order depending on the Python version.
|
||||
p.expect(re.compile(r"WebSocket sent: \{.*'event': '" + config + "'"), timeout=5)
|
||||
p.expect_exact('Waiting for debug finished event', timeout=5)
|
||||
p.expect(re.compile(r"WebSocket received: \{'event': 'debug_finished'\}"), timeout=5)
|
||||
p.expect_exact('Communications through WebSocket is finished', timeout=5)
|
||||
monitor_cmd = ' '.join([sys.executable, monitor_py, os.path.join(dut.app.binary_path, 'panic.elf'),
|
||||
'--port', str(dut.serial.port),
|
||||
'--ws', f'ws://{WebSocketServer.HOST}:{WebSocketServer.PORT}'])
|
||||
monitor_log_path = os.path.join(dut.logdir, 'monitor.log')
|
||||
|
||||
with open(monitor_log_path, 'w') as log, WebSocketServer(), pexpect.spawn(monitor_cmd,
|
||||
logfile=log,
|
||||
timeout=5,
|
||||
encoding='utf-8',
|
||||
codec_errors='ignore') as p:
|
||||
p.expect(re.compile(r'Guru Meditation Error'), timeout=10)
|
||||
p.expect_exact('Communicating through WebSocket')
|
||||
# The elements of dictionary can be printed in different order depending on the Python version.
|
||||
p.expect(re.compile(r"WebSocket sent: \{.*'event': '" + config + "'"))
|
||||
p.expect_exact('Waiting for debug finished event')
|
||||
p.expect(re.compile(r"WebSocket received: \{'event': 'debug_finished'\}"))
|
||||
p.expect_exact('Communications through WebSocket is finished')
|
||||
|
@ -9,7 +9,9 @@ import subprocess
|
||||
import sys
|
||||
from unittest import TestCase, main, mock
|
||||
|
||||
import elftools.common.utils as ecu
|
||||
import jsonschema
|
||||
from elftools.elf.elffile import ELFFile
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
@ -25,7 +27,8 @@ except ImportError:
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
idf_py_path = os.path.join(current_dir, '..', 'idf.py')
|
||||
extension_path = os.path.join(current_dir, 'test_idf_extensions', 'test_ext')
|
||||
link_path = os.path.join(current_dir, '..', 'idf_py_actions', 'test_ext')
|
||||
py_actions_path = os.path.join(current_dir, '..', 'idf_py_actions')
|
||||
link_path = os.path.join(py_actions_path, 'test_ext')
|
||||
|
||||
|
||||
class TestWithoutExtensions(TestCase):
|
||||
@ -246,5 +249,47 @@ class TestHelpOutput(TestWithoutExtensions):
|
||||
action_test(['idf.py', 'help', '--json', '--add-options'], schema_json)
|
||||
|
||||
|
||||
class TestROMs(TestWithoutExtensions):
|
||||
def get_string_from_elf_by_addr(self, filename: str, address: int) -> str:
|
||||
result = ''
|
||||
with open(filename, 'rb') as stream:
|
||||
elf_file = ELFFile(stream)
|
||||
ro = elf_file.get_section_by_name('.rodata')
|
||||
ro_addr_delta = ro['sh_addr'] - ro['sh_offset']
|
||||
cstring = ecu.parse_cstring_from_stream(ro.stream, address - ro_addr_delta)
|
||||
if cstring:
|
||||
result = str(cstring.decode('utf-8'))
|
||||
return result
|
||||
|
||||
def test_roms_validate_json(self):
|
||||
with open(os.path.join(py_actions_path, 'roms.json'), 'r') as f:
|
||||
roms_json = json.load(f)
|
||||
|
||||
with open(os.path.join(py_actions_path, 'roms_schema.json'), 'r') as f:
|
||||
schema_json = json.load(f)
|
||||
jsonschema.validate(roms_json, schema_json)
|
||||
|
||||
def test_roms_check_supported_chips(self):
|
||||
from idf_py_actions.constants import SUPPORTED_TARGETS
|
||||
with open(os.path.join(py_actions_path, 'roms.json'), 'r') as f:
|
||||
roms_json = json.load(f)
|
||||
for chip in SUPPORTED_TARGETS:
|
||||
self.assertTrue(chip in roms_json, msg=f'Have no ROM data for chip {chip}')
|
||||
|
||||
def test_roms_validate_build_date(self):
|
||||
sys.path.append(py_actions_path)
|
||||
|
||||
rom_elfs_dir = os.getenv('ESP_ROM_ELF_DIR')
|
||||
with open(os.path.join(py_actions_path, 'roms.json'), 'r') as f:
|
||||
roms_json = json.load(f)
|
||||
|
||||
for chip in roms_json:
|
||||
for k in roms_json[chip]:
|
||||
rom_file = os.path.join(rom_elfs_dir, f'{chip}_rev{k["rev"]}_rom.elf')
|
||||
build_date_str = self.get_string_from_elf_by_addr(rom_file, int(k['build_date_str_addr'], base=16))
|
||||
self.assertTrue(len(build_date_str) == 11)
|
||||
self.assertTrue(build_date_str == k['build_date_str'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -45,6 +45,7 @@ XTENSA_ESP32S2_ELF = 'xtensa-esp32s2-elf'
|
||||
XTENSA_ESP32S3_ELF = 'xtensa-esp32s3-elf'
|
||||
XTENSA_ESP_GDB = 'xtensa-esp-elf-gdb'
|
||||
RISCV_ESP_GDB = 'riscv32-esp-elf-gdb'
|
||||
ESP_ROM_ELFS = 'esp-rom-elfs'
|
||||
|
||||
|
||||
def get_version_dict():
|
||||
@ -70,6 +71,7 @@ XTENSA_ESP32S2_ELF_VERSION = version_dict[XTENSA_ESP32S2_ELF]
|
||||
XTENSA_ESP32S3_ELF_VERSION = version_dict[XTENSA_ESP32S3_ELF]
|
||||
XTENSA_ESP_GDB_VERSION = version_dict[XTENSA_ESP_GDB]
|
||||
RISCV_ESP_GDB_VERSION = version_dict[RISCV_ESP_GDB]
|
||||
ESP_ROM_ELFS_VERSION = version_dict[ESP_ROM_ELFS]
|
||||
|
||||
|
||||
class TestUsage(unittest.TestCase):
|
||||
@ -143,7 +145,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assertIn('* %s:' % XTENSA_ESP32S3_ELF, output)
|
||||
self.assertIn('- %s (recommended)' % XTENSA_ESP32S3_ELF_VERSION, output)
|
||||
|
||||
required_tools_installed = 8
|
||||
required_tools_installed = 9
|
||||
output = self.run_idf_tools_with_action(['install'])
|
||||
self.assert_tool_installed(output, OPENOCD, OPENOCD_VERSION)
|
||||
self.assert_tool_installed(output, RISCV_ELF, RISCV_ELF_VERSION)
|
||||
@ -153,6 +155,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assert_tool_installed(output, ESP32ULP, ESP32ULP_VERSION)
|
||||
self.assert_tool_installed(output, XTENSA_ESP_GDB, XTENSA_ESP_GDB_VERSION)
|
||||
self.assert_tool_installed(output, RISCV_ESP_GDB, RISCV_ESP_GDB_VERSION)
|
||||
self.assert_tool_installed(output, ESP_ROM_ELFS, ESP_ROM_ELFS_VERSION)
|
||||
self.assertIn('Destination: {}'.format(os.path.join(self.temp_tools_dir, 'dist')), output)
|
||||
self.assertEqual(required_tools_installed, output.count('Done'))
|
||||
|
||||
@ -165,6 +168,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP32S3_ELF_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + RISCV_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + ESP_ROM_ELFS_VERSION, output)
|
||||
|
||||
output = self.run_idf_tools_with_action(['export'])
|
||||
self.assertIn('%s/tools/esp32ulp-elf/%s/esp32ulp-elf/bin' %
|
||||
@ -183,9 +187,11 @@ class TestUsage(unittest.TestCase):
|
||||
(self.temp_tools_dir, XTENSA_ESP_GDB_VERSION), output)
|
||||
self.assertIn('%s/tools/riscv32-esp-elf-gdb/%s/riscv32-esp-elf-gdb/bin' %
|
||||
(self.temp_tools_dir, RISCV_ESP_GDB_VERSION), output)
|
||||
self.assertIn('%s/tools/esp-rom-elfs/%s/' %
|
||||
(self.temp_tools_dir, ESP_ROM_ELFS_VERSION), output)
|
||||
|
||||
def test_tools_for_esp32(self):
|
||||
required_tools_installed = 4
|
||||
required_tools_installed = 5
|
||||
output = self.run_idf_tools_with_action(['install', '--targets=esp32'])
|
||||
self.assert_tool_installed(output, XTENSA_ESP32_ELF, XTENSA_ESP32_ELF_VERSION)
|
||||
self.assert_tool_installed(output, OPENOCD, OPENOCD_VERSION)
|
||||
@ -195,6 +201,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32S2_ELF, XTENSA_ESP32S2_ELF_VERSION)
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32S3_ELF, XTENSA_ESP32S3_ELF_VERSION)
|
||||
self.assert_tool_not_installed(output, RISCV_ESP_GDB, RISCV_ESP_GDB_VERSION)
|
||||
self.assert_tool_installed(output, ESP_ROM_ELFS, ESP_ROM_ELFS_VERSION)
|
||||
self.assertIn('Destination: {}'.format(os.path.join(self.temp_tools_dir, 'dist')), output)
|
||||
self.assertEqual(required_tools_installed, output.count('Done'))
|
||||
|
||||
@ -203,6 +210,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP32_ELF_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + OPENOCD_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + ESP_ROM_ELFS_VERSION, output)
|
||||
|
||||
output = self.run_idf_tools_with_action(['export'])
|
||||
self.assertIn('%s/tools/esp32ulp-elf/%s/esp32ulp-elf/bin' %
|
||||
@ -221,9 +229,11 @@ class TestUsage(unittest.TestCase):
|
||||
(self.temp_tools_dir, XTENSA_ESP32S3_ELF_VERSION), output)
|
||||
self.assertNotIn('%s/tools/riscv32-esp-elf-gdb/%s/riscv32-esp-elf-gdb/bin' %
|
||||
(self.temp_tools_dir, RISCV_ESP_GDB_VERSION), output)
|
||||
self.assertIn('%s/tools/esp-rom-elfs/%s/' %
|
||||
(self.temp_tools_dir, ESP_ROM_ELFS_VERSION), output)
|
||||
|
||||
def test_tools_for_esp32c3(self):
|
||||
required_tools_installed = 3
|
||||
required_tools_installed = 4
|
||||
output = self.run_idf_tools_with_action(['install', '--targets=esp32c3'])
|
||||
self.assert_tool_installed(output, OPENOCD, OPENOCD_VERSION)
|
||||
self.assert_tool_installed(output, RISCV_ELF, RISCV_ELF_VERSION)
|
||||
@ -233,6 +243,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32S3_ELF, XTENSA_ESP32S3_ELF_VERSION)
|
||||
self.assert_tool_not_installed(output, ESP32ULP, ESP32ULP_VERSION)
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP_GDB_VERSION, XTENSA_ESP_GDB_VERSION)
|
||||
self.assert_tool_installed(output, ESP_ROM_ELFS, ESP_ROM_ELFS_VERSION)
|
||||
self.assertIn('Destination: {}'.format(os.path.join(self.temp_tools_dir, 'dist')), output)
|
||||
self.assertEqual(required_tools_installed, output.count('Done'))
|
||||
|
||||
@ -240,6 +251,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assertIn('version installed in tools directory: ' + OPENOCD_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + RISCV_ELF_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + RISCV_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + ESP_ROM_ELFS_VERSION, output)
|
||||
|
||||
output = self.run_idf_tools_with_action(['export'])
|
||||
self.assertIn('%s/tools/openocd-esp32/%s/openocd-esp32/bin' %
|
||||
@ -256,15 +268,18 @@ class TestUsage(unittest.TestCase):
|
||||
(self.temp_tools_dir, XTENSA_ESP32S3_ELF_VERSION), output)
|
||||
self.assertNotIn('%s/tools/xtensa-esp-elf-gdb/%s/xtensa-esp-elf-gdb/bin' %
|
||||
(self.temp_tools_dir, XTENSA_ESP_GDB_VERSION), output)
|
||||
self.assertIn('%s/tools/esp-rom-elfs/%s/' %
|
||||
(self.temp_tools_dir, ESP_ROM_ELFS_VERSION), output)
|
||||
|
||||
def test_tools_for_esp32s2(self):
|
||||
required_tools_installed = 5
|
||||
required_tools_installed = 6
|
||||
output = self.run_idf_tools_with_action(['install', '--targets=esp32s2'])
|
||||
self.assert_tool_installed(output, XTENSA_ESP32S2_ELF, XTENSA_ESP32S2_ELF_VERSION)
|
||||
self.assert_tool_installed(output, OPENOCD, OPENOCD_VERSION)
|
||||
self.assert_tool_installed(output, RISCV_ELF, RISCV_ELF_VERSION)
|
||||
self.assert_tool_installed(output, ESP32ULP, ESP32ULP_VERSION)
|
||||
self.assert_tool_installed(output, XTENSA_ESP_GDB, XTENSA_ESP_GDB_VERSION)
|
||||
self.assert_tool_installed(output, ESP_ROM_ELFS, ESP_ROM_ELFS_VERSION)
|
||||
self.assert_tool_not_installed(output, RISCV_ESP_GDB, RISCV_ESP_GDB_VERSION)
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32_ELF, XTENSA_ESP32_ELF_VERSION)
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32S3_ELF, XTENSA_ESP32S3_ELF_VERSION)
|
||||
@ -275,6 +290,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assertIn('version installed in tools directory: ' + OPENOCD_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP32S2_ELF_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + ESP_ROM_ELFS_VERSION, output)
|
||||
|
||||
output = self.run_idf_tools_with_action(['export'])
|
||||
self.assertIn('%s/tools/xtensa-esp32s2-elf/%s/xtensa-esp32s2-elf/bin' %
|
||||
@ -293,15 +309,18 @@ class TestUsage(unittest.TestCase):
|
||||
(self.temp_tools_dir, XTENSA_ESP_GDB_VERSION), output)
|
||||
self.assertNotIn('%s/tools/riscv32-esp-elf-gdb/%s/riscv32-esp-elf-gdb/bin' %
|
||||
(self.temp_tools_dir, RISCV_ESP_GDB_VERSION), output)
|
||||
self.assertIn('%s/tools/esp-rom-elfs/%s/' %
|
||||
(self.temp_tools_dir, ESP_ROM_ELFS_VERSION), output)
|
||||
|
||||
def test_tools_for_esp32s3(self):
|
||||
required_tools_installed = 5
|
||||
required_tools_installed = 6
|
||||
output = self.run_idf_tools_with_action(['install', '--targets=esp32s3'])
|
||||
self.assert_tool_installed(output, XTENSA_ESP32S3_ELF, XTENSA_ESP32S3_ELF_VERSION)
|
||||
self.assert_tool_installed(output, OPENOCD, OPENOCD_VERSION)
|
||||
self.assert_tool_installed(output, RISCV_ELF, RISCV_ELF_VERSION)
|
||||
self.assert_tool_installed(output, ESP32ULP, ESP32ULP_VERSION)
|
||||
self.assert_tool_installed(output, XTENSA_ESP_GDB, XTENSA_ESP_GDB_VERSION)
|
||||
self.assert_tool_installed(output, ESP_ROM_ELFS, ESP_ROM_ELFS_VERSION)
|
||||
self.assert_tool_not_installed(output, RISCV_ESP_GDB, RISCV_ESP_GDB_VERSION)
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32_ELF, XTENSA_ESP32_ELF_VERSION)
|
||||
self.assert_tool_not_installed(output, XTENSA_ESP32S2_ELF, XTENSA_ESP32S2_ELF_VERSION)
|
||||
@ -313,6 +332,7 @@ class TestUsage(unittest.TestCase):
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP32S3_ELF_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + XTENSA_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + RISCV_ESP_GDB_VERSION, output)
|
||||
self.assertIn('version installed in tools directory: ' + ESP_ROM_ELFS_VERSION, output)
|
||||
|
||||
output = self.run_idf_tools_with_action(['export'])
|
||||
self.assertIn('%s/tools/openocd-esp32/%s/openocd-esp32/bin' %
|
||||
@ -331,6 +351,8 @@ class TestUsage(unittest.TestCase):
|
||||
(self.temp_tools_dir, XTENSA_ESP_GDB_VERSION), output)
|
||||
self.assertNotIn('%s/tools/riscv32-esp-elf-gdb/%s/riscv32-esp-elf-gdb/bin' %
|
||||
(self.temp_tools_dir, RISCV_ESP_GDB_VERSION), output)
|
||||
self.assertIn('%s/tools/esp-rom-elfs/%s/' %
|
||||
(self.temp_tools_dir, ESP_ROM_ELFS_VERSION), output)
|
||||
|
||||
def test_uninstall_option(self):
|
||||
self.run_idf_tools_with_action(['install', '--targets=esp32,esp32c3'])
|
||||
|
@ -950,6 +950,40 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ESP ROM ELFs",
|
||||
"export_paths": [
|
||||
[
|
||||
""
|
||||
]
|
||||
],
|
||||
"export_vars": {
|
||||
"ESP_ROM_ELF_DIR": "${TOOL_PATH}/"
|
||||
},
|
||||
"info_url": "https://github.com/espressif/esp-rom-elfs",
|
||||
"install": "always",
|
||||
"is_executable": false,
|
||||
"license": "Apache-2.0",
|
||||
"name": "esp-rom-elfs",
|
||||
"supported_targets": [
|
||||
"all"
|
||||
],
|
||||
"version_cmd": [
|
||||
""
|
||||
],
|
||||
"version_regex": "",
|
||||
"versions": [
|
||||
{
|
||||
"any": {
|
||||
"sha256": "add4bedbdd950c8409ff45bbf5610316e7d14c4635ea6906f057f2183ab3e3e9",
|
||||
"size": 2454730,
|
||||
"url": "https://github.com/espressif/esp-rom-elfs/releases/download/20220823/esp-rom-elfs-20220823.tar.gz"
|
||||
},
|
||||
"name": "20220823",
|
||||
"status": "recommended"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
|
@ -47,6 +47,10 @@
|
||||
"$ref": "#/definitions/installRequirementInfo",
|
||||
"description": "If 'always', the tool will be installed by default. If 'on_request', tool will be installed when specifically requested. If 'never', tool will not be considered for installation."
|
||||
},
|
||||
"is_executable": {
|
||||
"description": "If false - tool does not contain executables. The version will not be checked but export_vars applied.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"license": {
|
||||
"description": "License name. Use SPDX license identifier if it exists, short name of the license otherwise.",
|
||||
"type": "string"
|
||||
|
Loading…
Reference in New Issue
Block a user