diff --git a/misc/roms.json b/misc/roms.json new file mode 100644 index 000000000..23209f74e --- /dev/null +++ b/misc/roms.json @@ -0,0 +1,80 @@ +{ + "esp32": [ + { + "rev": 0, + "build_date_str_addr": "0x3ff9ea80", + "build_date_str": "Jun 8 2016" + }, + { + "rev": 300, + "build_date_str_addr": "0x3ff9e986", + "build_date_str": "Jul 29 2019" + } + ], + "esp32s2": [ + { + "rev": 0, + "build_date_str_addr": "0x3ffaf34b", + "build_date_str": "Oct 25 2019" + } + ], + "esp32s3": [ + { + "rev": 0, + "build_date_str_addr": "0x3ff194ad", + "build_date_str": "Mar 1 2021" + } + ], + "esp32c2": [ + { + "rev": 100, + "build_date_str_addr": "0x3ff47874", + "build_date_str": "Jan 27 2022" + } + ], + "esp32c3": [ + { + "rev": 0, + "build_date_str_addr": "0x3ff1b878", + "build_date_str": "Sep 18 2020" + }, + { + "rev": 3, + "build_date_str_addr": "0x3ff1a374", + "build_date_str": "Feb 7 2021" + }, + { + "rev": 101, + "build_date_str_addr": "0x3ff1a3dc", + "build_date_str": "Mar 1 2023" + } + ], + "esp32c5": [ + { + "rev": 0, + "build_date_str_addr": "0x4004b3c4", + "build_date_str": "Mar 29 2024" + } + ], + "esp32c6": [ + { + "rev": 0, + "build_date_str_addr": "0x4004a798", + "build_date_str": "Sep 19 2022" + } + ], + "esp32h2": [ + { + "rev": 0, + "build_date_str_addr": "0x4001c7dc", + "build_date_str": "Nov 1 2022" + } + ], + "esp32p4": [ + { + "rev": 0, + "build_date_str_addr": "0x4fc1d1ac", + "build_date_str": "Aug 11 2023" + } + ] +} diff --git a/platform.py b/platform.py index 234415bd0..6cb37113a 100644 --- a/platform.py +++ b/platform.py @@ -48,6 +48,7 @@ import os import requests import shutil +import struct import subprocess from pathlib import Path from typing import Optional, Dict, List, Any, Union @@ -649,6 +650,7 @@ def _configure_mcu_toolchains( for debug_tool in mcu_config["debug_tools"]: self.install_tool(debug_tool) self.install_tool("tool-openocd-esp32") + self.install_tool("tool-esp-rom-elfs") def _configure_installer(self) -> None: """Configure the ESP-IDF tools installer with proper version checking.""" @@ -849,7 +851,6 @@ def _add_dynamic_options(self, board): if "tools" not in debug: debug["tools"] = {} - # Debug tool configuration for link in upload_protocols: if link in non_debug_protocols or link in debug["tools"]: continue @@ -857,6 +858,22 @@ def _add_dynamic_options(self, board): openocd_interface = self._get_openocd_interface(link, board) server_args = self._get_debug_server_args(openocd_interface, debug) + init_cmds = [ + "define pio_reset_halt_target", + " monitor reset halt", + " maintenance flush register-cache", + "end", + "define pio_reset_run_target", + " monitor reset", + "end", + ] + init_cmds.extend([ + "target extended-remote $DEBUG_PORT", + "$LOAD_CMDS", + "pio_reset_halt_target", + "$INIT_BREAK", + ]) + debug["tools"][link] = { "server": { "package": "tool-openocd-esp32", @@ -864,19 +881,7 @@ def _add_dynamic_options(self, board): "arguments": server_args, }, "init_break": "thb app_main", - "init_cmds": [ - "define pio_reset_halt_target", - " monitor reset halt", - " flushregs", - "end", - "define pio_reset_run_target", - " monitor reset", - "end", - "target extended-remote $DEBUG_PORT", - "$LOAD_CMDS", - "pio_reset_halt_target", - "$INIT_BREAK", - ], + "init_cmds": init_cmds, "onboard": link in debug.get("onboard_tools", []), "default": link == debug.get("default_tool"), } @@ -887,6 +892,126 @@ def _add_dynamic_options(self, board): board.manifest["debug"] = debug return board + def _gdb_has_python(self, mcu: str) -> bool: + """Probe whether the GDB binary for this MCU supports Python.""" + mcu_config = self._get_mcu_config(mcu) + if not mcu_config: + return False + for tool_pkg in mcu_config["debug_tools"]: + pkg_dir = self.get_package_dir(tool_pkg) + if not pkg_dir: + continue + toolchain_arch = "xtensa-esp-elf" if mcu in MCU_TOOLCHAIN_CONFIG["xtensa"]["mcus"] else "riscv32-esp-elf" + candidates = [Path(pkg_dir) / "bin" / f"{toolchain_arch}-gdb"] + if IS_WINDOWS: + candidates.insert(0, Path(pkg_dir) / "bin" / f"{toolchain_arch}-gdb.exe") + gdb_path = next((path for path in candidates if path.is_file()), None) + if not gdb_path: + continue + try: + result = subprocess.run( + [str(gdb_path), "--batch-silent", "--ex", "python import os"], + capture_output=True, timeout=10, + ) + return result.returncode == 0 + except (OSError, subprocess.TimeoutExpired): + logger.debug("GDB Python support probe failed for %s", gdb_path) + return False + return False + + @staticmethod + def _get_freertos_gdb_cmds() -> List[str]: + """Generate GDB commands to load FreeRTOS thread-awareness extension.""" + return [ + "python", + "try:", + " import freertos_gdb", + "except ModuleNotFoundError:", + " print('warning: python extension \"freertos_gdb\" not found.')", + "end", + ] + + def _get_rom_elf_gdb_cmds(self, mcu: str) -> List[str]: + """Generate GDB commands for automatic ROM ELF symbol loading. + + Produces a 'target hookpost-extended-remote' GDB hook that reads the ROM + build-date string from a chip-specific memory address after connecting + and loads the matching ROM ELF symbol file. + """ + rom_elfs_dir = self.get_package_dir("tool-esp-rom-elfs") + if not rom_elfs_dir or not Path(rom_elfs_dir).is_dir(): + return [] + + roms_json = Path(self.get_dir()) / "misc" / "roms.json" + if not roms_json.is_file(): + return [] + + try: + with open(roms_json, encoding="utf-8") as f: + roms = json.load(f) + except (json.JSONDecodeError, OSError): + return [] + + if mcu not in roms: + return [] + + rom_elfs_path = to_unix_path(str(Path(rom_elfs_dir).resolve())) + if not rom_elfs_path.endswith("/"): + rom_elfs_path += "/" + + entries = roms[mcu] + cmds = [ + "define target hookpost-extended-remote", + "set confirm off", + ] + cmds.extend( + self._build_rom_elf_conditions(entries, mcu, rom_elfs_path, depth=1) + ) + cmds.extend([ + "set confirm on", + "end", + ]) + return cmds + + @staticmethod + def _rom_date_condition(date_addr: int, date_str: str) -> str: + """Build a GDB if-condition comparing memory words to a date string.""" + parts = [] + for i in range(0, len(date_str), 4): + chunk = date_str[i:i + 4] + value = hex(struct.unpack(' List[str]: + """Recursively build nested if/else/end blocks for ROM revision matching.""" + if not entries: + return [] + indent = " " * depth + entry = entries[0] + addr = int(entry["build_date_str_addr"], 16) + rom_file = f"{mcu}_rev{entry['rev']}_rom.elf" + rom_path = f"{rom_dir}{rom_file}" + lines = [ + f"{indent}{cls._rom_date_condition(addr, entry['build_date_str'])}", + f'{indent} add-symbol-file "{rom_path}"', + ] + if len(entries) > 1: + lines.append(f"{indent}else") + lines.extend( + cls._build_rom_elf_conditions(entries[1:], mcu, rom_dir, depth + 1) + ) + else: + lines.append(f"{indent}else") + lines.append( + f"{indent} echo Warning: Unknown {mcu} ROM revision.\\n" + ) + lines.append(f"{indent}end") + return lines + def _get_openocd_interface(self, link: str, board) -> str: """Determine OpenOCD interface configuration for debug link.""" if link in ("jlink", "cmsis-dap"): @@ -894,7 +1019,7 @@ def _get_openocd_interface(self, link: str, board) -> str: if link in ("esp-prog", "ftdi"): if board.id == "esp32-s2-kaluga-1": return "ftdi/esp32s2_kaluga_v1" - return "ftdi/esp32_devkitj_v1" + return "ftdi/esp_ftdi" if link == "esp-bridge": return "esp_usb_bridge" if link == "esp-builtin": @@ -917,6 +1042,8 @@ def _get_debug_server_args(self, openocd_interface: str, debug: Dict) -> List[st def configure_debug_session(self, debug_config): """Configure debug session with flash image loading.""" + self._inject_debug_extensions(debug_config) + build_extra_data = debug_config.build_data.get("extra", {}) flash_images = build_extra_data.get("flash_images", []) @@ -960,3 +1087,28 @@ def configure_debug_session(self, debug_config): f'{app_offset} verify' ) debug_config.load_cmds = load_cmds + + def _inject_debug_extensions(self, debug_config): + """Inject FreeRTOS thread-awareness and ROM ELF commands into init_cmds. + + Called from configure_debug_session() so toolchain packages are + guaranteed to be installed when the probes run. + """ + mcu = debug_config.board_config.get("build.mcu", "") + if not mcu: + return + tool_init_cmds = debug_config.tool_settings.get("init_cmds") + if tool_init_cmds is None: + return + # Find insertion point: just before "target extended-remote" + insert_idx = next( + (i for i, cmd in enumerate(tool_init_cmds) + if "target extended-remote" in cmd), + len(tool_init_cmds), + ) + extra_cmds = [] + if self._gdb_has_python(mcu): + extra_cmds.extend(self._get_freertos_gdb_cmds()) + extra_cmds.extend(self._get_rom_elf_gdb_cmds(mcu)) + for i, cmd in enumerate(extra_cmds): + tool_init_cmds.insert(insert_idx + i, cmd)