freertos: Make backtrace work across interrupts

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.
This commit is contained in:
Darian Leung 2019-01-04 20:38:33 +08:00
parent 2eabed161a
commit 96af064209
3 changed files with 167 additions and 2 deletions

View file

@ -1423,7 +1423,7 @@ UT_004_15:
- UT_T1_1
- psram
UT_004_14:
UT_004_16:
<<: *unit_test_template
tags:
- ESP32_IDF

View file

@ -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=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
}

View 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 */