diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 425ecfe60..c7084e714 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1423,7 +1423,7 @@ UT_004_15: - UT_T1_1 - psram -UT_004_14: +UT_004_16: <<: *unit_test_template tags: - ESP32_IDF diff --git a/components/esp32/test/test_backtrace.c b/components/esp32/test/test_backtrace.c new file mode 100644 index 000000000..2c1f4d873 --- /dev/null +++ b/components/esp32/test/test_backtrace.c @@ -0,0 +1,71 @@ +/* + * Note: Currently, the backtraces must still be checked manually. Therefore, + * these test cases should always pass. + * Todo: Automate the checking of backtrace addresses. + */ +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/xtensa_api.h" +#include "esp_intr_alloc.h" + +#define SW_ISR_LEVEL_1 7 +#define SW_ISR_LEVEL_3 29 +#define RECUR_DEPTH 3 +#define ACTION_ABORT -1 +#define ACTION_INT_WDT -2 + +// Set to (-1) for abort(), (-2) for interrupt watchdog +static int backtrace_trigger_source; + +/* + * Recursive functions to generate a longer call stack. When the max specified + * recursion depth is reached, the following actions can be taken. + */ +static void __attribute__((__noinline__)) recursive_func(int recur_depth, int action) +{ + if (recur_depth > 1) { + recursive_func(recur_depth - 1, action); + } else if (action >= 0) { + xt_set_intset(1 << action); + } else if (action == ACTION_ABORT) { + abort(); + // Todo: abort() causes problems in GDB Stub backtrace due to being 'non returning'. + } else if (action == ACTION_INT_WDT) { + portDISABLE_INTERRUPTS(); + while (1) { + ; + } + } +} + +static void level_three_isr (void *arg) +{ + xt_set_intclear(1 << SW_ISR_LEVEL_3); //Clear interrupt + recursive_func(RECUR_DEPTH, backtrace_trigger_source); //Abort at the max recursive depth +} + +static void level_one_isr(void *arg) +{ + xt_set_intclear(1 << SW_ISR_LEVEL_1); //Clear interrupt + recursive_func(RECUR_DEPTH, SW_ISR_LEVEL_3); //Trigger nested interrupt max recursive depth +} + +TEST_CASE("Test backtrace from abort", "[reset_reason][reset=abort,SW_CPU_RESET]") +{ + //Allocate level one and three SW interrupts + esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, 0, level_one_isr, NULL, NULL); //Level 1 SW intr + esp_intr_alloc(ETS_INTERNAL_SW1_INTR_SOURCE, 0, level_three_isr, NULL, NULL); //Level 3 SW intr + backtrace_trigger_source = ACTION_ABORT; + recursive_func(RECUR_DEPTH, SW_ISR_LEVEL_1); //Trigger lvl 1 SW interrupt at max recursive depth +} + +TEST_CASE("Test backtrace from interrupt watchdog timeout", "[reset_reason][reset=Interrupt wdt timeout on CPU0,SW_CPU_RESET]") +{ + //Allocate level one and three SW interrupts + esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, 0, level_one_isr, NULL, NULL); //Level 1 SW intr + esp_intr_alloc(ETS_INTERNAL_SW1_INTR_SOURCE, 0, level_three_isr, NULL, NULL); //Level 3 SW intr + backtrace_trigger_source = ACTION_INT_WDT; + recursive_func(RECUR_DEPTH, SW_ISR_LEVEL_1); //Trigger lvl 1 SW interrupt at max recursive depth +} diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index b0238375a..101f08ab6 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -103,7 +103,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define TASKTCB_XCOREID_OFFSET (0x38+configMAX_TASK_NAME_LEN+3)&~3 .extern pxCurrentTCB -/* Enable stack backtrace across exception/interrupt - see below */ +/* +-------------------------------------------------------------------------------- + In order for backtracing to be able to trace from the pre-exception stack + across to the exception stack (including nested interrupts), we need to create + a pseudo base-save area to make it appear like the exception dispatcher was + triggered by a CALL4 from the pre-exception code. In reality, the exception + dispatcher uses the same window as pre-exception code, and only CALL0s are + used within the exception dispatcher. + + To create the pseudo base-save area, we need to store a copy of the pre-exception's + base save area (a0 to a4) below the exception dispatcher's SP. EXCSAVE_x will + be used to store a copy of the SP that points to the interrupted code's exception + frame just in case the exception dispatcher's SP does not point to the exception + frame (which is the case when switching from task to interrupt stack). + + Clearing the pseudo base-save area is uncessary as the interrupt dispatcher + will restore the current SP to that of the pre-exception SP. +-------------------------------------------------------------------------------- +*/ #ifdef CONFIG_FREERTOS_INTERRUPT_BACKTRACE #define XT_DEBUG_BACKTRACE 1 #endif @@ -202,9 +220,22 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* This bit of code provides a nice debug backtrace in the debugger. It does take a few more instructions, so undef XT_DEBUG_BACKTRACE if you want to save the cycles. + At this point, the exception frame should have been allocated and filled, + and current sp points to the interrupt stack (for non-nested interrupt) + or below the allocated exception frame (for nested interrupts). Copy the + pre-exception's base save area below the current SP. */ #ifdef XT_DEBUG_BACKTRACE #ifndef __XTENSA_CALL0_ABI__ + rsr a0, EXCSAVE_1 + \level - 1 /* Get exception frame pointer stored in EXCSAVE_x */ + l32i a3, a0, XT_STK_A0 /* Copy pre-exception a0 (return address) */ + s32e a3, a1, -16 + l32i a3, a0, XT_STK_A1 /* Copy pre-exception a1 (stack pointer) */ + s32e a3, a1, -12 + /* Backtracing only needs a0 and a1, no need to create full base save area. + Also need to change current frame's return address to point to pre-exception's + last run instruction. + */ rsr a0, EPC_1 + \level - 1 /* return address */ movi a4, 0xC0000000 /* constant with top 2 bits set (call size) */ or a0, a0, a4 /* set top 2 bits */ @@ -670,8 +701,16 @@ _xt_user_exc: #endif wsr a0, PS + /* + Create pseudo base save area. At this point, sp is still pointing to the + allocated and filled exception stack frame. + */ #ifdef XT_DEBUG_BACKTRACE #ifndef __XTENSA_CALL0_ABI__ + l32i a3, sp, XT_STK_A0 /* Copy pre-exception a0 (return address) */ + s32e a3, sp, -16 + l32i a3, sp, XT_STK_A1 /* Copy pre-exception a1 (stack pointer) */ + s32e a3, sp, -12 rsr a0, EPC_1 /* return address for debug backtrace */ movi a5, 0xC0000000 /* constant with top 2 bits set (call size) */ rsync /* wait for WSR.PS to complete */ @@ -1086,6 +1125,16 @@ _xt_lowint1: movi a0, _xt_user_exit /* save exit point for dispatch */ s32i a0, sp, XT_STK_EXIT + /* EXCSAVE_1 should now be free to use. Use it to keep a copy of the + current stack pointer that points to the exception frame (XT_STK_FRAME).*/ + #ifdef XT_DEBUG_BACKTRACE + #ifndef __XTENSA_CALL0_ABI__ + mov a0, sp + wsr a0, EXCSAVE_1 + #endif + #endif + + /* Save rest of interrupt context and enter RTOS. */ call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ @@ -1166,6 +1215,15 @@ _xt_medint2: movi a0, _xt_medint2_exit /* save exit point for dispatch */ s32i a0, sp, XT_STK_EXIT + /* EXCSAVE_2 should now be free to use. Use it to keep a copy of the + current stack pointer that points to the exception frame (XT_STK_FRAME).*/ + #ifdef XT_DEBUG_BACKTRACE + #ifndef __XTENSA_CALL0_ABI__ + mov a0, sp + wsr a0, EXCSAVE_2 + #endif + #endif + /* Save rest of interrupt context and enter RTOS. */ call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ @@ -1237,6 +1295,15 @@ _xt_medint3: movi a0, _xt_medint3_exit /* save exit point for dispatch */ s32i a0, sp, XT_STK_EXIT + /* EXCSAVE_3 should now be free to use. Use it to keep a copy of the + current stack pointer that points to the exception frame (XT_STK_FRAME).*/ + #ifdef XT_DEBUG_BACKTRACE + #ifndef __XTENSA_CALL0_ABI__ + mov a0, sp + wsr a0, EXCSAVE_3 + #endif + #endif + /* Save rest of interrupt context and enter RTOS. */ call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ @@ -1307,6 +1374,15 @@ _xt_medint4: movi a0, _xt_medint4_exit /* save exit point for dispatch */ s32i a0, sp, XT_STK_EXIT + /* EXCSAVE_4 should now be free to use. Use it to keep a copy of the + current stack pointer that points to the exception frame (XT_STK_FRAME).*/ + #ifdef XT_DEBUG_BACKTRACE + #ifndef __XTENSA_CALL0_ABI__ + mov a0, sp + wsr a0, EXCSAVE_4 + #endif + #endif + /* Save rest of interrupt context and enter RTOS. */ call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ @@ -1377,6 +1453,15 @@ _xt_medint5: movi a0, _xt_medint5_exit /* save exit point for dispatch */ s32i a0, sp, XT_STK_EXIT + /* EXCSAVE_5 should now be free to use. Use it to keep a copy of the + current stack pointer that points to the exception frame (XT_STK_FRAME).*/ + #ifdef XT_DEBUG_BACKTRACE + #ifndef __XTENSA_CALL0_ABI__ + mov a0, sp + wsr a0, EXCSAVE_5 + #endif + #endif + /* Save rest of interrupt context and enter RTOS. */ call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */ @@ -1447,6 +1532,15 @@ _xt_medint6: movi a0, _xt_medint6_exit /* save exit point for dispatch */ s32i a0, sp, XT_STK_EXIT + /* EXCSAVE_6 should now be free to use. Use it to keep a copy of the + current stack pointer that points to the exception frame (XT_STK_FRAME).*/ + #ifdef XT_DEBUG_BACKTRACE + #ifndef __XTENSA_CALL0_ABI__ + mov a0, sp + wsr a0, EXCSAVE_6 + #endif + #endif + /* Save rest of interrupt context and enter RTOS. */ call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */