diff --git a/tools/test_apps/system/panic/CMakeLists.txt b/tools/test_apps/system/panic/CMakeLists.txt index 5d597b1ec9..7bbc1c5d22 100644 --- a/tools/test_apps/system/panic/CMakeLists.txt +++ b/tools/test_apps/system/panic/CMakeLists.txt @@ -17,7 +17,7 @@ if(CONFIG_TEST_MEMPROT) endif() endif() -if(NOT CONFIG_TEST_MEMPROT AND NOT CONFIG_ESP_COREDUMP_CAPTURE_DRAM AND NOT CONFIG_SPIRAM) +if(NOT CONFIG_TEST_MEMPROT AND NOT CONFIG_ESP_COREDUMP_ENABLE AND NOT CONFIG_SPIRAM) # Enable UBSAN checks # # shift-base sanitizer is disabled due to the following pattern found in register header files: @@ -32,15 +32,10 @@ if(NOT CONFIG_TEST_MEMPROT AND NOT CONFIG_ESP_COREDUMP_CAPTURE_DRAM AND NOT CONF # Only enable UBSAN for a few components related to the panic test, # due to RAM size limitations. - set(ubsan_components main espcoredump esp_system spi_flash + set(ubsan_components main esp_system spi_flash esp_common esp_hw_support soc hal freertos) - if(CONFIG_ESP_COREDUMP_CHECKSUM_SHA256) - if(CONFIG_IDF_TARGET_ESP32S2) - # due to the ram limitation, coredump is removed from esp32s2 built - list(REMOVE_ITEM ubsan_components espcoredump) - endif() - endif() - foreach(component IN LISTS ubsan_components) + + foreach(component IN LISTS ubsan_components) idf_component_get_property(lib ${component} COMPONENT_LIB) target_compile_options(${lib} PRIVATE ${ubsan_options}) endforeach() diff --git a/tools/test_apps/system/panic/main/test_panic.c b/tools/test_apps/system/panic/main/test_panic.c index 43a4465b79..1a8340f24f 100644 --- a/tools/test_apps/system/panic/main/test_panic.c +++ b/tools/test_apps/system/panic/main/test_panic.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -382,10 +382,19 @@ void test_coredump_summary(void) void test_tcb_corrupted(void) { - uint32_t *tcb_ptr = (uint32_t *)xTaskGetIdleTaskHandleForCore(0); - for (size_t i = 0; i < sizeof(StaticTask_t) / sizeof(uint32_t); ++i) { - tcb_ptr[i] = 0xDEADBEE0; - } + StaticTask_t *tcb = (StaticTask_t *)xTaskGetIdleTaskHandleForCore(0); + + // Corrupt critical fields that are read by xTaskGetNext() and vTaskGetSnapshot(). + tcb->pxDummy1 = (void *)0xDEADBEE0; // pxTopOfStack + tcb->xDummy3[0].pvDummy3[0] = (void *)0xDEADBEE1; // xStateListItem.pxNext + tcb->xDummy3[0].pvDummy3[1] = (void *)0xDEADBEE2; // xStateListItem.pxPrevious + tcb->xDummy3[0].pvDummy3[2] = (void *)0xDEADBEE3; // xStateListItem.pvOwner + tcb->pxDummy6 = (void *)0xDEADBEE6; // pxStack +#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) ) + tcb->pxDummy8 = (void *)0xDEADBEE8; // pxEndOfStack +#endif + + // Trigger a context switch. vTaskDelay(2); } diff --git a/tools/test_apps/system/panic/pytest_panic.py b/tools/test_apps/system/panic/pytest_panic.py index 9f7ce34fd9..7a4f2c664b 100644 --- a/tools/test_apps/system/panic/pytest_panic.py +++ b/tools/test_apps/system/panic/pytest_panic.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import re from typing import Any @@ -57,6 +57,11 @@ CONFIGS = [ pytest.param('panic', marks=TARGETS_ALL), ] +CONFIGS_UBSAN = [ + pytest.param('gdbstub', marks=TARGETS_ALL), + pytest.param('panic', marks=TARGETS_ALL), +] + CONFIGS_DUAL_CORE = [ pytest.param('coredump_flash_bin_crc', marks=TARGETS_DUAL_CORE), pytest.param('coredump_flash_elf_sha', marks=TARGETS_DUAL_CORE), @@ -480,7 +485,7 @@ def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None: ) -@pytest.mark.parametrize('config', CONFIGS, indirect=True) +@pytest.mark.parametrize('config', CONFIGS_UBSAN, indirect=True) @pytest.mark.generic def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None: dut.run_test_func(test_func_name) @@ -493,7 +498,6 @@ def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None: dut.expect_elf_sha256() dut.expect_none(['Guru Meditation', 'Re-entered core dump']) - coredump_pattern = re.compile(PANIC_ABORT_PREFIX + regex_pattern.decode('utf-8')) common_test( dut, config, @@ -502,8 +506,7 @@ def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None: 'esp_system_abort', '__ubsan_default_handler', '__ubsan_handle_out_of_bounds' - ] + get_default_backtrace(test_func_name), - expected_coredump=[coredump_pattern] + ] + get_default_backtrace(test_func_name) ) @@ -1195,17 +1198,18 @@ def test_coredump_summary_flash_encrypted(dut: PanicTestDut, config: str) -> Non def test_tcb_corrupted(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None: dut.run_test_func(test_func_name) if dut.is_xtensa: - dut.expect_gme('LoadProhibited') + dut.expect(re.compile(rb"Guru Meditation Error: Core\s+\d\s+panic'ed \((LoadProhibited|StoreProhibited)\)")) dut.expect_reg_dump() dut.expect_backtrace() else: - dut.expect_gme('Load access fault') + dut.expect(re.compile(rb"Guru Meditation Error: Core\s+\d\s+panic'ed \((Load|Store) access fault\)")) dut.expect_reg_dump() dut.expect_stack_dump() dut.expect_elf_sha256() dut.expect_none('Guru Meditation') + # Verify that valid tasks are captured in coredump despite IDLE task corruption # TCB NAME # ---------- ---------------- if dut.is_multi_core: diff --git a/tools/test_apps/system/panic/test_panic_util/panic_dut.py b/tools/test_apps/system/panic/test_panic_util/panic_dut.py index 9ad40b8589..9861bc20eb 100644 --- a/tools/test_apps/system/panic/test_panic_util/panic_dut.py +++ b/tools/test_apps/system/panic/test_panic_util/panic_dut.py @@ -95,9 +95,17 @@ class PanicTestDut(IdfDut): else: assert match - def expect_stack_dump(self) -> None: + def expect_stack_dump(self) -> Any: + """Expect and capture the entire stack memory dump (RISC-V only). + + Returns: + str: The full stack dump hex data + """ assert not self.is_xtensa, 'Stack memory dump is only printed on RISC-V' self.expect_exact('Stack memory:') + pattern = re.compile(rb'\r?\n\r?\n') + result = self.expect(pattern, return_what_before_match=True).decode('utf-8') + return result.strip() def expect_gme(self, reason: str) -> None: """Expect method for Guru Meditation Errors"""