ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
exc_coredump.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file exc_coredump.cpp
6 * @brief Read the ESP32 core-dump partition and offload it (see exc_decoder.h).
7 *
8 * The panic text a device prints dies with the reboot; the core dump ESP-IDF writes to flash does
9 * not. This reads that image back on the next boot - as a summary for the live panel, and as raw
10 * bytes for a durable copy - then erases it so the next crash gets the space.
11 *
12 * Device-only: the summary structs and partition API are ESP-IDF's. The decoding/serialization of a
13 * panic stays pure in exc_decoder.cpp.
14 */
15
17
18#if PC_ENABLE_EXC_DECODER && defined(ARDUINO)
19
20#include <FS.h>
21#include <esp_core_dump.h>
22#include <esp_partition.h>
23#include <string.h>
24
25bool pc_exc_coredump_present(ExcCoreDump *out)
26{
27 size_t addr = 0;
28 size_t size = 0;
29 // image_get reports what is stored; image_check verifies its checksum, so a torn write from a
30 // crash mid-dump is reported as absent rather than handed on as a valid image.
31 if (esp_core_dump_image_get(&addr, &size) != ESP_OK || size == 0)
32 {
33 return false;
34 }
35 if (esp_core_dump_image_check() != ESP_OK)
36 {
37 return false;
38 }
39 if (out)
40 {
41 out->addr = (uint32_t)addr;
42 out->size = size;
43 }
44 return true;
45}
46
47bool pc_exc_coredump_summary(ExcInfo *out)
48{
49 if (!out)
50 {
51 return false;
52 }
53#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH && CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF
54 esp_core_dump_summary_t s;
55 memset(&s, 0, sizeof(s));
56 if (esp_core_dump_get_summary(&s) != ESP_OK)
57 {
58 return false;
59 }
60
61 memset(out, 0, sizeof(*out));
62 out->core = -1; // the summary does not name the core; the raw image does
63 out->pc = s.exc_pc;
64 // The crashing task name is the most useful short label the summary carries, so it fills the
65 // slot the console decoder puts the Guru Meditation cause in.
66 strncpy(out->cause, s.exc_task, sizeof(out->cause) - 1);
67 out->cause[sizeof(out->cause) - 1] = '\0';
68
69#if CONFIG_IDF_TARGET_ARCH_XTENSA
70 // Xtensa's windowed ABI lets the device walk its own stack, so a real backtrace is stored.
71 out->excvaddr = s.ex_info.exc_vaddr;
72 out->has_excvaddr = true;
73 size_t depth = s.exc_bt_info.depth;
74 if (depth > PC_EXC_MAX_FRAMES)
75 {
76 depth = PC_EXC_MAX_FRAMES;
77 }
78 for (size_t i = 0; i < depth; i++)
79 {
80 out->frames[i].pc = s.exc_bt_info.bt[i];
81 out->frames[i].sp = 0; // not part of the summary
82 }
83 out->frame_count = depth;
84#else
85 // RISC-V records a stack dump, not a backtrace - unwinding it needs DWARF, which lives off
86 // device. Report the trap cause/value and leave frame_count 0 rather than inventing frames.
87 out->excvaddr = s.ex_info.mtval;
88 out->has_excvaddr = true;
89 out->frame_count = 0;
90#endif
91 return true;
92#else
93 (void)out;
94 return false; // built without flash/ELF core dumps
95#endif
96}
97
98// Locate the image inside its partition. image_get reports an absolute flash address, but
99// esp_partition_read wants an offset within the partition, so every reader converts here once.
100static bool coredump_locate(const esp_partition_t **part_out, size_t *base_out, size_t *size_out)
101{
102 ExcCoreDump img;
103 if (!pc_exc_coredump_present(&img))
104 {
105 return false;
106 }
107 const esp_partition_t *part =
108 esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, nullptr);
109 if (!part || img.addr < part->address)
110 {
111 return false;
112 }
113 size_t base = (size_t)(img.addr - part->address);
114 if (base + img.size > part->size)
115 {
116 return false;
117 }
118 *part_out = part;
119 *base_out = base;
120 *size_out = img.size;
121 return true;
122}
123
124bool pc_exc_coredump_read(size_t offset, void *buf, size_t len)
125{
126 if (!buf)
127 {
128 return false;
129 }
130 if (len == 0)
131 {
132 return true;
133 }
134
135 const esp_partition_t *part = nullptr;
136 size_t base = 0;
137 size_t size = 0;
138 if (!coredump_locate(&part, &base, &size))
139 {
140 return false;
141 }
142 // Refuse a range that runs past the image rather than returning whatever flash follows it.
143 if (offset > size || len > size - offset)
144 {
145 return false;
146 }
147 return esp_partition_read(part, base + offset, buf, len) == ESP_OK;
148}
149
150bool pc_exc_coredump_save(fs::FS &file_sys, const char *path)
151{
152 if (!path || path[0] == '\0')
153 {
154 return false;
155 }
156
157 const esp_partition_t *part = nullptr;
158 size_t base = 0;
159 size_t size = 0;
160 if (!coredump_locate(&part, &base, &size))
161 {
162 return false;
163 }
164
165 fs::File f = file_sys.open(path, FILE_WRITE);
166 if (!f)
167 {
168 return false;
169 }
170
171 // Streamed in fixed chunks: a dump can be tens of KB and must never need to fit RAM at once.
172 uint8_t buf[PC_EXC_COREDUMP_CHUNK];
173 size_t off = 0;
174 bool ok = true;
175 while (off < size)
176 {
177 size_t n = (size - off < sizeof(buf)) ? size - off : sizeof(buf);
178 if (esp_partition_read(part, base + off, buf, n) != ESP_OK || f.write(buf, n) != n)
179 {
180 ok = false;
181 break;
182 }
183 off += n;
184 }
185 f.close();
186 if (!ok)
187 {
188 file_sys.remove(path); // never leave a half-written dump that looks complete
189 }
190 return ok;
191}
192
193bool pc_exc_coredump_erase(void)
194{
195 return esp_core_dump_image_erase() == ESP_OK;
196}
197
198#endif // PC_ENABLE_EXC_DECODER && ARDUINO
ESP32 panic / exception decoder for a live diagnostics panel (PC_ENABLE_EXC_DECODER).
#define PC_EXC_COREDUMP_CHUNK
Chunk the core-dump image is streamed out of flash in (PC_EXC_COREDUMP_CHUNK).