From 48a49c815411a9f829ebe4a5f68166069a21f48c Mon Sep 17 00:00:00 2001 From: Alexey Lapshin Date: Wed, 6 Nov 2024 17:01:33 +0700 Subject: [PATCH] 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 --- .gitlab/ci/host-test.yml | 11 + components/esp_rom/gen_gdbinit.py | 70 +++++ components/esp_rom/test_esp_rom.py | 54 ++++ docs/en/api-guides/jtag-debugging/index.rst | 2 +- .../jtag-debugging/using-debugger.rst | 88 +++--- docs/en/api-guides/reproducible-builds.rst | 6 +- docs/zh_CN/api-guides/reproducible-builds.rst | 4 +- tools/ci/idf_ci/uploader.py | 1 + tools/cmake/gdbinit.cmake | 79 ++++++ tools/cmake/idf.cmake | 2 + tools/cmake/openocd.cmake | 14 + tools/cmake/prefix_map.cmake | 12 +- tools/cmake/project.cmake | 7 + tools/cmake/project_description.json.in | 11 +- tools/cmake/symbols.gdbinit.in | 9 + tools/idf_py_actions/constants.py | 13 - tools/idf_py_actions/debug_ext.py | 265 ++++++------------ tools/test_apps/system/gdb/pytest_gdb.py | 22 +- tools/test_idf_py/test_idf_py.py | 44 --- 19 files changed, 394 insertions(+), 320 deletions(-) create mode 100644 components/esp_rom/gen_gdbinit.py create mode 100644 components/esp_rom/test_esp_rom.py create mode 100644 tools/cmake/gdbinit.cmake create mode 100644 tools/cmake/openocd.cmake create mode 100644 tools/cmake/symbols.gdbinit.in diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index e1dc5f59a4..70e979de9f 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -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 diff --git a/components/esp_rom/gen_gdbinit.py b/components/esp_rom/gen_gdbinit.py new file mode 100644 index 0000000000..be08121d89 --- /dev/null +++ b/components/esp_rom/gen_gdbinit.py @@ -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) diff --git a/components/esp_rom/test_esp_rom.py b/components/esp_rom/test_esp_rom.py new file mode 100644 index 0000000000..c59ec40f45 --- /dev/null +++ b/components/esp_rom/test_esp_rom.py @@ -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'] diff --git a/docs/en/api-guides/jtag-debugging/index.rst b/docs/en/api-guides/jtag-debugging/index.rst index f27904209b..631a81f362 100644 --- a/docs/en/api-guides/jtag-debugging/index.rst +++ b/docs/en/api-guides/jtag-debugging/index.rst @@ -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 ` 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 ` 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 diff --git a/docs/en/api-guides/jtag-debugging/using-debugger.rst b/docs/en/api-guides/jtag-debugging/using-debugger.rst index 45b41b94e7..e7f518239c 100644 --- a/docs/en/api-guides/jtag-debugging/using-debugger.rst +++ b/docs/en/api-guides/jtag-debugging/using-debugger.rst @@ -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 - 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: - . - Find the GDB manual and other documentation resources online at: - . - 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`` diff --git a/docs/en/api-guides/reproducible-builds.rst b/docs/en/api-guides/reproducible-builds.rst index 93ef078732..ac5be352f7 100644 --- a/docs/en/api-guides/reproducible-builds.rst +++ b/docs/en/api-guides/reproducible-builds.rst @@ -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 ` and :ref:`bootloader metadata structure ` 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 ` 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 ` 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 ---------------------------------------------- diff --git a/docs/zh_CN/api-guides/reproducible-builds.rst b/docs/zh_CN/api-guides/reproducible-builds.rst index 74d568b0e4..ccdb2ff66c 100644 --- a/docs/zh_CN/api-guides/reproducible-builds.rst +++ b/docs/zh_CN/api-guides/reproducible-builds.rst @@ -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 ` 开始调试时,此额外的 ``gdbinit`` 文件会自动传递给 GDB。当手动启动 GDB 或从 IDE 启动 GDB 时,请使用 ``-x build/prefix_map_gdbinit`` 参数将此额外的 ``gdbinit`` 脚本传递给 GDB。 +当使用 :ref:`idf.py gdb ` 开始调试时,此额外的 ``gdbinit`` 文件会自动传递给 GDB。当手动启动 GDB 或从 IDE 启动 GDB 时,请使用 ``-x build/gdbinit/prefix_map`` 参数将此额外的 ``gdbinit`` 脚本传递给 GDB。 仍可能影响可重复构建的因素 -------------------------- diff --git a/tools/ci/idf_ci/uploader.py b/tools/ci/idf_ci/uploader.py index 8ecff4ccbc..f04028b1e6 100644 --- a/tools/ci/idf_ci/uploader.py +++ b/tools/ci/idf_ci/uploader.py @@ -47,6 +47,7 @@ class AppUploader(AppDownloader): 'bootloader/*.elf', '*.map', '*.elf', + 'gdbinit/*', ], ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES: [ '*.bin', diff --git a/tools/cmake/gdbinit.cmake b/tools/cmake/gdbinit.cmake new file mode 100644 index 0000000000..d4b8775631 --- /dev/null +++ b/tools/cmake/gdbinit.cmake @@ -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() diff --git a/tools/cmake/idf.cmake b/tools/cmake/idf.cmake index 28c2b1d060..384401cd76 100644 --- a/tools/cmake/idf.cmake +++ b/tools/cmake/idf.cmake @@ -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}") diff --git a/tools/cmake/openocd.cmake b/tools/cmake/openocd.cmake new file mode 100644 index 0000000000..a1bd546bc1 --- /dev/null +++ b/tools/cmake/openocd.cmake @@ -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() diff --git a/tools/cmake/prefix_map.cmake b/tools/cmake/prefix_map.cmake index 6373b6b087..2e3a98f2a5 100644 --- a/tools/cmake/prefix_map.cmake +++ b/tools/cmake/prefix_map.cmake @@ -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() diff --git a/tools/cmake/project.cmake b/tools/cmake/project.cmake index 7527f19bd7..fe126c8bdd 100644 --- a/tools/cmake/project.cmake +++ b/tools/cmake/project.cmake @@ -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() diff --git a/tools/cmake/project_description.json.in b/tools/cmake/project_description.json.in index 1805db5339..5940be1ba3 100644 --- a/tools/cmake/project_description.json.in +++ b/tools/cmake/project_description.json.in @@ -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}" } diff --git a/tools/cmake/symbols.gdbinit.in b/tools/cmake/symbols.gdbinit.in new file mode 100644 index 0000000000..d94cbf5e52 --- /dev/null +++ b/tools/cmake/symbols.gdbinit.in @@ -0,0 +1,9 @@ +${rom_symbols} + +# Load bootloader symbols +set confirm off + ${add_bootloader_symbols} +set confirm on + +# Load application symbols +file ${application_elf} diff --git a/tools/idf_py_actions/constants.py b/tools/idf_py_actions/constants.py index a354e06f0a..7106b60d99 100644 --- a/tools/idf_py_actions/constants.py +++ b/tools/idf_py_actions/constants.py @@ -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', -} diff --git a/tools/idf_py_actions/debug_ext.py b/tools/idf_py_actions/debug_ext.py index 02d4f4f956..fd56665efa 100644 --- a/tools/idf_py_actions/debug_ext.py +++ b/tools/idf_py_actions/debug_ext.py @@ -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': { diff --git a/tools/test_apps/system/gdb/pytest_gdb.py b/tools/test_apps/system/gdb/pytest_gdb.py index 73242b1320..dbac6c8c70 100644 --- a/tools/test_apps/system/gdb/pytest_gdb.py +++ b/tools/test_apps/system/gdb/pytest_gdb.py @@ -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() diff --git a/tools/test_idf_py/test_idf_py.py b/tools/test_idf_py/test_idf_py.py index 9daac230c3..4e5826cc0f 100755 --- a/tools/test_idf_py/test_idf_py.py +++ b/tools/test_idf_py/test_idf_py.py @@ -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"""