diff --git a/components/espcoredump/Kconfig b/components/espcoredump/Kconfig index 8de6fdd26c..d95090c743 100644 --- a/components/espcoredump/Kconfig +++ b/components/espcoredump/Kconfig @@ -47,6 +47,18 @@ menu "Core dump" depends on ESP_COREDUMP_DATA_FORMAT_ELF endchoice + config ESP_COREDUMP_CAPTURE_DRAM + bool "Include whole .bss and .data sections and heap data into core dump file" + default n + #TODO: Heap walker api is not ready for the esp32c5 (IDF-9641) + depends on ESP_COREDUMP_DATA_FORMAT_ELF && !IDF_TARGET_ESP32C5 + help + Storing these sections can help with easier debugging and troubleshooting. + However, additional storage space will be required in the core dump partition. + At least 128KB should be reserved, but the actual amount required may vary based + on the application's DRAM usage. + Note that sections located in external RAM will not be stored. + config ESP_COREDUMP_CHECK_BOOT bool "Check core dump data integrity on boot" default y @@ -112,7 +124,8 @@ menu "Core dump" help Size of the memory to be reserved for core dump stack. If 0 core dump process will run on the stack of crashed task/ISR, otherwise special stack will be allocated. - To ensure that core dump itself will not overflow task/ISR stack set this to the value above 800. + To ensure that core dump itself will not overflow task/ISR stack set this to the value around 1300-1800 + depending on the chosen checksum calculation method. SHA256 method needs more stack space than CRC32. NOTE: It eats DRAM. config ESP_COREDUMP_SUMMARY_STACKDUMP_SIZE diff --git a/components/espcoredump/include_core_dump/esp_core_dump_common.h b/components/espcoredump/include_core_dump/esp_core_dump_common.h index f5a26749e7..9e9efb2ef1 100644 --- a/components/espcoredump/include_core_dump/esp_core_dump_common.h +++ b/components/espcoredump/include_core_dump/esp_core_dump_common.h @@ -20,15 +20,25 @@ extern "C" { * One can use these definitions to retrieve the start address and/or the size * of a specific region using the functions below. */ + typedef enum { - COREDUMP_MEMORY_DRAM, COREDUMP_MEMORY_IRAM, +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + COREDUMP_MEMORY_DRAM_BSS, + COREDUMP_MEMORY_DRAM_DATA, +#if CONFIG_IDF_TARGET_ESP32P4 + COREDUMP_MEMORY_DRAM_BSS_HIGH, + COREDUMP_MEMORY_DRAM_DATA_HIGH, +#endif +#else + COREDUMP_MEMORY_DRAM, +#endif #if SOC_RTC_MEM_SUPPORTED COREDUMP_MEMORY_RTC, COREDUMP_MEMORY_RTC_FAST, #endif COREDUMP_MEMORY_MAX, - COREDUMP_MEMORY_START = COREDUMP_MEMORY_DRAM + COREDUMP_MEMORY_START = COREDUMP_MEMORY_IRAM } coredump_region_t; /** @@ -126,6 +136,12 @@ esp_err_t esp_core_dump_write_data(core_dump_write_data_t *wr_data, void *data, */ esp_err_t esp_core_dump_write_end(core_dump_write_data_t *wr_data); +/** + * @brief Retrieve the stack information which will be used from the coredump module itself. + * It will show the whole stack boundaries in case the stack is shared with the crashed task. + */ +void esp_core_dump_get_own_stack_info(uint32_t *addr, uint32_t *size); + /** * @brief Stores the core dump in either binary or ELF format. */ @@ -157,7 +173,7 @@ static inline core_dump_task_handle_t esp_core_dump_get_current_task_handle(void * @brief Get the length, in bytes, of a given memory location. Padding is * taken into account in this calculation. * - * @param start Start address of the momery location. + * @param start Start address of the memory location. * @param end End address of the memory location. * * @return Size of the memory location, multiple of sizeof(uint32_t). diff --git a/components/espcoredump/src/core_dump_common.c b/components/espcoredump/src/core_dump_common.c index afca899b60..d455bc9d3b 100644 --- a/components/espcoredump/src/core_dump_common.c +++ b/components/espcoredump/src/core_dump_common.c @@ -25,6 +25,25 @@ const static char TAG[] __attribute__((unused)) = "esp_core_dump_common"; /** * @brief Memory regions to dump, defined at compile time. */ +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM +#if !CONFIG_IDF_TARGET_ESP32P4 +extern int _bss_start; +extern int _bss_end; +extern int _data_start; +extern int _data_end; +#else +extern int _bss_start_low; +extern int _bss_end_low; +extern int _data_start_low; +extern int _data_end_low; +extern int _bss_start_high; +extern int _bss_end_high; +extern int _data_start_high; +extern int _data_end_high; +#endif +#endif + +/* Regions for the user defined variable locations */ extern int _coredump_dram_start; extern int _coredump_dram_end; extern int _coredump_iram_start; @@ -160,6 +179,7 @@ FORCE_INLINE_ATTR void esp_core_dump_setup_stack(void) FORCE_INLINE_ATTR void esp_core_dump_report_stack_usage(void) { } + #endif // CONFIG_ESP_COREDUMP_STACK_SIZE > 0 static void* s_exc_frame = NULL; @@ -254,19 +274,29 @@ uint32_t esp_core_dump_get_user_ram_segments(void) return total_sz; } -uint32_t esp_core_dump_get_user_ram_size(void) -{ - uint32_t total_sz = 0; - - total_sz += COREDUMP_GET_MEMORY_SIZE(&_coredump_dram_end, &_coredump_dram_start); -#if SOC_RTC_MEM_SUPPORTED - total_sz += COREDUMP_GET_MEMORY_SIZE(&_coredump_rtc_end, &_coredump_rtc_start); - total_sz += COREDUMP_GET_MEMORY_SIZE(&_coredump_rtc_fast_end, &_coredump_rtc_fast_start); +static const struct { + int *start; + int *end; +} s_memory_sections[COREDUMP_MEMORY_MAX] = { + [COREDUMP_MEMORY_IRAM] = { &_coredump_iram_start, &_coredump_iram_end }, +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM +#if !CONFIG_IDF_TARGET_ESP32P4 + [COREDUMP_MEMORY_DRAM_BSS] = { &_bss_start, &_bss_end }, + [COREDUMP_MEMORY_DRAM_DATA] = { &_data_start, &_data_end }, +#else + [COREDUMP_MEMORY_DRAM_BSS] = { &_bss_start_low, &_bss_end_low }, + [COREDUMP_MEMORY_DRAM_DATA] = { &_data_start_low, &_data_end_low }, + [COREDUMP_MEMORY_DRAM_BSS_HIGH] = { &_bss_start_high, &_bss_end_high }, + [COREDUMP_MEMORY_DRAM_DATA_HIGH] = { &_data_start_high, &_data_end_high }, #endif - total_sz += COREDUMP_GET_MEMORY_SIZE(&_coredump_iram_end, &_coredump_iram_start); - - return total_sz; -} +#else + [COREDUMP_MEMORY_DRAM] = { &_coredump_dram_start, &_coredump_dram_end }, +#endif +#if SOC_RTC_MEM_SUPPORTED + [COREDUMP_MEMORY_RTC] = { &_coredump_rtc_start, &_coredump_rtc_end }, + [COREDUMP_MEMORY_RTC_FAST] = { &_coredump_rtc_fast_start, &_coredump_rtc_fast_end }, +#endif +}; int esp_core_dump_get_user_ram_info(coredump_region_t region, uint32_t *start) { @@ -274,36 +304,34 @@ int esp_core_dump_get_user_ram_info(coredump_region_t region, uint32_t *start) ESP_COREDUMP_DEBUG_ASSERT(start != NULL); - switch (region) { - case COREDUMP_MEMORY_DRAM: - *start = (uint32_t)&_coredump_dram_start; - total_sz = (uint8_t *)&_coredump_dram_end - (uint8_t *)&_coredump_dram_start; - break; - - case COREDUMP_MEMORY_IRAM: - *start = (uint32_t)&_coredump_iram_start; - total_sz = (uint8_t *)&_coredump_iram_end - (uint8_t *)&_coredump_iram_start; - break; - -#if SOC_RTC_MEM_SUPPORTED - case COREDUMP_MEMORY_RTC: - *start = (uint32_t)&_coredump_rtc_start; - total_sz = (uint8_t *)&_coredump_rtc_end - (uint8_t *)&_coredump_rtc_start; - break; - - case COREDUMP_MEMORY_RTC_FAST: - *start = (uint32_t)&_coredump_rtc_fast_start; - total_sz = (uint8_t *)&_coredump_rtc_fast_end - (uint8_t *)&_coredump_rtc_fast_start; - break; -#endif - - default: - break; + if (region >= COREDUMP_MEMORY_START && region < COREDUMP_MEMORY_MAX) { + total_sz = (uint8_t *)s_memory_sections[region].end - (uint8_t *)s_memory_sections[region].start; + *start = (uint32_t)s_memory_sections[region].start; } return total_sz; } +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM +void esp_core_dump_get_own_stack_info(uint32_t *addr, uint32_t *size) +{ +#if CONFIG_ESP_COREDUMP_STACK_SIZE > 0 + /* Custom stack reserved for the coredump */ + *addr = (uint32_t)s_coredump_stack; + *size = sizeof(s_coredump_stack); +#else + /* Shared stack with the crashed task */ + core_dump_task_handle_t handle = esp_core_dump_get_current_task_handle(); + TaskSnapshot_t rtos_snapshot = { 0 }; + vTaskGetSnapshot(handle, &rtos_snapshot); + StaticTask_t *current = (StaticTask_t *)handle; + *addr = (uint32_t)current->pxDummy6; //pxStack + *size = (uint32_t)rtos_snapshot.pxTopOfStack - (uint32_t)current->pxDummy6; /* free */ +#endif +} + +#endif /* CONFIG_ESP_COREDUMP_CAPTURE_DRAM */ + inline bool esp_core_dump_tcb_addr_is_sane(uint32_t addr) { return esp_core_dump_mem_seg_is_sane(addr, esp_core_dump_get_tcb_len()); diff --git a/components/espcoredump/src/core_dump_elf.c b/components/espcoredump/src/core_dump_elf.c index f83b49f16e..16f03c5ae5 100644 --- a/components/espcoredump/src/core_dump_elf.c +++ b/components/espcoredump/src/core_dump_elf.c @@ -80,6 +80,11 @@ typedef struct _core_dump_elf_t { uint16_t segs_count; core_dump_write_data_t write_data; uint32_t note_data_size; /* can be used where static storage needed */ +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + /* To avoid checksum failure, coredump stack region will be excluded while storing the sections. */ + uint32_t coredump_stack_start; + uint32_t coredump_stack_size; +#endif } core_dump_elf_t; typedef struct { @@ -474,10 +479,17 @@ static int elf_write_tasks_data(core_dump_elf_t *self) bad_tasks_num++; continue; } - ret = elf_save_task(self, &task_hdr); - ELF_CHECK_ERR((ret > 0), ret, - "Task %x, TCB write failed, return (%d).", task_iter.pxTaskHandle, ret); - elf_len += ret; + +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + /* Only crashed task data will be saved here. The other task's data will be automatically saved within the sections */ + if (esp_core_dump_get_current_task_handle() != task_iter.pxTaskHandle) +#endif + { + ret = elf_save_task(self, &task_hdr); + ELF_CHECK_ERR((ret > 0), ret, + "Task %x, TCB write failed, return (%d).", task_iter.pxTaskHandle, ret); + elf_len += ret; + } if (interrupted_stack.size > 0) { ESP_COREDUMP_LOG_PROCESS("Add interrupted task stack %lu bytes @ %x", interrupted_stack.size, interrupted_stack.start); @@ -493,28 +505,148 @@ static int elf_write_tasks_data(core_dump_elf_t *self) return elf_len; } +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + +/* Coredump stack will also be used by the checksum functions while saving sections. + * There is a potential for inconsistency when writing coredump stack to the flash and calculating checksum simultaneously. + * This is because, coredump stack will be modified during the process, leading to incorrect checksum calculations. + * To mitigate this issue, it's important to ensure that the coredump stack excluded from checksum calculation by + * filter out from the written regions. + * Typically, the coredump stack can be located in two different sections. + * 1. In the bss section; + * 1.a if `CONFIG_ESP_COREDUMP_STACK_SIZE` set to a nonzero value + * 1.b if the crashed task is created with a static task buffer using the xTaskCreateStatic() api + * 2. In the heap section, if custom stack is not defined and the crashed task buffer is allocated in the heap + * with the xTaskCreate() api + * + * esp_core_dump_store_section() will check if the coredump stack is located inside the section. + * If it is, this part will be skipped. + * |+++++++++| xxxxxxxxxxxxxx |++++++++| + * |+++++++++| coredump stack |++++++++| +*/ +static int esp_core_dump_store_section(core_dump_elf_t *self, uint32_t start, uint32_t data_len) +{ + uint32_t end = start + data_len; + int total_sz = 0; + int ret; + + if (self->coredump_stack_start > start && self->coredump_stack_start < end) { + /* write until the coredump stack. */ + data_len = self->coredump_stack_start - start; + ret = elf_add_segment(self, PT_LOAD, + start, + (void*)start, + data_len); + + if (ret <= 0) { + return ret; + } + total_sz += ret; + + /* Skip coredump stack and set offset for the rest of the section */ + start = self->coredump_stack_start + self->coredump_stack_size; + data_len = end - start; + } + + if (data_len > 0) { + ret = elf_add_segment(self, PT_LOAD, + (uint32_t)start, + (void*)start, + (uint32_t)data_len); + if (ret <= 0) { + return ret; + } + total_sz += ret; + } + + return total_sz; +} + +typedef struct { + core_dump_elf_t *self; + int *total_sz; + int ret; +} heap_block_data_t; + +bool esp_core_dump_write_heap_blocks(walker_heap_into_t heap_info, walker_block_info_t block_info, void* user_data) +{ + heap_block_data_t *param = user_data; + int *total_sz = param->total_sz; + core_dump_elf_t *self = param->self; + int *ret = ¶m->ret; + + if (*ret <= 0) { + /* There was a flash write failure at the previous write attempt */ + return false; + } + + if ((intptr_t)heap_info.end - (intptr_t)block_info.ptr < block_info.size) { + ESP_COREDUMP_LOGE("Block corruption detected in the heap (%p-%p)", heap_info.start, heap_info.end); + ESP_COREDUMP_LOGE("Corrupted block addr:%p size:%x)", block_info.ptr, block_info.size); + /* Heap walker will skip the next block in the same heap region and it will continue from the next heap region's block. */ + return false; + } + + if (block_info.used && block_info.size > 0) { + ESP_COREDUMP_LOG_PROCESS("heap block @%p sz:(%x)", (void *)block_info.ptr, block_info.size); + + if (!esp_core_dump_mem_seg_is_sane((uint32_t)block_info.ptr, block_info.size)) { + return false; + } + + if (self->coredump_stack_start == (uint32_t)block_info.ptr) { + /* skip writing coredump stack block */ + return true; + } + + *ret = elf_add_segment(self, PT_LOAD, + (uint32_t)block_info.ptr, + (void*)block_info.ptr, + block_info.size); + if (*ret <= 0) { + return false; + } + *total_sz += *ret; + } + + return true; +} + +#else + +static int esp_core_dump_store_section(core_dump_elf_t *self, uint32_t start, uint32_t data_len) +{ + return elf_add_segment(self, PT_LOAD, + start, + (void*)start, + data_len); +} + +#endif + static int elf_write_core_dump_user_data(core_dump_elf_t *self) { - int data_len = 0; int total_sz = 0; uint32_t start = 0; for (coredump_region_t i = COREDUMP_MEMORY_START; i < COREDUMP_MEMORY_MAX; i++) { - data_len = esp_core_dump_get_user_ram_info(i, &start); + int data_len = esp_core_dump_get_user_ram_info(i, &start); ELF_CHECK_ERR((data_len >= 0), ELF_PROC_ERR_OTHER, "invalid memory region"); if (data_len > 0) { - int ret = elf_add_segment(self, PT_LOAD, - (uint32_t)start, - (void*)start, - (uint32_t) data_len); - + int ret = esp_core_dump_store_section(self, start, data_len); ELF_CHECK_ERR((ret > 0), ret, "memory region write failed. Returned (%d).", ret); total_sz += ret; } } +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + heap_block_data_t user_data = {.self = self, .total_sz = &total_sz, .ret = 1}; + heap_caps_walk(MALLOC_CAP_8BIT, esp_core_dump_write_heap_blocks, &user_data); + ELF_CHECK_ERR((user_data.ret > 0), user_data.ret, "Heap memory write failed. Returned (%d).", user_data.ret); +#endif + return total_sz; } @@ -676,6 +808,12 @@ static esp_err_t esp_core_dump_write_elf(void) int tot_len = sizeof(dump_hdr); int write_len = sizeof(dump_hdr); +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + esp_core_dump_get_own_stack_info(&self.coredump_stack_start, &self.coredump_stack_size); + ESP_COREDUMP_LOG_PROCESS("Core dump stack start=%p size = %d", + (void *)self.coredump_stack_start, self.coredump_stack_size); +#endif + esp_err_t err = esp_core_dump_write_init(); if (err != ESP_OK) { ESP_COREDUMP_LOGE("Elf write init failed!"); @@ -794,7 +932,7 @@ static esp_err_t elf_core_dump_image_mmap(esp_partition_mmap_handle_t* core_data ESP_COREDUMP_LOGE("Failed to read core dump data size"); return ret; } - /* map the full core dump parition, including the checksum. */ + /* map the full core dump partition, including the checksum. */ return esp_partition_mmap(core_part, 0, out_size, ESP_PARTITION_MMAP_DATA, map_addr, core_data_handle); } diff --git a/docs/en/api-guides/core_dump.rst b/docs/en/api-guides/core_dump.rst index c544ab9827..065101d6bf 100644 --- a/docs/en/api-guides/core_dump.rst +++ b/docs/en/api-guides/core_dump.rst @@ -56,9 +56,28 @@ Setting this option to 0 bytes will cause the core dump routines to run from the .. note:: - If a separate stack is used, the recommended stack size should be larger than 800 bytes to ensure that the core dump routines themselves do not cause a stack overflow. + If a separate stack is used, the recommended stack size should be larger than 1300 bytes to ensure that the core dump routines themselves do not cause a stack overflow. +.. only:: not esp32c5 + + Core Dump Memory Regions + ^^^^^^^^^^^^^^^^^^^^^^^^ + + By default, core dumps typically save CPU registers, tasks data and summary of the panic reason. When the :ref:`CONFIG_ESP_COREDUMP_CAPTURE_DRAM` option is selected, ``.bss`` and ``.data`` sections and ``heap`` data will also be part of the dump. + + For a better debugging experience, it is recommended to dump these sections. However, this will result in a larger coredump file. The required additional storage space may vary based on the amount of DRAM the application uses. + + .. only:: SOC_SPIRAM_SUPPORTED + + .. note:: + + Apart from the crashed task's TCB and stack, data located in the external RAM will not be stored in the core dump file, this include variables defined with ``EXT_RAM_BSS_ATTR`` or ``EXT_RAM_NOINIT_ATTR`` attributes, as well as any data stored in the ``extram_bss`` section. + + .. note:: + + This feature is only enabled when using the ELF file format. + Core Dump to Flash ------------------ diff --git a/docs/zh_CN/api-guides/core_dump.rst b/docs/zh_CN/api-guides/core_dump.rst index 83e4e8af77..27505a3270 100644 --- a/docs/zh_CN/api-guides/core_dump.rst +++ b/docs/zh_CN/api-guides/core_dump.rst @@ -56,8 +56,26 @@ ELF 格式具备扩展特性,支持在发生崩溃时保存更多关于错误 .. note:: - 如果使用了独立的栈,建议栈大小应大于 800 字节,确保核心转储例程本身不会导致栈溢出。 + 如果使用了独立的栈,建议栈大小应大于 1300 字节,确保核心转储例程本身不会导致栈溢出。 +.. only:: not esp32c5 + + 核心转储内存区域 + ^^^^^^^^^^^^^^^^ + + 核心转储默认保存 CPU 寄存器、任务数据和崩溃原因。选择 :ref:`CONFIG_ESP_COREDUMP_CAPTURE_DRAM` 选项后,``.bss`` 段和 ``.data`` 段以及 ``heap`` 数据也将保存到转储中。 + + 推荐将上面提到的几个数据段都保存到核心转储中,以方便调试。但这会导致核心转储文件变大,具体所需的额外存储空间取决于应用程序使用的 DRAM 大小。 + + .. only:: SOC_SPIRAM_SUPPORTED + + .. note:: + + 除了崩溃任务的 TCB 和栈外,位于外部 RAM 中的数据不会保存到核心转储文件中,包括使用 ``EXT_RAM_BSS_ATTR`` 或 ``EXT_RAM_NOINIT_ATTR`` 属性定义的变量,以及存储在 ``extram_bss`` 段中的任何数据。 + + .. note:: + + 该功能仅在使用 ELF 文件格式时可用。 将核心转储保存到 flash ----------------------- diff --git a/tools/test_apps/system/gdbstub_runtime/main/test_app_main.c b/tools/test_apps/system/gdbstub_runtime/main/test_app_main.c index 69bae474a8..7f3980e3a9 100644 --- a/tools/test_apps/system/gdbstub_runtime/main/test_app_main.c +++ b/tools/test_apps/system/gdbstub_runtime/main/test_app_main.c @@ -21,7 +21,7 @@ void foo(void) void app_main(void) { - printf("tested app is runnig.\n"); + printf("tested app is running.\n"); vTaskDelay(5000 / portTICK_PERIOD_MS); diff --git a/tools/test_apps/system/gdbstub_runtime/pytest_runtime.py b/tools/test_apps/system/gdbstub_runtime/pytest_runtime.py index b97fdc3b71..040e7f2c83 100644 --- a/tools/test_apps/system/gdbstub_runtime/pytest_runtime.py +++ b/tools/test_apps/system/gdbstub_runtime/pytest_runtime.py @@ -1,6 +1,5 @@ -# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 - import os.path as path import sys @@ -23,9 +22,9 @@ def get_line_number(lookup: str, offset: int = 0) -> int: @pytest.mark.supported_targets @pytest.mark.generic def test_gdbstub_runtime(dut: PanicTestDut) -> None: - dut.expect_exact('tested app is runnig.') + dut.expect_exact('tested app is running.') dut.write(b'\x03') # send Ctrl-C - dut.start_gdb() + dut.start_gdb_for_gdbstub() # Test breakpoint cmd = '-break-insert --source test_app_main.c --function app_main --label label_1' @@ -162,9 +161,9 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None: @pytest.mark.generic @pytest.mark.temp_skip_ci(targets=['esp32', 'esp32s2', 'esp32s3'], reason='fix IDF-7927') def test_gdbstub_runtime_xtensa_stepping_bug(dut: PanicTestDut) -> None: - dut.expect_exact('tested app is runnig.') + dut.expect_exact('tested app is running.') dut.write(b'\x03') # send Ctrl-C - dut.start_gdb() + dut.start_gdb_for_gdbstub() # Test breakpoint cmd = '-break-insert --source test_app_main.c --function app_main --label label_1' diff --git a/tools/test_apps/system/panic/main/include/test_panic.h b/tools/test_apps/system/panic/main/include/test_panic.h index 5e96b2000c..6454d9db82 100644 --- a/tools/test_apps/system/panic/main/include/test_panic.h +++ b/tools/test_apps/system/panic/main/include/test_panic.h @@ -61,6 +61,8 @@ void test_assert_cache_disabled(void); void test_illegal_access(void); +void test_capture_dram(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 index 1a387f0018..d426ebc6a7 100644 --- a/tools/test_apps/system/panic/main/test_app_main.c +++ b/tools/test_apps/system/panic/main/test_app_main.c @@ -98,6 +98,9 @@ void app_main(void) #if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH && CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY HANDLE_TEST(test_name, test_panic_extram_stack); #endif +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM + HANDLE_TEST(test_name, test_capture_dram); +#endif #if !CONFIG_FREERTOS_UNICORE HANDLE_TEST(test_name, test_task_wdt_cpu1); #endif diff --git a/tools/test_apps/system/panic/main/test_panic.c b/tools/test_apps/system/panic/main/test_panic.c index eab2c082ee..2e1e47aca3 100644 --- a/tools/test_apps/system/panic/main/test_panic.c +++ b/tools/test_apps/system/panic/main/test_panic.c @@ -252,3 +252,30 @@ void test_illegal_access(void) printf("[2] val: %d at %p\n", val, (void *)addr); } #endif + +#if CONFIG_ESP_COREDUMP_CAPTURE_DRAM +int g_data_var = 42; +int g_bss_var; +char *g_heap_ptr; +COREDUMP_IRAM_DATA_ATTR uint32_t g_cd_iram = 0x4242; +COREDUMP_DRAM_ATTR uint32_t g_cd_dram = 0x4343; +#if SOC_RTC_MEM_SUPPORTED +COREDUMP_RTC_FAST_ATTR uint32_t g_rtc_fast_var; +COREDUMP_RTC_DATA_ATTR uint32_t g_rtc_data_var = 0x55A9; +#endif + +void test_capture_dram(void) +{ + g_data_var++; + g_bss_var = 55; + g_heap_ptr = strdup("Coredump Test"); + assert(g_heap_ptr); + g_cd_iram++; + g_cd_dram++; +#if SOC_RTC_MEM_SUPPORTED + g_rtc_fast_var = 0xAABBCCDD; + g_rtc_data_var++; +#endif + assert(0); +} +#endif diff --git a/tools/test_apps/system/panic/partitions_capture_dram.csv b/tools/test_apps/system/panic/partitions_capture_dram.csv new file mode 100644 index 0000000000..3d69de3875 --- /dev/null +++ b/tools/test_apps/system/panic/partitions_capture_dram.csv @@ -0,0 +1,6 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs,data,nvs,0x9000,24K, +phy_init,data,phy,0xf000,4K, +factory,app,factory,0x10000,1M, +coredump,data,coredump,0x110000,128K, diff --git a/tools/test_apps/system/panic/pytest_panic.py b/tools/test_apps/system/panic/pytest_panic.py index 90154a40d2..bb0d4b8f29 100644 --- a/tools/test_apps/system/panic/pytest_panic.py +++ b/tools/test_apps/system/panic/pytest_panic.py @@ -89,6 +89,8 @@ CONFIGS_HW_STACK_GUARD_DUAL_CORE = [ pytest.param('panic', marks=TARGETS_RISCV_DUAL_CORE), ] +CONFIG_CAPTURE_DRAM = [pytest.param('coredump_flash_capture_dram', marks=TARGETS_ALL)] + # Panic abort information will start with this string. PANIC_ABORT_PREFIX = 'Panic reason: ' @@ -101,7 +103,7 @@ def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[Lis expected_coredump: Optional[List[Union[str, re.Pattern]]] = None) -> None: if 'gdbstub' in config: dut.expect_exact('Entering gdb stub now.') - dut.start_gdb() + dut.start_gdb_for_gdbstub() frames = dut.gdb_backtrace() if expected_backtrace is not None: dut.verify_gdb_backtrace(frames, expected_backtrace) @@ -861,7 +863,7 @@ def test_gdbstub_coredump(dut: PanicTestDut) -> None: dut.process_coredump_uart() dut.expect_exact('Entering gdb stub now.') - dut.start_gdb() + dut.start_gdb_for_gdbstub() frames = dut.gdb_backtrace() dut.verify_gdb_backtrace(frames, get_default_backtrace(test_func_name)) dut.revert_log_level() @@ -913,3 +915,25 @@ def test_illegal_access(dut: PanicTestDut, config: str, test_func_name: str) -> dut.expect_backtrace() dut.expect_elf_sha256() dut.expect_none('Guru Meditation') + + +@pytest.mark.parametrize('config', CONFIG_CAPTURE_DRAM, indirect=True) +@pytest.mark.generic +def test_capture_dram(dut: PanicTestDut, config: str, test_func_name: str) -> None: + dut.run_test_func(test_func_name) + + dut.expect_elf_sha256() + dut.expect_none(['Guru Meditation', 'Re-entered core dump']) + + core_elf_file = dut.process_coredump_flash() + dut.start_gdb_for_coredump(core_elf_file) + + assert dut.gdb_data_eval_expr('g_data_var') == '43' + assert dut.gdb_data_eval_expr('g_bss_var') == '55' + assert re.search(r'0x[0-9a-fA-F]+ "Coredump Test"', dut.gdb_data_eval_expr('g_heap_ptr')) + assert int(dut.gdb_data_eval_expr('g_cd_iram')) == 0x4243 + assert int(dut.gdb_data_eval_expr('g_cd_dram')) == 0x4344 + + if dut.target != 'esp32c2': + assert int(dut.gdb_data_eval_expr('g_rtc_data_var')) == 0x55AA + assert int(dut.gdb_data_eval_expr('g_rtc_fast_var')) == 0xAABBCCDD diff --git a/tools/test_apps/system/panic/sdkconfig.ci.coredump_flash_capture_dram b/tools/test_apps/system/panic/sdkconfig.ci.coredump_flash_capture_dram new file mode 100644 index 0000000000..03f5a73805 --- /dev/null +++ b/tools/test_apps/system/panic/sdkconfig.ci.coredump_flash_capture_dram @@ -0,0 +1,6 @@ +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y +CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y +CONFIG_ESP_COREDUMP_CHECKSUM_SHA256=y +CONFIG_ESP_COREDUMP_CAPTURE_DRAM=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_capture_dram.csv" 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 d7eae8d306..205035e5b0 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 @@ -120,15 +120,15 @@ class PanicTestDut(IdfDut): for pattern in patterns: if isinstance(pattern, str): position = coredump.find(pattern) - assert position != -1, f"'{pattern}' not found in the coredump output" + assert position != -1, f"'{pattern}' not in the coredump output" elif isinstance(pattern, re.Pattern): match = pattern.findall(coredump) - assert match, f"'{pattern.pattern}' not found in the coredump output" + assert match, f"'{pattern.pattern}' not 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 + self, extra_args: List[str], output_file_name: str ) -> None: # no "with" here, since we need the file to be open for later inspection by the test case if not self.coredump_output: @@ -142,14 +142,13 @@ class PanicTestDut(IdfDut): espcoredump_script, '-b115200', 'info_corefile', - '--core', - coredump_file_name, ] espcoredump_args += extra_args espcoredump_args.append(self.app.elf_file) logging.info('Running %s', ' '.join(espcoredump_args)) logging.info('espcoredump output is written to %s', self.coredump_output.name) + self.serial.close() subprocess.check_call(espcoredump_args, stdout=self.coredump_output) self.coredump_output.flush() self.coredump_output.seek(0) @@ -165,33 +164,31 @@ class PanicTestDut(IdfDut): output_file_name = os.path.join(self.logdir, 'coredump_uart_result.txt') self._call_espcoredump( - ['--core-format', 'b64'], coredump_file.name, output_file_name + ['--core-format', 'b64', '--core', coredump_file.name], output_file_name ) if expected: self.expect_coredump(output_file_name, expected) - def process_coredump_flash(self, expected: Optional[List[Union[str, re.Pattern]]] = None) -> None: - """Extract the core dump from flash, run espcoredump on it""" + def process_coredump_flash(self, expected: Optional[List[Union[str, re.Pattern]]] = None) -> Any: coredump_file_name = os.path.join(self.logdir, 'coredump_data.bin') logging.info('Writing flash binary core dump to %s', coredump_file_name) - self.serial.dump_flash(partition='coredump', output=coredump_file_name) - output_file_name = os.path.join(self.logdir, 'coredump_flash_result.txt') self._call_espcoredump( - ['--core-format', 'raw'], coredump_file_name, output_file_name + ['--core-format', 'raw', '--save-core', coredump_file_name], output_file_name ) if expected: self.expect_coredump(output_file_name, expected) + return coredump_file_name def gdb_write(self, command: str) -> Any: """ Wrapper to write to gdb with a longer timeout, as test runner host can be slow sometimes """ - assert self.gdbmi, 'This function should be called only after start_gdb' + assert self.gdbmi, 'This function should be called only after run_gdb' return self.gdbmi.write(command, timeout_sec=10) - def start_gdb(self) -> None: + def run_gdb(self) -> None: """ Runs GDB and connects it to the "serial" port of the DUT. After this, the DUT expect methods can no longer be used to capture output. @@ -262,6 +259,11 @@ class PanicTestDut(IdfDut): # Load the ELF file self.gdb_write('-file-exec-and-symbols {}'.format(self.app.elf_file)) + # Prepare gdb for the gdb stub + def start_gdb_for_gdbstub(self) -> None: + + self.run_gdb() + # Connect GDB to UART self.serial.close() logging.info('Connecting to GDB Stub...') @@ -290,7 +292,14 @@ class PanicTestDut(IdfDut): logging.info('Stopped in {func} at {addr} ({file}:{line})'.format(**frame)) # Drain remaining responses - self.gdbmi.get_gdb_response(raise_error_on_timeout=False) + if self.gdbmi: + self.gdbmi.get_gdb_response(raise_error_on_timeout=False) + + # Prepare gdb to debug coredump file + def start_gdb_for_coredump(self, elf_file: str) -> None: + + self.run_gdb() + self.gdb_write('core {}'.format(elf_file)) def gdb_backtrace(self) -> Any: """ @@ -302,6 +311,10 @@ class PanicTestDut(IdfDut): responses = self.gdb_write('-stack-list-frames') return self.find_gdb_response('done', 'result', responses)['payload']['stack'] + def gdb_data_eval_expr(self, expr: str) -> Any: + responses = self.gdb_write('-data-evaluate-expression "%s"' % expr) + return self.find_gdb_response('done', 'result', responses)['payload']['value'] + @staticmethod def verify_gdb_backtrace( gdb_backtrace: List[Any], expected_functions_list: List[Any]