diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index bf64b5aab2..ad975c6510 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -99,14 +99,19 @@ menu "FreeRTOS" 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 + the stack overflow may already be long gone by then. This option will use the last debug memory + watchpoint 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. + effectively have one hardware watchpoint less because the last one is overwritten as soon as a task + switch happens. - This check only triggers if the stack overflow writes within 4 bytes of the end of the stack, rather than - overshooting further, so it is worth combining this approach with one of the other stack overflow check - methods. + Another consequence is that due to alignment requirements of the watchpoint, the usable stack size + decreases by up to 60 bytes. This is because the watchpoint region has to be aligned to its size and the + size for the stack watchpoint in IDF is 32 bytes. + + This check only triggers if the stack overflow writes within 32 bytes near the end of the stack, rather + than overshooting further, so it is worth combining this approach with one of the other stack overflow + check methods. When this watchpoint is hit, gdb will stop with a SIGTRAP message. When no JTAG OCD is attached, esp-idf will panic on an unhandled debug exception. diff --git a/components/freertos/port/riscv/port.c b/components/freertos/port/riscv/port.c index a2256b9add..95d5ace0a4 100644 --- a/components/freertos/port/riscv/port.c +++ b/components/freertos/port/riscv/port.c @@ -94,6 +94,7 @@ #include "esp_intr_alloc.h" #include "esp_private/crosscore_int.h" #include "esp_attr.h" +#include "esp_debug_helpers.h" #include "esp_log.h" /** @@ -279,9 +280,12 @@ void vPortYield(void) } +#define STACK_WATCH_AREA_SIZE 32 void vPortSetStackWatchpoint(void *pxStackStart) { - (void)pxStackStart; // TODO ESP32-C3 IDF-2207 + uint32_t addr = (uint32_t)pxStackStart; + addr = (addr + (STACK_WATCH_AREA_SIZE - 1)) & (~(STACK_WATCH_AREA_SIZE - 1)); + esp_set_watchpoint(7, (char *)addr, STACK_WATCH_AREA_SIZE, ESP_WATCHPOINT_STORE); } BaseType_t xPortInIsrContext(void) diff --git a/components/hal/esp32c3/include/hal/cpu_ll.h b/components/hal/esp32c3/include/hal/cpu_ll.h index 286ec5dffb..fb8d9b32bc 100644 --- a/components/hal/esp32c3/include/hal/cpu_ll.h +++ b/components/hal/esp32c3/include/hal/cpu_ll.h @@ -107,14 +107,15 @@ static inline void cpu_ll_set_watchpoint(int id, { uint32_t addr_napot; RV_WRITE_CSR(tselect,id); - RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE); + RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE); RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE); RV_SET_CSR_FIELD(CSR_TDATA1, TDATA1_MATCH, 1); - addr_napot = ((uint32_t)addr)|((size>>1)-1); - if(on_read) { + // add 0 in napot encoding + addr_napot = ((uint32_t) addr) | ((size >> 1) - 1); + if (on_read) { RV_SET_CSR(CSR_TDATA1, TDATA1_LOAD); } - if(on_write) { + if (on_write) { RV_SET_CSR(CSR_TDATA1, TDATA1_STORE); } RV_WRITE_CSR(tdata2,addr_napot); diff --git a/components/riscv/include/riscv/csr.h b/components/riscv/include/riscv/csr.h index 030eb53a93..dae9d5b556 100644 --- a/components/riscv/include/riscv/csr.h +++ b/components/riscv/include/riscv/csr.h @@ -90,13 +90,14 @@ extern "C" { #define CSR_TDATA1 0x7a1 #define TCONTROL_MTE (1<<3) /*R/W, Current M mode trigger enable bit*/ +#define TCONTROL_MPTE (1<<7) /*R/W, Previous M mode trigger enable bit*/ #define TDATA1_LOAD (1<<0) /*R/W,Fire trigger on load address match*/ #define TDATA1_STORE (1<<1) /*R/W,Fire trigger on store address mat*/ #define TDATA1_EXECUTE (1<<2) /*R/W,Fire trigger on instruction fetch address match*/ #define TDATA1_USER (1<<3) /*R/W,allow trigger to be fired in user mode*/ #define TDATA1_MACHINE (1<<6) /*R/W,Allow trigger to be fired while hart is executing in machine mode*/ -#define TDATA1_MATCH +#define TDATA1_MATCH (1<<7) #define TDATA1_MATCH_V (0xF) /*R/W,Address match type :0 : Exact byte match 1 : NAPOT range match */ #define TDATA1_MATCH_S (7) diff --git a/tools/test_apps/system/panic/main/test_panic_main.c b/tools/test_apps/system/panic/main/test_panic_main.c index 8e4c4f7d81..b7e42a1c3f 100644 --- a/tools/test_apps/system/panic/main/test_panic_main.c +++ b/tools/test_apps/system/panic/main/test_panic_main.c @@ -100,19 +100,39 @@ static void IRAM_ATTR test_int_wdt_cache_disabled(void) } } +/** + * This function overwrites the stack beginning from the valid area continuously towards and beyond + * the end of the stack (stack base) of the current task. + * This is to test stack protection measures like a watchpoint at the end of the stack. + * + * @note: This test DOES NOT write beyond the stack limit. It only writes up to exactly the limit itself. + * The FreeRTOS stack protection mechanisms all trigger shortly before the end of the stack. + */ static void test_stack_overflow(void) { register uint32_t* sp asm("sp"); - uint32_t *end = sp - CONFIG_ESP_MAIN_TASK_STACK_SIZE; + TaskStatus_t pxTaskStatus; + vTaskGetInfo(NULL, &pxTaskStatus, pdFALSE, pdFALSE); + uint32_t *end = (uint32_t*) pxTaskStatus.pxStackBase; + // offset - 20 bytes from SP in order to not corrupt the current frame. + // Need to write from higher to lower addresses since the stack grows downwards and the watchpoint/canary is near + // the end of the stack (lowest address). for (uint32_t* ptr = sp - 5; ptr != end; --ptr) { - *ptr = rand(); + *ptr = 0; } + + // trigger a context switch to initiate checking the FreeRTOS stack canary + vTaskDelay(pdMS_TO_TICKS(0)); } static void test_illegal_instruction(void) { +#if __XTENSA__ __asm__ __volatile__("ill"); +#elif __riscv + __asm__ __volatile__("unimp"); +#endif } static void test_instr_fetch_prohibited(void) diff --git a/tools/test_apps/system/panic/sdkconfig.defaults b/tools/test_apps/system/panic/sdkconfig.defaults index 20d6175bb6..12b39b52fc 100644 --- a/tools/test_apps/system/panic/sdkconfig.defaults +++ b/tools/test_apps/system/panic/sdkconfig.defaults @@ -10,3 +10,6 @@ CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y # To panic on task WDT CONFIG_ESP_TASK_WDT_PANIC=y + +# For vTaskGetInfo() used in test_stack_overflow() +CONFIG_FREERTOS_USE_TRACE_FACILITY=y