Kaynağa Gözat

Merge branch 'feature/coredump_panic_details' into 'master'

feat(coredump): add panic details to the elf file

Closes IDF-7788 and IDF-7789

See merge request espressif/esp-idf!25198
Roland Dobai 2 yıl önce
ebeveyn
işleme
2fbb63124f

+ 1 - 1
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

+ 5 - 5
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
  */
@@ -65,7 +65,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();
 
@@ -224,7 +224,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
@@ -239,7 +239,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

+ 25 - 1
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.
  *

+ 1 - 1
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
  */

+ 1 - 1
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
  */

+ 118 - 38
components/espcoredump/src/core_dump_elf.c

@@ -16,6 +16,7 @@
 #include "hal/efuse_hal.h"
 
 #ifdef CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF
+#include <sys/param.h>      // for the MIN macro
 #include "esp_app_desc.h"
 #endif
 
@@ -30,6 +31,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
 
@@ -521,13 +523,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);
@@ -647,8 +659,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;
@@ -705,62 +727,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 (!summary) {
-        return ESP_ERR_INVALID_ARG;
-    }
-    esp_err_t err = elf_core_dump_image_mmap(&core_data_handle, &map_addr);
+	if (!core_data_handle) {
+		return NULL;
+	}
+
+	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);
+}
+
+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("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 *)&note[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;
             }
@@ -770,7 +848,9 @@ esp_err_t esp_core_dump_get_summary(esp_core_dump_summary_t *summary)
             }
         }
     }
+
     esp_partition_munmap(core_data_handle);
+
     return ESP_OK;
 }
 

+ 1 - 1
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
  */

+ 1 - 1
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.
 

+ 37 - 15
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
@@ -67,12 +67,16 @@ CONFIGS_HW_STACK_GUARD = [
     pytest.param('panic', marks=TARGETS_HW_STACK_GUARD),
 ]
 
+# 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, check_cpu_reset: Optional[bool] = True) -> 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()
@@ -82,10 +86,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
 
@@ -371,7 +379,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:
@@ -379,6 +388,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,
@@ -387,6 +397,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]
     )
 
 
@@ -394,7 +405,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:
@@ -402,6 +414,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,
@@ -411,6 +424,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]
     )
 
 
@@ -422,7 +436,8 @@ 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:
@@ -430,6 +445,7 @@ def test_abort_cache_disabled(
     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,
@@ -438,6 +454,7 @@ def test_abort_cache_disabled(
             'esp_system_abort',
             'abort'
         ] + get_default_backtrace(test_func_name),
+        expected_coredump=[coredump_pattern]
     )
 
 
@@ -445,17 +462,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,
@@ -463,7 +479,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)
@@ -474,7 +492,8 @@ 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:
@@ -482,6 +501,7 @@ def test_assert_cache_disabled(
     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,
@@ -489,7 +509,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

+ 21 - 3
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
@@ -100,6 +101,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:
@@ -126,7 +140,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)
@@ -139,8 +153,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)
@@ -150,6 +166,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:
         """