Skip to content
Closed
80 changes: 80 additions & 0 deletions misc/roms.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
Comment thread
Jason2866 marked this conversation as resolved.
182 changes: 167 additions & 15 deletions platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -849,34 +851,37 @@ 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

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",
"executable": "bin/openocd",
"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"),
}
Expand All @@ -887,14 +892,134 @@ 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
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@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 []
Comment thread
Jason2866 marked this conversation as resolved.

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",
])
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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('<I', chunk.encode('utf-8').ljust(4, b'\x00'))[0])
parts.append(f"(*(int*) {hex(date_addr + i)}) == {value}")
return "if " + " && ".join(parts)

@classmethod
def _build_rom_elf_conditions(
cls, entries: list, mcu: str, rom_dir: str, depth: int
) -> 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"):
return link
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":
Expand All @@ -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", [])

Expand Down Expand Up @@ -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)