feat(debugging): move gdbinit generation to CMake

This feature is useful for 3rd-party software to run GDB with predefined
options that described in project_description.json file

allow to pass custom options to "idf.py gdb":

  --gdb-commands: command line arguments for gdb. (without changes)
  -ex: pass command to gdb.
  -x: pass gdbinit file to gdb. Alias for old --gdbinit command
This commit is contained in:
Alexey Lapshin 2024-11-06 17:01:33 +07:00
parent 16469297b4
commit 48a49c8154
19 changed files with 394 additions and 320 deletions

View File

@ -399,6 +399,17 @@ test_nvs_gen_check:
- cd ${IDF_PATH}/components/nvs_flash/nvs_partition_tool
- pytest --noconftest test_nvs_gen_check.py --junitxml=XUNIT_RESULT.xml
test_esp_rom:
extends: .host_test_template
artifacts:
paths:
- XUNIT_RESULT.xml
reports:
junit: XUNIT_RESULT.xml
script:
- cd ${IDF_PATH}/components/esp_rom/
- pytest --noconftest test_esp_rom.py --junitxml=XUNIT_RESULT.xml
make_sure_soc_caps_compatible_in_idf_build_apps:
extends:
- .host_test_template

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
import sys
from textwrap import indent
IDF_PATH = os.getenv('IDF_PATH', '')
ROMS_JSON = os.path.join(IDF_PATH, 'tools', 'idf_py_actions', 'roms.json') # type: ignore
# Direct string extraction and comparison is not feasible due to:
# 1. ROM ELF binaries for esp32XX chips are little-endian. The byte order in
# the binary may differ from the system's endianness. We must ensure the
# bytes are read correctly for the system where the script runs.
# 2. GDB lacks built-in string comparison functionality. To work around this,
# strings are converted to numeric values for comparison.
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 EnvironmentError(
'ESP_ROM_ELF_DIR environment variable is not defined. Please try to run IDF "install" and "export" scripts.')
with open(ROMS_JSON, 'r') as f:
roms = json.load(f)
if target not in roms:
msg_body = f'Warning: ROM ELF is not supported yet for "{target}".' # noqa: E713
return f'echo {msg_body}\\n\n'
r = ['', f'# Load {target} ROM ELF symbols']
r.append('define target hookpost-remote')
r.append('set confirm off')
# 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 = 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_elfs_dir}{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 warning
indent_str += base_ident
msg_body = f'Warning: Unknown {target} ROM revision.'
r.append(indent(f'echo {msg_body}\\n', indent_str))
# Close 'else' operators
for i in range(len(roms[target]), 0, -1):
r.append(indent('end', base_ident * i))
r.append('set confirm on')
r.append('end')
return '\n'.join(r)[1:]
if __name__ == '__main__':
if len(sys.argv) != 2:
raise ValueError('Please pass only one argument (target).')
target = sys.argv[1]
gdbinit_lines = generate_gdbinit_rom_add_symbols(target)
print(gdbinit_lines)

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
import elftools.common.utils as ecu
import jsonschema
from elftools.elf.elffile import ELFFile
from idf_build_apps.constants import SUPPORTED_TARGETS
IDF_PATH = os.getenv('IDF_PATH', '')
ROMS_JSON = os.path.join(IDF_PATH, 'tools', 'idf_py_actions', 'roms.json') # type: ignore
def test_roms_validate_json() -> None:
with open(ROMS_JSON, 'r') as f:
roms_json = json.load(f)
json_schema_path = os.path.join(os.path.dirname(ROMS_JSON), 'roms_schema.json')
with open(json_schema_path, 'r') as f:
schema_json = json.load(f)
jsonschema.validate(roms_json, schema_json)
def test_roms_check_supported_chips() -> None:
with open(ROMS_JSON, 'r') as f:
roms_json = json.load(f)
for chip in SUPPORTED_TARGETS:
assert chip in roms_json, f'Have no ROM data for chip {chip}'
def test_roms_validate_build_date() -> None:
def get_string_from_elf_by_addr(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
rom_elfs_dir = os.getenv('ESP_ROM_ELF_DIR', '')
with open(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 = get_string_from_elf_by_addr(rom_file, int(k['build_date_str_addr'], base=16))
assert len(build_date_str) == 11
assert build_date_str == k['build_date_str']

View File

@ -174,7 +174,7 @@ Once target is configured and connected to computer, you are ready to launch Ope
.. highlight:: bash
Open a terminal and set it up for using the ESP-IDF as described in the :ref:`setting up the environment <get-started-set-up-env>` section of the Getting Started Guide. Then run OpenOCD (this command works on Windows, Linux, and macOS):
Open a terminal and set it up for using the ESP-IDF as described in the :ref:`setting up the environment <get-started-set-up-env>` section of the Getting Started Guide. To run OpenOCD for a specific board, you must pass the board-specific configuration. The default configuration for the built project can be found in the ``debug_arguments_openocd`` field of the ``build/project_description.json`` file. There is an example to run OpenOCD (this command works on Windows, Linux, and macOS):
.. include:: {IDF_TARGET_PATH_NAME}.inc
:start-after: run-openocd

View File

@ -137,28 +137,35 @@ Command Line
.. highlight:: none
3. When launching a debugger, you will need to provide a couple of configuration parameters and commands. Instead of entering them one by one in the command line, create a configuration file and name it ``gdbinit``:
3. When launching a debugger, you will need to provide a couple of configuration parameters and commands. The build system generates several ``.gdbinit`` files to facilitate efficient debugging. Paths to these files can be found in the ``build/project_description.json``, under the ``gdbinit_files`` section. The paths to these files are defined as follows:
::
.. code-block:: json
target remote :3333
set remote hardware-watchpoint-limit 2
mon reset halt
maintenance flush register-cache
thb app_main
c
"gdbinit_files": {
"01_symbols": "application_path/build/gdbinit/symbols",
"02_prefix_map": "application_path/build/gdbinit/prefix_map",
"03_py_extensions": "application_path/build/gdbinit/py_extensions",
"04_connect": "application_path/build/gdbinit/connect"
}
Save this file in the current directory.
The ``XX_`` prefix in the JSON keys is included to have ability to sort them. Sorted fields indicate the recommended order in which to provide the data to GDB.
For more details on what is inside ``gdbinit`` file, see :ref:`jtag-debugging-tip-debugger-startup-commands`
Descriptions of the generated ``.gdbinit`` files:
* ``symbols`` - contains symbol sources for debugging.
* ``prefix_map`` - configures the prefix map to modify source paths in GDB. For more details, see :ref:`reproducible-builds-and-debugging`.
* ``py_extensions`` - initializes of Python extensions in GDB. Requires Python built with ``libpython`` and Python version supported in GDB. Verify compatibility with ``{IDF_TARGET_TOOLCHAIN_PREFIX}-gdb --batch-silent --ex "python import os"``, which should complete without error.
* ``connect`` - contains commands necessary for establishing a connection to the target device.
To enhance your debugging experience, you can also create custom ``.gdbinit`` files, used either alongside or in place of the generated configurations.
.. highlight:: bash
4. Now you are ready to launch GDB. Type the following in terminal:
4. Now you are ready to launch GDB. Use the following example command to load symbols and connect to the target (``-q`` option added to minimize startup output):
::
{IDF_TARGET_TOOLCHAIN_PREFIX}-gdb -x gdbinit build/blink.elf
{IDF_TARGET_TOOLCHAIN_PREFIX}-gdb -q -x build/gdbinit/symbols -x build/gdbinit/prefix_map -x build/gdbinit/connect build/blink.elf
.. highlight:: none
@ -166,51 +173,24 @@ Command Line
::
user-name@computer-name:~/esp/blink$ {IDF_TARGET_TOOLCHAIN_PREFIX}-gdb -x gdbinit build/blink.elf
GNU gdb (crosstool-NG crosstool-ng-1.22.0-61-gab8375a) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-build_pc-linux-gnu --target={IDF_TARGET_TOOLCHAIN_PREFIX}".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from build/blink.elf...done.
0x400d10d8 in esp_vApplicationIdleHook () at /home/user-name/esp/esp-idf/components/{IDF_TARGET_PATH_NAME}/./freertos_hooks.c:52
52 asm("waiti 0");
JTAG tap: {IDF_TARGET_PATH_NAME}.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
JTAG tap: {IDF_TARGET_PATH_NAME}.slave tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
{IDF_TARGET_PATH_NAME}: Debug controller was reset (pwrstat=0x5F, after clear 0x0F).
{IDF_TARGET_PATH_NAME}: Core was reset (pwrstat=0x5F, after clear 0x0F).
Target halted. PRO_CPU: PC=0x5000004B (active) APP_CPU: PC=0x00000000
{IDF_TARGET_PATH_NAME}: target state: halted
{IDF_TARGET_PATH_NAME}: Core was reset (pwrstat=0x1F, after clear 0x0F).
Target halted. PRO_CPU: PC=0x40000400 (active) APP_CPU: PC=0x40000400
{IDF_TARGET_PATH_NAME}: target state: halted
Hardware assisted breakpoint 1 at 0x400db717: file /home/user-name/esp/blink/main/./blink.c, line 43.
0x0: 0x00000000
Target halted. PRO_CPU: PC=0x400DB717 (active) APP_CPU: PC=0x400D10D8
[New Thread 1073428656]
[New Thread 1073413708]
[New Thread 1073431316]
[New Thread 1073410672]
[New Thread 1073408876]
[New Thread 1073432196]
[New Thread 1073411552]
[Switching to Thread 1073411996]
user-name@computer-name:~/esp-idf/examples/get-started/blink$ {IDF_TARGET_TOOLCHAIN_PREFIX}-gdb -q -x build/gdbinit/symbols -x build/gdbinit/connect build/blink.elf
Reading symbols from build/blink.elf...
add symbol table from file "/home/user-name/esp-idf/examples/get-started/blink/build/bootloader/bootloader.elf"
[Switching to Thread 1070141764]
app_main () at /home/user-name/esp-idf/examples/get-started/blink/main/blink_example_main.c:95
95 configure_led();
add symbol table from file "/home/alex/.espressif/tools/esp-rom-elfs/20241011/{IDF_TARGET_PATH_NAME}_rev0_rom.elf"
JTAG tap: {IDF_TARGET_PATH_NAME}.tap0 tap/device found: 0x00005c25 (mfg: 0x612 (Espressif Systems), part: 0x0005, ver: 0x0)
[{IDF_TARGET_PATH_NAME}] Reset cause (3) - (Software core reset)
Hardware assisted breakpoint 1 at 0x42009436: file /home/user-name/esp-idf/examples/get-started/blink/main/blink_example_main.c, line 92.
[Switching to Thread 1070139884]
Temporary breakpoint 1, app_main () at /home/user-name/esp/blink/main/./blink.c:43
43 xTaskCreate(&blink_task, "blink_task", 512, NULL, 5, NULL);
Thread 2 "main" hit Temporary breakpoint 1, app_main () at /home/user-name/esp-idf/examples/get-started/blink/main/blink_example_main.c:92
92 {
(gdb)
Note the third-to-last line, which shows debugger halting at breakpoint established in ``gdbinit`` file at function ``app_main()``. Since the processor is halted, the LED should not be blinking. If this is what you see as well, you are ready to start debugging.
Note the third-to-last line, which shows debugger halting at breakpoint established in ``build/gdbinit/connect`` file at function ``app_main()``. Since the processor is halted, the LED should not be blinking. If this is what you see as well, you are ready to start debugging.
If you are not sure how to use GDB, check :ref:`jtag-debugging-examples-command-line` example debugging session in section :ref:`jtag-debugging-examples`.
@ -236,7 +216,7 @@ It is also possible to execute the described debugging tools conveniently from `
2. ``idf.py gdb``
Starts the GDB the same way as the :ref:`jtag-debugging-using-debugger-command-line`, but generates the initial GDB scripts referring to the current project elf file.
Starts the GDB the same way as the :ref:`jtag-debugging-using-debugger-command-line`, uses generated GDB scripts referring to the current project elf file, see :ref:`jtag-debugging-using-debugger-command-line`.
3. ``idf.py gdbtui``

View File

@ -52,6 +52,8 @@ ESP-IDF achieves reproducible builds using the following measures:
- Build date and time are not included into the :ref:`application metadata structure <app-image-format-application-description>` and :ref:`bootloader metadata structure <image-format-bootloader-description>` if :ref:`CONFIG_APP_REPRODUCIBLE_BUILD` is enabled.
- ESP-IDF build system ensures that source file lists, component lists and other sequences are sorted before passing them to CMake. Various other parts of the build system, such as the linker script generator also perform sorting to ensure that same output is produced regardless of the environment.
.. _reproducible-builds-and-debugging:
Reproducible Builds and Debugging
---------------------------------
@ -63,9 +65,9 @@ This issue can be solved using GDB ``set substitute-path`` command. For example,
set substitute-path /COMPONENT_FREERTOS_DIR /home/user/esp/esp-idf/components/freertos
ESP-IDF build system generates a file with the list of such ``set substitute-path`` commands automatically during the build process. The file is called ``prefix_map_gdbinit`` and is located in the project ``build`` directory.
ESP-IDF build system generates a file with the list of such ``set substitute-path`` commands automatically during the build process. The file is called ``prefix_map`` and is located in the project ``build/gdbinit`` directory.
When :ref:`idf.py gdb <jtag-debugging-with-idf-py>` is used to start debugging, this additional ``gdbinit`` file is automatically passed to GDB. When launching GDB manually or from IDE, please pass this additional ``gdbinit`` script to GDB using ``-x build/prefix_map_gdbinit`` argument.
When :ref:`idf.py gdb <jtag-debugging-with-idf-py>` is used to start debugging, this additional ``gdbinit`` file is automatically passed to GDB. When launching GDB manually or from IDE, please pass this additional ``gdbinit`` script to GDB using ``-x build/gdbinit/prefix_map`` argument.
Factors Which Still Affect Reproducible Builds
----------------------------------------------

View File

@ -63,9 +63,9 @@ ESP-IDF 可通过以下方式实现可重复构建:
set substitute-path /COMPONENT_FREERTOS_DIR /home/user/esp/esp-idf/components/freertos
ESP-IDF 构建系统在构建过程中会自动生成一个包含 ``set substitute-path`` 命令列表的文件。该文件名为 ``prefix_map_gdbinit``,位于项目 ``build`` 目录中。
ESP-IDF 构建系统在构建过程中会自动生成一个包含 ``set substitute-path`` 命令列表的文件。该文件名为 ``prefix_map``,位于项目 ``build/gdbinit`` 目录中。
当使用 :ref:`idf.py gdb <jtag-debugging-with-idf-py>` 开始调试时,此额外的 ``gdbinit`` 文件会自动传递给 GDB。当手动启动 GDB 或从 IDE 启动 GDB 时,请使用 ``-x build/prefix_map_gdbinit`` 参数将此额外的 ``gdbinit`` 脚本传递给 GDB。
当使用 :ref:`idf.py gdb <jtag-debugging-with-idf-py>` 开始调试时,此额外的 ``gdbinit`` 文件会自动传递给 GDB。当手动启动 GDB 或从 IDE 启动 GDB 时,请使用 ``-x build/gdbinit/prefix_map`` 参数将此额外的 ``gdbinit`` 脚本传递给 GDB。
仍可能影响可重复构建的因素
--------------------------

View File

@ -47,6 +47,7 @@ class AppUploader(AppDownloader):
'bootloader/*.elf',
'*.map',
'*.elf',
'gdbinit/*',
],
ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES: [
'*.bin',

79
tools/cmake/gdbinit.cmake Normal file
View File

@ -0,0 +1,79 @@
# __generate_gdbinit
# Prepares gdbinit files to pass into the debugger.
function(__generate_gdbinit)
# Define static gdbinit commands
if(CONFIG_IDF_TARGET_LINUX)
set(gdbinit_connect
"# Run the application and stop on app_main()\n"
"break app_main\n"
"run\n")
else()
set(gdbinit_connect
"# Connect to the default openocd-esp port and stop on app_main()\n"
"set remotetimeout 10\n"
"target remote :3333\n"
"monitor reset halt\n"
"maintenance flush register-cache\n"
"thbreak app_main\n"
"continue\n")
endif()
set(gdbinit_py_extensions
"# Add Python GDB extensions\n"
"python\n"
"try:\n"
" import freertos_gdb\n"
"except ModuleNotFoundError:\n"
" print('warning: python extension \"freertos_gdb\" not found.')\n"
"end\n")
# Define paths
set(gdbinit_dir ${BUILD_DIR}/gdbinit)
set(gdbinit_rom_in_path ${gdbinit_dir}/rom.gdbinit.in)
set(gdbinit_rom_path ${gdbinit_dir}/rom.gdbinit)
set(symbols_gdbinit_path ${gdbinit_dir}/symbols)
set(py_extensions_gdbinit_path ${gdbinit_dir}/py_extensions)
set(connect_gdbinit_path ${gdbinit_dir}/connect)
idf_build_get_property(PROJECT_EXECUTABLE EXECUTABLE)
set(application_elf ${BUILD_DIR}/${PROJECT_EXECUTABLE})
file(TO_CMAKE_PATH $ENV{ESP_ROM_ELF_DIR} ESP_ROM_ELF_DIR)
file(MAKE_DIRECTORY ${gdbinit_dir})
# Get ROM ELFs gdbinit part
if(CONFIG_IDF_TARGET_LINUX)
set(rom_symbols)
else()
execute_process(
COMMAND python "${idf_path}/components/esp_rom/gen_gdbinit.py" ${IDF_TARGET}
OUTPUT_VARIABLE rom_symbols
RESULT_VARIABLE result
)
if(NOT result EQUAL 0)
set(rom_symbols)
message(WARNING "Error while generating esp_rom gdbinit")
endif()
endif()
# Check if bootloader ELF is defined and set symbol-file accordingly
if(DEFINED BOOTLOADER_ELF_FILE)
set(add_bootloader_symbols " add-symbol-file ${BOOTLOADER_ELF_FILE}")
else()
set(add_bootloader_symbols " # Bootloader elf was not found")
endif()
# Configure and generate files
configure_file(${idf_path}/tools/cmake/symbols.gdbinit.in ${symbols_gdbinit_path})
file(WRITE ${py_extensions_gdbinit_path} ${gdbinit_py_extensions})
file(WRITE ${connect_gdbinit_path} ${gdbinit_connect})
# TODO IDF-11667
file(WRITE ${gdbinit_dir}/gdbinit "source ${symbols_gdbinit_path}\n")
file(APPEND ${gdbinit_dir}/gdbinit "source ${connect_gdbinit_path}\n")
# NOTE: prefix_map gbinit file generates by prefix_map.cmake file
idf_build_set_property(GDBINIT_FILES_SYMBOLS ${symbols_gdbinit_path})
idf_build_set_property(GDBINIT_FILES_PY_EXTENSIONS ${py_extensions_gdbinit_path})
idf_build_set_property(GDBINIT_FILES_CONNECT ${connect_gdbinit_path})
endfunction()

View File

@ -48,7 +48,9 @@ if(NOT __idf_env_set)
include(ldgen)
include(dfu)
include(version)
include(gdbinit)
include(prefix_map)
include(openocd)
__build_init("${idf_path}")

14
tools/cmake/openocd.cmake Normal file
View File

@ -0,0 +1,14 @@
# __get_openocd_options
# Prepares openocd default options for the target.
function(__get_openocd_options openocd_option_var)
if(CONFIG_IDF_TARGET STREQUAL "esp32")
set(${openocd_option_var} "-f board/esp32-wrover-kit-3.3v.cfg" PARENT_SCOPE)
elseif(CONFIG_IDF_TARGET STREQUAL "esp32s2")
set(${openocd_option_var} "-f board/esp32s2-kaluga-1.cfg" PARENT_SCOPE)
elseif(CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED)
set(${openocd_option_var} "-f board/${CONFIG_IDF_TARGET}-builtin.cfg" PARENT_SCOPE)
else()
set(${openocd_option_var}
"-f interface/ftdi/esp32_devkitj_v1.cfg -f target/${CONFIG_IDF_TARGET}.cfg" PARENT_SCOPE)
endif()
endfunction()

View File

@ -7,6 +7,8 @@
# remap the substituted paths back to the real paths in the filesystem.
function(__generate_prefix_map compile_options_var)
set(compile_options)
set(gdbinit_dir ${BUILD_DIR}/gdbinit)
set(gdbinit_path "${gdbinit_dir}/prefix_map")
idf_build_get_property(idf_path IDF_PATH)
idf_build_get_property(build_components BUILD_COMPONENTS)
@ -44,11 +46,15 @@ function(__generate_prefix_map compile_options_var)
list(APPEND compile_options "-fdebug-prefix-map=${compiler_sysroot}=/TOOLCHAIN")
string(APPEND gdbinit_file_lines "set substitute-path /TOOLCHAIN ${compiler_sysroot}\n")
# Write the final gdbinit file
set(gdbinit_path "${BUILD_DIR}/prefix_map_gdbinit")
file(WRITE "${gdbinit_path}" "${gdbinit_file_lines}")
file(WRITE "${BUILD_DIR}/prefix_map_gdbinit" "${gdbinit_file_lines}") # TODO IDF-11667
idf_build_set_property(DEBUG_PREFIX_MAP_GDBINIT "${gdbinit_path}")
else()
set(gdbinit_file_lines "# There is no prefix map defined for the project.\n")
endif()
# Write prefix_map_gdbinit file even it is empty.
file(MAKE_DIRECTORY ${gdbinit_dir})
file(WRITE "${gdbinit_path}" "${gdbinit_file_lines}")
idf_build_set_property(GDBINIT_FILES_PREFIX_MAP "${gdbinit_path}")
set(${compile_options_var} ${compile_options} PARENT_SCOPE)
endfunction()

View File

@ -353,6 +353,13 @@ function(__project_info test_components)
idf_build_get_property(COMPONENT_KCONFIGS_PROJBUILD KCONFIG_PROJBUILDS)
idf_build_get_property(debug_prefix_map_gdbinit DEBUG_PREFIX_MAP_GDBINIT)
__generate_gdbinit()
idf_build_get_property(gdbinit_files_prefix_map GDBINIT_FILES_PREFIX_MAP)
idf_build_get_property(gdbinit_files_symbols GDBINIT_FILES_SYMBOLS)
idf_build_get_property(gdbinit_files_py_extensions GDBINIT_FILES_PY_EXTENSIONS)
idf_build_get_property(gdbinit_files_connect GDBINIT_FILES_CONNECT)
__get_openocd_options(debug_arguments_openocd)
if(CONFIG_APP_BUILD_TYPE_RAM)
set(PROJECT_BUILD_TYPE ram_app)
else()

View File

@ -1,5 +1,5 @@
{
"version": "1.1",
"version": "1.2",
"project_name": "${PROJECT_NAME}",
"project_version": "${PROJECT_VER}",
"project_path": "${PROJECT_PATH}",
@ -29,5 +29,12 @@
"build_component_paths" : ${build_component_paths_json},
"build_component_info" : ${build_component_info_json},
"all_component_info" : ${all_component_info_json},
"debug_prefix_map_gdbinit": "${debug_prefix_map_gdbinit}"
"debug_prefix_map_gdbinit": "${debug_prefix_map_gdbinit}",
"gdbinit_files": {
"01_symbols": "${gdbinit_files_symbols}",
"02_prefix_map": "${gdbinit_files_prefix_map}",
"03_py_extensions": "${gdbinit_files_py_extensions}",
"04_connect": "${gdbinit_files_connect}"
},
"debug_arguments_openocd": "${debug_arguments_openocd}"
}

View File

@ -0,0 +1,9 @@
${rom_symbols}
# Load bootloader symbols
set confirm off
${add_bootloader_symbols}
set confirm on
# Load application symbols
file ${application_elf}

View File

@ -35,16 +35,3 @@ URL_TO_DOC = 'https://docs.espressif.com/projects/esp-idf'
SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2', 'esp32p4']
PREVIEW_TARGETS = ['linux', 'esp32c5', 'esp32c61', 'esp32h21']
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',
'esp32c6': '-f board/esp32c6-builtin.cfg',
'esp32c61': '-f board/esp32c61-builtin.cfg',
'esp32c5': '-f board/esp32c5-builtin.cfg',
'esp32h2': '-f board/esp32h2-builtin.cfg',
'esp32p4': '-f board/esp32p4-builtin.cfg',
}

View File

@ -4,12 +4,10 @@ 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
from typing import Dict
@ -21,8 +19,6 @@ from typing import Union
from click import INT
from click.core import Context
from esp_coredump import CoreDump
from idf_py_actions.constants import OPENOCD_TAGET_CONFIG
from idf_py_actions.constants import OPENOCD_TAGET_CONFIG_DEFAULT
from idf_py_actions.errors import FatalError
from idf_py_actions.serial_ext import BAUD_RATE
from idf_py_actions.serial_ext import PORT
@ -33,53 +29,6 @@ from idf_py_actions.tools import get_sdkconfig_value
from idf_py_actions.tools import PropertyDict
from idf_py_actions.tools import yellow_print
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()
set remotetimeout 10
target remote :3333
monitor reset halt
maintenance flush register-cache
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 chip_rev_to_int(chip_rev: Optional[str]) -> Union[int, None]:
# The chip rev will be derived from the elf file if none are returned.
@ -223,103 +172,6 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
return subprocess.run([gdb, '--batch-silent', '--ex', 'python import os'],
stderr=subprocess.DEVNULL).returncode == 0
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.' # noqa: E713
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']
r.append('define target hookpost-remote')
r.append('set confirm off')
# 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))
r.append('set confirm on')
r.append('end')
r.append('')
return '\n'.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 = '/'.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 = '/'.join([gdbinit_dir, 'py_extensions'])
with open(py_extensions, 'w') as f:
if is_gdb_with_python(gdb):
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 = '/'.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 = '/'.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')
for t in processes['threads_to_join']:
@ -375,8 +227,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
project_desc = get_project_desc(args, ctx)
if openocd_arguments is None:
# use default value if commands not defined in the environment nor command line
target = project_desc['target']
openocd_arguments = get_openocd_arguments(target)
openocd_arguments = project_desc.get('debug_arguments_openocd', '')
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))
@ -399,22 +250,54 @@ 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(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:
args.append('-ix={}'.format(debug_prefix_gdbinit))
return args
def get_gdb_args(project_desc: Dict[str, Any], gdb_x: Tuple, gdb_ex: Tuple, gdb_commands: Optional[str]) -> List[str]:
# debugger application name (xtensa-esp32-elf-gdb, riscv32-esp-elf-gdb, ...)
gdb_name = project_desc.get('monitor_toolprefix', '') + 'gdb'
gdb_args = [gdb_name]
gdbinit_files = project_desc.get('gdbinit_files')
if not gdbinit_files:
raise FatalError('Please check if the project was configured correctly ("gdbinit_files" not found in "project_description.json").')
gdbinit_files = sorted(gdbinit_files.items())
gdb_x_list = list(gdb_x)
gdb_x_names = [os.path.basename(x) for x in gdb_x_list]
# compile predefined gdbinit files options.
for name, path in gdbinit_files:
name = name[len('xx_'):]
if name == 'py_extensions':
if not is_gdb_with_python(gdb_name):
continue
# Replace predefined gdbinit with user's if passed with the same name.
if name in gdb_x_names:
gdb_x_index = gdb_x_names.index(name)
gdb_args.append(f'-x={gdb_x_list[gdb_x_index]}')
gdb_x_list.pop(gdb_x_index)
continue
if name == 'connect' and gdb_x_list: # TODO IDF-11692
continue
gdb_args.append(f'-x={path}')
# append user-defined gdbinit files
for x in gdb_x_list:
gdb_args.append(f'-x={x}')
# add user-defined commands
if gdb_ex:
for ex in gdb_ex:
gdb_args.append('-ex')
gdb_args.append(ex)
# add user defined options
if gdb_commands:
gdb_args += shlex.split(gdb_commands)
return gdb_args
def _get_gdbgui_version(ctx: Context) -> Tuple[int, ...]:
subprocess_success = False
try:
completed_process = subprocess.run(['gdbgui', '--version'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
subprocess_success = True
captured_output = completed_process.stdout.decode('utf-8', 'ignore')
subprocess_success = True
except FileNotFoundError:
# This is happening at least with Python 3.12 when gdbgui is not installed
subprocess_success = False
pass
if not subprocess_success or completed_process.returncode != 0:
if sys.version_info[:2] >= (3, 11) and sys.platform == 'win32':
@ -427,41 +310,41 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'Please use "idf.py gdb" or debug in Eclipse/Vscode instead.')
raise FatalError('Error starting gdbgui. Please make sure gdbgui has been installed with '
'"install.{sh,bat,ps1,fish} --enable-gdbgui" and can be started. '
f'Error: {captured_output}', ctx)
f'Error: {captured_output if subprocess_success else "Unknown"}', ctx)
v = re.search(r'(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?', captured_output)
if not v:
raise SystemExit(f'Error: "gdbgui --version" returned "{captured_output}"')
return tuple(int(i) if i else 0 for i in (v[1], v[2], v[3], v[4]))
def gdbui(action: str, ctx: Context, args: PropertyDict, gdbgui_port: Optional[str], gdbinit: Optional[str],
require_openocd: bool) -> None:
def gdbui(action: str, ctx: Context, args: PropertyDict, gdbgui_port: Optional[str], gdbinit: Tuple,
ex: Tuple, gdb_commands: Optional[str], require_openocd: bool) -> None:
"""
Asynchronous GDB-UI target
"""
project_desc = get_project_desc(args, ctx)
local_dir = project_desc['build_dir']
gdb = project_desc['monitor_toolprefix'] + 'gdb'
generate_gdbinit_files(gdb, gdbinit, project_desc)
gdbgui_version = _get_gdbgui_version(ctx)
gdb_args_list = get_gdb_args(project_desc)
gdb_args = get_gdb_args(project_desc, gdbinit, ex, gdb_commands)
if gdbgui_version >= (0, 14, 0, 0):
# See breaking changes https://github.com/cs01/gdbgui/blob/master/CHANGELOG.md#01400, especially the
# replacement of command line arguments.
gdb_args = ' '.join(gdb_args_list)
args = ['gdbgui', '-g', ' '.join((gdb, gdb_args))]
gdbgui_args = ['gdbgui', '-g', ' '.join(gdb_args)]
else:
# 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 = '"{}"'.format(' '.join(gdb_args_list)) if len(gdb_args_list) == 1 else ' '.join(gdb_args_list)
args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args]
gdb = gdb_args[0]
gdb_args_list = gdb_args[1:]
gdb_args_str = '"{}"'.format(' '.join(gdb_args_list)) if len(gdb_args_list) == 1 else ' '.join(gdb_args_list)
gdbgui_args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args_str]
if gdbgui_port is not None:
args += ['--port', gdbgui_port]
gdbgui_args += ['--port', gdbgui_port]
gdbgui_out_name = os.path.join(local_dir, GDBGUI_OUT_FILE)
gdbgui_out = open(gdbgui_out_name, 'w')
env = os.environ.copy()
@ -470,8 +353,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
# pygdbmi).
env['PURE_PYTHON'] = '1'
try:
print('Running: ', args)
process = subprocess.Popen(args, stdout=gdbgui_out, stderr=subprocess.STDOUT, bufsize=1, env=env)
print('Running: ', gdbgui_args)
process = subprocess.Popen(gdbgui_args, stdout=gdbgui_out, stderr=subprocess.STDOUT, bufsize=1, env=env)
except (OSError, subprocess.CalledProcessError) as e:
print(e)
raise FatalError('Error starting gdbgui', ctx)
@ -509,14 +392,14 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
if task.name in ('gdb', 'gdbgui', 'gdbtui'):
task.action_args['require_openocd'] = True
def gdbtui(action: str, ctx: Context, args: PropertyDict, gdbinit: str, require_openocd: bool) -> None:
def gdbtui(action: str, ctx: Context, args: PropertyDict, gdbinit: Tuple, ex: Tuple, gdb_commands: str, require_openocd: bool) -> None:
"""
Synchronous GDB target with text ui mode
"""
gdb(action, ctx, args, False, 1, gdbinit, require_openocd)
gdb(action, ctx, args, False, 1, gdbinit, ex, gdb_commands, require_openocd)
def gdb(action: str, ctx: Context, args: PropertyDict, batch: bool, 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: Tuple,
ex: Tuple, gdb_commands: Optional[str], require_openocd: bool) -> None:
"""
Synchronous GDB target
"""
@ -524,14 +407,12 @@ 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)
gdb = project_desc['monitor_toolprefix'] + 'gdb'
generate_gdbinit_files(gdb, gdbinit, project_desc)
args = [gdb, *get_gdb_args(project_desc)]
gdb_args = get_gdb_args(project_desc, gdbinit, ex, gdb_commands)
if gdb_tui is not None:
args += ['-tui']
gdb_args += ['-tui']
if batch:
args += ['--batch']
p = subprocess.Popen(args)
gdb_args += ['--batch']
p = subprocess.Popen(gdb_args)
processes['gdb'] = p
while True:
try:
@ -600,9 +481,23 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'default': False,
}
gdbinit = {
'names': ['--gdbinit'],
'names': ['--gdbinit', '--x', '-x'],
'help': 'Specify the name of gdbinit file to use\n',
'default': None,
'multiple': True,
}
ex = {
'names': ['--ex', '-ex'],
'help':
('Execute given GDB command.\n'),
'default': None,
'multiple': True,
}
gdb_commands = {
'names': ['--gdb-commands', '--gdb_commands'],
'help':
('Command line arguments for gdb.\n'),
'default': None,
}
debug_actions = {
'global_action_callbacks': [global_callback],
@ -642,7 +537,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'names': ['--gdb-tui', '--gdb_tui'],
'help': ('run gdb in TUI mode\n'),
'default': None,
}, gdbinit, fail_if_openocd_failed
}, gdbinit, ex, gdb_commands, fail_if_openocd_failed
],
'order_dependencies': ['all', 'flash'],
},
@ -656,14 +551,14 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
('The port on which gdbgui will be hosted. Default: 5000\n'),
'default':
None,
}, gdbinit, fail_if_openocd_failed
}, gdbinit, ex, gdb_commands, fail_if_openocd_failed
],
'order_dependencies': ['all', 'flash'],
},
'gdbtui': {
'callback': gdbtui,
'help': 'GDB TUI mode.',
'options': [gdbinit, fail_if_openocd_failed],
'options': [gdbinit, ex, gdb_commands, fail_if_openocd_failed],
'order_dependencies': ['all', 'flash'],
},
'coredump-info': {

View File

@ -1,21 +1,15 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os
import re
import subprocess
import sys
import pexpect
import pytest
from pytest_embedded_idf import IdfDut
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.jtag
@ -27,8 +21,12 @@ def test_idf_gdb(dut: IdfDut) -> None:
# Don't need to have output from UART anymore
dut.serial.stop_redirect_thread()
desc_path = os.path.join(dut.app.binary_path, 'project_description.json')
with open(desc_path, 'r') as f:
project_desc = json.load(f)
with open(os.path.join(dut.logdir, 'ocd.txt'), 'w') as ocd_log:
cmd = ['openocd', *get_openocd_arguments(dut.target).split()]
cmd = ['openocd'] + project_desc['debug_arguments_openocd'].split()
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if openocd_scripts:
cmd.extend(['-s', openocd_scripts])
@ -37,18 +35,14 @@ def test_idf_gdb(dut: IdfDut) -> None:
ocd = subprocess.Popen(cmd, stdout=ocd_log, stderr=ocd_log)
try:
gdb_env = os.environ.copy()
gdb_env['ESP_IDF_GDB_TESTING'] = '1'
with open(os.path.join(dut.logdir, 'gdb.txt'), '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(re.compile(r'add symbol table from file.*rom.elf')) # if fail here: add target support here https://github.com/espressif/esp-rom-elfs
p.expect_exact('hit Temporary breakpoint 1, app_main ()')
finally:
ocd.terminate()

View File

@ -13,9 +13,7 @@ from unittest import main
from unittest import mock
from unittest import TestCase
import elftools.common.utils as ecu
import jsonschema
from elftools.elf.elffile import ELFFile
try:
from StringIO import StringIO
@ -265,48 +263,6 @@ class TestHelpOutput(TestWithoutExtensions):
self.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'])
class TestFileArgumentExpansion(TestCase):
def test_file_expansion(self):
"""Test @filename expansion functionality"""