diff --git a/tools/test_apps/system/panic/main/CMakeLists.txt b/tools/test_apps/system/panic/main/CMakeLists.txt index 83427066bf..f3f6d05ef7 100644 --- a/tools/test_apps/system/panic/main/CMakeLists.txt +++ b/tools/test_apps/system/panic/main/CMakeLists.txt @@ -1,3 +1,3 @@ -idf_component_register(SRCS "test_panic_main.c" - INCLUDE_DIRS "." +idf_component_register(SRCS "test_app_main.c" "test_panic.c" + INCLUDE_DIRS "include" REQUIRES spi_flash esp_system esp_partition) diff --git a/tools/test_apps/system/panic/main/include/test_panic.h b/tools/test_apps/system/panic/main/include/test_panic.h new file mode 100644 index 0000000000..da43598134 --- /dev/null +++ b/tools/test_apps/system/panic/main/include/test_panic.h @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Utility functions */ + +void die(const char* msg) __attribute__ ((noreturn)); + +/* Functions causing an exception/panic in different ways */ + +void test_abort(void); + +void test_abort_cache_disabled(void); + +void test_int_wdt(void); + +void test_task_wdt_cpu0(void); + +#if !CONFIG_FREERTOS_UNICORE +void test_task_wdt_cpu1(void); +void test_task_wdt_both_cpus(void); +#endif + +void test_storeprohibited(void); + +void test_cache_error(void); + +void test_int_wdt_cache_disabled(void); + +void test_stack_overflow(void); + +void test_illegal_instruction(void); + +void test_instr_fetch_prohibited(void); + +void test_ub(void); + +void test_assert(void); + +void test_assert_cache_disabled(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/test_apps/system/panic/main/test_app_main.c b/tools/test_apps/system/panic/main/test_app_main.c new file mode 100644 index 0000000000..c749978e9c --- /dev/null +++ b/tools/test_apps/system/panic/main/test_app_main.c @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_system.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "test_panic.h" + +/* Test Utility Functions */ + +#define BOOT_CMD_MAX_LEN (128) + +#define HANDLE_TEST(test_name, name_) \ + if (strcmp(test_name, #name_) == 0) { \ + name_(); \ + die("Test function has returned"); \ + } + +static const char* get_test_name(void) +{ + static char test_name_str[BOOT_CMD_MAX_LEN] = {0}; + + printf("Enter test name: "); + fflush(stdout); + + /* Not using blocking fgets(stdin) here, as QEMU doesn't yet implement RX timeout interrupt, + * which is required for the UART driver and blocking stdio to work. + */ + int c = EOF; + char *p = test_name_str; + const char *end = test_name_str + sizeof(test_name_str) - 1; + while (p < end) { + c = getchar(); + if (c == EOF) { + vTaskDelay(pdMS_TO_TICKS(10)); + } else if ((c == '\r' || c == '\n') && p != test_name_str) { + /* terminate the line */ + puts("\n\r"); + fflush(stdout); + *p = '\0'; + break; + } else { + /* echo the received character */ + putchar(c); + fflush(stdout); + /* and save it */ + *p = c; + ++p; + } + } + + return test_name_str; +} + +/* app_main */ + +void app_main(void) +{ + /* Needed to allow the tick hook to set correct INT WDT timeouts */ + vTaskDelay(2); + + /* Test script sends to command over UART. Read it and determine how to proceed. */ + const char* test_name = get_test_name(); + if (test_name == NULL) { + /* Nothing to do */ + return; + } + printf("Got test name: %s\n", test_name); + + HANDLE_TEST(test_name, test_abort); + HANDLE_TEST(test_name, test_abort_cache_disabled); + HANDLE_TEST(test_name, test_int_wdt); + HANDLE_TEST(test_name, test_task_wdt_cpu0); +#if !CONFIG_FREERTOS_UNICORE + HANDLE_TEST(test_name, test_task_wdt_cpu1); + HANDLE_TEST(test_name, test_task_wdt_both_cpus); +#endif + HANDLE_TEST(test_name, test_storeprohibited); + HANDLE_TEST(test_name, test_cache_error); + HANDLE_TEST(test_name, test_int_wdt_cache_disabled); + HANDLE_TEST(test_name, test_stack_overflow); + HANDLE_TEST(test_name, test_illegal_instruction); + HANDLE_TEST(test_name, test_instr_fetch_prohibited); + HANDLE_TEST(test_name, test_ub); + HANDLE_TEST(test_name, test_assert); + HANDLE_TEST(test_name, test_assert_cache_disabled); + + die("Unknown test name"); +} diff --git a/tools/test_apps/system/panic/main/test_panic.c b/tools/test_apps/system/panic/main/test_panic.c new file mode 100644 index 0000000000..849ed4b318 --- /dev/null +++ b/tools/test_apps/system/panic/main/test_panic.c @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include + +#include "esp_partition.h" +#include "esp_flash.h" +#include "esp_system.h" + +#include "esp_private/cache_utils.h" +#include "esp_memory_utils.h" +#include "esp_heap_caps.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +/* Test utility function */ + +extern void esp_restart_noos(void) __attribute__ ((noreturn)); + +void die(const char* msg) +{ + printf("Test error: %s\n\n", msg); + fflush(stdout); + usleep(1000); + /* Don't use abort here as it would enter the panic handler */ + esp_restart_noos(); +} + +/* implementations of the test functions */ + +void test_abort(void) +{ + abort(); +} + +void IRAM_ATTR test_abort_cache_disabled(void) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + abort(); +} + +void test_int_wdt(void) +{ + portDISABLE_INTERRUPTS(); + while (true) { + ; + } +} + +void test_task_wdt_cpu0(void) +{ + while (true) { + ; + } +} + +#if !CONFIG_FREERTOS_UNICORE +static void infinite_loop(void* arg) { + (void) arg; + while(1) { + ; + } +} + +void test_task_wdt_cpu1(void) +{ + xTaskCreatePinnedToCore(infinite_loop, "Infinite loop", 1024, NULL, 1, NULL, 1); + while (true) { + vTaskDelay(1); + } +} + +void test_task_wdt_both_cpus(void) +{ + xTaskCreatePinnedToCore(infinite_loop, "Infinite loop", 1024, NULL, 4, NULL, 1); + /* Give some time to the task on CPU 1 to be scheduled */ + vTaskDelay(1); + xTaskCreatePinnedToCore(infinite_loop, "Infinite loop", 1024, NULL, 4, NULL, 0); + while (true) { + ; + } +} +#endif + +void __attribute__((no_sanitize_undefined)) test_storeprohibited(void) +{ + *(int*) 0x1 = 0; +} + +void IRAM_ATTR test_cache_error(void) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + die("this should not be printed"); +} + +void IRAM_ATTR test_int_wdt_cache_disabled(void) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + portDISABLE_INTERRUPTS(); + while (true) { + ; + } +} + +void test_assert(void) +{ + assert(0); +} + +void IRAM_ATTR test_assert_cache_disabled(void) +{ + spi_flash_disable_interrupts_caches_and_other_cpu(); + assert(0); +} + +/** + * 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. + */ +void test_stack_overflow(void) +{ + register uint32_t* sp asm("sp"); + 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 = 0; + } + + // trigger a context switch to initiate checking the FreeRTOS stack canary + vTaskDelay(pdMS_TO_TICKS(0)); +} + +void test_illegal_instruction(void) +{ +#if __XTENSA__ + __asm__ __volatile__("ill"); +#elif __riscv + __asm__ __volatile__("unimp"); +#endif +} + +void test_instr_fetch_prohibited(void) +{ + typedef void (*fptr_t)(void); + volatile fptr_t fptr = (fptr_t) 0x4; + fptr(); +} + +void test_ub(void) +{ + uint8_t stuff[1] = {rand()}; + printf("%d\n", stuff[rand()]); +} diff --git a/tools/test_apps/system/panic/main/test_panic_main.c b/tools/test_apps/system/panic/main/test_panic_main.c deleted file mode 100644 index 2b4c4512ef..0000000000 --- a/tools/test_apps/system/panic/main/test_panic_main.c +++ /dev/null @@ -1,261 +0,0 @@ -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_partition.h" -#include "esp_flash.h" -#include "esp_system.h" - -/* utility functions */ -static void die(const char* msg) __attribute__ ((noreturn)); -static const char* get_test_name(void); - -/* functions which cause an exception/panic in different ways */ -static void test_abort(void); -static void test_abort_cache_disabled(void); -static void test_int_wdt(void); -static void test_task_wdt_cpu0(void); -#if !CONFIG_FREERTOS_UNICORE -static void test_task_wdt_cpu1(void); -static void test_task_wdt_both_cpus(void); -#endif -static void test_storeprohibited(void); -static void test_cache_error(void); -static void test_int_wdt_cache_disabled(void); -static void test_stack_overflow(void); -static void test_illegal_instruction(void); -static void test_instr_fetch_prohibited(void); -static void test_ub(void); -static void test_assert(void); -static void test_assert_cache_disabled(void); - - -void app_main(void) -{ - /* Needed to allow the tick hook to set correct INT WDT timeouts */ - vTaskDelay(2); - - /* Test script sends to command over UART. Read it and determine how to proceed. */ - const char* test_name = get_test_name(); - if (test_name == NULL) { - /* Nothing to do */ - return; - } - printf("Got test name: %s\n", test_name); - - #define HANDLE_TEST(name_) \ - if (strcmp(test_name, #name_) == 0) { \ - name_(); \ - die("Test function has returned"); \ - } - - HANDLE_TEST(test_abort); - HANDLE_TEST(test_abort_cache_disabled); - HANDLE_TEST(test_int_wdt); - HANDLE_TEST(test_task_wdt_cpu0); -#if !CONFIG_FREERTOS_UNICORE - HANDLE_TEST(test_task_wdt_cpu1); - HANDLE_TEST(test_task_wdt_both_cpus); -#endif - HANDLE_TEST(test_storeprohibited); - HANDLE_TEST(test_cache_error); - HANDLE_TEST(test_int_wdt_cache_disabled); - HANDLE_TEST(test_stack_overflow); - HANDLE_TEST(test_illegal_instruction); - HANDLE_TEST(test_instr_fetch_prohibited); - HANDLE_TEST(test_ub); - HANDLE_TEST(test_assert); - HANDLE_TEST(test_assert_cache_disabled); - - #undef HANDLE_TEST - - die("Unknown test name"); -} - -/* implementations of the test functions */ - -static void test_abort(void) -{ - abort(); -} - -static void IRAM_ATTR test_abort_cache_disabled(void) -{ - esp_flash_default_chip->os_func->start(esp_flash_default_chip->os_func_data); - abort(); -} - -static void test_int_wdt(void) -{ - portDISABLE_INTERRUPTS(); - while (true) { - ; - } -} - -static void test_task_wdt_cpu0(void) -{ - while (true) { - ; - } -} - -#if !CONFIG_FREERTOS_UNICORE -static void infinite_loop(void* arg) { - (void) arg; - while(1) { - ; - } -} - -static void test_task_wdt_cpu1(void) -{ - xTaskCreatePinnedToCore(infinite_loop, "Infinite loop", 1024, NULL, 1, NULL, 1); - while (true) { - vTaskDelay(1); - } -} - -static void test_task_wdt_both_cpus(void) -{ - xTaskCreatePinnedToCore(infinite_loop, "Infinite loop", 1024, NULL, 4, NULL, 1); - /* Give some time to the task on CPU 1 to be scheduled */ - vTaskDelay(1); - xTaskCreatePinnedToCore(infinite_loop, "Infinite loop", 1024, NULL, 4, NULL, 0); - while (true) { - ; - } -} -#endif - -static void __attribute__((no_sanitize_undefined)) test_storeprohibited(void) -{ - *(int*) 0x1 = 0; -} - -static IRAM_ATTR void test_cache_error(void) -{ - esp_flash_default_chip->os_func->start(esp_flash_default_chip->os_func_data); - die("this should not be printed"); -} - -static void IRAM_ATTR test_int_wdt_cache_disabled(void) -{ - esp_flash_default_chip->os_func->start(esp_flash_default_chip->os_func_data); - portDISABLE_INTERRUPTS(); - while (true) { - ; - } -} - -static void test_assert(void) -{ - assert(0); -} - -static void IRAM_ATTR test_assert_cache_disabled(void) -{ - esp_flash_default_chip->os_func->start(esp_flash_default_chip->os_func_data); - assert(0); -} - -/** - * 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"); - 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 = 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) -{ - typedef void (*fptr_t)(void); - volatile fptr_t fptr = (fptr_t) 0x4; - fptr(); -} - -static void test_ub(void) -{ - uint8_t stuff[1] = {rand()}; - printf("%d\n", stuff[rand()]); -} - -/* implementations of the utility functions */ - -#define BOOT_CMD_MAX_LEN (128) - -static const char* get_test_name(void) -{ - static char test_name_str[BOOT_CMD_MAX_LEN] = {0}; - - printf("Enter test name: "); - fflush(stdout); - - /* Not using blocking fgets(stdin) here, as QEMU doesn't yet implement RX timeout interrupt, - * which is required for the UART driver and blocking stdio to work. - */ - int c = EOF; - char *p = test_name_str; - const char *end = test_name_str + sizeof(test_name_str) - 1; - while (p < end) { - c = getchar(); - if (c == EOF) { - vTaskDelay(pdMS_TO_TICKS(10)); - } else if ((c == '\r' || c == '\n') && p != test_name_str) { - /* terminate the line */ - puts("\n\r"); - fflush(stdout); - *p = '\0'; - break; - } else { - /* echo the received character */ - putchar(c); - fflush(stdout); - /* and save it */ - *p = c; - ++p; - } - } - - return test_name_str; -} - -extern void esp_restart_noos(void) __attribute__ ((noreturn)); - -static void die(const char* msg) -{ - printf("Test error: %s\n\n", msg); - fflush(stdout); - usleep(1000); - /* Don't use abort here as it would enter the panic handler */ - esp_restart_noos(); -}