From 6019f9689af1edb27486bcfffcea3b1655456584 Mon Sep 17 00:00:00 2001 From: Alexey Lapshin Date: Wed, 18 Feb 2026 12:16:19 +0700 Subject: [PATCH] feat(esp_gdbstub): support watchpoint trigger reason --- .../private_include/esp_gdbstub_common.h | 9 +++++ components/esp_gdbstub/src/gdbstub.c | 36 +++++++++++++++++++ .../src/port/riscv/gdbstub_riscv.c | 16 +++++++++ .../src/port/xtensa/gdbstub_xtensa.c | 17 +++++++++ .../gdbstub_runtime/pytest_gdbstub_runtime.py | 9 ++--- 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/components/esp_gdbstub/private_include/esp_gdbstub_common.h b/components/esp_gdbstub/private_include/esp_gdbstub_common.h index 04abcf9b97..4d36c407d8 100644 --- a/components/esp_gdbstub/private_include/esp_gdbstub_common.h +++ b/components/esp_gdbstub/private_include/esp_gdbstub_common.h @@ -169,6 +169,15 @@ void esp_gdbstub_clear_step(void); void esp_gdbstub_do_step(esp_gdbstub_frame_t *regs_frame); void esp_gdbstub_trigger_cpu(void); +#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME +/** + * Check if a watchpoint triggered the current debug exception. + * @param[out] addr Address of the triggered watchpoint (only valid if return is true) + * @return true if a watchpoint triggered, false otherwise (breakpoint/step/other) + */ +bool esp_gdbstub_get_watchpoint_trigger_addr(uint32_t *addr); +#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME + /** * Write a value to register in frame * @param frame gdbstub frame diff --git a/components/esp_gdbstub/src/gdbstub.c b/components/esp_gdbstub/src/gdbstub.c index 2b1fda16ae..fc94d34f9e 100644 --- a/components/esp_gdbstub/src/gdbstub.c +++ b/components/esp_gdbstub/src/gdbstub.c @@ -28,6 +28,10 @@ #define GDBSTUB_QXFER_SUPPORTED_STR "" #endif +#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME +static void send_watchpoint_reason(void); +#endif + #ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS static inline int gdb_tid_to_task_index(int tid); static inline int task_index_to_gdb_tid(int tid); @@ -107,6 +111,9 @@ static void send_reason(void) esp_gdbstub_send_start(); esp_gdbstub_send_char('T'); esp_gdbstub_send_hex(s_scratch.signal, 8); +#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME + send_watchpoint_reason(); +#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME esp_gdbstub_send_end(); } @@ -212,6 +219,35 @@ static bool not_send_reason = false; static bool process_gdb_kill = false; static bool gdb_debug_int = false; +/** + * Detect if a watchpoint triggered and append the corresponding + * GDB RSP stop-reply field (watch/rwatch/awatch) to the current packet. + */ +static void send_watchpoint_reason(void) +{ + uint32_t wp_addr = 0; + if (!esp_gdbstub_get_watchpoint_trigger_addr(&wp_addr)) { + return; + } + + const char *type_str = "watch"; + for (size_t i = 0; i < SOC_CPU_WATCHPOINTS_NUM; i++) { + if (wp_list[i] == wp_addr) { + if (wp_access[i] == ESP_CPU_WATCHPOINT_LOAD) { + type_str = "rwatch"; + } else if (wp_access[i] == ESP_CPU_WATCHPOINT_ACCESS) { + type_str = "awatch"; + } + break; + } + } + + esp_gdbstub_send_str(type_str); + esp_gdbstub_send_char(':'); + esp_gdbstub_send_hex(wp_addr, 32); + esp_gdbstub_send_char(';'); +} + /** * @brief Handle UART interrupt * diff --git a/components/esp_gdbstub/src/port/riscv/gdbstub_riscv.c b/components/esp_gdbstub/src/port/riscv/gdbstub_riscv.c index 0c7c5e4284..25b518787e 100644 --- a/components/esp_gdbstub/src/port/riscv/gdbstub_riscv.c +++ b/components/esp_gdbstub/src/port/riscv/gdbstub_riscv.c @@ -250,6 +250,22 @@ void esp_gdbstub_init_dports(void) { } +bool esp_gdbstub_get_watchpoint_trigger_addr(uint32_t *addr) +{ + for (int i = 0; i < SOC_CPU_BREAKPOINTS_NUM; i++) { + RV_WRITE_CSR(tselect, i); + uint32_t tdata1 = RV_READ_CSR(tdata1); + bool is_load = tdata1 & TDATA1_LOAD; + bool is_store = tdata1 & TDATA1_STORE; + bool is_exec = tdata1 & TDATA1_EXECUTE; + if (!is_exec && (is_load || is_store)) { + *addr = RV_READ_CSR(tdata2); + return true; + } + } + return false; +} + #endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME #if (!CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME diff --git a/components/esp_gdbstub/src/port/xtensa/gdbstub_xtensa.c b/components/esp_gdbstub/src/port/xtensa/gdbstub_xtensa.c index f62886c7de..c813a39a6e 100644 --- a/components/esp_gdbstub/src/port/xtensa/gdbstub_xtensa.c +++ b/components/esp_gdbstub/src/port/xtensa/gdbstub_xtensa.c @@ -307,6 +307,23 @@ void esp_gdbstub_init_dports(void) { } +#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME +bool esp_gdbstub_get_watchpoint_trigger_addr(uint32_t *addr) +{ + uint32_t debugcause; + RSR(XT_REG_DEBUGCAUSE, debugcause); + if (debugcause & XCHAL_DEBUGCAUSE_DBREAK_MASK) { + if (debugcause & (1 << 8)) { + RSR(XT_REG_DBREAKA_1, *addr); + } else { + RSR(XT_REG_DBREAKA_0, *addr); + } + return true; + } + return false; +} +#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME + #if CONFIG_IDF_TARGET_ARCH_XTENSA && (!CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME static bool stall_started = false; #endif diff --git a/tools/test_apps/system/gdbstub_runtime/pytest_gdbstub_runtime.py b/tools/test_apps/system/gdbstub_runtime/pytest_gdbstub_runtime.py index 6204059f93..9a0ec2e0ca 100644 --- a/tools/test_apps/system/gdbstub_runtime/pytest_gdbstub_runtime.py +++ b/tools/test_apps/system/gdbstub_runtime/pytest_gdbstub_runtime.py @@ -368,14 +368,11 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None: assert dut.find_gdb_response('done', 'result', responses) is not None cmd = '-exec-continue' payload = run_and_break(dut, cmd) - assert payload['reason'] == 'signal-received' + assert payload['reason'] == 'watchpoint-trigger' + assert int(payload['value']['new']) == int(payload['value']['old']) + 2 assert payload['frame']['func'] == 'foo' + assert payload['frame']['line'] == str(get_line_number('var_2--;')) assert payload['stopped-threads'] == 'all' - # Uncomment this when implement send reason to gdb: GCC-313 - # - # assert payload['reason'] == 'watchpoint-trigger' - # assert int(payload['value']['new']) == int(payload['value']['old']) + 1 - # assert payload['frame']['line'] == '14' cmd = '-break-delete 2' responses = dut.gdb_write(cmd)