diff --git a/components/esp32/test/test_backtrace.c b/components/esp32/test/test_backtrace.c
new file mode 100644
index 0000000000000000000000000000000000000000..e2d6fc53d182c9b22216ad40da8189d2d8fa425a
--- /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 <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
+}
diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S
index b0238375a86a44c88880272f3d932b310d770379..101f08ab6d1f2f948716242d2db32f9060c112d8 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 */