diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index f0f74e9895..5a36b30a5d 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -54,6 +54,47 @@ config TRACEMEM_RESERVE_DRAM default 0x4000 if MEMMAP_TRACEMEM && !MEMMAP_TRACEMEM_TWOBANKS default 0x0 +choice ESP32_COREDUMP_TO_FLASH_OR_UART + prompt "Core dump destination" + default ESP32_ENABLE_COREDUMP_TO_NONE + help + Select place to store core dump: flash, uart or none (to disable core dumps generation). + + If core dump is configured to be stored in flash and custom partition table is used add + corresponding entry to your CSV. For examples, please see predefined partition table CSV descriptions + in the components/partition_table directory. + +config ESP32_ENABLE_COREDUMP_TO_FLASH + bool "Flash" + select ESP32_ENABLE_COREDUMP +config ESP32_ENABLE_COREDUMP_TO_UART + bool "UART" + select ESP32_ENABLE_COREDUMP +config ESP32_ENABLE_COREDUMP_TO_NONE + bool "None" +endchoice + +config ESP32_ENABLE_COREDUMP + bool + default F + help + Enables/disable core dump module. + +config ESP32_CORE_DUMP_UART_DELAY + int "Core dump print to UART delay" + depends on ESP32_ENABLE_COREDUMP_TO_UART + default 0 + help + Config delay (in ms) before printing core dump to UART. + Delay can be interrupted by pressing Enter key. + +config ESP32_CORE_DUMP_LOG_LEVEL + int "Core dump module logging level" + depends on ESP32_ENABLE_COREDUMP + default 1 + help + Config core dump module logging level (0-5). + # Not implemented and/or needs new silicon rev to work config MEMMAP_SPISRAM bool "Use external SPI SRAM chip as main memory" diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c new file mode 100644 index 0000000000..85301f4dd6 --- /dev/null +++ b/components/esp32/core_dump.c @@ -0,0 +1,474 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/uart_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" +#include "driver/gpio.h" + +#include "esp_panic.h" +#include "esp_partition.h" + +#if CONFIG_ESP32_ENABLE_COREDUMP +#define LOG_LOCAL_LEVEL CONFIG_ESP32_CORE_DUMP_LOG_LEVEL +#include "esp_log.h" +const static char *TAG = "esp_core_dump"; + +#define ESP_COREDUMP_LOGE( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_ERROR) { ets_printf(LOG_FORMAT(E, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGW( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_WARN) { ets_printf(LOG_FORMAT(W, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGI( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { ets_printf(LOG_FORMAT(I, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGD( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { ets_printf(LOG_FORMAT(D, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGV( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_VERBOSE) { ets_printf(LOG_FORMAT(V, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } + +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH +#define ESP_COREDUMP_LOG_PROCESS( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { ets_printf(LOG_FORMAT(D, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#else +#define ESP_COREDUMP_LOG_PROCESS( format, ... ) do{/*(__VA_ARGS__);*/}while(0) +#endif + + +// TODO: allow user to set this in menuconfig or get tasks iteratively +#define COREDUMP_MAX_TASKS_NUM 32 + +typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len); +typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv); +typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv); +typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len); + +typedef struct _core_dump_write_config_t +{ + esp_core_dump_write_prepare_t prepare; + esp_core_dump_write_start_t start; + esp_core_dump_write_end_t end; + esp_core_dump_flash_write_data_t write; + void * priv; + +} core_dump_write_config_t; + +static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *write_cfg) +{ + union + { + uint8_t data8[12]; + uint32_t data32[3]; + } rom_data; + esp_err_t err; + TaskSnapshot_t tasks[COREDUMP_MAX_TASKS_NUM]; + UBaseType_t tcb_sz, task_num; + uint32_t data_len = 0, i, len; + + task_num = uxTaskGetSnapshotAll(tasks, COREDUMP_MAX_TASKS_NUM, &tcb_sz); + // take TCB padding into account, actual TCB size will be stored in header + if (tcb_sz % sizeof(uint32_t)) + len = (tcb_sz / sizeof(uint32_t) + 1) * sizeof(uint32_t); + else + len = tcb_sz; + // header + tasknum*(tcb + stack start/end + tcb addr) + data_len = 3*sizeof(uint32_t) + task_num*(len + 2*sizeof(uint32_t) + sizeof(uint32_t *)); + for (i = 0; i < task_num; i++) { + if (tasks[i].pxTCB == xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID())) { + // set correct stack top for current task + tasks[i].pxTopOfStack = (StackType_t *)frame; + ESP_COREDUMP_LOG_PROCESS("Current task EXIT/PC/PS/A0/SP %x %x %x %x %x", frame->exit, frame->pc, frame->ps, frame->a0, frame->a1); + } + else { + XtSolFrame *task_frame = (XtSolFrame *)tasks[i].pxTopOfStack; + if (task_frame->exit == 0) { + ESP_COREDUMP_LOG_PROCESS("Task EXIT/PC/PS/A0/SP %x %x %x %x %x", task_frame->exit, task_frame->pc, task_frame->ps, task_frame->a0, task_frame->a1); + } + else { +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + XtExcFrame *task_frame2 = (XtExcFrame *)tasks[i].pxTopOfStack; +#endif + ESP_COREDUMP_LOG_PROCESS("Task EXIT/PC/PS/A0/SP %x %x %x %x %x", task_frame2->exit, task_frame2->pc, task_frame2->ps, task_frame2->a0, task_frame2->a1); + } + } +#if( portSTACK_GROWTH < 0 ) + len = (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack; +#else + len = (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack; +#endif + ESP_COREDUMP_LOG_PROCESS("Stack len = %lu (%x %x)", len, tasks[i].pxTopOfStack, tasks[i].pxEndOfStack); + // take stack padding into account + if (len % sizeof(uint32_t)) + len = (len / sizeof(uint32_t) + 1) * sizeof(uint32_t); + data_len += len; + } + + // prepare write + if (write_cfg->prepare) { + err = write_cfg->prepare(write_cfg->priv, &data_len); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to prepare core dump (%d)!", err); + return; + } + } + + ESP_COREDUMP_LOG_PROCESS("Core dump len = %lu", data_len); + + // write start + if (write_cfg->start) { + err = write_cfg->start(write_cfg->priv); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to start core dump (%d)!", err); + return; + } + } + + // write header + rom_data.data32[0] = data_len; + rom_data.data32[1] = task_num; + rom_data.data32[2] = tcb_sz; + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to write core dump header (%d)!", err); + return; + } + + // write tasks + for (i = 0; i < task_num; i++) { + ESP_COREDUMP_LOG_PROCESS("Dump task %x", tasks[i].pxTCB); + // save TCB address, stack base and stack top addr + rom_data.data32[0] = (uint32_t)tasks[i].pxTCB; + rom_data.data32[1] = (uint32_t)tasks[i].pxTopOfStack; + rom_data.data32[2] = (uint32_t)tasks[i].pxEndOfStack; + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to write task header (%d)!", err); + return; + } + // save TCB + err = write_cfg->write(write_cfg->priv, tasks[i].pxTCB, tcb_sz); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to write TCB (%d)!", err); + return; + } + // save task stack + err = write_cfg->write(write_cfg->priv, +#if( portSTACK_GROWTH < 0 ) + tasks[i].pxTopOfStack, + (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack +#else + tasks[i].pxEndOfStack, + (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack +#endif + ); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to write task stack (%d)!", err); + return; + } + } + + // write end + if (write_cfg->end) { + err = write_cfg->end(write_cfg->priv); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to end core dump (%d)!", err); + return; + } + } +} + +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + +// magic numbers to control core dump data consistency +#define COREDUMP_FLASH_MAGIC_START 0xE32C04EDUL +#define COREDUMP_FLASH_MAGIC_END 0xE32C04EDUL + +typedef struct _core_dump_write_flash_data_t +{ + uint32_t off; +} core_dump_write_flash_data_t; + +// core dump partition start +static uint32_t s_core_part_start; +// core dump partition size +static uint32_t s_core_part_size; + +static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint32_t data_size) +{ + esp_err_t err; + uint32_t data_len = 0, k, len; + union + { + uint8_t data8[4]; + uint32_t data32; + } rom_data; + + data_len = (data_size / sizeof(uint32_t)) * sizeof(uint32_t); + err = spi_flash_write(off, data, data_len); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to write data to flash (%d)!", err); + return 0; + } + + len = data_size % sizeof(uint32_t); + if (len) { + // write last bytes with padding, actual TCB len can be retrieved by esptool from core dump header + rom_data.data32 = 0; + for (k = 0; k < len; k++) + rom_data.data8[k] = *(data + data_len + k); + err = spi_flash_write(off + data_len, &rom_data, sizeof(uint32_t)); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to finish write data to flash (%d)!", err); + return 0; + } + data_len += sizeof(uint32_t); + } + + return data_len; +} + +static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len) +{ + esp_err_t err; + uint32_t sec_num; + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + + // add space for 2 magics. TODO: change to CRC + if ((*data_len + 2*sizeof(uint32_t)) > s_core_part_size) { + ESP_COREDUMP_LOGE("Not enough space to save core dump!"); + return ESP_ERR_NO_MEM; + } + *data_len += 2*sizeof(uint32_t); + + wr_data->off = 0; + + sec_num = *data_len / SPI_FLASH_SEC_SIZE; + if (*data_len % SPI_FLASH_SEC_SIZE) + sec_num++; + err = spi_flash_erase_range(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to erase flash (%d)!", err); + return err; + } + + return err; +} + +static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr_data, uint32_t word) +{ + esp_err_t err = ESP_OK; + uint32_t data32 = word; + + err = spi_flash_write(s_core_part_start + wr_data->off, &data32, sizeof(uint32_t)); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to write to flash (%d)!", err); + return err; + } + wr_data->off += sizeof(uint32_t); + + return err; +} + +static esp_err_t esp_core_dump_flash_write_start(void *priv) +{ + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + // save magic 1 + return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_START); +} + +static esp_err_t esp_core_dump_flash_write_end(void *priv) +{ + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; +#if LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG + uint32_t i; + union + { + uint8_t data8[16]; + uint32_t data32[4]; + } rom_data; + + esp_err_t err = spi_flash_read(s_core_part_start + 0, &rom_data, sizeof(rom_data)); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to read flash (%d)!", err); + return err; + } + else { + ESP_COREDUMP_LOG_PROCESS("Data from flash:"); + for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { + ESP_COREDUMP_LOG_PROCESS("%x", rom_data.data32[i]); + } + } +#endif + + // save magic 2 + return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_END); +} + +static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len) +{ + esp_err_t err = ESP_OK; + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + + uint32_t len = esp_core_dump_write_flash_padded(s_core_part_start + wr_data->off, data, data_len); + if (len != data_len) + return ESP_FAIL; + + wr_data->off += len; + + return err; +} + +void esp_core_dump_to_flash(XtExcFrame *frame) +{ + core_dump_write_config_t wr_cfg; + core_dump_write_flash_data_t wr_data; + + /* init non-OS flash access critical section */ + spi_flash_guard_set(&g_flash_guard_no_os_ops); + + wr_cfg.prepare = esp_core_dump_flash_write_prepare; + wr_cfg.start = esp_core_dump_flash_write_start; + wr_cfg.end = esp_core_dump_flash_write_end; + wr_cfg.write = esp_core_dump_flash_write_data; + wr_cfg.priv = &wr_data; + + ESP_COREDUMP_LOGI("Save core dump to flash..."); + esp_core_dump_write(frame, &wr_cfg); + ESP_COREDUMP_LOGI("Core dump has been saved to flash."); +} +#endif + +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8_t *dst) { + static const char *b64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i, j, a, b, c; + + for (i = j = 0; i < src_len; i += 3) { + a = src[i]; + b = i + 1 >= src_len ? 0 : src[i + 1]; + c = i + 2 >= src_len ? 0 : src[i + 2]; + + dst[j++] = b64[a >> 2]; + dst[j++] = b64[((a & 3) << 4) | (b >> 4)]; + if (i + 1 < src_len) { + dst[j++] = b64[(b & 0x0F) << 2 | (c >> 6)]; + } + if (i + 2 < src_len) { + dst[j++] = b64[c & 0x3F]; + } + } + while (j % 4 != 0) { + dst[j++] = '='; + } + dst[j++] = '\0'; +} + +static esp_err_t esp_core_dump_uart_write_start(void *priv) +{ + esp_err_t err = ESP_OK; + ets_printf("================= CORE DUMP START =================\r\n"); + return err; +} + +static esp_err_t esp_core_dump_uart_write_end(void *priv) +{ + esp_err_t err = ESP_OK; + ets_printf("================= CORE DUMP END =================\r\n"); + return err; +} + +static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len) +{ + esp_err_t err = ESP_OK; + char buf[64 + 4], *addr = data; + char *end = addr + data_len; + + while (addr < end) { + size_t len = end - addr; + if (len > 48) len = 48; + /* Copy to stack to avoid alignment restrictions. */ + char *tmp = buf + (sizeof(buf) - len); + memcpy(tmp, addr, len); + esp_core_dump_b64_encode((const uint8_t *)tmp, len, (uint8_t *)buf); + addr += len; + ets_printf("%s\r\n", buf); + } + + return err; +} + +static int esp_core_dump_uart_get_char() { + int i; + uint32_t reg = (READ_PERI_REG(UART_STATUS_REG(0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT; + if (reg) + i = READ_PERI_REG(UART_FIFO_REG(0)); + else + i = -1; + return i; +} + +void esp_core_dump_to_uart(XtExcFrame *frame) +{ + core_dump_write_config_t wr_cfg; + uint32_t tm_end, tm_cur; + int ch; + + wr_cfg.prepare = NULL; + wr_cfg.start = esp_core_dump_uart_write_start; + wr_cfg.end = esp_core_dump_uart_write_end; + wr_cfg.write = esp_core_dump_uart_write_data; + wr_cfg.priv = NULL; + + //Make sure txd/rxd are enabled + gpio_pullup_dis(1); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD); + + ESP_COREDUMP_LOGI("Press Enter to print core dump to UART..."); + tm_end = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000) + CONFIG_ESP32_CORE_DUMP_UART_DELAY; + ch = esp_core_dump_uart_get_char(); + while (!(ch == '\n' || ch == '\r')) { + tm_cur = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000); + if (tm_cur >= tm_end) + break; + /* Feed the Cerberus. */ + TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; + TIMERG0.wdt_feed = 1; + TIMERG0.wdt_wprotect = 0; + ch = esp_core_dump_uart_get_char(); + } + ESP_COREDUMP_LOGI("Print core dump to uart..."); + esp_core_dump_write(frame, &wr_cfg); + ESP_COREDUMP_LOGI("Core dump has been written to uart."); +} +#endif + +void esp_core_dump_init() +{ +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + const esp_partition_t *core_part; + + ESP_LOGI(TAG, "Init core dump to flash"); + core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL); + if (!core_part) { + ESP_LOGE(TAG, "No core dump partition found!"); + return; + } + ESP_LOGI(TAG, "Found partition '%s' @ %x %d bytes", core_part->label, core_part->address, core_part->size); + s_core_part_start = core_part->address; + s_core_part_size = core_part->size; +#endif +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART + ESP_LOGI(TAG, "Init core dump to UART"); +#endif +} + +#endif + diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 864a17a1b6..5ae68fc643 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -55,6 +55,7 @@ #include "esp_task_wdt.h" #include "esp_phy_init.h" #include "esp_coexist.h" +#include "esp_core_dump.h" #include "trax.h" #define STRINGIFY(s) STRINGIFY2(s) @@ -202,6 +203,8 @@ void start_cpu0_default(void) #endif esp_ipc_init(); spi_flash_init(); + /* init default OS-aware flash access critical section */ + spi_flash_guard_set(&g_flash_guard_default_ops); #if CONFIG_ESP32_PHY_AUTO_INIT nvs_flash_init(); @@ -214,6 +217,10 @@ void start_cpu0_default(void) } #endif +#if CONFIG_ESP32_ENABLE_COREDUMP + esp_core_dump_init(); +#endif + xTaskCreatePinnedToCore(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL, 0); diff --git a/components/esp32/include/esp_core_dump.h b/components/esp32/include/esp_core_dump.h new file mode 100644 index 0000000000..c6634364c5 --- /dev/null +++ b/components/esp32/include/esp_core_dump.h @@ -0,0 +1,64 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef ESP_CORE_DUMP_H_ +#define ESP_CORE_DUMP_H_ + +/** + * @brief Initializes core dump module internal data. + * + * @note Should be called at system startup. + */ +void esp_core_dump_init(); + +/** + * @brief Saves core dump to flash. + * + * The structure of data stored in flash is as follows: + * | MAGIC1 | + * | TOTAL_LEN | TASKS_NUM | TCB_SIZE | + * | TCB_ADDR_1 | STACK_TOP_1 | STACK_END_1 | TCB_1 | STACK_1 | + * . . . . + * . . . . + * | TCB_ADDR_N | STACK_TOP_N | STACK_END_N | TCB_N | STACK_N | + * | MAGIC2 | + * Core dump in flash consists of header and data for every task in the system at the moment of crash. + * For flash data integrity control two magic numbers are used at the beginning and the end of core dump. + * The structure of core dump data is described below in details. + * 1) MAGIC1 and MAGIC2 are special numbers stored at the beginning and the end of core dump. + * They are used to control core dump data integrity. Size of every number is 4 bytes. + * 2) Core dump starts with header: + * 2.1) TOTAL_LEN is total length of core dump data in flash including magic numbers. Size is 4 bytes. + * 2.2) TASKS_NUM is the number of tasks for which data are stored. Size is 4 bytes. + * 2.3) TCB_SIZE is the size of task's TCB structure. Size is 4 bytes. + * 3) Core dump header is followed by the data for every task in the system. + * Task data are started with task header: + * 3.1) TCB_ADDR is the address of TCB in memory. Size is 4 bytes. + * 3.2) STACK_TOP is the top of task's stack (address of the topmost stack item). Size is 4 bytes. + * 3.2) STACK_END is the end of task's stack (address from which task's stack starts). Size is 4 bytes. + * 4) Task header is followed by TCB data. Size is TCB_SIZE bytes. + * 5) Task's stack is placed after TCB data. Size is (STACK_END - STACK_TOP) bytes. + */ +void esp_core_dump_to_flash(); + +/** + * @brief Print base64-encoded core dump to UART. + * + * The structure of core dump data is the same as for data stored in flash (@see esp_core_dump_to_flash) with some notes: + * 1) Magic numbers are not present in core dump printed to UART. + * 2) Since magic numbers are omitted TOTAL_LEN does not include their size. + * 3) Printed base64 data are surrounded with special messages to help user recognize the start and end of actual data. + */ +void esp_core_dump_to_uart(); + +#endif diff --git a/components/esp32/panic.c b/components/esp32/panic.c index c5b18870a9..3db09370ac 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -33,6 +33,7 @@ #include "esp_panic.h" #include "esp_attr.h" #include "esp_err.h" +#include "esp_core_dump.h" /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level @@ -46,13 +47,13 @@ #if !CONFIG_ESP32_PANIC_SILENT_REBOOT //printf may be broken, so we fix our own printing fns... -inline static void panicPutChar(char c) +static void panicPutChar(char c) { while (((READ_PERI_REG(UART_STATUS_REG(0)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ; WRITE_PERI_REG(UART_FIFO_REG(0), c); } -inline static void panicPutStr(const char *c) +static void panicPutStr(const char *c) { int x = 0; while (c[x] != 0) { @@ -61,7 +62,7 @@ inline static void panicPutStr(const char *c) } } -inline static void panicPutHex(int a) +static void panicPutHex(int a) { int x; int c; @@ -76,7 +77,7 @@ inline static void panicPutHex(int a) } } -inline static void panicPutDec(int a) +static void panicPutDec(int a) { int n1, n2; n1 = a % 10; @@ -90,10 +91,10 @@ inline static void panicPutDec(int a) } #else //No printing wanted. Stub out these functions. -inline static void panicPutChar(char c) { } -inline static void panicPutStr(const char *c) { } -inline static void panicPutHex(int a) { } -inline static void panicPutDec(int a) { } +static void panicPutChar(char c) { } +static void panicPutStr(const char *c) { } +static void panicPutHex(int a) { } +static void panicPutDec(int a) { } #endif void __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) @@ -301,7 +302,7 @@ static void putEntry(uint32_t pc, uint32_t sp) static void doBacktrace(XtExcFrame *frame) { uint32_t i = 0, pc = frame->pc, sp = frame->a1; - panicPutStr("\nBacktrace:"); + panicPutStr("\r\nBacktrace:"); /* Do not check sanity on first entry, PC could be smashed. */ putEntry(pc, sp); pc = frame->a0; @@ -317,7 +318,7 @@ static void doBacktrace(XtExcFrame *frame) break; } } - panicPutStr("\n\n"); + panicPutStr("\r\n\r\n"); } /* @@ -351,8 +352,8 @@ static void commonErrorHandler(XtExcFrame *frame) panicPutHex(regs[x + y + 1]); panicPutStr(" "); } + panicPutStr("\r\n"); } - panicPutStr("\r\n"); } } @@ -363,7 +364,14 @@ static void commonErrorHandler(XtExcFrame *frame) disableAllWdts(); panicPutStr("Entering gdb stub now.\r\n"); esp_gdbstub_panic_handler(frame); -#elif CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT +#else +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + esp_core_dump_to_flash(frame); +#endif +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART && !CONFIG_ESP32_PANIC_SILENT_REBOOT + esp_core_dump_to_uart(frame); +#endif +#if CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT panicPutStr("Rebooting...\r\n"); for (x = 0; x < 100; x++) { ets_delay_us(1000); @@ -374,6 +382,7 @@ static void commonErrorHandler(XtExcFrame *frame) panicPutStr("CPU halted.\r\n"); while (1); #endif +#endif } diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py new file mode 100755 index 0000000000..1589a362b9 --- /dev/null +++ b/components/espcoredump/espcoredump.py @@ -0,0 +1,1109 @@ +#!/usr/bin/env python +# +# ESP32 core dump Utility + +import sys +import os +import argparse +import subprocess +import tempfile +import struct +import array +import errno +import base64 + +try: + import esptool +except ImportError: + idf_path = os.getenv('IDF_PATH') + if idf_path is None: + print "Esptool is not found! Install it or set proper $IDF_PATH in environment." + sys.exit(2) + sys.path.append('%s/components/esptool_py/esptool' % idf_path) + import esptool + +__version__ = "0.1-dev" + +if os.name == 'nt': + CLOSE_FDS = False +else: + CLOSE_FDS = True + + +class ESPCoreDumpError(RuntimeError): + """Core dump runtime error class + """ + def __init__(self, message): + """Constructor for core dump error + """ + super(ESPCoreDumpError, self).__init__(message) + +class BinStruct(object): + """Binary structure representation + + Subclasses must specify actual structure layout using 'fields' and 'format' members. + For example, the following subclass represents structure with two fields: + f1 of size 2 bytes and 4 bytes f2. Little endian. + class SomeStruct(BinStruct): + fields = ("f1", + "f2") + format = " 0: + self._read_sections(f, shoff, shstrndx) + else: + self.sections = [] + if phnum > 0: + self._read_program_segments(f, phoff, phentsize, phnum) + else: + self.program_segments = [] + + def _read_sections(self, f, section_header_offs, shstrndx): + """Reads core dump sections from ELF file + """ + f.seek(section_header_offs) + section_header = f.read() + LEN_SEC_HEADER = 0x28 + if len(section_header) == 0: + raise ESPCoreDumpError("No section header found at offset %04x in ELF file." % section_header_offs) + if len(section_header) % LEN_SEC_HEADER != 0: + print 'WARNING: Unexpected ELF section header length %04x is not mod-%02x' % (len(section_header),LEN_SEC_HEADER) + + # walk through the section header and extract all sections + section_header_offsets = range(0, len(section_header), LEN_SEC_HEADER) + + def read_section_header(offs): + name_offs,sec_type,flags,lma,sec_offs,size = struct.unpack_from("= ps.addr and addr < (ps.addr + seg_len): + raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % + (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) + if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len): + raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % + (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) + # append + self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags)) + + def dump(self, f): + """Write core dump contents to file + """ + # TODO: currently dumps only program segments. + # dumping sections is not supported yet + # write ELF header + ehdr = Elf32FileHeader() + ehdr.e_type = self.e_type + ehdr.e_machine = self.e_machine + ehdr.e_entry = 0 + ehdr.e_phoff = ehdr.sizeof() + ehdr.e_shoff = 0 + ehdr.e_flags = 0 + ehdr.e_phentsize = Elf32ProgramHeader().sizeof() + ehdr.e_phnum = len(self.program_segments) + ehdr.e_shentsize = 0 + ehdr.e_shnum = 0 + ehdr.e_shstrndx = self.SHN_UNDEF + f.write(ehdr.dump()) + # write program header table + cur_off = ehdr.e_ehsize + ehdr.e_phnum * ehdr.e_phentsize + for i in range(len(self.program_segments)): + phdr = Elf32ProgramHeader() + phdr.p_type = self.program_segments[i].type + phdr.p_offset = cur_off + phdr.p_vaddr = self.program_segments[i].addr + phdr.p_paddr = phdr.p_vaddr # TODO + phdr.p_filesz = len(self.program_segments[i].data) + phdr.p_memsz = phdr.p_filesz # TODO + phdr.p_flags = self.program_segments[i].flags + phdr.p_align = 0 # TODO + f.write(phdr.dump()) + cur_off += phdr.p_filesz + # write program segments + for i in range(len(self.program_segments)): + f.write(self.program_segments[i].data) + + +class ESPCoreDumpLoaderError(ESPCoreDumpError): + """Core dump loader error class + """ + def __init__(self, message): + """Constructor for core dump loader error + """ + super(ESPCoreDumpLoaderError, self).__init__(message) + + +class ESPCoreDumpLoader(object): + """Core dump loader base class + """ + ESP32_COREDUMP_HDR_FMT = '<3L' + ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) + ESP32_COREDUMP_TSK_HDR_FMT = '<3L' + ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT) + + def __init__(self): + """Base constructor for core dump loader + """ + self.fcore = None + + def _get_registers_from_stack(self, data, grows_down): + """Returns list of registers (in GDB format) from xtensa stack frame + """ + # from "gdb/xtensa-tdep.h" + # typedef struct + # { + #0 xtensa_elf_greg_t pc; + #1 xtensa_elf_greg_t ps; + #2 xtensa_elf_greg_t lbeg; + #3 xtensa_elf_greg_t lend; + #4 xtensa_elf_greg_t lcount; + #5 xtensa_elf_greg_t sar; + #6 xtensa_elf_greg_t windowstart; + #7 xtensa_elf_greg_t windowbase; + #8..63 xtensa_elf_greg_t reserved[8+48]; + #64 xtensa_elf_greg_t ar[64]; + # } xtensa_elf_gregset_t; + REG_PC_IDX=0 + REG_PS_IDX=1 + REG_LB_IDX=2 + REG_LE_IDX=3 + REG_LC_IDX=4 + REG_SAR_IDX=5 + REG_WS_IDX=6 + REG_WB_IDX=7 + REG_AR_START_IDX=64 + REG_AR_NUM=64 + # FIXME: acc to xtensa_elf_gregset_t number of regs must be 128, + # but gdb complanis when it less then 129 + REG_NUM=129 + + XT_SOL_EXIT=0 + XT_SOL_PC=1 + XT_SOL_PS=2 + XT_SOL_NEXT=3 + XT_SOL_AR_START=4 + XT_SOL_AR_NUM=4 + XT_SOL_FRMSZ=8 + + XT_STK_EXIT=0 + XT_STK_PC=1 + XT_STK_PS=2 + XT_STK_AR_START=3 + XT_STK_AR_NUM=16 + XT_STK_SAR=19 + XT_STK_EXCCAUSE=20 + XT_STK_EXCVADDR=21 + XT_STK_LBEG=22 + XT_STK_LEND=23 + XT_STK_LCOUNT=24 + XT_STK_FRMSZ=25 + + regs = [0] * REG_NUM + # TODO: support for growing up stacks + if not grows_down: + raise ESPCoreDumpLoaderError("Growing up stacks are not supported for now!") + ex_struct = "<%dL" % XT_STK_FRMSZ + if len(data) < struct.calcsize(ex_struct): + raise ESPCoreDumpLoaderError("Too small stack to keep frame: %d bytes!" % len(data)) + + stack = struct.unpack(ex_struct, data[:struct.calcsize(ex_struct)]) + # Stack frame type indicator is always the first item + rc = stack[XT_STK_EXIT] + if rc != 0: + regs[REG_PC_IDX] = stack[XT_STK_PC] + regs[REG_PS_IDX] = stack[XT_STK_PS] + for i in range(XT_STK_AR_NUM): + regs[REG_AR_START_IDX + i] = stack[XT_STK_AR_START + i] + regs[REG_SAR_IDX] = stack[XT_STK_SAR] + regs[REG_LB_IDX] = stack[XT_STK_LBEG] + regs[REG_LE_IDX] = stack[XT_STK_LEND] + regs[REG_LC_IDX] = stack[XT_STK_LCOUNT] + # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set + # and GDB can not unwind callstack properly (it implies not windowed call0) + if regs[REG_PS_IDX] & (1 << 5): + regs[REG_PS_IDX] &= ~(1 << 4) + else: + regs[REG_PC_IDX] = stack[XT_SOL_PC] + regs[REG_PS_IDX] = stack[XT_SOL_PS] + for i in range(XT_SOL_AR_NUM): + regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i] + nxt = stack[XT_SOL_NEXT] + + # TODO: remove magic hack with saved PC to get proper value + regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000) + if regs[REG_PC_IDX] & 0x80000000: + regs[REG_PC_IDX] = (regs[REG_PC_IDX] & 0x3fffffff) | 0x40000000; + if regs[REG_AR_START_IDX + 0] & 0x80000000: + regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000; + return regs + + def remove_tmp_file(self, fname): + """Silently removes temporary file + """ + try: + os.remove(fname) + except OSError as e: + if e.errno != errno.ENOENT: + print "Warning failed to remove temp file '%s' (%d)!" % (fname, e.errno) + + def cleanup(self): + """Cleans up loader resources + """ + if self.fcore: + self.fcore.close() + if self.fcore_name: + self.remove_tmp_file(self.fcore_name) + + def create_corefile(self, core_fname=None, off=0): + """Creates core dump ELF file + """ + core_off = off + data = self.read_data(core_off, self.ESP32_COREDUMP_HDR_SZ) + tot_len,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_HDR_FMT, data) + tcbsz_aligned = tcbsz + if tcbsz_aligned % 4: + tcbsz_aligned = 4*(tcbsz_aligned/4 + 1) + core_off += self.ESP32_COREDUMP_HDR_SZ + core_elf = ESPCoreDumpElfFile() + notes = b'' + for i in range(task_num): + data = self.read_data(core_off, self.ESP32_COREDUMP_TSK_HDR_SZ) + tcb_addr,stack_top,stack_end = struct.unpack_from(self.ESP32_COREDUMP_TSK_HDR_FMT, data) + if stack_end > stack_top: + stack_len = stack_end - stack_top + stack_base = stack_top + else: + stack_len = stack_top - stack_end + stack_base = stack_end + + stack_len_aligned = stack_len + if stack_len_aligned % 4: + stack_len_aligned = 4*(stack_len_aligned/4 + 1) + + core_off += self.ESP32_COREDUMP_TSK_HDR_SZ + data = self.read_data(core_off, tcbsz_aligned) + if tcbsz != tcbsz_aligned: + core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + else: + core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + core_off += tcbsz_aligned + data = self.read_data(core_off, stack_len_aligned) + if stack_len != stack_len_aligned: + data = data[:stack_len - stack_len_aligned] + core_elf.add_program_segment(stack_base, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + core_off += stack_len_aligned + try: + task_regs = self._get_registers_from_stack(data, stack_end > stack_top) + except Exception as e: + print e + return None + prstatus = XtensaPrStatus() + prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task + prstatus.pr_pid = i # TODO: use pid assigned by OS + note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump() + notes += note + + # add notes + core_elf.add_program_segment(0, notes, ESPCoreDumpElfFile.PT_NOTE, 0) + + core_elf.e_type = ESPCoreDumpElfFile.ET_CORE + core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA + if core_fname: + fce = open(core_fname, 'wb') + else: + fhnd,core_fname = tempfile.mkstemp() + fce = os.fdopen(fhnd, 'wb') + core_elf.dump(fce) + fce.close() + return core_fname + + def read_data(self, off, sz): + """Reads data from raw core dump got from flash or UART + """ + self.fcore.seek(off) + data = self.fcore.read(sz) + return data + + +class ESPCoreDumpFileLoader(ESPCoreDumpLoader): + """Core dump file loader class + """ + def __init__(self, path, b64 = False): + """Constructor for core dump file loader + """ + super(ESPCoreDumpFileLoader, self).__init__() + self.fcore = self._load_coredump(path, b64) + + def _load_coredump(self, path, b64): + """Loads core dump from (raw binary or base64-encoded) file + """ + self.fcore_name = None + if b64: + fhnd,self.fcore_name = tempfile.mkstemp() + fcore = os.fdopen(fhnd, 'wb') + fb64 = open(path, 'rb') + try: + while True: + line = fb64.readline() + if len(line) == 0: + break + data = base64.standard_b64decode(line.rstrip('\r\n')) + fcore.write(data) + fcore.close() + fcore = open(self.fcore_name, 'rb') + except Exception as e: + if self.fcore_name: + self.remove_tmp_file(self.fcore_name) + raise e + finally: + fb64.close() + else: + fcore = open(path, 'rb') + return fcore + + +class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): + """Core dump flash loader class + """ + ESP32_COREDUMP_FLASH_MAGIC_START = 0xE32C04ED + ESP32_COREDUMP_FLASH_MAGIC_END = 0xE32C04ED + ESP32_COREDUMP_FLASH_MAGIC_FMT = ' sl: + self.result_str = ln[sl:] + if self.result_str.startswith(','): + self.result_str = self.result_str[1:] + else: + print "Invalid result format: '%s'" % ln + else: + self.result_str = '' + return True + return False + + def execute(self, ln): + """Executes GDB/MI result handler function + """ + GDBMIOutRecordHandler.execute(self, ln) + if self._parse_rc(ln, self.RC_DONE): + return + if self._parse_rc(ln, self.RC_RUNNING): + return + if self._parse_rc(ln, self.RC_CONNECTED): + return + if self._parse_rc(ln, self.RC_ERROR): + return + if self._parse_rc(ln, self.RC_EXIT): + return + print "Unknown GDB/MI result: '%s'" % ln + + +class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler): + """GDB/MI console stream handler class + """ + TAG = '~' + + +def dbg_corefile(args): + """ Command to load core dump from file or flash and run GDB debug session with it + """ + global CLOSE_FDS + loader = None + if not args.core: + loader = ESPCoreDumpFlashLoader(args.off, port=args.port) + core_fname = loader.create_corefile(args.save_core) + if not core_fname: + print "Failed to create corefile!" + loader.cleanup() + return + else: + core_fname = args.core + if args.core_format and args.core_format != 'elf': + loader = ESPCoreDumpFileLoader(core_fname, args.core_format == 'b64') + core_fname = loader.create_corefile(args.save_core) + if not core_fname: + print "Failed to create corefile!" + loader.cleanup() + return + + p = subprocess.Popen( + bufsize = 0, + args = [args.gdb, + '--nw', # ignore .gdbinit + '--core=%s' % core_fname, # core file + args.prog], + stdin = None, stdout = None, stderr = None, + close_fds = CLOSE_FDS + ) + p.wait() + + if loader: + if not args.core and not args.save_core: + loader.remove_tmp_file(core_fname) + loader.cleanup() + print 'Done!' + + +def info_corefile(args): + """ Command to load core dump from file or flash and print it's data in user friendly form + """ + global CLOSE_FDS + def gdbmi_console_stream_handler(ln): + sys.stdout.write(ln) + sys.stdout.flush() + + def gdbmi_read2prompt(f, out_handlers=None): + while True: + ln = f.readline().rstrip(' \r\n') + if ln == '(gdb)': + break + elif len(ln) == 0: + break + elif out_handlers: + for h in out_handlers: + if ln.startswith(out_handlers[h].TAG): + out_handlers[h].execute(ln) + break + + loader = None + if not args.core: + loader = ESPCoreDumpFlashLoader(args.off, port=args.port) + core_fname = loader.create_corefile(args.save_core) + if not core_fname: + print "Failed to create corefile!" + loader.cleanup() + return + else: + core_fname = args.core + if args.core_format and args.core_format != 'elf': + loader = ESPCoreDumpFileLoader(core_fname, args.core_format == 'b64') + core_fname = loader.create_corefile(args.save_core) + if not core_fname: + print "Failed to create corefile!" + loader.cleanup() + return + + handlers = {} + handlers[GDBMIResultHandler.TAG] = GDBMIResultHandler(verbose=False) + handlers[GDBMIStreamConsoleHandler.TAG] = GDBMIStreamConsoleHandler(None, verbose=False) + p = subprocess.Popen( + bufsize = 0, + args = [args.gdb, + '--quiet', # inhibit dumping info at start-up + '--nx', # inhibit window interface + '--nw', # ignore .gdbinit + '--interpreter=mi2', # use GDB/MI v2 + '--core=%s' % core_fname, # core file + args.prog], + stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, + close_fds = CLOSE_FDS + ) + gdbmi_read2prompt(p.stdout, handlers) + exe_elf = ESPCoreDumpElfFile(args.prog) + core_elf = ESPCoreDumpElfFile(core_fname) + merged_segs = [] + core_segs = core_elf.program_segments + for s in exe_elf.sections: + merged = False + for ps in core_segs: + if ps.addr <= s.addr and ps.addr + len(ps.data) >= s.addr: + # sec: |XXXXXXXXXX| + # seg: |...XXX.............| + seg_addr = ps.addr + if ps.addr + len(ps.data) <= s.addr + len(s.data): + # sec: |XXXXXXXXXX| + # seg: |XXXXXXXXXXX...| + # merged: |XXXXXXXXXXXXXX| + seg_len = len(s.data) + (s.addr - ps.addr) + else: + # sec: |XXXXXXXXXX| + # seg: |XXXXXXXXXXXXXXXXX| + # merged: |XXXXXXXXXXXXXXXXX| + seg_len = len(ps.data) + merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) + core_segs.remove(ps) + merged = True + elif ps.addr >= s.addr and ps.addr <= s.addr + len(s.data): + # sec: |XXXXXXXXXX| + # seg: |...XXX.............| + seg_addr = s.addr + if (ps.addr + len(ps.data)) >= (s.addr + len(s.data)): + # sec: |XXXXXXXXXX| + # seg: |..XXXXXXXXXXX| + # merged: |XXXXXXXXXXXXX| + seg_len = len(s.data) + (ps.addr + len(ps.data)) - (s.addr + len(s.data)) + else: + # sec: |XXXXXXXXXX| + # seg: |XXXXXX| + # merged: |XXXXXXXXXX| + seg_len = len(s.data) + merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) + core_segs.remove(ps) + merged = True + + if not merged: + merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False)) + + print "===============================================================" + print "==================== ESP32 CORE DUMP START ====================" + + handlers[GDBMIResultHandler.TAG].result_class = None + handlers[GDBMIStreamConsoleHandler.TAG].func = gdbmi_console_stream_handler + print "\n================== CURRENT THREAD REGISTERS ===================" + p.stdin.write("-interpreter-exec console \"info registers\"\n") + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + print "\n==================== CURRENT THREAD STACK =====================" + p.stdin.write("-interpreter-exec console \"bt\"\n") + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + print "\n======================== THREADS INFO =========================" + p.stdin.write("-interpreter-exec console \"info threads\"\n") + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + print "\n======================= ALL MEMORY REGIONS ========================" + print "Name Address Size Attrs" + for ms in merged_segs: + print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3]) + for cs in core_segs: + print ".coredump.tasks 0x%x 0x%x %s" % (cs.addr, len(cs.data), cs.attr_str()) + if args.print_mem: + print "\n====================== CORE DUMP MEMORY CONTENTS ========================" + for cs in core_elf.program_segments: + print ".coredump.tasks 0x%x 0x%x %s" % (cs.addr, len(cs.data), cs.attr_str()) + p.stdin.write("-interpreter-exec console \"x/%dx 0x%x\"\n" % (len(cs.data)/4, cs.addr)) + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + + print "\n===================== ESP32 CORE DUMP END =====================" + print "===============================================================" + + p.stdin.write('q\n') + p.wait() + p.stdin.close() + p.stdout.close() + + if loader: + if not args.core and not args.save_core: + loader.remove_tmp_file(core_fname) + loader.cleanup() + print 'Done!' + + +def main(): + parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__, prog='espcoredump') + + parser.add_argument('--chip', '-c', + help='Target chip type', + choices=['auto', 'esp32'], + default=os.environ.get('ESPTOOL_CHIP', 'auto')) + + parser.add_argument( + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT)) + + parser.add_argument( + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=int, + default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD)) + + subparsers = parser.add_subparsers( + dest='operation', + help='Run coredumper {command} -h for additional help') + + parser_debug_coredump = subparsers.add_parser( + 'dbg_corefile', + help='Starts GDB debugging session with specified corefile') + parser_debug_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') + parser_debug_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) + parser_debug_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary', type=str, default='elf') + parser_debug_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000) + parser_debug_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c"', type=str) + parser_debug_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) + + parser_info_coredump = subparsers.add_parser( + 'info_corefile', + help='Print core dump info from file') + parser_info_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') + parser_info_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) + parser_info_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary', type=str, default='elf') + parser_info_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000) + parser_info_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. Does not work with "-c"', type=str) + parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true') + parser_info_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation + + args = parser.parse_args() + + print 'espcoredump.py v%s' % __version__ + + operation_func = globals()[args.operation] + operation_func(args) + + +if __name__ == '__main__': + try: + main() + except ESPCoreDumpError as e: + print '\nA fatal error occurred: %s' % e + sys.exit(2) diff --git a/components/espcoredump/test/component.mk b/components/espcoredump/test/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/espcoredump/test/test_core_dump.c b/components/espcoredump/test/test_core_dump.c new file mode 100644 index 0000000000..41137f9302 --- /dev/null +++ b/components/espcoredump/test/test_core_dump.c @@ -0,0 +1,105 @@ +/* Application For Core Dumps Generation + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "nvs_flash.h" + + +// task crash indicators +#define TCI_NULL_PTR 0x1 +#define TCI_UNALIGN_PTR 0x2 +#define TCI_FAIL_ASSERT 0x4 + +volatile unsigned long crash_flags = TCI_UNALIGN_PTR; + +void bad_ptr_func() +{ + unsigned long *ptr = (unsigned long *)0; + volatile int cnt; + int i = 0; + + for (i = 0; i < 1000; i++) { + cnt++; + } + + if(crash_flags & TCI_NULL_PTR) { + printf("Write to bad address 0x%lx.\n", (unsigned long)ptr); + *ptr = 0xDEADBEEF; + } +} + +void bad_ptr_task(void *pvParameter) +{ + printf("Task 'bad_ptr_task' start.\n"); + while (1) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Task 'bad_ptr_task' run.\n"); + bad_ptr_func(); + } + fflush(stdout); +} + +void recur_func() +{ + static int rec_cnt; + unsigned short *ptr = (unsigned short *)0x5; + volatile int cnt; + int i = 0; + + if (rec_cnt++ > 2) { + return; + } + for (i = 0; i < 4; i++) { + cnt++; + if(i == 2) { + recur_func(); + break; + } + } + + if(crash_flags & TCI_UNALIGN_PTR) { + printf("Write to unaligned address 0x%lx.\n", (unsigned long)ptr); + *ptr = 0xDEAD; + } +} + +void unaligned_ptr_task(void *pvParameter) +{ + printf("Task 'unaligned_ptr_task' start.\n"); + while (1) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Task 'unaligned_ptr_task' run.\n"); + recur_func(); + } + fflush(stdout); +} + +void failed_assert_task(void *pvParameter) +{ + printf("Task 'failed_assert_task' start.\n"); + while (1) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Task 'failed_assert_task' run.\n"); + if(crash_flags & TCI_FAIL_ASSERT) { + printf("Assert.\n"); + assert(0); + } + } + fflush(stdout); +} + +void app_main() +{ + nvs_flash_init(); + xTaskCreate(&bad_ptr_task, "bad_ptr_task", 2048, NULL, 5, NULL); + xTaskCreatePinnedToCore(&unaligned_ptr_task, "unaligned_ptr_task", 2048, NULL, 7, NULL, 1); + xTaskCreatePinnedToCore(&failed_assert_task, "failed_assert_task", 2048, NULL, 10, NULL, 0); +} diff --git a/components/freertos/include/freertos/FreeRTOS.h b/components/freertos/include/freertos/FreeRTOS.h index 4c60308f78..0e93acf271 100644 --- a/components/freertos/include/freertos/FreeRTOS.h +++ b/components/freertos/include/freertos/FreeRTOS.h @@ -863,8 +863,8 @@ typedef struct xSTATIC_TCB void *pxDummy6; uint8_t ucDummy7[ configMAX_TASK_NAME_LEN ]; UBaseType_t uxDummyCoreId; - #if ( portSTACK_GROWTH > 0 ) - void *pxDummy8; + #if ( portSTACK_GROWTH > 0 || configENABLE_TASK_SNAPSHOT == 1 ) + void *pxDummy8; #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxDummy9; diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index 4f1012657a..b2fc077bc3 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -268,7 +268,9 @@ #define configXT_BOARD 1 /* Board mode */ #define configXT_SIMULATOR 0 - +#if CONFIG_ESP32_ENABLE_COREDUMP +#define configENABLE_TASK_SNAPSHOT 1 +#endif #endif /* FREERTOS_CONFIG_H */ diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index f7b9181fcf..590b07a2a4 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -181,6 +181,18 @@ typedef struct xTASK_STATUS uint16_t usStackHighWaterMark; /* The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */ } TaskStatus_t; +/* + * Used with the uxTaskGetSnapshotAll() function to save memory snapshot of each task in the system. + * We need this struct because TCB_t is defined (hidden) in tasks.c. + */ +typedef struct xTASK_SNAPSHOT +{ + void *pxTCB; /* Address of task control block. */ + StackType_t *pxTopOfStack; /* Points to the location of the last item placed on the tasks stack. */ + StackType_t *pxEndOfStack; /* Points to the end of the stack. pxTopOfStack < pxEndOfStack, stack grows hi2lo + pxTopOfStack > pxEndOfStack, stack grows lo2hi*/ +} TaskSnapshot_t; + /* Possible return values for eTaskConfirmSleepModeStatus(). */ typedef enum { @@ -2173,6 +2185,17 @@ eSleepModeStatus eTaskConfirmSleepModeStatus( void ) PRIVILEGED_FUNCTION; */ void *pvTaskIncrementMutexHeldCount( void ); +/* + * This function fills array with TaskSnapshot_t structures for every task in the system. + * Used by core dump facility to get snapshots of all tasks in the system. + * Only available when configENABLE_TASK_SNAPSHOT is set to 1. + * @param pxTaskSnapshotArray Pointer to array of TaskSnapshot_t structures to store tasks snapshot data. + * @param uxArraySize Size of tasks snapshots array. + * @param pxTcbSz Pointer to store size of TCB. + * @return Number of elements stored in array. + */ +UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz ); + #ifdef __cplusplus } #endif diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 64031cfbca..6caabaa38e 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -181,7 +181,7 @@ typedef struct tskTaskControlBlock char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ BaseType_t xCoreID; /*< Core this task is pinned to */ /* If this moves around (other than pcTaskName size changes), please change the define in xtensa_vectors.S as well. */ - #if ( portSTACK_GROWTH > 0 ) + #if ( portSTACK_GROWTH > 0 || configENABLE_TASK_SNAPSHOT == 1 ) StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */ #endif @@ -885,6 +885,12 @@ UBaseType_t x; /* Check the alignment of the calculated top of stack is correct. */ configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + #if ( configENABLE_TASK_SNAPSHOT == 1 ) + { + /* need stack end for core dumps */ + pxNewTCB->pxEndOfStack = pxTopOfStack; + } +#endif } #else /* portSTACK_GROWTH */ { @@ -4912,6 +4918,82 @@ TickType_t uxReturn; #endif /* configUSE_TASK_NOTIFICATIONS */ +#if ( configENABLE_TASK_SNAPSHOT == 1 ) + + static void prvTaskGetSnapshotsFromList( TaskSnapshot_t *pxTaskSnapshotArray, UBaseType_t *uxTask, const UBaseType_t uxArraySize, List_t *pxList ) + { + TCB_t *pxNextTCB, *pxFirstTCB; + + if( listCURRENT_LIST_LENGTH( pxList ) > ( UBaseType_t ) 0 ) + { + listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxList ); + do + { + listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxList ); + + if( *uxTask >= uxArraySize ) + break; + + pxTaskSnapshotArray[ *uxTask ].pxTCB = pxNextTCB; + pxTaskSnapshotArray[ *uxTask ].pxTopOfStack = (StackType_t *)pxNextTCB->pxTopOfStack; + #if( portSTACK_GROWTH < 0 ) + { + pxTaskSnapshotArray[ *uxTask ].pxEndOfStack = pxNextTCB->pxEndOfStack; + } + #else + { + pxTaskSnapshotArray[ *uxTask ].pxEndOfStack = pxNextTCB->pxStack; + } + #endif + + (*uxTask)++; + + } while( pxNextTCB != pxFirstTCB ); + } + else + { + mtCOVERAGE_TEST_MARKER(); + } + } + + UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz ) + { + UBaseType_t uxTask = 0, i = 0; + + *pxTcbSz = sizeof(TCB_t); + + { + /* Fill in an TaskStatus_t structure with information on each + task in the Ready state. */ + i = configMAX_PRIORITIES; + do + { + i--; + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &( pxReadyTasksLists[ i ] ) ); + } while( i > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + + /* Fill in an TaskStatus_t structure with information on each + task in the Blocked state. */ + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, ( List_t * ) pxDelayedTaskList ); + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, ( List_t * ) pxOverflowDelayedTaskList ); + + #if( INCLUDE_vTaskDelete == 1 ) + { + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xTasksWaitingTermination ); + } + #endif + + #if ( INCLUDE_vTaskSuspend == 1 ) + { + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xSuspendedTaskList ); + } + #endif + } + return uxTask; + } + +#endif + #ifdef FREERTOS_MODULE_TEST #include "tasks_test_access_functions.h" #endif diff --git a/components/log/include/esp_log.h b/components/log/include/esp_log.h index 33bc10b42a..6fda8d6054 100644 --- a/components/log/include/esp_log.h +++ b/components/log/include/esp_log.h @@ -75,6 +75,16 @@ void esp_log_set_vprintf(vprintf_like_t func); */ uint32_t esp_log_timestamp(void); +/** + * @brief Function which returns timestamp to be used in log output + * + * This function uses HW cycle counter and does not depend on OS, + * so it can be safely used after application crash. + * + * @return timestamp, in milliseconds + */ +uint32_t esp_log_early_timestamp(void); + /** * @brief Write message into the log * diff --git a/components/partition_table/Kconfig.projbuild b/components/partition_table/Kconfig.projbuild index 1f019a6e3f..36e435ba2f 100644 --- a/components/partition_table/Kconfig.projbuild +++ b/components/partition_table/Kconfig.projbuild @@ -45,8 +45,10 @@ config PARTITION_TABLE_CUSTOM_PHY_DATA_OFFSET config PARTITION_TABLE_FILENAME string - default partitions_singleapp.csv if PARTITION_TABLE_SINGLE_APP - default partitions_two_ota.csv if PARTITION_TABLE_TWO_OTA + default partitions_singleapp.csv if PARTITION_TABLE_SINGLE_APP && !ESP32_ENABLE_COREDUMP_TO_FLASH + default partitions_singleapp_coredump.csv if PARTITION_TABLE_SINGLE_APP && ESP32_ENABLE_COREDUMP_TO_FLASH + default partitions_two_ota.csv if PARTITION_TABLE_TWO_OTA && !ESP32_ENABLE_COREDUMP_TO_FLASH + default partitions_two_ota_coredump.csv if PARTITION_TABLE_TWO_OTA && ESP32_ENABLE_COREDUMP_TO_FLASH default PARTITION_TABLE_CUSTOM_FILENAME if PARTITION_TABLE_CUSTOM config APP_OFFSET diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index a4412ad45b..0491204885 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -127,6 +127,7 @@ class PartitionDefinition(object): "ota" : 0x00, "phy" : 0x01, "nvs" : 0x02, + "coredump" : 0x03, "esphttpd" : 0x80, "fat" : 0x81, "spiffs" : 0x82, diff --git a/components/partition_table/partitions_singleapp_coredump.csv b/components/partition_table/partitions_singleapp_coredump.csv new file mode 100644 index 0000000000..a9f12c0fd3 --- /dev/null +++ b/components/partition_table/partitions_singleapp_coredump.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000 +phy_init, data, phy, 0xf000, 0x1000 +factory, app, factory, 0x10000, 1M +coredump, data, coredump,, 64K diff --git a/components/partition_table/partitions_two_ota_coredump.csv b/components/partition_table/partitions_two_ota_coredump.csv new file mode 100644 index 0000000000..64d70b0d8e --- /dev/null +++ b/components/partition_table/partitions_two_ota_coredump.csv @@ -0,0 +1,9 @@ +# Name, Type, SubType, Offset, Size +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, 0xd000, 0x2000 +phy_init, data, phy, 0xf000, 0x1000 +factory, 0, 0, 0x10000, 1M +coredump, data, coredump,, 64K +ota_0, 0, ota_0, , 1M +ota_1, 0, ota_1, , 1M diff --git a/components/spi_flash/cache_utils.c b/components/spi_flash/cache_utils.c index f30db80cd8..983ff5d256 100644 --- a/components/spi_flash/cache_utils.c +++ b/components/spi_flash/cache_utils.c @@ -141,6 +141,29 @@ void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() esp_intr_noniram_enable(); } +void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu_no_os() +{ + const uint32_t cpuid = xPortGetCoreID(); + const uint32_t other_cpuid = (cpuid == 0) ? 1 : 0; + + // do not care about other CPU, it was halted upon entering panic handler + spi_flash_disable_cache(other_cpuid, &s_flash_op_cache_state[other_cpuid]); + // Kill interrupts that aren't located in IRAM + esp_intr_noniram_disable(); + // Disable cache on this CPU as well + spi_flash_disable_cache(cpuid, &s_flash_op_cache_state[cpuid]); +} + +void IRAM_ATTR spi_flash_enable_interrupts_caches_no_os() +{ + const uint32_t cpuid = xPortGetCoreID(); + + // Re-enable cache on this CPU + spi_flash_restore_cache(cpuid, s_flash_op_cache_state[cpuid]); + // Re-enable non-iram interrupts + esp_intr_noniram_enable(); +} + #else // CONFIG_FREERTOS_UNICORE void spi_flash_init_lock() @@ -172,6 +195,22 @@ void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() esp_intr_noniram_enable(); } +void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu_no_os() +{ + // Kill interrupts that aren't located in IRAM + esp_intr_noniram_disable(); + // Disable cache on this CPU as well + spi_flash_disable_cache(0, &s_flash_op_cache_state[0]); +} + +void IRAM_ATTR spi_flash_enable_interrupts_caches_no_os() +{ + // Re-enable cache on this CPU + spi_flash_restore_cache(0, s_flash_op_cache_state[0]); + // Re-enable non-iram interrupts + esp_intr_noniram_enable(); +} + #endif // CONFIG_FREERTOS_UNICORE /** diff --git a/components/spi_flash/cache_utils.h b/components/spi_flash/cache_utils.h index 899a31c651..598b8fba77 100644 --- a/components/spi_flash/cache_utils.h +++ b/components/spi_flash/cache_utils.h @@ -40,5 +40,12 @@ void spi_flash_disable_interrupts_caches_and_other_cpu(); // Enable cache, enable interrupts (to be added in future), resume scheduler void spi_flash_enable_interrupts_caches_and_other_cpu(); +// Disables non-IRAM interrupt handlers on current CPU and caches on both CPUs. +// This function is implied to be called when other CPU is not running or running code from IRAM. +void spi_flash_disable_interrupts_caches_and_other_cpu_no_os(); + +// Enable cache, enable interrupts on current CPU. +// This function is implied to be called when other CPU is not running or running code from IRAM. +void spi_flash_enable_interrupts_caches_no_os(); #endif //ESP_SPI_FLASH_CACHE_UTILS_H diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index 993e68a572..fffe487bd1 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -60,6 +60,18 @@ static spi_flash_counters_t s_flash_stats; static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); +const DRAM_ATTR spi_flash_guard_funcs_t g_flash_guard_default_ops = { + .start = spi_flash_disable_interrupts_caches_and_other_cpu, + .end = spi_flash_enable_interrupts_caches_and_other_cpu +}; + +const DRAM_ATTR spi_flash_guard_funcs_t g_flash_guard_no_os_ops = { + .start = spi_flash_disable_interrupts_caches_and_other_cpu_no_os, + .end = spi_flash_enable_interrupts_caches_no_os +}; + +static const spi_flash_guard_funcs_t *s_flash_guard_ops; + void spi_flash_init() { spi_flash_init_lock(); @@ -68,6 +80,11 @@ void spi_flash_init() #endif } +void spi_flash_guard_set(const spi_flash_guard_funcs_t* funcs) +{ + s_flash_guard_ops = funcs; +} + size_t spi_flash_get_chip_size() { return g_rom_flashchip.chip_size; @@ -86,6 +103,18 @@ SpiFlashOpResult IRAM_ATTR spi_flash_unlock() return SPI_FLASH_RESULT_OK; } +static inline void spi_flash_guard_start() +{ + if (s_flash_guard_ops) + s_flash_guard_ops->start(); +} + +static inline void spi_flash_guard_end() +{ + if (s_flash_guard_ops) + s_flash_guard_ops->end(); +} + esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec) { return spi_flash_erase_range(sec * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE); @@ -106,7 +135,7 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) size_t end = start + size / SPI_FLASH_SEC_SIZE; const size_t sectors_per_block = BLOCK_ERASE_SIZE / SPI_FLASH_SEC_SIZE; COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); + spi_flash_guard_start(); SpiFlashOpResult rc; rc = spi_flash_unlock(); if (rc == SPI_FLASH_RESULT_OK) { @@ -122,7 +151,7 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) } } } - spi_flash_enable_interrupts_caches_and_other_cpu(); + spi_flash_guard_end(); COUNTER_STOP(erase); return spi_flash_translate_rc(rc); } @@ -160,9 +189,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) if (left_size > 0) { uint32_t t = 0xffffffff; memcpy(((uint8_t *) &t) + (dst - left_off), srcc, left_size); - spi_flash_disable_interrupts_caches_and_other_cpu(); + spi_flash_guard_start(); rc = SPIWrite(left_off, &t, 4); - spi_flash_enable_interrupts_caches_and_other_cpu(); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -178,9 +207,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) bool in_dram = true; #endif if (in_dram && (((uintptr_t) srcc) + mid_off) % 4 == 0) { - spi_flash_disable_interrupts_caches_and_other_cpu(); + spi_flash_guard_start(); rc = SPIWrite(dst + mid_off, (const uint32_t *) (srcc + mid_off), mid_size); - spi_flash_enable_interrupts_caches_and_other_cpu(); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -194,9 +223,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) uint32_t t[8]; uint32_t write_size = MIN(mid_size, sizeof(t)); memcpy(t, srcc + mid_off, write_size); - spi_flash_disable_interrupts_caches_and_other_cpu(); + spi_flash_guard_start(); rc = SPIWrite(dst + mid_off, t, write_size); - spi_flash_enable_interrupts_caches_and_other_cpu(); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -209,9 +238,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) if (right_size > 0) { uint32_t t = 0xffffffff; memcpy(&t, srcc + right_off, right_size); - spi_flash_disable_interrupts_caches_and_other_cpu(); + spi_flash_guard_start(); rc = SPIWrite(dst + right_off, &t, 4); - spi_flash_enable_interrupts_caches_and_other_cpu(); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -271,7 +300,7 @@ esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) SpiFlashOpResult rc = SPI_FLASH_RESULT_OK; COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); + spi_flash_guard_start(); /* To simplify boundary checks below, we handle small reads separately. */ if (size < 16) { uint32_t t[6]; /* Enough for 16 bytes + 4 on either side for padding. */ @@ -345,7 +374,7 @@ esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) memcpy(dstc + pad_right_off, t, pad_right_size); } out: - spi_flash_enable_interrupts_caches_and_other_cpu(); + spi_flash_guard_end(); COUNTER_STOP(read); return spi_flash_translate_rc(rc); } diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index b67891ae15..b149e10234 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -69,6 +69,7 @@ typedef enum { ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00, //!< OTA selection partition ESP_PARTITION_SUBTYPE_DATA_PHY = 0x01, //!< PHY init data partition ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02, //!< NVS partition + ESP_PARTITION_SUBTYPE_DATA_COREDUMP = 0x03, //!< COREDUMP partition ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD = 0x80, //!< ESPHTTPD partition ESP_PARTITION_SUBTYPE_DATA_FAT = 0x81, //!< FAT partition diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index bb3ec39b45..bf897e8995 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -173,6 +173,49 @@ void spi_flash_munmap(spi_flash_mmap_handle_t handle); */ void spi_flash_mmap_dump(); +/** + * @brief SPI flash critical section enter function. + */ +typedef void (*spi_flash_guard_start_func_t)(void); +/** + * @brief SPI flash critical section exit function. + */ +typedef void (*spi_flash_guard_end_func_t)(void); + +/** + * Structure holding SPI flash access critical section management functions + * + * @note Structure and corresponding guard functions should not reside in flash. + * For example structure can be placed in DRAM and functions in IRAM sections. + */ +typedef struct { + spi_flash_guard_start_func_t start; /**< critical section start func */ + spi_flash_guard_end_func_t end; /**< critical section end func */ +} spi_flash_guard_funcs_t; + +/** + * @brief Sets guard functions to access flash. + * + * @note Pointed structure and corresponding guard functions should not reside in flash. + * For example structure can be placed in DRAM and functions in IRAM sections. + * + * @param funcs pointer to structure holding flash access guard functions. + */ +void spi_flash_guard_set(const spi_flash_guard_funcs_t* funcs); + +/** + * @brief Default OS-aware flash access guard functions + */ +extern const spi_flash_guard_funcs_t g_flash_guard_default_ops; + +/** + * @brief Non-OS flash access guard functions + * + * @note This version of flash guard functions is to be used when no OS is present or from panic handler. + * It does not use any OS primitives and IPC and implies that only calling CPU is active. + */ +extern const spi_flash_guard_funcs_t g_flash_guard_no_os_ops; + #if CONFIG_SPI_FLASH_ENABLE_COUNTERS /** diff --git a/docs/Doxyfile b/docs/Doxyfile index 7dbd1cc8d1..a3fc5faa12 100755 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -35,7 +35,8 @@ INPUT = ../components/esp32/include/esp_wifi.h \ ../components/esp32/include/esp_deep_sleep.h \ ../components/sdmmc/include/sdmmc_cmd.h \ ../components/fatfs/src/esp_vfs_fat.h \ - ../components/fatfs/src/diskio.h + ../components/fatfs/src/diskio.h \ + ../components/esp32/include/esp_core_dump.h ## Get warnings for functions that have no documentation for their parameters or return value ## diff --git a/docs/core_dump.rst b/docs/core_dump.rst new file mode 100644 index 0000000000..a8e328996f --- /dev/null +++ b/docs/core_dump.rst @@ -0,0 +1,81 @@ +ESP32 Core Dump +=============== + +Overview +-------- + +ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analysis of software state at the moment of failure. +Upon the crash system enters panic state, prints some information and halts or reboots depending configuration. User can choose to generate core dump in order to analyse +the reason of failure on PC later on. Core dump contains snapshots of all tasks in the system at the moment of failure. Snapshots include tasks control blocks (TCB) and stacks. +So it is possible to find out what task, at what instruction (line of code) and what callstack of that task lead to the crash. +ESP-IDF provides special script `espcoredump.py` to help users to retrieve and analyse core dumps. This tool provides two commands for core dumps analysis: + +* info_corefile - prints crashed task's registers, callstack, list of available tasks in the system, memory regions and contents of memory stored in core dump (TCBs and stacks) +* dbg_corefile - creates core dump ELF file and runs GDB debug session with this file. User can examine memory, variables and tasks states manually. Note that since not all memory is saved in core dump only values of variables allocated on stack will be meaningfull + +Configuration +------------- + +Currently there are three options related to core dump generation which user can choose in configuration menu of the application (`make menuconfig`): + +* Disable core dump generation +* Save core dump to flash +* Print core dump to UART + +These options can be choosen in Components -> ESP32-specific config -> Core dump destination menu item. + +Save core dump to flash +----------------------- + +When this option is selected core dumps are saved to special partition on flash. When using default partition table files which are provided with ESP-IDF it automatically +allocates necessary space on flash, But if user wants to use its own layout file together with core dump feature it should define separate partition for core dump +as it is shown below:: + + # Name, Type, SubType, Offset, Size + # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild + nvs, data, nvs, 0x9000, 0x6000 + phy_init, data, phy, 0xf000, 0x1000 + factory, app, factory, 0x10000, 1M + coredump, data, coredump,, 64K + +There are no special requrements for partition name. It can be choosen according to the user application needs, but partition type should be 'data' and +sub-type should be 'coredump'. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes. +This overhead does not include size of TCB and stack for every task. So partirion size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes. + +The example of generic command to analyze core dump from flash is: `espcoredump.py -p info_corefile ` +or `espcoredump.py -p dbg_corefile ` + +Print core dump to UART +----------------------- + +When this option is selected base64-encoded core dumps are printed on UART upon system panic. In this case user should save core dump text body to some file manually and +then run the following command: `espcoredump.py -p info_corefile -t b64 -c ` +or `espcoredump.py -p dbg_corefile -t b64 -c ` + +Base64-encoded body of core dump will be between the following header and footer:: + + ================= CORE DUMP START ================= + + ================= CORE DUMP END =================== + +Running 'espcoredump.py' +------------------------------------ + +Generic command syntax: + +`espcoredump.py [options] command [args]` + +:Script Options: + * --chip,-c {auto,esp32}. Target chip type. Supported values are `auto` and `esp32`. + * --port,-p PORT. Serial port device. + * --baud,-b BAUD. Serial port baud rate used when flashing/reading. +:Commands: + * info_corefile. Retrieve core dump and print useful info. + * dbg_corefile. Retrieve core dump and start GDB session with it. +:Command Arguments: + * --gdb,-g GDB. Path to gdb to use for data retrieval. + * --core,-c CORE. Path to core dump file to use (if skipped core dump will be read from flash). + * --core-format,-t CORE_FORMAT. Specifies that file passed with "-c" is an ELF ("elf"), dumped raw binary ("raw") or base64-encoded ("b64") format. + * --off,-o OFF. Ofsset of coredump partition in flash (type "make partition_table" to see it). + * --save-core,-s SAVE_CORE. Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c". + * --print-mem,-m Print memory dump. Used only with "info_corefile". diff --git a/docs/index.rst b/docs/index.rst index 2d9a62f14b..b994648848 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ Contents: partition-tables build_system openocd + core_dump Flash encryption Secure Boot ULP coprocessor