]> granicus.if.org Git - esp-idf/commitdiff
esp32: Add core dump saving to flash feature
authorAlexey Gerenkov <alexey@espressif.com>
Wed, 21 Dec 2016 23:56:23 +0000 (02:56 +0300)
committerAlexey Gerenkov <alexey@espressif.com>
Wed, 11 Jan 2017 17:51:28 +0000 (20:51 +0300)
Complimentary changes:
1) Partition table definitions files with core dump partition
2) Special sub-type for core dump partition
3) Special version of spi_flash_xxx
4) espcoredump.py is script to get core dump from flash and print useful info
5) FreeRTOS API was extended to get tasks snapshots

19 files changed:
components/esp32/Kconfig
components/esp32/core_dump.c [new file with mode: 0644]
components/esp32/cpu_start.c
components/esp32/include/esp_core_dump.h [new file with mode: 0644]
components/esp32/include/esp_panic.h
components/esp32/panic.c
components/espcoredump/espcoredump.py [new file with mode: 0755]
components/freertos/include/freertos/FreeRTOS.h
components/freertos/include/freertos/FreeRTOSConfig.h
components/freertos/include/freertos/task.h
components/freertos/tasks.c
components/partition_table/Kconfig.projbuild
components/partition_table/partitions_singleapp_coredump.csv [new file with mode: 0644]
components/partition_table/partitions_two_ota_coredump.csv [new file with mode: 0644]
components/spi_flash/cache_utils.c
components/spi_flash/cache_utils.h
components/spi_flash/flash_ops.c
components/spi_flash/include/esp_partition.h
components/spi_flash/include/esp_spi_flash.h

index bc35ca0e8f64b1d039f4d3fa4744afdd49021062..981158514067daf0c4955e50130ac78a8859cf9f 100644 (file)
@@ -54,6 +54,24 @@ 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"
+config ESP32_ENABLE_COREDUMP_TO_UART
+    bool "UART"
+config ESP32_ENABLE_COREDUMP_TO_NONE
+    bool "None"
+endchoice
+
 # 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 (file)
index 0000000..165ec74
--- /dev/null
@@ -0,0 +1,276 @@
+// 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 "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+//#include "esp_attr.h"
+#include "esp_panic.h"
+#include "esp_partition.h"
+
+#ifdef ESP_PLATFORM
+// Uncomment this line to force output from this module
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#include "esp_log.h"
+static const char* TAG = "esp_core_dump_init";
+#else
+#define ESP_LOGD(...)
+#endif
+
+// TODO: allow user to set this in menuconfig or get tasks iteratively
+#define COREDUMP_MAX_TASKS_NUM    32
+
+#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
+
+// magic numbers to control core dump data consistency
+#define COREDUMP_FLASH_MAGIC_START    0xDEADBEEFUL
+#define COREDUMP_FLASH_MAGIC_END      0xACDCFEEDUL
+
+// 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_panic(off, data, data_len);
+    if (err != ESP_OK) {
+        esp_panicPutStr("ERROR: Failed to write data");
+        esp_panicPutHex(err);
+        esp_panicPutStr("!\r\n");
+        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_panic(off + data_len, &rom_data, sizeof(uint32_t));
+        if (err != ESP_OK) {
+            esp_panicPutStr("ERROR: Failed to write data end");
+            esp_panicPutHex(err);
+            esp_panicPutStr("!\r\n");
+            return 0;
+        }
+        data_len += sizeof(uint32_t);
+    }
+
+    return data_len;
+}
+
+/*
+ * |   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   |
+ */
+void esp_core_dump_to_flash(XtExcFrame *frame)
+{
+    union
+    {
+        uint8_t    data8[16];
+        uint32_t   data32[4];
+    } rom_data;
+    //const esp_partition_t *core_part;
+    esp_err_t err;
+    TaskSnapshot_t tasks[COREDUMP_MAX_TASKS_NUM];
+    UBaseType_t tcb_sz, task_num;
+    uint32_t data_len = 0, i, len, sec_num;
+    size_t off;
+
+    esp_panicPutStr("Save core dump to flash...\r\n");
+    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 + magic2 + tasknum*(tcb + stack start/end + tcb addr)
+    data_len = 5*sizeof(uint32_t) + task_num*(len + 2*sizeof(uint32_t) + sizeof(uint32_t *));
+    for (i = 0; i < task_num; i++) {
+        if (tasks[i].pxTCB == xTaskGetCurrentTaskHandle()) {
+            // set correct stack top for current task
+            tasks[i].pxTopOfStack = (StackType_t *)frame;
+            esp_panicPutStr("Current task PC/A0/SP ");
+            esp_panicPutHex(frame->pc);
+            esp_panicPutStr(" ");
+            esp_panicPutHex(frame->a0);
+            esp_panicPutStr(" ");
+            esp_panicPutHex(frame->a1);
+            esp_panicPutStr("\r\n");
+        }
+#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_panicPutStr("stack len = ");
+        esp_panicPutHex(len);
+        esp_panicPutStr(" ");
+        esp_panicPutHex((int)tasks[i].pxTopOfStack);
+        esp_panicPutStr(" ");
+        esp_panicPutHex((int)tasks[i].pxEndOfStack);
+        esp_panicPutStr("\r\n");
+        // take stack padding into account
+        if (len % sizeof(uint32_t))
+            len = (len / sizeof(uint32_t) + 1) * sizeof(uint32_t);
+        data_len += len;
+    }
+    esp_panicPutStr("Core dump len =");
+    esp_panicPutHex(data_len);
+    esp_panicPutStr("\r\n");
+    if (data_len > s_core_part_size) {
+        esp_panicPutStr("ERROR: Not enough space to save core dump!");
+        return;
+    }
+
+    // TEST READ START
+    err = spi_flash_read_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data));
+    if (err != ESP_OK) {
+        esp_panicPutStr("ERROR: Failed to read flash ");
+        esp_panicPutHex(err);
+        esp_panicPutStr("!\r\n");
+        return;
+    }
+    else {
+        esp_panicPutStr("Data from flash:\r\n");
+        for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) {
+            esp_panicPutHex(rom_data.data32[i]);
+            esp_panicPutStr("\r\n");
+        }
+//            rom_data[4] = 0;
+//            esp_panicPutStr(rom_data);
+//        esp_panicPutStr("\r\n");
+    }
+    // TEST READ END
+
+    sec_num = data_len / SPI_FLASH_SEC_SIZE;
+    if (data_len % SPI_FLASH_SEC_SIZE)
+        sec_num++;
+    err = spi_flash_erase_range_panic(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE);
+    if (err != ESP_OK) {
+        esp_panicPutStr("ERROR: Failed to erase flash ");
+        esp_panicPutHex(err);
+        esp_panicPutStr("!\r\n");
+        return;
+    }
+
+    rom_data.data32[0] = COREDUMP_FLASH_MAGIC_START;
+    rom_data.data32[1] = data_len;
+    rom_data.data32[2] = task_num;
+    rom_data.data32[3] = tcb_sz;
+    err = spi_flash_write_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data));
+    if (err != ESP_OK) {
+        esp_panicPutStr("ERROR: Failed to write core dump header ");
+        esp_panicPutHex(err);
+        esp_panicPutStr("!\r\n");
+        return;
+    }
+    off = sizeof(rom_data);
+
+    for (i = 0; i < task_num; i++) {
+        esp_panicPutStr("Dump task ");
+        esp_panicPutHex((int)tasks[i].pxTCB);
+        esp_panicPutStr("\r\n");
+
+        // 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 = spi_flash_write_panic(s_core_part_start + off, &rom_data, 3*sizeof(uint32_t));
+        if (err != ESP_OK) {
+            esp_panicPutStr("ERROR: Failed to write task header ");
+            esp_panicPutHex(err);
+            esp_panicPutStr("!\r\n");
+            return;
+        }
+        off += 3*sizeof(uint32_t);
+        // save TCB
+        len = esp_core_dump_write_flash_padded(s_core_part_start + off, tasks[i].pxTCB, tcb_sz);
+        if (len == 0)
+            return;
+        off += len;
+        // save task stack
+        /*int k;
+        for (k = 0; k < 8*4; k++) {
+            esp_panicPutStr("stack[");
+            esp_panicPutDec(k);
+            esp_panicPutStr("] = ");
+            esp_panicPutHex(((uint8_t *)tasks[i].pxTopOfStack)[k]);
+            esp_panicPutStr("\r\n");
+        }*/
+        len = esp_core_dump_write_flash_padded(s_core_part_start + off,
+#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 (len == 0)
+            return;
+        off += len;
+    }
+
+    rom_data.data32[0] = COREDUMP_FLASH_MAGIC_END;
+    err = spi_flash_write_panic(s_core_part_start + off, &rom_data, sizeof(uint32_t));
+    if (err != ESP_OK) {
+        esp_panicPutStr("Failed to write to flash ");
+        esp_panicPutHex(err);
+        esp_panicPutStr("!\r\n");
+        return;
+    }
+
+    esp_panicPutStr("Core dump has been saved to flash partition.\r\n");
+}
+#endif
+
+#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART
+void esp_core_dump_to_uart(XtExcFrame *frame)
+{
+}
+#endif
+
+void esp_core_dump_init()
+{
+#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
+    const esp_partition_t *core_part;
+
+    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
+#endif
+}
+
index 864a17a1b6f9fdaea2587850d5ae0294f7553b08..95d5c5e6a2af05379806ba18ab4c212ab8cab9ea 100644 (file)
@@ -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)
@@ -214,6 +215,10 @@ void start_cpu0_default(void)
     }
 #endif
 
+#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
+    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 (file)
index 0000000..d130aa2
--- /dev/null
@@ -0,0 +1,21 @@
+// 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_
+
+void esp_core_dump_init();
+void esp_core_dump_to_flash();
+void esp_core_dump_to_uart();
+
+#endif
index aa83c6d3811d911d695ea3d426d019ecfb712ee0..25816d31f285ed510de3c8e07bfc3272f5edf8b3 100644 (file)
  */
 
 void esp_set_breakpoint_if_jtag(void *fn);
+void esp_panicPutchar(char c);
+void esp_panicPutStr(const char *c);
+void esp_panicPutHex(int a);
+void esp_panicPutDec(int a);
 
 #define ESP_WATCHPOINT_LOAD 0x40000000
 #define ESP_WATCHPOINT_STORE 0x80000000
index c5b18870a939afc05b662de857f133b12c117ab0..94aebfbac46fc43bd002e4799c234c20d411cc82 100644 (file)
@@ -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
 
 #if !CONFIG_ESP32_PANIC_SILENT_REBOOT
 //printf may be broken, so we fix our own printing fns...
-inline static void panicPutChar(char c)
+void esp_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)
+void esp_panicPutStr(const char *c)
 {
     int x = 0;
     while (c[x] != 0) {
-        panicPutChar(c[x]);
+        esp_panicPutChar(c[x]);
         x++;
     }
 }
 
-inline static void panicPutHex(int a)
+void esp_panicPutHex(int a)
 {
     int x;
     int c;
     for (x = 0; x < 8; x++) {
         c = (a >> 28) & 0xf;
         if (c < 10) {
-            panicPutChar('0' + c);
+            esp_panicPutChar('0' + c);
         } else {
-            panicPutChar('a' + c - 10);
+            esp_panicPutChar('a' + c - 10);
         }
         a <<= 4;
     }
 }
 
-inline static void panicPutDec(int a)
+void esp_panicPutDec(int a)
 {
     int n1, n2;
     n1 = a % 10;
     n2 = a / 10;
     if (n2 == 0) {
-        panicPutChar(' ');
+        esp_panicPutChar(' ');
     } else {
-        panicPutChar(n2 + '0');
+        esp_panicPutChar(n2 + '0');
     }
-    panicPutChar(n1 + '0');
+    esp_panicPutChar(n1 + '0');
 }
 #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) { }
+void esp_panicPutChar(char c) { }
+void esp_panicPutStr(const char *c) { }
+void esp_panicPutHex(int a) { }
+void esp_panicPutDec(int a) { }
 #endif
 
 void  __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName )
 {
-    panicPutStr("***ERROR*** A stack overflow in task ");
-    panicPutStr((char *)pcTaskName);
-    panicPutStr(" has been detected.\r\n");
+    esp_panicPutStr("***ERROR*** A stack overflow in task ");
+    esp_panicPutStr((char *)pcTaskName);
+    esp_panicPutStr(" has been detected.\r\n");
     abort();
 }
 
@@ -161,39 +162,39 @@ void panicHandler(XtExcFrame *frame)
         reason = reasons[regs[20]];
     }
     haltOtherCore();
-    panicPutStr("Guru Meditation Error: Core ");
-    panicPutDec(xPortGetCoreID());
-    panicPutStr(" panic'ed (");
+    esp_panicPutStr("Guru Meditation Error: Core ");
+    esp_panicPutDec(xPortGetCoreID());
+    esp_panicPutStr(" panic'ed (");
     if (!abort_called) {
-        panicPutStr(reason);
-        panicPutStr(")\r\n");
+        esp_panicPutStr(reason);
+        esp_panicPutStr(")\r\n");
             if (regs[20]==PANIC_RSN_DEBUGEXCEPTION) {
                 int debugRsn;
                 asm("rsr.debugcause %0":"=r"(debugRsn));
-                panicPutStr("Debug exception reason: ");
-                if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) panicPutStr("SingleStep ");
-                if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) panicPutStr("HwBreakpoint ");
+                esp_panicPutStr("Debug exception reason: ");
+                if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) esp_panicPutStr("SingleStep ");
+                if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) esp_panicPutStr("HwBreakpoint ");
                 if (debugRsn&XCHAL_DEBUGCAUSE_DBREAK_MASK) {
                     //Unlike what the ISA manual says, this core seemingly distinguishes from a DBREAK
                     //reason caused by watchdog 0 and one caused by watchdog 1 by setting bit 8 of the
                     //debugcause if the cause is watchdog 1 and clearing it if it's watchdog 0.
                     if (debugRsn&(1<<8)) {
 #if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK
-                        panicPutStr("Stack canary watchpoint triggered ");
+                        esp_panicPutStr("Stack canary watchpoint triggered ");
 #else
-                        panicPutStr("Watchpoint 1 triggered ");
+                        esp_panicPutStr("Watchpoint 1 triggered ");
 #endif
                     } else {
-                        panicPutStr("Watchpoint 0 triggered ");
+                        esp_panicPutStr("Watchpoint 0 triggered ");
                     }
                 }
-                if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) panicPutStr("BREAK instr ");
-                if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) panicPutStr("BREAKN instr ");
-                if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) panicPutStr("DebugIntr ");
-                panicPutStr("\r\n");
+                if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) esp_panicPutStr("BREAK instr ");
+                if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) esp_panicPutStr("BREAKN instr ");
+                if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) esp_panicPutStr("DebugIntr ");
+                esp_panicPutStr("\r\n");
             }
         } else {
-            panicPutStr("abort)\r\n");
+            esp_panicPutStr("abort)\r\n");
         }
 
     if (esp_cpu_in_ocd_debug_mode()) {
@@ -219,25 +220,25 @@ void xt_unhandled_exception(XtExcFrame *frame)
     int x;
 
     haltOtherCore();
-    panicPutStr("Guru Meditation Error of type ");
+    esp_panicPutStr("Guru Meditation Error of type ");
     x = regs[20];
     if (x < 40) {
-        panicPutStr(edesc[x]);
+        esp_panicPutStr(edesc[x]);
     } else {
-        panicPutStr("Unknown");
+        esp_panicPutStr("Unknown");
     }
-    panicPutStr(" occurred on core ");
-    panicPutDec(xPortGetCoreID());
+    esp_panicPutStr(" occurred on core ");
+    esp_panicPutDec(xPortGetCoreID());
     if (esp_cpu_in_ocd_debug_mode()) {
-        panicPutStr(" at pc=");
-        panicPutHex(regs[1]);
-        panicPutStr(". Setting bp and returning..\r\n");
+        esp_panicPutStr(" at pc=");
+        esp_panicPutHex(regs[1]);
+        esp_panicPutStr(". Setting bp and returning..\r\n");
         //Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger
         //will kick in exactly at the context the error happened.
         setFirstBreakpoint(regs[1]);
         return;
     }
-    panicPutStr(". Exception was unhandled.\r\n");
+    esp_panicPutStr(". Exception was unhandled.\r\n");
     commonErrorHandler(frame);
 }
 
@@ -292,16 +293,16 @@ static void putEntry(uint32_t pc, uint32_t sp)
     if (pc & 0x80000000) {
         pc = (pc & 0x3fffffff) | 0x40000000;
     }
-    panicPutStr(" 0x");
-    panicPutHex(pc);
-    panicPutStr(":0x");
-    panicPutHex(sp);
+    esp_panicPutStr(" 0x");
+    esp_panicPutHex(pc);
+    esp_panicPutStr(":0x");
+    esp_panicPutHex(sp);
 }
 
 static void doBacktrace(XtExcFrame *frame)
 {
     uint32_t i = 0, pc = frame->pc, sp = frame->a1;
-    panicPutStr("\nBacktrace:");
+    esp_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");
+    esp_panicPutStr("\r\n\r\n");
 }
 
 /*
@@ -341,18 +342,18 @@ static void commonErrorHandler(XtExcFrame *frame)
        the register window is no longer useful.
     */
     if (!abort_called) {
-        panicPutStr("Register dump:\r\n");
+        esp_panicPutStr("Register dump:\r\n");
 
         for (x = 0; x < 24; x += 4) {
             for (y = 0; y < 4; y++) {
                 if (sdesc[x + y][0] != 0) {
-                    panicPutStr(sdesc[x + y]);
-                    panicPutStr(": 0x");
-                    panicPutHex(regs[x + y + 1]);
-                    panicPutStr("  ");
+                    esp_panicPutStr(sdesc[x + y]);
+                    esp_panicPutStr(": 0x");
+                    esp_panicPutHex(regs[x + y + 1]);
+                    esp_panicPutStr("  ");
                 }
+                esp_panicPutStr("\r\n");
             }
-            panicPutStr("\r\n");
         }
     }
 
@@ -361,19 +362,27 @@ static void commonErrorHandler(XtExcFrame *frame)
 
 #if CONFIG_ESP32_PANIC_GDBSTUB
     disableAllWdts();
-    panicPutStr("Entering gdb stub now.\r\n");
+    esp_panicPutStr("Entering gdb stub now.\r\n");
     esp_gdbstub_panic_handler(frame);
-#elif CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT
-    panicPutStr("Rebooting...\r\n");
+#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
+    esp_panicPutStr("Rebooting...\r\n");
     for (x = 0; x < 100; x++) {
         ets_delay_us(1000);
     }
     software_reset();
 #else
     disableAllWdts();
-    panicPutStr("CPU halted.\r\n");
+    esp_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 (executable)
index 0000000..065b323
--- /dev/null
@@ -0,0 +1,1013 @@
+#!/usr/bin/env python
+#
+# ESP32 core dump Utility
+
+import sys
+import os
+import argparse
+import subprocess
+import tempfile
+import struct
+import array
+import errno
+
+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"
+
+ESP32_COREDUMP_HDR_FMT              = '<4L'
+ESP32_COREDUMP_FLASH_MAGIC_START    = 0xDEADBEEF
+ESP32_COREDUMP_FLASH_MAGIC_END      = 0xACDCFEED
+
+
+class Struct(object):
+    def __init__(self, buf=None):
+        if buf is None:
+            buf = b'\0' * self.sizeof()
+        fields = struct.unpack(self.__class__.fmt, buf[:self.sizeof()])
+        self.__dict__.update(zip(self.__class__.fields, fields))
+
+    def sizeof(self):
+        return struct.calcsize(self.__class__.fmt)
+
+    def dump(self):
+        keys =  self.__class__.fields
+        if sys.version_info > (3, 0):
+            # Convert strings into bytearrays if this is Python 3
+            for k in keys:
+                if type(self.__dict__[k]) is str:
+                    self.__dict__[k] = bytearray(self.__dict__[k], encoding='ascii')
+        return struct.pack(self.__class__.fmt, *(self.__dict__[k] for k in keys))
+
+    def __str__(self):
+        keys =  self.__class__.fields
+        return (self.__class__.__name__ + "({" +
+            ", ".join("%s:%r" % (k, self.__dict__[k]) for k in keys) +
+            "})")
+
+
+class Elf32FileHeader(Struct):
+    """ELF32 File header"""
+    fields = ("e_ident",
+              "e_type",
+              "e_machine",
+              "e_version",
+              "e_entry",
+              "e_phoff",
+              "e_shoff",
+              "e_flags",
+              "e_ehsize",
+              "e_phentsize",
+              "e_phnum",
+              "e_shentsize",
+              "e_shnum",
+              "e_shstrndx")
+    fmt = "<16sHHLLLLLHHHHHH"
+
+    def __init__(self, buf=None):
+        super(Elf32FileHeader, self).__init__(buf)
+        if buf is None:
+            # Fill in sane ELF header for LSB32
+            self.e_ident = "\x7fELF\1\1\1\0\0\0\0\0\0\0\0\0"
+            self.e_version = ESPCoreDumpFile.EV_CURRENT
+            self.e_ehsize = self.sizeof()
+
+
+class Elf32ProgramHeader(Struct):
+  """ELF32 Program Header"""
+  fields = ("p_type",
+            "p_offset",
+            "p_vaddr",
+            "p_paddr",
+            "p_filesz",
+            "p_memsz",
+            "p_flags",
+            "p_align")
+  fmt = "<LLLLLLLL"
+
+
+class Elf32NoteDesc(object):
+    """ELF32 Note Descriptor"""
+    def __init__(self, name, type, data):
+        self.name = bytearray(name, encoding='ascii') + b'\0'
+        self.type = type
+        self.data = data
+
+    def dump(self):
+        """Conveninece function to format a note descriptor.
+           All note descriptors must be concatenated and added to a
+           PT_NOTE segment."""
+        header = struct.pack("<LLL", len(self.name), len(self.data), self.type)
+        # pad up to 4 byte alignment
+        name = self.name + ((4 - len(self.name)) % 4) * b'\0'
+        desc = self.data + ((4 - len(self.data)) % 4) * b'\0'
+        print "dump %d %d %d %d %d" % (len(header), len(name), len(self.name), len(desc), len(self.data))
+        return header + name + desc
+
+
+class XtensaPrStatus(Struct):
+    """Xtensa Program Status structure"""
+    # Only pr_cursig and pr_pid are read by bfd
+    # Structure followed by 72 bytes representing general-purpose registers
+    # check elf32-xtensa.c in libbfd for details
+    fields = ("si_signo", "si_code", "si_errno",
+              "pr_cursig", # Current signal
+              "pr_pad0",
+              "pr_sigpend",
+              "pr_sighold",
+              "pr_pid", # LWP ID
+              "pr_ppid",
+              "pr_pgrp",
+              "pr_sid",
+              "pr_utime",
+              "pr_stime",
+              "pr_cutime",
+              "pr_cstime")
+    fmt = "<3LHHLLLLLLQQQQ"
+
+
+class ESPCoreDumpSegment(esptool.ImageSegment):
+    """ Wrapper class for a program segment in an ELF image, has a section
+    name as well as the common properties of an ImageSegment. """
+    # segment flags
+    PF_X = 0x1 # Execute
+    PF_W = 0x2 # Write
+    PF_R = 0x4 # Read
+
+    def __init__(self, addr, data, type, flags):
+        super(ESPCoreDumpSegment, self).__init__(addr, data)
+        self.flags = flags
+        self.type = type
+
+    def __repr__(self):
+        return "%s %s %s" % (self.type, self.attr_str(), super(ESPCoreDumpSegment, self).__repr__())
+
+    def attr_str(self):
+        str = ''
+        if self.flags & self.PF_R:
+            str += 'R'
+        else:
+            str += ' '
+        if self.flags & self.PF_W:
+            str += 'W'
+        else:
+            str += ' '
+        if self.flags & self.PF_X:
+            str += 'X'
+        else:
+            str += ' '
+        return str
+
+
+class ESPCoreDumpSection(esptool.ELFSection):
+    """
+    TBD
+    """
+    # section flags
+    SHF_WRITE       = 0x1
+    SHF_ALLOC       = 0x2
+    SHF_EXECINSTR   = 0x4
+
+    def __init__(self, name, addr, data, flags):
+        super(ESPCoreDumpSection, self).__init__(name, addr, data)
+        self.flags = flags
+
+    def __repr__(self):
+        return "%s %s" % (super(ESPCoreDumpSection, self).__repr__(), self.attr_str())
+
+    def attr_str(self):
+        str = "R"
+        if self.flags & self.SHF_WRITE:
+            str += 'W'
+        else:
+            str += ' '
+        if self.flags & self.SHF_EXECINSTR:
+            str += 'X'
+        else:
+            str += ' '
+        if self.flags & self.SHF_ALLOC:
+            str += 'A'
+        else:
+            str += ' '
+        return str
+
+
+class ESPCoreDumpFile(esptool.ELFFile):
+    # ELF file type
+    ET_NONE             = 0x0 # No file type
+    ET_REL              = 0x1 # Relocatable file
+    ET_EXEC             = 0x2 # Executable file
+    ET_DYN              = 0x3 # Shared object file
+    ET_CORE             = 0x4 # Core file
+    # ELF file version
+    EV_NONE             = 0x0
+    EV_CURRENT          = 0x1
+    # ELF file machine type
+    EM_NONE             = 0x0
+    EM_XTENSA           = 0x5E
+    # section types
+    SEC_TYPE_PROGBITS   = 0x01
+    SEC_TYPE_STRTAB     = 0x03
+    # special section index
+    SHN_UNDEF           = 0x0
+    # program segment types
+    PT_NULL             = 0x0
+    PT_LOAD             = 0x1
+    PT_DYNAMIC          = 0x2
+    PT_INTERP           = 0x3
+    PT_NOTE             = 0x4
+    PT_SHLIB            = 0x5
+    PT_PHDR             = 0x6
+
+    def __init__(self, name=None):
+        if name:
+            super(ESPCoreDumpFile, self).__init__(name)
+        else:
+            self.sections = []
+            self.program_segments = []
+            self.e_type = self.ET_NONE
+            self.e_machine = self.EM_NONE
+
+    def _read_elf_file(self, f):
+        # read the ELF file header
+        LEN_FILE_HEADER = 0x34
+        try:
+            (ident,type,machine,_version,
+             self.entrypoint,phoff,shoff,_flags,
+             _ehsize, phentsize,phnum,_shentsize,
+             shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER))
+        except struct.error as e:
+            raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e))
+
+        if ident[0] != '\x7f' or ident[1:4] != 'ELF':
+            raise FatalError("%s has invalid ELF magic header" % self.name)
+        if machine != self.EM_XTENSA:
+            raise FatalError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine))
+        self.e_type = type
+        self.e_machine = machine
+        if shnum > 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):
+        f.seek(section_header_offs)
+        section_header = f.read()
+        LEN_SEC_HEADER = 0x28
+        if len(section_header) == 0:
+            raise FatalError("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("<LLLLLL", section_header[offs:])
+            return (name_offs, sec_type, flags, lma, size, sec_offs)
+        all_sections = [read_section_header(offs) for offs in section_header_offsets]
+        prog_sections = [s for s in all_sections if s[1] == esptool.ELFFile.SEC_TYPE_PROGBITS]
+
+        # search for the string table section
+        if not shstrndx * LEN_SEC_HEADER in section_header_offsets:
+            raise FatalError("ELF file has no STRTAB section at shstrndx %d" % shstrndx)
+        _,sec_type,_,_,sec_size,sec_offs = read_section_header(shstrndx * LEN_SEC_HEADER)
+        if sec_type != esptool.ELFFile.SEC_TYPE_STRTAB:
+            print 'WARNING: ELF file has incorrect STRTAB section type 0x%02x' % sec_type
+        f.seek(sec_offs)
+        string_table = f.read(sec_size)
+
+        # build the real list of ELFSections by reading the actual section names from the
+        # string table section, and actual data for each section from the ELF file itself
+        def lookup_string(offs):
+            raw = string_table[offs:]
+            return raw[:raw.index('\x00')]
+
+        def read_data(offs,size):
+            f.seek(offs)
+            return f.read(size)
+
+        prog_sections = [ESPCoreDumpSection(lookup_string(n_offs), lma, read_data(offs, size), flags) for (n_offs, _type, flags, lma, size, offs) in prog_sections
+                         if lma != 0]
+        self.sections = prog_sections
+
+    def _read_program_segments(self, f, seg_table_offs, entsz, num):
+        f.seek(seg_table_offs)
+        seg_table = f.read(entsz*num)
+        LEN_SEG_HEADER = 0x20
+        if len(seg_table) == 0:
+            raise FatalError("No program header table found at offset %04x in ELF file." % seg_table_offs)
+        if len(seg_table) % LEN_SEG_HEADER != 0:
+            print 'WARNING: Unexpected ELF program header table length %04x is not mod-%02x' % (len(seg_table),LEN_SEG_HEADER)
+
+        # walk through the program segment table and extract all segments
+        seg_table_offs = range(0, len(seg_table), LEN_SEG_HEADER)
+
+        def read_program_header(offs):
+            type,offset,vaddr,_paddr,filesz,_memsz,_flags,_align = struct.unpack_from("<LLLLLLLL", seg_table[offs:])
+            return (type,offset,vaddr,filesz)
+        all_segments = [read_program_header(offs) for offs in seg_table_offs]
+        prog_segments = [s for s in all_segments if s[0] == self.PT_LOAD]
+
+        # build the real list of ImageSegment by reading actual data for each segment from the ELF file itself
+        def read_data(offs,size):
+            f.seek(offs)
+            return f.read(size)
+
+        prog_segments = [esptool.ImageSegment(vaddr, read_data(offset, filesz), offset) for (_type, offset, vaddr, filesz) in prog_segments
+                         if vaddr != 0]
+        self.program_segments = prog_segments
+#         print "prog_segments=%s" % (self.program_segments)
+
+    # currently merging is not supported
+    def add_program_segment(self, addr, data, type, flags):
+        data_sz = len(data)
+        print "add_program_segment: %x %d" % (addr, data_sz)
+        # check for overlapping and merge if needed
+        if addr != 0 and data_sz != 0:
+            for ps in self.program_segments:
+                seg_len = len(ps.data)
+                if addr >= ps.addr and addr < (ps.addr + seg_len):
+                    raise FatalError("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 FatalError("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))
+
+    # currently dumps only program segments.
+    # dumping sections is not supported yet
+    def dump(self, f):
+        print "dump to '%s'" % f
+        # 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
+#         print "" % (ehdr.e_ehsize, ehdr.e_phnum, ehdr.e_phentsize)
+        for i in range(len(self.program_segments)):
+            print "dump header for seg '%s'" % self.program_segments[i]
+            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
+#             print "header '%s'" % phdr
+            f.write(phdr.dump())
+            cur_off += phdr.p_filesz
+        # write program segments
+        for i in range(len(self.program_segments)):
+            print "dump seg '%s'" % self.program_segments[i]
+            f.write(self.program_segments[i].data)
+
+
+class ESPCoreDumpError(RuntimeError):
+    """
+    TBD
+    """
+    def __init__(self, message):
+        super(ESPCoreDumpError, self).__init__(message)
+
+
+class ESPCoreDumpLoaderError(ESPCoreDumpError):
+    """
+    TBD
+    """
+    def __init__(self, message):
+        super(ESPCoreDumpLoaderError, self).__init__(message)
+
+
+class ESPCoreDumpLoader(object):
+    """
+    TBD
+    """
+    FLASH_READ_BLOCK_SZ = 0x2000
+    def __init__(self, off, path=None, chip='esp32', port=None, baud=None):
+#         print "esptool.__file__ %s" % esptool.__file__
+        if not path:
+            self.path = esptool.__file__
+            self.path = self.path[:-1]
+        else:
+            self.path =  path
+        self.port = port
+        self.baud = baud
+        self.chip = chip
+        self.fcores = []
+        self.fgdbcore = None 
+        self._load_coredump(off)
+        
+    def _load_coredump(self, off):
+        args = [self.path, '-c', self.chip]
+        if self.port:
+            args.extend(['-p', self.port])
+        if self.baud:
+            args.extend(['-b', str(self.baud)])
+        read_sz = self.FLASH_READ_BLOCK_SZ
+        read_off = off
+        args.extend(['read_flash', str(read_off), str(read_sz), ''])
+        try:
+            dump_sz = 0
+            tot_len = 0
+            while True:
+                fhnd,fname = tempfile.mkstemp()
+#                 print "tmpname %s" % fname
+#                 os.close(fhnd)
+                args[-1] = fname
+                et_out = subprocess.check_output(args)
+                print et_out
+    #             data = os.fdopen(fhnd, 'r').read(sz)
+                self.fcores.append(os.fdopen(fhnd, 'r'))
+                if dump_sz == 0:
+                    # read dump length from the first block
+                    dump_sz = self._read_core_dump_length(self.fcores[0])
+                tot_len += read_sz
+                if tot_len >= dump_sz:
+                    break
+                read_off += read_sz
+                if dump_sz - tot_len >= self.FLASH_READ_BLOCK_SZ:
+                    read_sz = self.FLASH_READ_BLOCK_SZ
+                else:
+                    read_sz = dump_sz - tot_len
+                args[-3] = str(read_off)
+                args[-2] = str(read_sz)
+                
+        except subprocess.CalledProcessError as e: 
+            print "esptool script execution failed with err %d" % e.returncode
+            print "Command ran: '%s'" % e.cmd
+            print "Command out:"
+            print e.output
+            self.cleanup()
+            return []
+
+    def _read_core_dump_length(self, f):
+        global ESP32_COREDUMP_HDR_FMT
+        global ESP32_COREDUMP_FLASH_MAGIC_START
+        print "Read core dump header from '%s'" % f.name
+        data = f.read(4*4)
+        mag1,tot_len,task_num,tcbsz = struct.unpack_from(ESP32_COREDUMP_HDR_FMT, data)
+        if mag1 != ESP32_COREDUMP_FLASH_MAGIC_START:
+            raise ESPCoreDumpLoaderError("Invalid start magic number!")
+        return tot_len
+                
+    def remove_tmp_file(self, fname):
+        try:
+            os.remove(fname)
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                print "Warning failed to remove temp file '%s'!" % fname
+
+    def _get_registers_from_stack(self, data, grows_down):
+    # 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:
+            print "Growing up stacks are not supported for now!"
+            return regs
+    #     for i in range(REG_NUM):
+    #         regs[i] = i
+    #     return regs
+        ex_struct = "<%dL" % XT_STK_FRMSZ
+        if len(data) < struct.calcsize(ex_struct):
+            print "Too small stack to keep frame: %d bytes!" % len(data)
+            return regs
+    
+        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:
+            print "EXCSTACKFRAME %d" % rc
+            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]
+            print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % (
+                regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0],
+                regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3])
+        else:
+            print "SOLSTACKFRAME %d" % rc
+            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]
+            print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % (
+                regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0],
+                regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3])
+             
+        # 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 cleanup(self):
+#         if self.fgdbcore:
+#             self.fgdbcore.close()
+#            self.remove_tmp_file(self.fgdbcore.name)
+        for f in self.fcores:
+            if f:
+                f.close()
+                self.remove_tmp_file(f.name)
+
+    def get_corefile_from_flash(self):
+        """ TBD
+        """
+        global ESP32_COREDUMP_HDR_FMT
+        ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT)
+        ESP32_COREDUMP_TSK_HDR_FMT = '<LLL'
+        ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT)
+        ESP32_COREDUMP_MAGIC_FMT = '<L'
+        ESP32_COREDUMP_MAGIC_SZ = struct.calcsize(ESP32_COREDUMP_MAGIC_FMT)
+        no_progress = True #False
+        if no_progress:
+            flash_progress = None
+        else:
+            def flash_progress(progress, length):
+                msg = '%d (%d %%)' % (progress, progress * 100.0 / length)
+                padding = '\b' * len(msg)
+                if progress == length:
+                    padding = '\n'
+                sys.stdout.write(msg + padding)
+                sys.stdout.flush()
+    
+        core_off = 0
+        print "Read core dump header"
+        data = self.read_flash(core_off, ESP32_COREDUMP_HDR_SZ, flash_progress)
+        mag1,tot_len,task_num,tcbsz = struct.unpack_from(ESP32_COREDUMP_HDR_FMT, data)
+        tcbsz_aligned = tcbsz
+        if tcbsz_aligned % 4:
+            tcbsz_aligned = 4*(tcbsz_aligned/4 + 1)
+        print "mag1=%x, tot_len=%d, task_num=%d, tcbsz=%d" % (mag1,tot_len,task_num,tcbsz)
+        core_off += ESP32_COREDUMP_HDR_SZ
+        core_elf = ESPCoreDumpFile()
+        notes = b''
+        for i in range(task_num):
+            print "Read task[%d] header" % i
+            data = self.read_flash(core_off, ESP32_COREDUMP_TSK_HDR_SZ, flash_progress)
+            tcb_addr,stack_top,stack_end = struct.unpack_from(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
+            print "tcb_addr=%x, stack_top=%x, stack_end=%x, stack_len=%d" % (tcb_addr,stack_top,stack_end,stack_len)
+    
+            stack_len_aligned = stack_len
+            if stack_len_aligned % 4:
+                stack_len_aligned = 4*(stack_len_aligned/4 + 1)
+                
+            core_off += ESP32_COREDUMP_TSK_HDR_SZ
+            print "Read task[%d] TCB" % i
+            data = self.read_flash(core_off, tcbsz_aligned, flash_progress)
+            if tcbsz != tcbsz_aligned:
+                core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
+            else:
+                core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
+    #         print "tcb=%s" % data
+            core_off += tcbsz_aligned
+            print "Read task[%d] stack %d bytes" % (i,stack_len)
+            data = self.read_flash(core_off, stack_len_aligned, flash_progress)
+    #         print "stk=%s" % data
+            if stack_len != stack_len_aligned:
+                data = data[:stack_len - stack_len_aligned]
+            core_elf.add_program_segment(stack_base, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
+            core_off += stack_len_aligned
+    
+            task_regs = self._get_registers_from_stack(data, stack_end > stack_top)
+            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()
+            print "NOTE_LEN %d" % len(note)
+            notes += note
+    
+        print "Read core dump endmarker"
+        data = self.read_flash(core_off, ESP32_COREDUMP_MAGIC_SZ, flash_progress)
+        mag = struct.unpack_from(ESP32_COREDUMP_MAGIC_FMT, data)
+        print "mag2=%x" % (mag)
+    
+        # add notes
+        core_elf.add_program_segment(0, notes, ESPCoreDumpFile.PT_NOTE, 0)
+    
+        core_elf.e_type = ESPCoreDumpFile.ET_CORE
+        core_elf.e_machine = ESPCoreDumpFile.EM_XTENSA
+        fhnd,fname = tempfile.mkstemp()
+        self.fgdbcore = os.fdopen(fhnd, 'wb')
+        core_elf.dump(self.fgdbcore)
+        return fname
+    ######################### END ###########################
+
+    def read_flash(self, off, sz, progress=None):
+#         print "read_flash: %x %d" % (off, sz)
+        id = off / self.FLASH_READ_BLOCK_SZ
+        if id >= len(self.fcores):
+            return ''
+        self.fcores[id].seek(off % self.FLASH_READ_BLOCK_SZ)
+        data = self.fcores[id].read(sz)
+#         print "data1: %s" % data
+        return data
+
+class GDBMIOutRecordHandler(object):
+    """ TBD
+    """
+    TAG = ''
+
+    def __init__(self, f, verbose=False):
+        self.verbose = verbose
+
+    def execute(self, ln):
+        if self.verbose:
+            print "%s.execute '%s'" % (self.__class__.__name__, ln)
+
+
+class GDBMIOutStreamHandler(GDBMIOutRecordHandler):
+    """ TBD
+    """
+    def __init__(self, f, verbose=False):
+        super(GDBMIOutStreamHandler, self).__init__(None, verbose)
+        self.func = f
+
+    def execute(self, ln):
+        GDBMIOutRecordHandler.execute(self, ln)
+        if self.func:
+            # remove TAG / quotes and replace c-string \n with actual NL
+            self.func(ln[1:].strip('"').replace('\\n', '\n').replace('\\t', '\t'))
+
+
+class GDBMIResultHandler(GDBMIOutRecordHandler):
+    """ TBD
+    """
+    TAG = '^'
+    RC_DONE = 'done'
+    RC_RUNNING = 'running'
+    RC_CONNECTED = 'connected'
+    RC_ERROR = 'error'
+    RC_EXIT = 'exit'
+
+    def __init__(self, verbose=False):
+        super(GDBMIResultHandler, self).__init__(None, verbose)
+        self.result_class = None
+        self.result_str = None
+
+    def _parse_rc(self, ln, rc):
+        rc_str = "{0}{1}".format(self.TAG, rc)
+        if ln.startswith(rc_str):
+            self.result_class = rc
+            sl = len(rc_str)
+            if len(ln) > 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):
+        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 result: '%s'" % ln
+
+
+class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler):
+    """ TBD
+    """
+    TAG = '~'
+
+
+def dbg_corefile(args):
+    """ TBD
+    """
+    print "dbg_corefile %s %s %s" % (args.gdb, args.prog, args.core)
+    loader = None
+    if not args.core:
+        loader = ESPCoreDumpLoader(args.off, port=args.port)
+        core_fname = loader.get_corefile_from_flash()
+        loader.fgdbcore.close()
+#         core_fname = 'esp_core.elf'
+    else:
+        core_fname = args.core
+#     print core_fname
+#     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 = True
+            )
+    p.wait()
+    if loader:
+        loader.remove_tmp_file(loader.fgdbcore.name)
+        loader.cleanup()
+    print 'Done!'
+
+
+def info_corefile(args):
+# def info_corefile(args):
+    """ TBD
+    """
+    print "info_corefile %s %s %s" % (args.gdb, args.prog, args.core)
+
+
+    def gdbmi_console_stream_handler(ln):
+    #     print ln
+        sys.stdout.write(ln)
+        sys.stdout.flush()
+    
+    
+    def gdbmi_read2prompt(f, out_handlers=None):
+        """ TBD
+        """
+        while True:
+            ln = f.readline().rstrip(' \n')
+    #         print "LINE='{0}'".format(ln)
+            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 = ESPCoreDumpLoader(args.off, port=args.port)
+        core_fname = loader.get_corefile_from_flash()
+        loader.fgdbcore.close()
+    else:
+        core_fname = args.core
+
+    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 = True
+            )
+
+    gdbmi_read2prompt(p.stdout, handlers)
+    exe_elf = ESPCoreDumpFile(args.prog)
+    core_elf = ESPCoreDumpFile(core_fname)
+    merged_segs = []#[(s, 0) for s in exe_elf.sections if s.flags & (esptool.ELFSection.SHF_ALLOC | esptool.ELFSection.SHF_WRITE)]
+    for s in exe_elf.sections:
+        merged = False
+        for ps in core_elf.program_segments:
+            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))
+                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))
+                merged = True
+
+        if not merged:
+            merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False))
+#                 merged_segs.append(('None', ps.addr, len(ps.data), 'None'))
+
+    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======================= 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])
+    if args.print_mem:
+        print "\n====================== MEMORY CONTENTS ========================"
+        for ms in merged_segs:
+#             if ms[3].find('W') == -1:
+            if not ms[4]:
+                continue
+            print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3])
+            p.stdin.write("-interpreter-exec console \"x/%dx 0x%x\"\n" % (ms[2]/4, ms[1]))
+            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.terminate()
+    p.stdin.close()
+    p.stdout.close()
+    if loader:
+        loader.remove_tmp_file(loader.fgdbcore.name)
+        loader.cleanup()
+        
+
+def main():
+    parser = argparse.ArgumentParser(description='coredumper.py v%s - ESP32 Core Dump Utility' % __version__, prog='coredumper')
+
+    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))
+
+#     parser.add_argument(
+#         '--no-stub',
+#         help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.",
+#         action='store_true')
+
+    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('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000)
+    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('--print-mem', '-m', help='Print memory dump', action='store_true')
+    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('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000)
+    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 'coredumper.py v%s' % __version__
+
+    # operation function can take 1 arg (args), 2 args (esp, arg)
+    # or be a member function of the ESPLoader class.
+
+    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)
index 4c60308f78033fb55dc0ef85e6c7552fd1763487..0e93acf271899107cb1a0ce52a91c30584b09295 100644 (file)
@@ -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;
index 4f1012657a0ed7178cc846f9ee60b27056fa99c0..2bedaa02e856626dec669fa32035371ffd491eff 100644 (file)
 #define configXT_BOARD                      1   /* Board mode */
 #define configXT_SIMULATOR                                     0
 
-
+#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH | CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
+#define configENABLE_TASK_SNAPSHOT                     1
+#endif
 
 
 #endif /* FREERTOS_CONFIG_H */
index f7b9181fcfd2cea72658291f716fb3afc15411a2..c6896e5386383484a471ca950e9223e2699e1ab1 100644 (file)
@@ -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,9 @@ eSleepModeStatus eTaskConfirmSleepModeStatus( void ) PRIVILEGED_FUNCTION;
  */
 void *pvTaskIncrementMutexHeldCount( void );
 
+/* Used by core dump facility to get list of task handles. */
+UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz );
+
 #ifdef __cplusplus
 }
 #endif
index 64031cfbca4de0ced624e8fa1a38303d0617ef5b..100b6e470ba680ee109fb74de9042e112eb193d2 100644 (file)
@@ -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,102 @@ 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);
+
+               //vTaskSuspendAll(); //WARNING: This only suspends one CPU. ToDo: suspend others as well. Mux using taskQueueMutex maybe?
+               {
+                       /* 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
+               }
+               //( void ) xTaskResumeAll();
+#if 0
+               /* Convention: First num_cpus slots will have current task for that cpu. */
+               for (i = 0; i < portNUM_PROCESSORS; i++) {
+                       if (pxCurrentTCB[i] == NULL || pxCurrentTCB == pxTaskSnapshotArray[i]) {
+                               continue;
+                       } else {
+                               UBaseType_t j;
+                               for (j = i; j < uxTask; j++) {
+                                       if (pxTaskSnapshotArray[j] == pxCurrentTCB[i]) {
+                                               TaskHandle_t tmp = pxTaskSnapshotArray[i];
+                                               pxTaskSnapshotArray[i] = pxTaskSnapshotArray[j];
+                                               pxTaskSnapshotArray[j] = tmp;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+#endif
+               return uxTask;
+       }
+
+#endif
+
 #ifdef FREERTOS_MODULE_TEST
        #include "tasks_test_access_functions.h"
 #endif
index 1f019a6e3fd21b497e814305de3ef70a9a20f3b0..36e435ba2f82741c5d56ac0e02651800e37bf419 100644 (file)
@@ -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/partitions_singleapp_coredump.csv b/components/partition_table/partitions_singleapp_coredump.csv
new file mode 100644 (file)
index 0000000..96719a4
--- /dev/null
@@ -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, 3,       ,        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 (file)
index 0000000..3d2b34e
--- /dev/null
@@ -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, 3,       ,         64K
+ota_0,    0,    ota_0,   ,         1M
+ota_1,    0,    ota_1,   ,         1M
index f30db80cd8d23a52f03206318df767692da3562e..748ccf9042e606a3941aa04fb5ade59f49c5bd1a 100644 (file)
@@ -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_panic()
+{
+    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_panic()
+{
+    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_panic()
+{
+    // 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_panic()
+{
+    // 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
 
 /**
index 899a31c651541aeb4f502e823deb8ed5cea2d730..a40bab046517dd7ba341cc1fb2d6665b63c4305c 100644 (file)
@@ -40,5 +40,14 @@ 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 from panic handler
+// when non-current CPU is halted and can not execute code from flash.
+void spi_flash_disable_interrupts_caches_and_other_cpu_panic();
+
+// Enable cache, enable interrupts (to be added in future) on current CPU.
+// This function is implied to be called from panic handler
+// when non-current CPU is halted and can not execute code from flash.
+void spi_flash_enable_interrupts_caches_panic();
 
 #endif //ESP_SPI_FLASH_CACHE_UTILS_H
index 993e68a5728d89b407009f9354bd93243076810c..7f2d2d4d183d6ed4d13f04b514f84769a45f30c1 100644 (file)
@@ -58,7 +58,35 @@ static spi_flash_counters_t s_flash_stats;
 
 #endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS
 
+/* SPI flash access critical section management functions */
+typedef void (*spi_flash_guard_start_func_t)(void);
+typedef void (*spi_flash_guard_end_func_t)(void);
+
+/**
+ * Structure holding SPI flash access critical section management functions
+ */
+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;
+
+#define FLASH_GUARD_START(_gp_) do{if((_gp_)) (_gp_)->start();}while(0)
+#define FLASH_GUARD_END(_gp_)   do{if((_gp_)) (_gp_)->end();}while(0)
+
 static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc);
+static esp_err_t spi_flash_erase_range_internal(uint32_t start_addr, uint32_t size, const spi_flash_guard_funcs_t *flash_guard);
+static esp_err_t spi_flash_write_internal(size_t dst, const void *srcv, size_t size, const spi_flash_guard_funcs_t *flash_guard);
+static esp_err_t spi_flash_read_internal(size_t src, void *dstv, size_t size, const spi_flash_guard_funcs_t *flash_guard);
+
+const DRAM_ATTR spi_flash_guard_funcs_t s_flash_guard_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 s_flash_guard_panic_ops = {
+        .start  = spi_flash_disable_interrupts_caches_and_other_cpu_panic,
+        .end    = spi_flash_enable_interrupts_caches_panic
+};
 
 void spi_flash_init()
 {
@@ -92,6 +120,16 @@ esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec)
 }
 
 esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size)
+{
+    return spi_flash_erase_range_internal(start_addr, size, &s_flash_guard_ops);
+}
+
+esp_err_t IRAM_ATTR spi_flash_erase_range_panic(uint32_t start_addr, uint32_t size)
+{
+    return spi_flash_erase_range_internal(start_addr, size, &s_flash_guard_panic_ops);
+}
+
+static esp_err_t IRAM_ATTR spi_flash_erase_range_internal(uint32_t start_addr, uint32_t size, const spi_flash_guard_funcs_t *flash_guard)
 {
     if (start_addr % SPI_FLASH_SEC_SIZE != 0) {
         return ESP_ERR_INVALID_ARG;
@@ -106,7 +144,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();
+    FLASH_GUARD_START(flash_guard);
     SpiFlashOpResult rc;
     rc = spi_flash_unlock();
     if (rc == SPI_FLASH_RESULT_OK) {
@@ -122,12 +160,22 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size)
             }
         }
     }
-    spi_flash_enable_interrupts_caches_and_other_cpu();
+    FLASH_GUARD_END(flash_guard);
     COUNTER_STOP(erase);
     return spi_flash_translate_rc(rc);
 }
 
 esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size)
+{
+    return spi_flash_write_internal(dst, srcv, size, &s_flash_guard_ops);
+}
+
+esp_err_t IRAM_ATTR spi_flash_write_panic(size_t dst, const void *srcv, size_t size)
+{
+    return spi_flash_write_internal(dst, srcv, size, &s_flash_guard_panic_ops);
+}
+
+static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv, size_t size, const spi_flash_guard_funcs_t *flash_guard)
 {
     // Out of bound writes are checked in ROM code, but we can give better
     // error code here
@@ -160,9 +208,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();
+        FLASH_GUARD_START(flash_guard);
         rc = SPIWrite(left_off, &t, 4);
-        spi_flash_enable_interrupts_caches_and_other_cpu();
+        FLASH_GUARD_END(flash_guard);
         if (rc != SPI_FLASH_RESULT_OK) {
             goto out;
         }
@@ -178,9 +226,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();
+            FLASH_GUARD_START(flash_guard);
             rc = SPIWrite(dst + mid_off, (const uint32_t *) (srcc + mid_off), mid_size);
-            spi_flash_enable_interrupts_caches_and_other_cpu();
+            FLASH_GUARD_END(flash_guard);
             if (rc != SPI_FLASH_RESULT_OK) {
                 goto out;
             }
@@ -194,9 +242,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();
+                FLASH_GUARD_START(flash_guard);
                 rc = SPIWrite(dst + mid_off, t, write_size);
-                spi_flash_enable_interrupts_caches_and_other_cpu();
+                FLASH_GUARD_END(flash_guard);
                 if (rc != SPI_FLASH_RESULT_OK) {
                     goto out;
                 }
@@ -209,9 +257,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();
+        FLASH_GUARD_START(flash_guard);
         rc = SPIWrite(dst + right_off, &t, 4);
-        spi_flash_enable_interrupts_caches_and_other_cpu();
+        FLASH_GUARD_END(flash_guard);
         if (rc != SPI_FLASH_RESULT_OK) {
             goto out;
         }
@@ -259,6 +307,16 @@ esp_err_t IRAM_ATTR spi_flash_write_encrypted(size_t dest_addr, const void *src,
 }
 
 esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size)
+{
+    return spi_flash_read_internal(src, dstv, size, &s_flash_guard_ops);
+}
+
+esp_err_t IRAM_ATTR spi_flash_read_panic(size_t src, void *dstv, size_t size)
+{
+    return spi_flash_read_internal(src, dstv, size, &s_flash_guard_panic_ops);
+}
+
+static esp_err_t IRAM_ATTR spi_flash_read_internal(size_t src, void *dstv, size_t size, const spi_flash_guard_funcs_t *flash_guard)
 {
     // Out of bound reads are checked in ROM code, but we can give better
     // error code here
@@ -271,7 +329,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();
+    FLASH_GUARD_START(flash_guard);
     /* 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 +403,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();
+    FLASH_GUARD_END(flash_guard);
     COUNTER_STOP(read);
     return spi_flash_translate_rc(rc);
 }
index b67891ae15f0273dad1be0d711bb15fa4b2b65e4..b149e1023402b3c0c309c222b2cda3083ca2a5e4 100644 (file)
@@ -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
index bb3ec39b45e55c5fb5a51e827b943d8afbf8811d..e78c389b4e1e2849f84315607ed7ae5b2ce555ba 100644 (file)
@@ -173,6 +173,56 @@ void spi_flash_munmap(spi_flash_mmap_handle_t handle);
  */
 void spi_flash_mmap_dump();
 
+/**
+ * @brief  Erase a range of flash sectors.
+ *
+ * @note This version of function is to be called from panic handler.
+ *       It does not use any OS primitives and IPC and implies that
+ *       only calling CPU is active.
+ *
+ * @param  start_address  Address where erase operation has to start.
+ *                                  Must be 4kB-aligned
+ * @param  size  Size of erased range, in bytes. Must be divisible by 4kB.
+ *
+ * @return esp_err_t
+ */
+esp_err_t spi_flash_erase_range_panic(size_t start_address, size_t size);
+
+
+/**
+ * @brief  Write data to Flash.
+ *
+ * @note This version of function is to be called from panic handler.
+ *       It does not use any OS primitives and IPC and implies that
+ *       only calling CPU is active.
+
+ * @note If source address is in DROM, this function will return
+ *       ESP_ERR_INVALID_ARG.
+ *
+ * @param  dest  destination address in Flash. Must be a multiple of 4 bytes.
+ * @param  src   pointer to the source buffer.
+ * @param  size  length of data, in bytes. Must be a multiple of 4 bytes.
+ *
+ * @return esp_err_t
+ */
+esp_err_t spi_flash_write_panic(size_t dest, const void *src, size_t size);
+
+
+/**
+ * @brief  Read data from Flash.
+ *
+ * @note This version of function is to be called from panic handler.
+ *       It does not use any OS primitives and IPC and implies that
+ *       only calling CPU is active.
+ *
+ * @param  src   source address of the data in Flash.
+ * @param  dest  pointer to the destination buffer
+ * @param  size  length of data
+ *
+ * @return esp_err_t
+ */
+esp_err_t spi_flash_read_panic(size_t src, void *dest, size_t size);
+
 #if CONFIG_SPI_FLASH_ENABLE_COUNTERS
 
 /**