freertos: Make backtrace work across interrupts (backport v3.1)
authorDarian Leung <darian@espressif.com>
Fri, 4 Jan 2019 12:38:33 +0000 (20:38 +0800)
committerDarian Leung <darian@espressif.com>
Wed, 30 Jan 2019 14:13:28 +0000 (22:13 +0800)
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 merge request !4084
Note: "Test backtrace from abort" reset reason modified by removing abort as a reason.

components/esp32/test/test_backtrace.c [new file with mode: 0644]
components/freertos/xtensa_vectors.S

diff --git a/components/esp32/test/test_backtrace.c b/components/esp32/test/test_backtrace.c
new file mode 100644 (file)
index 0000000..e2d6fc5
--- /dev/null
@@ -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 <stdlib.h>
+#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=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
+}
index b0238375a86a44c88880272f3d932b310d770379..101f08ab6d1f2f948716242d2db32f9060c112d8 100644 (file)
@@ -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 */