Merge branch 'feature/watchpoint_on_stack_canary' into 'master'

Add option to automatically set a watchpoint at the end of the swapped-in task

This should make stack overflows easier to debug because it triggers a debug exception as soon as the stack is overwritten, not later when a context switch happens. Marked as a FreeRTOS debug feature because it doesn't give a nice error message and uses up a watchpoint.

See merge request !389
This commit is contained in:
Jeroen Domburg 2017-01-10 14:01:07 +08:00
commit a3f6728797
6 changed files with 166 additions and 3 deletions

View file

@ -14,8 +14,49 @@
#ifndef __ASSEMBLER__
#include "esp_err.h"
/**
* @brief If an OCD is connected over JTAG. set breakpoint 0 to the given function
* address. Do nothing otherwise.
* @param data Pointer to the target breakpoint position
*/
void esp_set_breakpoint_if_jtag(void *fn);
#define ESP_WATCHPOINT_LOAD 0x40000000
#define ESP_WATCHPOINT_STORE 0x80000000
#define ESP_WATCHPOINT_ACCESS 0xC0000000
/**
* @brief Set a watchpoint to break/panic when a certain memory range is accessed.
*
* @param no Watchpoint number. On the ESP32, this can be 0 or 1.
* @param adr Base address to watch
* @param size Size of the region, starting at the base address, to watch. Must
* be one of 2^n, with n in [0..6].
* @param flags One of ESP_WATCHPOINT_* flags
*
* @return ESP_ERR_INVALID_ARG on invalid arg, ESP_OK otherwise
*
* @warning The ESP32 watchpoint hardware watches a region of bytes by effectively
* masking away the lower n bits for a region with size 2^n. If adr does
* not have zero for these lower n bits, you may not be watching the
* region you intended.
*/
esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags);
/**
* @brief Clear a watchpoint
*
* @param no Watchpoint to clear
*
*/
void esp_clear_watchpoint(int no);
#endif
#endif
#endif

View file

@ -32,6 +32,7 @@
#include "esp_gdbstub.h"
#include "esp_panic.h"
#include "esp_attr.h"
#include "esp_err.h"
/*
Panic handlers; these get called when an unhandled exception occurs or the assembly-level
@ -163,8 +164,37 @@ void panicHandler(XtExcFrame *frame)
panicPutStr("Guru Meditation Error: Core ");
panicPutDec(xPortGetCoreID());
panicPutStr(" panic'ed (");
panicPutStr(reason);
panicPutStr(")\r\n");
if (!abort_called) {
panicPutStr(reason);
panicPutStr(")\r\n");
if (regs[20]==PANIC_RSN_DEBUGEXCEPTION) {
int debugRsn;
asm("rsr.debugcause %0":"=r"(debugRsn));
panicPutStr("Debug exception reason: ");
if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) panicPutStr("SingleStep ");
if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) panicPutStr("HwBreakpoint ");
if (debugRsn&XCHAL_DEBUGCAUSE_DBREAK_MASK) {
//Unlike what the ISA manual says, this core seemingly distinguishes from a DBREAK
//reason caused by watchdog 0 and one caused by watchdog 1 by setting bit 8 of the
//debugcause if the cause is watchdog 1 and clearing it if it's watchdog 0.
if (debugRsn&(1<<8)) {
#if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK
panicPutStr("Stack canary watchpoint triggered ");
#else
panicPutStr("Watchpoint 1 triggered ");
#endif
} else {
panicPutStr("Watchpoint 0 triggered ");
}
}
if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) panicPutStr("BREAK instr ");
if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) panicPutStr("BREAKN instr ");
if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) panicPutStr("DebugIntr ");
panicPutStr("\r\n");
}
} else {
panicPutStr("abort)\r\n");
}
if (esp_cpu_in_ocd_debug_mode()) {
asm("break.n 1");
@ -353,3 +383,50 @@ void esp_set_breakpoint_if_jtag(void *fn)
setFirstBreakpoint((uint32_t)fn);
}
}
esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags)
{
int x;
if (no<0 || no>1) return ESP_ERR_INVALID_ARG;
if (flags&(~0xC0000000)) return ESP_ERR_INVALID_ARG;
int dbreakc=0x3F;
//We support watching 2^n byte values, from 1 to 64. Calculate the mask for that.
for (x=0; x<7; x++) {
if (size==(1<<x)) break;
dbreakc<<=1;
}
if (x==7) return ESP_ERR_INVALID_ARG;
//Mask mask and add in flags.
dbreakc=(dbreakc&0x3f)|flags;
if (no==0) {
asm volatile(
"wsr.dbreaka0 %0\n" \
"wsr.dbreakc0 %1\n" \
::"r"(adr),"r"(dbreakc));
} else {
asm volatile(
"wsr.dbreaka1 %0\n" \
"wsr.dbreakc1 %1\n" \
::"r"(adr),"r"(dbreakc));
}
return ESP_OK;
}
void esp_clear_watchpoint(int no)
{
//Setting a dbreakc register to 0 makes it trigger on neither load nor store, effectively disabling it.
int dbreakc=0;
if (no==0) {
asm volatile(
"wsr.dbreakc0 %0\n" \
::"r"(dbreakc));
} else {
asm volatile(
"wsr.dbreakc1 %0\n" \
::"r"(dbreakc));
}
}

View file

@ -197,6 +197,22 @@ config FREERTOS_PORTMUX_DEBUG_RECURSIVE
portMUX usage.
endif #FREERTOS_UNICORE
config FREERTOS_WATCHPOINT_END_OF_STACK
bool "Set a debug watchpoint at the end of each stack"
default n
help
FreeRTOS can check if a stack has overflown its bounds by checking either the value of
the stack pointer or by checking the integrity of canary bytes. (See FREERTOS_CHECK_STACKOVERFLOW
for more information.) These checks only happen on a context switch, and the situation that caused
the stack overflow may already be long gone by then. This option will use the debug memory
watchpoint 1 (the second one) to allow breaking into the debugger (or panic'ing) as soon as any
of the last 32 bytes on the stack of a task are overwritten. The side effect is that using gdb, you
effectively only have one watchpoint; the 2nd one is overwritten as soon as a task switch happens.
When this watchpoint is hit, gdb will stop with a SIGTRAP message. When no OCD is attached, esp-idf
will panic on an unhandled debug exception.
endif # FREERTOS_DEBUG_INTERNALS
endmenu

View file

@ -187,6 +187,13 @@ void vPortEndScheduler( void ) PRIVILEGED_FUNCTION;
void vPortYieldOtherCore( BaseType_t coreid) PRIVILEGED_FUNCTION;
/*
Callback to set a watchpoint on the end of the stack. Called every context switch to change the stack
watchpoint around.
*/
void vPortSetStackWatchpoint( void* pxStackStart );
/*
* The structures and methods of manipulating the MPU are contained within the
* port layer.

View file

@ -394,3 +394,19 @@ void vPortFirstTaskHook(TaskFunction_t function) {
#endif
void vPortSetStackWatchpoint( void* pxStackStart ) {
//Set watchpoint 1 to watch the last 32 bytes of the stack.
//Unfortunately, the Xtensa watchpoints can't set a watchpoint on a random [base - base+n] region because
//the size works by masking off the lowest address bits. For that reason, we futz a bit and watch the lowest 32
//bytes of the stack we can actually watch. In general, this can cause the watchpoint to be triggered at most
//28 bytes early. The value 32 is chosen because it's larger than the stack canary, which in FreeRTOS is 20 bytes.
//This way, we make sure we trigger before/when the stack canary is corrupted, not after.
int addr=(int)pxStackStart;
addr=(addr+31)&(~31);
esp_set_watchpoint(1, (char*)addr, 32, ESP_WATCHPOINT_STORE);
}

View file

@ -78,6 +78,7 @@ task.h is included from an application file. */
#include "rom/ets_sys.h"
#include "esp_newlib.h"
#include "esp_panic.h"
/* FreeRTOS includes. */
#include "FreeRTOS.h"
@ -2809,6 +2810,11 @@ void vTaskSwitchContext( void )
traceTASK_SWITCHED_IN();
#if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK
vPortSetStackWatchpoint(pxCurrentTCB[xPortGetCoreID()]->pxStack);
#endif
}
portEXIT_CRITICAL_NESTED(irqstate);
}