mirror of
https://github.com/espressif/esp-idf
synced 2025-03-11 10:09:08 -04:00
Merge branch 'feature/coredump_panic_details_v5.1' into 'release/v5.1'
feat(coredump): add panic details to the elf file (v5.1) See merge request espressif/esp-idf!25597
This commit is contained in:
commit
b3011ed759
@ -19,7 +19,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern bool g_panic_abort;
|
extern bool g_panic_abort;
|
||||||
|
extern char *g_panic_abort_details;
|
||||||
extern void *g_exc_frames[SOC_CPU_CORES_NUM];
|
extern void *g_exc_frames[SOC_CPU_CORES_NUM];
|
||||||
|
|
||||||
// Function to print longer amounts of information such as the details
|
// Function to print longer amounts of information such as the details
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -63,7 +63,7 @@
|
|||||||
#define MWDT_DEFAULT_TICKS_PER_US 500
|
#define MWDT_DEFAULT_TICKS_PER_US 500
|
||||||
|
|
||||||
bool g_panic_abort = false;
|
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();
|
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)
|
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
|
// 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 the exception was due to an abort, override some of the panic info
|
||||||
if (g_panic_abort) {
|
if (g_panic_abort) {
|
||||||
info->description = NULL;
|
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->reason = NULL;
|
||||||
info->exception = PANIC_EXCEPTION_ABORT;
|
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)
|
void IRAM_ATTR __attribute__((noreturn, no_sanitize_undefined)) panic_abort(const char *details)
|
||||||
{
|
{
|
||||||
g_panic_abort = true;
|
g_panic_abort = true;
|
||||||
s_panic_abort_details = (char *) details;
|
g_panic_abort_details = (char *) details;
|
||||||
|
|
||||||
#if CONFIG_APPTRACE_ENABLE
|
#if CONFIG_APPTRACE_ENABLE
|
||||||
#if CONFIG_APPTRACE_SV_ENABLE
|
#if CONFIG_APPTRACE_SV_ENABLE
|
||||||
|
@ -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
|
* 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
|
#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.
|
* @brief Get the summary of a core dump.
|
||||||
*
|
*
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include "esp_core_dump_common.h"
|
#include "esp_core_dump_common.h"
|
||||||
|
|
||||||
#ifdef CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF
|
#ifdef CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF
|
||||||
|
#include <sys/param.h> // for the MIN macro
|
||||||
#include "esp_app_desc.h"
|
#include "esp_app_desc.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -29,6 +30,7 @@
|
|||||||
#define ELF_PR_STATUS_SEG_NUM 0
|
#define ELF_PR_STATUS_SEG_NUM 0
|
||||||
#define ELF_ESP_CORE_DUMP_INFO_TYPE 8266
|
#define ELF_ESP_CORE_DUMP_INFO_TYPE 8266
|
||||||
#define ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE 677
|
#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_NOTE_NAME_MAX_SIZE 32
|
||||||
#define ELF_APP_SHA256_SIZE 66
|
#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,
|
ret = elf_add_note(self,
|
||||||
"EXTRA_INFO",
|
"ESP_EXTRA_INFO",
|
||||||
ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE,
|
ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE,
|
||||||
extra_info,
|
extra_info,
|
||||||
extra_info_len);
|
extra_info_len);
|
||||||
ELF_CHECK_ERR((ret > 0), ret, "Extra info note write failed. Returned (%d).", ret);
|
ELF_CHECK_ERR((ret > 0), ret, "Extra info note write failed. Returned (%d).", ret);
|
||||||
data_len += 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);
|
ret = elf_process_note_segment(self, data_len);
|
||||||
ELF_CHECK_ERR((ret > 0), ret,
|
ELF_CHECK_ERR((ret > 0), ret,
|
||||||
"EXTRA_INFO note segment processing failure, returned(%d).", 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
|
#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)
|
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;
|
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_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;
|
if (!core_data_handle) {
|
||||||
elf_phdr *ph;
|
return NULL;
|
||||||
elf_note *note;
|
}
|
||||||
const void *map_addr;
|
|
||||||
size_t consumed_note_sz;
|
|
||||||
esp_partition_mmap_handle_t core_data_handle;
|
|
||||||
|
|
||||||
if (!summary) {
|
const void *map_addr;
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
esp_err_t err = elf_core_dump_image_mmap(core_data_handle, &map_addr);
|
||||||
esp_err_t err = elf_core_dump_image_mmap(&core_data_handle, &map_addr);
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
return err;
|
return NULL;
|
||||||
}
|
}
|
||||||
uint8_t *ptr = (uint8_t *) map_addr + sizeof(core_dump_header_t);
|
return (uint8_t *)map_addr + sizeof(core_dump_header_t);
|
||||||
elfhdr *eh = (elfhdr *)ptr;
|
}
|
||||||
|
|
||||||
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);
|
ESP_COREDUMP_LOGD("Ph_num %d offset %x", eh->e_phnum, eh->e_phoff);
|
||||||
|
|
||||||
for (i = 0; i < eh->e_phnum; i++) {
|
for (unsigned int i = 0; i < eh->e_phnum; i++) {
|
||||||
ph = (elf_phdr *)((ptr + i * sizeof(*ph)) + eh->e_phoff);
|
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",
|
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_type, ph->p_offset, ph->p_vaddr, ph->p_paddr, ph->p_filesz, ph->p_memsz,
|
||||||
ph->p_flags, ph->p_align);
|
ph->p_flags, ph->p_align);
|
||||||
if (ph->p_type == PT_NOTE) {
|
if (ph->p_type == PT_NOTE) {
|
||||||
consumed_note_sz = 0;
|
size_t consumed_note_sz = 0;
|
||||||
while(consumed_note_sz < ph->p_memsz) {
|
while (consumed_note_sz < ph->p_memsz) {
|
||||||
note = (elf_note *)(ptr + ph->p_offset + consumed_note_sz);
|
const elf_note *note = (const elf_note *)(coredump_data + ph->p_offset + consumed_note_sz);
|
||||||
char *nm = (char *)(ptr + ph->p_offset + consumed_note_sz + sizeof(elf_note));
|
for (size_t idx = 0; idx < size; ++idx) {
|
||||||
ESP_COREDUMP_LOGD("Note NameSZ %x DescSZ %x Type %x name %s", note->n_namesz,
|
if (target_notes[idx].n_type == note->n_type) {
|
||||||
note->n_descsz, note->n_type, nm);
|
char *nm = (char *)¬e[1];
|
||||||
if (strncmp(nm, "EXTRA_INFO", note->n_namesz) == 0 ) {
|
target_notes[idx].n_ptr = nm + note->n_namesz;
|
||||||
esp_core_dump_summary_parse_extra_info(summary, (void *)(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",
|
||||||
if (strncmp(nm, "ESP_CORE_DUMP_INFO", note->n_namesz) == 0 ) {
|
note->n_descsz, note->n_type);
|
||||||
elf_parse_version_info(summary, (void *)(nm + note->n_namesz));
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
consumed_note_sz += note->n_namesz + note->n_descsz + sizeof(elf_note);
|
consumed_note_sz += note->n_namesz + note->n_descsz + sizeof(elf_note);
|
||||||
ALIGN(4, consumed_note_sz);
|
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.
|
/* 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.
|
* 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.
|
* 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;
|
int flag = 0;
|
||||||
for (i = 0; i < eh->e_phnum; i++) {
|
for (unsigned int i = 0; i < eh->e_phnum; i++) {
|
||||||
ph = (elf_phdr *)((ptr + i * sizeof(*ph)) + eh->e_phoff);
|
const elf_phdr *ph = &phdr[i];
|
||||||
if (ph->p_type == PT_LOAD) {
|
if (ph->p_type == PT_LOAD) {
|
||||||
if (flag) {
|
if (flag) {
|
||||||
esp_core_dump_summary_parse_exc_regs(summary, (void *)(ptr + ph->p_offset));
|
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);
|
(void *)(ptr + ph->p_offset), ph->p_memsz);
|
||||||
break;
|
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);
|
esp_partition_munmap(core_data_handle);
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,7 @@ This test app is relatively complex because it has to check many possible combin
|
|||||||
- Chip target: esp32, esp32c3, ...
|
- Chip target: esp32, esp32c3, ...
|
||||||
- Configuration: default, GDB Stub, Core Dump to UART, ...
|
- 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.
|
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.
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# SPDX-License-Identifier: CC0-1.0
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
import pexpect
|
import pexpect
|
||||||
import pytest
|
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])
|
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]:
|
def get_default_backtrace(config: str) -> List[str]:
|
||||||
return [config, 'app_main', 'main_task', 'vPortTaskWrapper']
|
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:
|
if 'gdbstub' in config:
|
||||||
dut.expect_exact('Entering gdb stub now.')
|
dut.expect_exact('Entering gdb stub now.')
|
||||||
dut.start_gdb()
|
dut.start_gdb()
|
||||||
@ -67,10 +71,14 @@ def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[Lis
|
|||||||
dut.revert_log_level()
|
dut.revert_log_level()
|
||||||
return # don't expect "Rebooting" output below
|
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:
|
if 'uart' in config:
|
||||||
dut.process_coredump_uart()
|
dut.process_coredump_uart(expected_coredump)
|
||||||
elif 'flash' in config:
|
elif 'flash' in config:
|
||||||
dut.process_coredump_flash()
|
dut.process_coredump_flash(expected_coredump)
|
||||||
elif 'panic' in config:
|
elif 'panic' in config:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -347,7 +355,8 @@ def test_storeprohibited(dut: PanicTestDut, config: str, test_func_name: str) ->
|
|||||||
@pytest.mark.generic
|
@pytest.mark.generic
|
||||||
def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
||||||
dut.run_test_func(test_func_name)
|
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:
|
if dut.is_xtensa:
|
||||||
dut.expect_backtrace()
|
dut.expect_backtrace()
|
||||||
else:
|
else:
|
||||||
@ -355,6 +364,7 @@ def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
|||||||
dut.expect_elf_sha256()
|
dut.expect_elf_sha256()
|
||||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
||||||
|
|
||||||
|
coredump_pattern = re.compile(PANIC_ABORT_PREFIX + regex_pattern.decode('utf-8'))
|
||||||
common_test(
|
common_test(
|
||||||
dut,
|
dut,
|
||||||
config,
|
config,
|
||||||
@ -363,6 +373,7 @@ def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
|||||||
'esp_system_abort',
|
'esp_system_abort',
|
||||||
'abort'
|
'abort'
|
||||||
] + get_default_backtrace(test_func_name),
|
] + 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
|
@pytest.mark.generic
|
||||||
def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
||||||
dut.run_test_func(test_func_name)
|
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:
|
if dut.is_xtensa:
|
||||||
dut.expect_backtrace()
|
dut.expect_backtrace()
|
||||||
else:
|
else:
|
||||||
@ -378,6 +390,7 @@ def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
|||||||
dut.expect_elf_sha256()
|
dut.expect_elf_sha256()
|
||||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
||||||
|
|
||||||
|
coredump_pattern = re.compile(PANIC_ABORT_PREFIX + regex_pattern.decode('utf-8'))
|
||||||
common_test(
|
common_test(
|
||||||
dut,
|
dut,
|
||||||
config,
|
config,
|
||||||
@ -387,6 +400,7 @@ def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
|||||||
'__ubsan_default_handler',
|
'__ubsan_default_handler',
|
||||||
'__ubsan_handle_out_of_bounds'
|
'__ubsan_handle_out_of_bounds'
|
||||||
] + get_default_backtrace(test_func_name),
|
] + get_default_backtrace(test_func_name),
|
||||||
|
expected_coredump=[coredump_pattern]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -398,13 +412,16 @@ def test_abort_cache_disabled(
|
|||||||
if dut.target == 'esp32s2':
|
if dut.target == 'esp32s2':
|
||||||
pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
|
pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
|
||||||
dut.run_test_func(test_func_name)
|
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:
|
if dut.is_xtensa:
|
||||||
dut.expect_backtrace()
|
dut.expect_backtrace()
|
||||||
else:
|
else:
|
||||||
dut.expect_stack_dump()
|
dut.expect_stack_dump()
|
||||||
dut.expect_elf_sha256()
|
dut.expect_elf_sha256()
|
||||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
||||||
|
|
||||||
|
coredump_pattern = re.compile(PANIC_ABORT_PREFIX + regex_pattern.decode('utf-8'))
|
||||||
common_test(
|
common_test(
|
||||||
dut,
|
dut,
|
||||||
config,
|
config,
|
||||||
@ -413,6 +430,7 @@ def test_abort_cache_disabled(
|
|||||||
'esp_system_abort',
|
'esp_system_abort',
|
||||||
'abort'
|
'abort'
|
||||||
] + get_default_backtrace(test_func_name),
|
] + get_default_backtrace(test_func_name),
|
||||||
|
expected_coredump=[coredump_pattern]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -420,17 +438,16 @@ def test_abort_cache_disabled(
|
|||||||
@pytest.mark.generic
|
@pytest.mark.generic
|
||||||
def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
||||||
dut.run_test_func(test_func_name)
|
dut.run_test_func(test_func_name)
|
||||||
dut.expect(
|
regex_pattern = rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$'
|
||||||
re.compile(
|
dut.expect(re.compile(regex_pattern, re.MULTILINE))
|
||||||
rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$', re.MULTILINE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if dut.is_xtensa:
|
if dut.is_xtensa:
|
||||||
dut.expect_backtrace()
|
dut.expect_backtrace()
|
||||||
else:
|
else:
|
||||||
dut.expect_stack_dump()
|
dut.expect_stack_dump()
|
||||||
dut.expect_elf_sha256()
|
dut.expect_elf_sha256()
|
||||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
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(
|
common_test(
|
||||||
dut,
|
dut,
|
||||||
config,
|
config,
|
||||||
@ -438,7 +455,9 @@ def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
|
|||||||
'panic_abort',
|
'panic_abort',
|
||||||
'esp_system_abort',
|
'esp_system_abort',
|
||||||
'__assert_func'
|
'__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)
|
@pytest.mark.parametrize('config', CONFIGS, indirect=True)
|
||||||
@ -449,13 +468,16 @@ def test_assert_cache_disabled(
|
|||||||
if dut.target == 'esp32s2':
|
if dut.target == 'esp32s2':
|
||||||
pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
|
pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
|
||||||
dut.run_test_func(test_func_name)
|
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:
|
if dut.is_xtensa:
|
||||||
dut.expect_backtrace()
|
dut.expect_backtrace()
|
||||||
else:
|
else:
|
||||||
dut.expect_stack_dump()
|
dut.expect_stack_dump()
|
||||||
dut.expect_elf_sha256()
|
dut.expect_elf_sha256()
|
||||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
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(
|
common_test(
|
||||||
dut,
|
dut,
|
||||||
config,
|
config,
|
||||||
@ -463,7 +485,9 @@ def test_assert_cache_disabled(
|
|||||||
'panic_abort',
|
'panic_abort',
|
||||||
'esp_system_abort',
|
'esp_system_abort',
|
||||||
'__assert_func'
|
'__assert_func'
|
||||||
] + get_default_backtrace(test_func_name))
|
] + get_default_backtrace(test_func_name),
|
||||||
|
expected_coredump=[coredump_pattern]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.esp32
|
@pytest.mark.esp32
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List, Optional, TextIO
|
from typing import Any, Dict, List, Optional, TextIO, Union
|
||||||
|
|
||||||
import pexpect
|
import pexpect
|
||||||
from panic_utils import NoGdbProcessError, attach_logger, quote_string, sha256, verify_valid_gdb_subprocess
|
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])
|
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(
|
def _call_espcoredump(
|
||||||
self, extra_args: List[str], coredump_file_name: str, output_file_name: str
|
self, extra_args: List[str], coredump_file_name: str, output_file_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -122,7 +136,7 @@ class PanicTestDut(IdfDut):
|
|||||||
self.coredump_output.flush()
|
self.coredump_output.flush()
|
||||||
self.coredump_output.seek(0)
|
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"""
|
"""Extract the core dump from UART output of the test, run espcoredump on it"""
|
||||||
self.expect(self.COREDUMP_UART_START)
|
self.expect(self.COREDUMP_UART_START)
|
||||||
res = self.expect('(.+)' + self.COREDUMP_UART_END)
|
res = self.expect('(.+)' + self.COREDUMP_UART_END)
|
||||||
@ -135,8 +149,10 @@ class PanicTestDut(IdfDut):
|
|||||||
self._call_espcoredump(
|
self._call_espcoredump(
|
||||||
['--core-format', 'b64'], coredump_file.name, output_file_name
|
['--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"""
|
"""Extract the core dump from flash, run espcoredump on it"""
|
||||||
coredump_file_name = os.path.join(self.logdir, 'coredump_data.bin')
|
coredump_file_name = os.path.join(self.logdir, 'coredump_data.bin')
|
||||||
logging.info('Writing flash binary core dump to %s', coredump_file_name)
|
logging.info('Writing flash binary core dump to %s', coredump_file_name)
|
||||||
@ -146,6 +162,8 @@ class PanicTestDut(IdfDut):
|
|||||||
self._call_espcoredump(
|
self._call_espcoredump(
|
||||||
['--core-format', 'raw'], coredump_file_name, output_file_name
|
['--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:
|
def gdb_write(self, command: str) -> Any:
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user