From bab80ad20243ff84d68e4d6675e918499afb6660 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Fri, 4 Jan 2019 20:38:33 +0800 Subject: [PATCH] freertos: Make backtrace work across interrupts (backport v3.2) This commit adds the ability for backtracing to trace from the itnerrupt to the task stack, and across nested interrupts. Test cases have also been added. See MR !4084 --- .gitlab-ci.yml | 7 ++ components/esp32/test/test_backtrace.c | 71 +++++++++++++++++++ components/freertos/xtensa_vectors.S | 96 +++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 components/esp32/test/test_backtrace.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f2ab2c847..a1f3a7b73 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1543,6 +1543,13 @@ UT_012_03: - UT_T1_1 - 8Mpsram +UT_012_04: + <<: *unit_test_template + tags: + - ESP32_IDF + - UT_T1_1 + - 8Mpsram + UT_017_01: <<: *unit_test_template tags: 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 */