feat(coredump): save .bss, .data and .heap sections to the elf file

This commit is contained in:
Erhan Kurubas 2024-03-11 21:19:17 +01:00
parent 9a24782dd2
commit 3b8191cf5d
15 changed files with 388 additions and 78 deletions

View File

@ -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

View File

@ -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).

View File

@ -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());

View File

@ -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 = &param->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);
}

View File

@ -56,9 +56,26 @@ 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.
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.
.. note::
.. only:: SOC_SPIRAM_SUPPORTED
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
------------------

View File

@ -56,7 +56,25 @@ ELF 格式具备扩展特性,支持在发生崩溃时保存更多关于错误
.. note::
如果使用了独立的栈,建议栈大小应大于 800 字节,确保核心转储例程本身不会导致栈溢出。
如果使用了独立的栈,建议栈大小应大于 1300 字节,确保核心转储例程本身不会导致栈溢出。
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.
.. note::
.. only:: SOC_SPIRAM_SUPPORTED
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.
将核心转储保存到 flash

View File

@ -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);

View File

@ -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'

View File

@ -61,6 +61,8 @@ void test_assert_cache_disabled(void);
void test_illegal_access(void);
void test_capture_dram(void);
#ifdef __cplusplus
}
#endif

View File

@ -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

View File

@ -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

View File

@ -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,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs,data,nvs,0x9000,24K,
4 phy_init,data,phy,0xf000,4K,
5 factory,app,factory,0x10000,1M,
6 coredump,data,coredump,0x110000,128K,

View File

@ -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

View File

@ -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"

View File

@ -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]