From 568c3978223a1718d9d73cb490e7031a34402238 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Thu, 3 Aug 2023 23:47:58 +0200 Subject: [PATCH] feat(coredump): add panic details to the elf file --- .../include/esp_private/panic_internal.h | 2 +- components/esp_system/panic.c | 10 +- .../espcoredump/include/esp_core_dump.h | 26 ++- .../include_core_dump/esp_core_dump_port.h | 2 +- components/espcoredump/src/core_dump_common.c | 2 +- components/espcoredump/src/core_dump_elf.c | 154 +++++++++++++----- .../src/port/xtensa/core_dump_port.c | 2 +- tools/test_apps/system/panic/README.md | 2 +- tools/test_apps/system/panic/pytest_panic.py | 54 ++++-- .../system/panic/test_panic_util/panic_dut.py | 24 ++- 10 files changed, 212 insertions(+), 66 deletions(-) diff --git a/components/esp_system/include/esp_private/panic_internal.h b/components/esp_system/include/esp_private/panic_internal.h index bdf8a26420..5ead30abee 100644 --- a/components/esp_system/include/esp_private/panic_internal.h +++ b/components/esp_system/include/esp_private/panic_internal.h @@ -19,7 +19,7 @@ extern "C" { #endif extern bool g_panic_abort; - +extern char *g_panic_abort_details; extern void *g_exc_frames[SOC_CPU_CORES_NUM]; // Function to print longer amounts of information such as the details diff --git a/components/esp_system/panic.c b/components/esp_system/panic.c index 1804e84f0f..7e5a3f2c9a 100644 --- a/components/esp_system/panic.c +++ b/components/esp_system/panic.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -63,7 +63,7 @@ #define MWDT_DEFAULT_TICKS_PER_US 500 bool g_panic_abort = false; -static char *s_panic_abort_details = NULL; +char *g_panic_abort_details = NULL; static wdt_hal_context_t rtc_wdt_ctx = RWDT_HAL_CONTEXT_DEFAULT(); @@ -222,7 +222,7 @@ static inline void disable_all_wdts(void) static void print_abort_details(const void *f) { - panic_print_str(s_panic_abort_details); + panic_print_str(g_panic_abort_details); } // Control arrives from chip-specific panic handler, environment prepared for @@ -237,7 +237,7 @@ void esp_panic_handler(panic_info_t *info) // If the exception was due to an abort, override some of the panic info if (g_panic_abort) { info->description = NULL; - info->details = s_panic_abort_details ? print_abort_details : NULL; + info->details = g_panic_abort_details ? print_abort_details : NULL; info->reason = NULL; info->exception = PANIC_EXCEPTION_ABORT; } @@ -438,7 +438,7 @@ void esp_panic_handler(panic_info_t *info) void IRAM_ATTR __attribute__((noreturn, no_sanitize_undefined)) panic_abort(const char *details) { g_panic_abort = true; - s_panic_abort_details = (char *) details; + g_panic_abort_details = (char *) details; #if CONFIG_APPTRACE_ENABLE #if CONFIG_APPTRACE_SV_ENABLE diff --git a/components/espcoredump/include/esp_core_dump.h b/components/espcoredump/include/esp_core_dump.h index c5f5e0a822..04b69f47c9 100644 --- a/components/espcoredump/include/esp_core_dump.h +++ b/components/espcoredump/include/esp_core_dump.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -127,6 +127,30 @@ esp_err_t esp_core_dump_image_erase(void); #if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH && CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF +/** + * @brief Get panic reason from the core dump. + * + * This function retrieves the panic reason from the core dump data and copies it to the provided buffer. + * + * @param[in,out] reason_buffer Pointer to the buffer where the panic reason will be copied. + * @param[in] buffer_size Size of the destination buffer in bytes. + * @return + * - ESP_OK if the panic reason was successfully copied. + * - ESP_ERR_INVALID_ARG if reason_buffer is NULL or buffer_size is 0. + * - Other error codes indicating the outcome of the core dump retrieval. + * - ESP_ERR_NOT_FOUND if the panic reason is not found in the core dump. + * + * Example usage: + * @code{c} + char panic_reason[200]; + esp_err_t err = esp_core_dump_get_panic_reason(panic_reason, sizeof(panic_reason)); + if (err == ESP_OK) { + ESP_LOGW(TAG, "%s", panic_reason); + } + * @endcode + */ +esp_err_t esp_core_dump_get_panic_reason(char *reason_buffer, size_t buffer_size); + /** * @brief Get the summary of a core dump. * diff --git a/components/espcoredump/include_core_dump/esp_core_dump_port.h b/components/espcoredump/include_core_dump/esp_core_dump_port.h index c73e86b035..7f342972bf 100644 --- a/components/espcoredump/include_core_dump/esp_core_dump_port.h +++ b/components/espcoredump/include_core_dump/esp_core_dump_port.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/espcoredump/src/core_dump_common.c b/components/espcoredump/src/core_dump_common.c index e387c3fbc1..c8d905917d 100644 --- a/components/espcoredump/src/core_dump_common.c +++ b/components/espcoredump/src/core_dump_common.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/espcoredump/src/core_dump_elf.c b/components/espcoredump/src/core_dump_elf.c index 35be9347a1..6d14a93472 100644 --- a/components/espcoredump/src/core_dump_elf.c +++ b/components/espcoredump/src/core_dump_elf.c @@ -15,6 +15,7 @@ #include "esp_core_dump_common.h" #ifdef CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF +#include // for the MIN macro #include "esp_app_desc.h" #endif @@ -29,6 +30,7 @@ #define ELF_PR_STATUS_SEG_NUM 0 #define ELF_ESP_CORE_DUMP_INFO_TYPE 8266 #define ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE 677 +#define ELF_ESP_CORE_DUMP_PANIC_DETAILS_TYPE 679 #define ELF_NOTE_NAME_MAX_SIZE 32 #define ELF_APP_SHA256_SIZE 66 @@ -517,13 +519,23 @@ static int elf_write_core_dump_info(core_dump_elf_t *self) } ret = elf_add_note(self, - "EXTRA_INFO", + "ESP_EXTRA_INFO", ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE, extra_info, extra_info_len); ELF_CHECK_ERR((ret > 0), ret, "Extra info note write failed. Returned (%d).", ret); data_len += ret; + if (g_panic_abort_details && strlen(g_panic_abort_details) > 0) { + ret = elf_add_note(self, + "ESP_PANIC_DETAILS", + ELF_ESP_CORE_DUMP_PANIC_DETAILS_TYPE, + g_panic_abort_details, + strlen(g_panic_abort_details)); + ELF_CHECK_ERR((ret > 0), ret, "Panic details note write failed. Returned (%d).", ret); + data_len += ret; + } + ret = elf_process_note_segment(self, data_len); ELF_CHECK_ERR((ret > 0), ret, "EXTRA_INFO note segment processing failure, returned(%d).", ret); @@ -642,8 +654,18 @@ esp_err_t esp_core_dump_write_elf(core_dump_write_config_t *write_cfg) #if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH -/* Below are the helper function to parse the core dump ELF stored in flash */ +typedef struct { +#if ELF_CLASS == ELFCLASS32 + Elf32_Word n_type; + Elf32_Word n_descsz; +#else + Elf64_Word n_type; + Elf64_Word n_descsz; +#endif + void *n_ptr; +} elf_note_content_t; +/* Below are the helper function to parse the core dump ELF stored in flash */ static esp_err_t elf_core_dump_image_mmap(esp_partition_mmap_handle_t* core_data_handle, const void **map_addr) { size_t out_size; @@ -700,62 +722,118 @@ static void elf_parse_exc_task_name(esp_core_dump_summary_t *summary, void *tcb_ ESP_COREDUMP_LOGD("Crashing task %s", summary->exc_task); } -esp_err_t esp_core_dump_get_summary(esp_core_dump_summary_t *summary) +static uint8_t *elf_core_dump_image_ptr(esp_partition_mmap_handle_t *core_data_handle) { - int i; - elf_phdr *ph; - elf_note *note; - const void *map_addr; - size_t consumed_note_sz; - esp_partition_mmap_handle_t core_data_handle; + if (!core_data_handle) { + return NULL; + } - if (!summary) { - return ESP_ERR_INVALID_ARG; - } - esp_err_t err = elf_core_dump_image_mmap(&core_data_handle, &map_addr); + const void *map_addr; + + esp_err_t err = elf_core_dump_image_mmap(core_data_handle, &map_addr); if (err != ESP_OK) { - return err; + return NULL; } - uint8_t *ptr = (uint8_t *) map_addr + sizeof(core_dump_header_t); - elfhdr *eh = (elfhdr *)ptr; + return (uint8_t *)map_addr + sizeof(core_dump_header_t); +} - ESP_COREDUMP_LOGD("ELF ident %02x %c %c %c", eh->e_ident[0], eh->e_ident[1], eh->e_ident[2], eh->e_ident[3]); +static void esp_core_dump_parse_note_section(uint8_t *coredump_data, elf_note_content_t *target_notes, size_t size) +{ + elfhdr *eh = (elfhdr *)coredump_data; + elf_phdr *phdr = (elf_phdr *)(coredump_data + eh->e_phoff); + + ESP_COREDUMP_LOGD("ELF ident %02x %c %c %c", eh->e_ident[0], eh->e_ident[1], eh->e_ident[2], eh->e_ident[3]); ESP_COREDUMP_LOGD("Ph_num %d offset %x", eh->e_phnum, eh->e_phoff); - for (i = 0; i < eh->e_phnum; i++) { - ph = (elf_phdr *)((ptr + i * sizeof(*ph)) + eh->e_phoff); - ESP_COREDUMP_LOGD("PHDR type %d off %x vaddr %x paddr %x filesz %x memsz %x flags %x align %x", - ph->p_type, ph->p_offset, ph->p_vaddr, ph->p_paddr, ph->p_filesz, ph->p_memsz, - ph->p_flags, ph->p_align); + for (unsigned int i = 0; i < eh->e_phnum; i++) { + const elf_phdr *ph = &phdr[i]; + ESP_COREDUMP_LOGD("PHDR type %d off %x vaddr %x paddr %x filesz %x memsz %x flags %x align %x", + ph->p_type, ph->p_offset, ph->p_vaddr, ph->p_paddr, ph->p_filesz, ph->p_memsz, + ph->p_flags, ph->p_align); if (ph->p_type == PT_NOTE) { - consumed_note_sz = 0; - while(consumed_note_sz < ph->p_memsz) { - note = (elf_note *)(ptr + ph->p_offset + consumed_note_sz); - char *nm = (char *)(ptr + ph->p_offset + consumed_note_sz + sizeof(elf_note)); - ESP_COREDUMP_LOGD("Note NameSZ %x DescSZ %x Type %x name %s", note->n_namesz, - note->n_descsz, note->n_type, nm); - if (strncmp(nm, "EXTRA_INFO", note->n_namesz) == 0 ) { - esp_core_dump_summary_parse_extra_info(summary, (void *)(nm + note->n_namesz)); - } - if (strncmp(nm, "ESP_CORE_DUMP_INFO", note->n_namesz) == 0 ) { - elf_parse_version_info(summary, (void *)(nm + note->n_namesz)); - } + size_t consumed_note_sz = 0; + while (consumed_note_sz < ph->p_memsz) { + const elf_note *note = (const elf_note *)(coredump_data + ph->p_offset + consumed_note_sz); + for (size_t idx = 0; idx < size; ++idx) { + if (target_notes[idx].n_type == note->n_type) { + char *nm = (char *)¬e[1]; + target_notes[idx].n_ptr = nm + note->n_namesz; + target_notes[idx].n_descsz = note->n_descsz; + ESP_COREDUMP_LOGD("%d bytes target note (%X) found in the note section", + note->n_descsz, note->n_type); + break; + } + } consumed_note_sz += note->n_namesz + note->n_descsz + sizeof(elf_note); ALIGN(4, consumed_note_sz); } } } +} + +esp_err_t esp_core_dump_get_panic_reason(char *reason_buffer, size_t buffer_size) +{ + if (!reason_buffer || buffer_size == 0) { + return ESP_ERR_INVALID_ARG; + } + + esp_partition_mmap_handle_t core_data_handle; + uint8_t *ptr = elf_core_dump_image_ptr(&core_data_handle); + if (ptr == NULL) { + return ESP_FAIL; + } + + elf_note_content_t target_note = { .n_type = ELF_ESP_CORE_DUMP_PANIC_DETAILS_TYPE, .n_ptr = NULL }; + + esp_core_dump_parse_note_section(ptr, &target_note, 1); + if (target_note.n_ptr) { + size_t len = MIN(target_note.n_descsz, buffer_size - 1); + strncpy(reason_buffer, target_note.n_ptr, len); + reason_buffer[len] = '\0'; + } + esp_partition_munmap(core_data_handle); + + return target_note.n_ptr ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t esp_core_dump_get_summary(esp_core_dump_summary_t *summary) +{ + if (!summary) { + return ESP_ERR_INVALID_ARG; + } + + esp_partition_mmap_handle_t core_data_handle; + uint8_t *ptr = elf_core_dump_image_ptr(&core_data_handle); + if (ptr == NULL) { + return ESP_FAIL; + } + + elf_note_content_t target_notes[2] = { + [0] = { .n_type = ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE, .n_ptr = NULL }, + [1] = { .n_type = ELF_ESP_CORE_DUMP_INFO_TYPE, .n_ptr = NULL } + }; + + esp_core_dump_parse_note_section(ptr, target_notes, sizeof(target_notes) / sizeof(target_notes[0])); + if (target_notes[0].n_ptr) { + esp_core_dump_summary_parse_extra_info(summary, target_notes[0].n_ptr); + } + if (target_notes[1].n_ptr) { + elf_parse_version_info(summary, target_notes[1].n_ptr); + } + /* Following code assumes that task stack segment follows the TCB segment for the respective task. * In general ELF does not impose any restrictions on segments' order so this can be changed without impacting core dump version. * More universal and flexible way would be to retrieve stack start address from crashed task TCB segment and then look for the stack segment with that address. */ + elfhdr *eh = (elfhdr *)ptr; + elf_phdr *phdr = (elf_phdr *)(ptr + eh->e_phoff); int flag = 0; - for (i = 0; i < eh->e_phnum; i++) { - ph = (elf_phdr *)((ptr + i * sizeof(*ph)) + eh->e_phoff); + for (unsigned int i = 0; i < eh->e_phnum; i++) { + const elf_phdr *ph = &phdr[i]; if (ph->p_type == PT_LOAD) { if (flag) { esp_core_dump_summary_parse_exc_regs(summary, (void *)(ptr + ph->p_offset)); - esp_core_dump_summary_parse_backtrace_info(&summary->exc_bt_info, (void *) ph->p_vaddr, + esp_core_dump_summary_parse_backtrace_info(&summary->exc_bt_info, (void *)ph->p_vaddr, (void *)(ptr + ph->p_offset), ph->p_memsz); break; } @@ -765,7 +843,9 @@ esp_err_t esp_core_dump_get_summary(esp_core_dump_summary_t *summary) } } } + esp_partition_munmap(core_data_handle); + return ESP_OK; } diff --git a/components/espcoredump/src/port/xtensa/core_dump_port.c b/components/espcoredump/src/port/xtensa/core_dump_port.c index fb4704b3e3..4ba2107e79 100644 --- a/components/espcoredump/src/port/xtensa/core_dump_port.c +++ b/components/espcoredump/src/port/xtensa/core_dump_port.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/tools/test_apps/system/panic/README.md b/tools/test_apps/system/panic/README.md index 693fbda6da..87ef04a8a6 100644 --- a/tools/test_apps/system/panic/README.md +++ b/tools/test_apps/system/panic/README.md @@ -10,7 +10,7 @@ This test app is relatively complex because it has to check many possible combin - Chip target: esp32, esp32c3, ... - Configuration: default, GDB Stub, Core Dump to UART, ... -Failure scenarios are implemented in [test_panic_main.c](main/test_panic_main.c). The test application receives the name of the scenario from console (e.g. `test_illegal_instruction` ). The failure scenario is executed and the app panics. Once the panic output is printed, the pytest-based test case parses the output and verifies that the behavior of the panic handler was correct. +Failure scenarios are implemented in [test_panic.c](main/test_panic.c). The test application receives the name of the scenario from console (e.g. `test_illegal_instruction` ). The failure scenario is executed and the app panics. Once the panic output is printed, the pytest-based test case parses the output and verifies that the behavior of the panic handler was correct. In [pytest_panic.py](pytest_panic.py), there typically is one test function for each failure scenario. Each test function is then parametrized by `config` parameter. This creates "copies" of the test case for each of the configurations (default, GDB Stub, etc.) Tests are also parametrized with target-specific markers. Most tests can run on every target, but there are a few exceptions, such as failure scenarios specific to the dual-core chips. diff --git a/tools/test_apps/system/panic/pytest_panic.py b/tools/test_apps/system/panic/pytest_panic.py index 9132e77943..8efb3c06b4 100644 --- a/tools/test_apps/system/panic/pytest_panic.py +++ b/tools/test_apps/system/panic/pytest_panic.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: CC0-1.0 import re -from typing import List, Optional +from typing import List, Optional, Union import pexpect import pytest @@ -52,12 +52,16 @@ CONFIGS_EXTRAM_STACK = [ pytest.param('coredump_extram_stack', marks=[pytest.mark.esp32, pytest.mark.esp32s2, pytest.mark.psram, pytest.mark.esp32s3, pytest.mark.quad_psram]) ] +# Panic abort information will start with this string. +PANIC_ABORT_PREFIX = 'Panic reason: ' + def get_default_backtrace(config: str) -> List[str]: return [config, 'app_main', 'main_task', 'vPortTaskWrapper'] -def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[List[str]] = None) -> None: +def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[List[str]] = None, check_cpu_reset: Optional[bool] = True, + expected_coredump: Optional[List[Union[str, re.Pattern]]] = None) -> None: if 'gdbstub' in config: dut.expect_exact('Entering gdb stub now.') dut.start_gdb() @@ -67,10 +71,14 @@ def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[Lis dut.revert_log_level() return # don't expect "Rebooting" output below + # We will only perform comparisons for ELF files, as we are not introducing any new fields to the binary file format. + if 'bin' in config: + expected_coredump = None + if 'uart' in config: - dut.process_coredump_uart() + dut.process_coredump_uart(expected_coredump) elif 'flash' in config: - dut.process_coredump_flash() + dut.process_coredump_flash(expected_coredump) elif 'panic' in config: pass @@ -347,7 +355,8 @@ def test_storeprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> @pytest.mark.generic def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None: dut.run_test_func(test_func_name) - dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0') + regex_pattern = rb'abort\(\) was called at PC [0-9xa-f]+ on core 0' + dut.expect(regex_pattern) if dut.is_xtensa: dut.expect_backtrace() else: @@ -355,6 +364,7 @@ def test_abort(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, @@ -363,6 +373,7 @@ def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None: 'esp_system_abort', 'abort' ] + get_default_backtrace(test_func_name), + expected_coredump=[coredump_pattern] ) @@ -370,7 +381,8 @@ def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None: @pytest.mark.generic def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None: dut.run_test_func(test_func_name) - dut.expect('Undefined behavior of type out_of_bounds') + regex_pattern = rb'Undefined behavior of type out_of_bounds' + dut.expect(regex_pattern) if dut.is_xtensa: dut.expect_backtrace() else: @@ -378,6 +390,7 @@ 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, @@ -387,6 +400,7 @@ def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None: '__ubsan_default_handler', '__ubsan_handle_out_of_bounds' ] + get_default_backtrace(test_func_name), + expected_coredump=[coredump_pattern] ) @@ -398,13 +412,16 @@ def test_abort_cache_disabled( if dut.target == 'esp32s2': pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572') dut.run_test_func(test_func_name) - dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0') + regex_pattern = rb'abort\(\) was called at PC [0-9xa-f]+ on core 0' + dut.expect(regex_pattern) if dut.is_xtensa: dut.expect_backtrace() else: dut.expect_stack_dump() 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, @@ -413,6 +430,7 @@ def test_abort_cache_disabled( 'esp_system_abort', 'abort' ] + get_default_backtrace(test_func_name), + expected_coredump=[coredump_pattern] ) @@ -420,17 +438,16 @@ def test_abort_cache_disabled( @pytest.mark.generic def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None: dut.run_test_func(test_func_name) - dut.expect( - re.compile( - rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$', re.MULTILINE - ) - ) + regex_pattern = rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$' + dut.expect(re.compile(regex_pattern, re.MULTILINE)) if dut.is_xtensa: dut.expect_backtrace() else: dut.expect_stack_dump() 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'), re.MULTILINE) common_test( dut, config, @@ -438,7 +455,9 @@ def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None: 'panic_abort', 'esp_system_abort', '__assert_func' - ] + get_default_backtrace(test_func_name)) + ] + get_default_backtrace(test_func_name), + expected_coredump=[coredump_pattern] + ) @pytest.mark.parametrize('config', CONFIGS, indirect=True) @@ -449,13 +468,16 @@ def test_assert_cache_disabled( if dut.target == 'esp32s2': pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572') dut.run_test_func(test_func_name) - dut.expect(re.compile(rb'assert failed: [0-9xa-fA-F]+.*$', re.MULTILINE)) + regex_pattern = rb'assert failed: [0-9xa-fA-F]+.*$' + dut.expect(re.compile(regex_pattern, re.MULTILINE)) if dut.is_xtensa: dut.expect_backtrace() else: dut.expect_stack_dump() 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'), re.MULTILINE) common_test( dut, config, @@ -463,7 +485,9 @@ def test_assert_cache_disabled( 'panic_abort', 'esp_system_abort', '__assert_func' - ] + get_default_backtrace(test_func_name)) + ] + get_default_backtrace(test_func_name), + expected_coredump=[coredump_pattern] + ) @pytest.mark.esp32 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 4afa79855e..beb7965d6b 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 @@ -2,9 +2,10 @@ # SPDX-License-Identifier: Unlicense OR CC0-1.0 import logging import os +import re import subprocess import sys -from typing import Any, Dict, List, Optional, TextIO +from typing import Any, Dict, List, Optional, TextIO, Union import pexpect from panic_utils import NoGdbProcessError, attach_logger, quote_string, sha256, verify_valid_gdb_subprocess @@ -96,6 +97,19 @@ class PanicTestDut(IdfDut): ) self.expect_exact('ELF file SHA256: ' + elf_sha256[0:elf_sha256_len]) + def expect_coredump(self, output_file_name: str, patterns: List[Union[str, re.Pattern]]) -> None: + with open(output_file_name, 'r') as file: + coredump = file.read() + for pattern in patterns: + if isinstance(pattern, str): + position = coredump.find(pattern) + assert position != -1, f"'{pattern}' not found in the coredump output" + elif isinstance(pattern, re.Pattern): + match = pattern.findall(coredump) + assert match, f"'{pattern.pattern}' not found in the coredump output" + else: + raise ValueError(f'Unsupported input type: {type(pattern).__name__}') + def _call_espcoredump( self, extra_args: List[str], coredump_file_name: str, output_file_name: str ) -> None: @@ -122,7 +136,7 @@ class PanicTestDut(IdfDut): self.coredump_output.flush() self.coredump_output.seek(0) - def process_coredump_uart(self) -> None: + def process_coredump_uart(self, expected: Optional[List[Union[str, re.Pattern]]] = None) -> None: """Extract the core dump from UART output of the test, run espcoredump on it""" self.expect(self.COREDUMP_UART_START) res = self.expect('(.+)' + self.COREDUMP_UART_END) @@ -135,8 +149,10 @@ class PanicTestDut(IdfDut): self._call_espcoredump( ['--core-format', 'b64'], coredump_file.name, output_file_name ) + if expected: + self.expect_coredump(output_file_name, expected) - def process_coredump_flash(self) -> None: + def process_coredump_flash(self, expected: Optional[List[Union[str, re.Pattern]]] = None) -> None: """Extract the core dump from flash, run espcoredump on it""" coredump_file_name = os.path.join(self.logdir, 'coredump_data.bin') logging.info('Writing flash binary core dump to %s', coredump_file_name) @@ -146,6 +162,8 @@ class PanicTestDut(IdfDut): self._call_espcoredump( ['--core-format', 'raw'], coredump_file_name, output_file_name ) + if expected: + self.expect_coredump(output_file_name, expected) def gdb_write(self, command: str) -> Any: """