Просмотр исходного кода

Merge branch 'bugfix/interrupted_thread_gdb_bt' into 'master'

riscv: Fixes GDB backtrace of interrupted threads

See merge request espressif/esp-idf!16939
Ivan Grokhotkov 4 лет назад
Родитель
Сommit
4a553510b7

+ 1 - 3
components/app_trace/port/riscv/port.c

@@ -27,8 +27,6 @@ typedef struct {
     esp_apptrace_mem_block_t *  mem_blocks;
 } esp_apptrace_riscv_ctrl_block_t;
 
-#define RISCV_APPTRACE_SYSNR    0x64
-
 #define ESP_APPTRACE_RISCV_BLOCK_LEN_MSK         0x7FFFUL
 #define ESP_APPTRACE_RISCV_BLOCK_LEN(_l_)        ((_l_) & ESP_APPTRACE_RISCV_BLOCK_LEN_MSK)
 #define ESP_APPTRACE_RISCV_BLOCK_LEN_GET(_v_)    ((_v_) & ESP_APPTRACE_RISCV_BLOCK_LEN_MSK)
@@ -104,7 +102,7 @@ __attribute__((weak)) int esp_apptrace_advertise_ctrl_block(void *ctrl_block_add
     if (!esp_cpu_in_ocd_debug_mode()) {
         return 0;
     }
-    return (int) semihosting_call_noerrno(RISCV_APPTRACE_SYSNR, (long*)ctrl_block_addr);
+    return (int) semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_APPTRACE_INIT, (long*)ctrl_block_addr);
 }
 
 /* Returns up buffers config.

+ 1 - 2
components/esp_system/port/arch/riscv/debug_stubs.c

@@ -14,7 +14,6 @@
 
 const static char *TAG = "esp_dbg_stubs";
 
-#define RISCV_DBG_STUBS_SYSNR    0x65
 
 /* Advertises apptrace control block address to host */
 static int esp_dbg_stubs_advertise_table(void *stub_table_addr)
@@ -22,7 +21,7 @@ static int esp_dbg_stubs_advertise_table(void *stub_table_addr)
     if (!esp_cpu_in_ocd_debug_mode()) {
         return 0;
     }
-    return (int) semihosting_call_noerrno(RISCV_DBG_STUBS_SYSNR, (long*)stub_table_addr);
+    return (int) semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_DBG_STUBS_INIT, (long*)stub_table_addr);
 }
 
 void esp_dbg_stubs_ll_init(void *stub_table_addr)

+ 16 - 2
components/freertos/FreeRTOS-Kernel/portable/riscv/port.c

@@ -115,7 +115,7 @@ void vPortEndScheduler(void)
 
 // ------------------------ Stack --------------------------
 
-static void prvTaskExitError(void)
+__attribute__((noreturn)) static void _prvTaskExitError(void)
 {
     /* A function that implements a task must not exit or attempt to return to
     its caller as there is nothing to return to.  If a task wants to exit it
@@ -128,6 +128,18 @@ static void prvTaskExitError(void)
     abort();
 }
 
+__attribute__((naked)) static void prvTaskExitError(void)
+{
+    asm volatile(".option push\n" \
+                ".option norvc\n" \
+                "nop\n" \
+                ".option pop");
+    /* Task entry's RA will point here. Shifting RA into prvTaskExitError is necessary
+       to make GDB backtrace ending inside that function.
+       Otherwise backtrace will end in the function laying just before prvTaskExitError in address space. */
+    _prvTaskExitError();
+}
+
 StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
 {
     extern uint32_t __global_pointer$;
@@ -190,7 +202,9 @@ StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxC
     sp -= RV_STK_FRMSZ;
     RvExcFrame *frame = (RvExcFrame *)sp;
     memset(frame, 0, sizeof(*frame));
-    frame->ra = (UBaseType_t)prvTaskExitError;
+    /* Shifting RA into prvTaskExitError is necessary to make GDB backtrace ending inside that function.
+       Otherwise backtrace will end in the function laying just before prvTaskExitError in address space. */
+    frame->ra = (UBaseType_t)prvTaskExitError + 4/*size of the nop insruction at the beginning of prvTaskExitError*/;
     frame->mepc = (UBaseType_t)pxCode;
     frame->a0 = (UBaseType_t)pvParameters;
     frame->gp = (UBaseType_t)&__global_pointer$;

+ 49 - 5
components/hal/esp32c2/include/hal/cpu_ll.h

@@ -14,6 +14,7 @@
 #include "soc/assist_debug_reg.h"
 #include "esp_attr.h"
 #include "riscv/csr.h"
+#include "riscv/semihosting.h"
 
 /*performance counter*/
 #define CSR_PCER_MACHINE    0x7e0
@@ -71,8 +72,29 @@ static inline void cpu_ll_init_hwloop(void)
     // Nothing needed here for ESP32-C3
 }
 
+FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
+{
+    return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
+}
+
 static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* If we want to set breakpoint which when hit transfers control to debugger
+         * we need to set `action` in `mcontrol` to 1 (Enter Debug Mode).
+         * That `action` value is supported only when `dmode` of `tdata1` is set.
+         * But `dmode` can be modified by debugger only (from Debug Mode).
+         *
+         * So when debugger is connected we use special syscall to ask it to set breakpoint for us.
+         */
+        long args[] = {true, id, (long)pc};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
+        if (ret == 0) {
+            return;
+        }
+    }
+    /* The code bellow sets breakpoint which will trigger `Breakpoint` exception
+     * instead transfering control to debugger. */
     RV_WRITE_CSR(tselect,id);
     RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@@ -82,6 +104,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
 
 static inline void cpu_ll_clear_breakpoint(int id)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {false, id};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
+        if (ret == 0){
+            return;
+        }
+    }
     RV_WRITE_CSR(tselect,id);
     RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@@ -105,6 +135,17 @@ static inline void cpu_ll_set_watchpoint(int id,
                                         bool on_write)
 {
     uint32_t addr_napot;
+
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {true, id, (long)addr, (long)size,
+            (long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
+        if (ret == 0) {
+            return;
+        }
+    }
+
     RV_WRITE_CSR(tselect,id);
     RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
     RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@@ -123,6 +164,14 @@ static inline void cpu_ll_set_watchpoint(int id,
 
 static inline void cpu_ll_clear_watchpoint(int id)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {false, id};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
+        if (ret == 0){
+            return;
+        }
+    }
     RV_WRITE_CSR(tselect,id);
     RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@@ -132,11 +181,6 @@ static inline void cpu_ll_clear_watchpoint(int id)
     return;
 }
 
-FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
-{
-    return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
-}
-
 static inline void cpu_ll_break(void)
 {
     asm volatile("ebreak\n");

+ 49 - 5
components/hal/esp32c3/include/hal/cpu_ll.h

@@ -14,6 +14,7 @@
 #include "soc/assist_debug_reg.h"
 #include "esp_attr.h"
 #include "riscv/csr.h"
+#include "riscv/semihosting.h"
 
 /*performance counter*/
 #define CSR_PCER_MACHINE    0x7e0
@@ -71,8 +72,29 @@ static inline void cpu_ll_init_hwloop(void)
     // Nothing needed here for ESP32-C3
 }
 
+FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
+{
+    return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
+}
+
 static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* If we want to set breakpoint which when hit transfers control to debugger
+         * we need to set `action` in `mcontrol` to 1 (Enter Debug Mode).
+         * That `action` value is supported only when `dmode` of `tdata1` is set.
+         * But `dmode` can be modified by debugger only (from Debug Mode).
+         *
+         * So when debugger is connected we use special syscall to ask it to set breakpoint for us.
+         */
+        long args[] = {true, id, (long)pc};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
+        if (ret == 0) {
+            return;
+        }
+    }
+    /* The code bellow sets breakpoint which will trigger `Breakpoint` exception
+     * instead transfering control to debugger. */
     RV_WRITE_CSR(tselect,id);
     RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@@ -82,6 +104,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
 
 static inline void cpu_ll_clear_breakpoint(int id)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {false, id};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
+        if (ret == 0){
+            return;
+        }
+    }
     RV_WRITE_CSR(tselect,id);
     RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@@ -105,6 +135,17 @@ static inline void cpu_ll_set_watchpoint(int id,
                                         bool on_write)
 {
     uint32_t addr_napot;
+
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {true, id, (long)addr, (long)size,
+            (long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
+        if (ret == 0) {
+            return;
+        }
+    }
+
     RV_WRITE_CSR(tselect,id);
     RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
     RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@@ -123,6 +164,14 @@ static inline void cpu_ll_set_watchpoint(int id,
 
 static inline void cpu_ll_clear_watchpoint(int id)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {false, id};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
+        if (ret == 0){
+            return;
+        }
+    }
     RV_WRITE_CSR(tselect,id);
     RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@@ -132,11 +181,6 @@ static inline void cpu_ll_clear_watchpoint(int id)
     return;
 }
 
-FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
-{
-    return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
-}
-
 static inline void cpu_ll_break(void)
 {
     asm volatile("ebreak\n");

+ 49 - 5
components/hal/esp32h2/include/hal/cpu_ll.h

@@ -12,6 +12,7 @@
 #include "soc/assist_debug_reg.h"
 #include "esp_attr.h"
 #include "riscv/csr.h"
+#include "riscv/semihosting.h"
 
 /*performance counter*/
 #define CSR_PCER_MACHINE    0x7e0
@@ -69,8 +70,29 @@ static inline void cpu_ll_init_hwloop(void)
     // Nothing needed here for ESP32-H2
 }
 
+FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
+{
+    return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
+}
+
 static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* If we want to set breakpoint which when hit transfers control to debugger
+         * we need to set `action` in `mcontrol` to 1 (Enter Debug Mode).
+         * That `action` value is supported only when `dmode` of `tdata1` is set.
+         * But `dmode` can be modified by debugger only (from Debug Mode).
+         *
+         * So when debugger is connected we use special syscall to ask it to set breakpoint for us.
+         */
+        long args[] = {true, id, (long)pc};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
+        if (ret == 0) {
+            return;
+        }
+    }
+    /* The code bellow sets breakpoint which will trigger `Breakpoint` exception
+     * instead transfering control to debugger. */
     RV_WRITE_CSR(tselect,id);
     RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@@ -80,6 +102,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
 
 static inline void cpu_ll_clear_breakpoint(int id)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {false, id};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
+        if (ret == 0){
+            return;
+        }
+    }
     RV_WRITE_CSR(tselect,id);
     RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@@ -103,6 +133,17 @@ static inline void cpu_ll_set_watchpoint(int id,
                                         bool on_write)
 {
     uint32_t addr_napot;
+
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {true, id, (long)addr, (long)size,
+            (long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
+        if (ret == 0) {
+            return;
+        }
+    }
+
     RV_WRITE_CSR(tselect,id);
     RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
     RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@@ -121,6 +162,14 @@ static inline void cpu_ll_set_watchpoint(int id,
 
 static inline void cpu_ll_clear_watchpoint(int id)
 {
+    if (cpu_ll_is_debugger_attached()) {
+        /* see description in cpu_ll_set_breakpoint()  */
+        long args[] = {false, id};
+        int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
+        if (ret == 0){
+            return;
+        }
+    }
     RV_WRITE_CSR(tselect,id);
     RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
     RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@@ -130,11 +179,6 @@ static inline void cpu_ll_clear_watchpoint(int id)
     return;
 }
 
-FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
-{
-    return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
-}
-
 static inline void cpu_ll_break(void)
 {
     asm volatile("ebreak\n");

+ 47 - 0
components/riscv/include/riscv/semihosting.h

@@ -10,6 +10,53 @@
 extern "C" {
 #endif
 
+
+/* ESP custom semihosting calls numbers */
+
+/**
+ * @brief Initialize apptrace data at host side
+ *
+ * @param addr    address of apptrace control data block
+ * @return        return 0 on sucess or non-zero error code
+ */
+#define ESP_SEMIHOSTING_SYS_APPTRACE_INIT    0x64
+
+/**
+ * @brief Initialize debug stubs table at host side
+ *
+ * @param addr    address of debug stubs table
+ * @return        return 0 on sucess or non-zero error code
+ */
+#define ESP_SEMIHOSTING_SYS_DBG_STUBS_INIT   0x65
+
+/**
+ * @brief Set/clear breakpoint
+ *
+ * @param set     if true set breakpoint, otherwise clear it
+ * @param id      breakpoint ID
+ * @param addr    address to set breakpoint at. Ignored if `set` is false.
+ * @return        return 0 on sucess or non-zero error code
+ */
+#define ESP_SEMIHOSTING_SYS_BREAKPOINT_SET   0x66
+
+/**
+ * @brief Set/clear watchpoint
+ *
+ * @param set     if true set watchpoint, otherwise clear it
+ * @param id      watchpoint ID
+ * @param addr    address to set watchpoint at. Ignored if `set` is false.
+ * @param size    size of watchpoint. Ignored if `set` is false.
+ * @param flags   watchpoint flags, see description below. Ignored if `set` is false.
+ * @return        return 0 on sucess or non-zero error code
+ */
+#define ESP_SEMIHOSTING_SYS_WATCHPOINT_SET   0x67
+
+/* bit values for `flags` argument of ESP_SEMIHOSTING_SYS_WATCHPOINT_SET call. Can be ORed. */
+/* watch for 'reads' at `addr` */
+#define ESP_SEMIHOSTING_WP_FLG_RD   (1UL << 0)
+/* watch for 'writes' at `addr` */
+#define ESP_SEMIHOSTING_WP_FLG_WR   (1UL << 1)
+
 /**
  * @brief Perform semihosting call
  *

+ 30 - 4
components/riscv/vectors.S

@@ -176,16 +176,31 @@ _panic_handler:
     bgeu  a1, t0, _call_panic_handler
     sw    a1, RV_STK_MCAUSE(sp)
     /* exception_from_panic never returns */
-    j panic_from_exception
+    jal panic_from_exception
+    /* We arrive here if the exception handler has returned. */
+    j _return_from_exception
+
 _call_panic_handler:
     /* Remove highest bit from mcause (a1) register and save it in the
      * structure */
     not   t0, t0
     and   a1, a1, t0
     sw    a1, RV_STK_MCAUSE(sp)
-    /* exception_from_isr never returns */
-    j panic_from_isr
-    .size  panic_from_isr, .-panic_from_isr
+    jal panic_from_isr
+
+    /* We arrive here if the exception handler has returned. This means that
+     * the exception was handled, and the execution flow should resume.
+     * Restore the registers and return from the exception.
+     */
+_return_from_exception:
+    restore_mepc
+    /* MTVEC and SP are assumed to be unmodified.
+     * MSTATUS, MHARTID, MTVAL are read-only and not restored.
+     */
+    lw gp,  RV_STK_GP(sp)
+    restore_general_regs RV_STK_FRMSZ
+    mret
+    .size  _panic_handler, .-_panic_handler
 
     /* This is the interrupt handler.
      * It saves the registers on the stack,
@@ -201,6 +216,17 @@ _interrupt_handler:
     save_general_regs
     save_mepc
 
+    /* Though it is not necessary we save GP and SP here.
+     * SP is necessary to help GDB to properly unwind
+     * the backtrace of threads preempted by interrupts (OS tick etc.).
+     * GP is saved just to have its proper value in GDB. */
+    /* As gp register is not saved by the macro, save it here */
+    sw    gp, RV_STK_GP(sp)
+    /* Same goes for the SP value before trapping */
+    addi  t0, sp, CONTEXT_SIZE /* restore sp with the value when interrupt happened */
+    /* Save SP */
+    sw    t0, RV_STK_SP(sp)
+
     /* Before doing anythig preserve the stack pointer */
     /* It will be saved in current TCB, if needed */
     mv      a0, sp