ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
coredump.cpp
Go to the documentation of this file.
1#ifdef USE_ZEPHYR
2#include "coredump.h"
6#include "esphome/core/log.h"
7#include <cinttypes>
8#include <zephyr/version.h>
9// The header has no C++ linkage guards of its own
10extern "C" {
11#include <zephyr/debug/coredump.h>
12}
13
15
16ESPHOME_LOG_TAG(TAG, "zephyr_coredump");
17
18// Lines in the "#CD:" format that Zephyr's coredump_serial_log_parser.py reads: the log header (time, level,
19// tag, line number and colour codes, under 64 characters), "#CD:" and two hex characters per byte must fit the
20// logger's line buffer, so a small buffer gets shorter lines
21static constexpr size_t LINE_OVERHEAD = 64;
22static constexpr size_t CHUNK_SIZE =
23 std::min<size_t>(64, (ESPHOME_LOGGER_TX_BUFFER_SIZE - LINE_OVERHEAD) / 2 / 16 * 16);
24static_assert(CHUNK_SIZE >= 16, "logger line buffer too small for a coredump line");
25// A USB host starts reading the port only some 100 ms after opening it; until then the CDC driver discards output
26static constexpr uint32_t START_DELAY_MS = 1000;
27static constexpr uint32_t START_DELAY_ID = 0;
28// Between lines, so a loop that runs faster than usual (a stepper moving) cannot overrun the USB CDC ring buffer
29static constexpr uint32_t LINE_INTERVAL_MS = 10;
30
31// On a UART the logger reports the reader before this component exists, so the flag outlives the pointer
32static Coredump *global_coredump = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
33static bool reader_present = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
34
35bool coredump_pending() { return global_coredump != nullptr && global_coredump->stored_; }
36
38 reader_present = true;
39 if (global_coredump != nullptr)
40 global_coredump->start_print_();
41}
42
44 reader_present = false;
45 if (global_coredump != nullptr)
46 global_coredump->stop_print_();
47}
48
50 global_coredump = this;
51 this->disable_loop();
52 // The size comes from the header alone; "has stored dump" also checks the checksum and would hide a
53 // corrupt dump instead of letting it be reported and erased
54 const int size = coredump_query(COREDUMP_QUERY_GET_STORED_DUMP_SIZE, nullptr);
55 if (size <= 0) {
56 return;
57 }
58 this->stored_ = true;
59 this->total_ = size;
60 // A bad dump is reported and erased from loop() like a good one, once somebody can read the message
61 this->verify_ = coredump_cmd(COREDUMP_CMD_VERIFY_STORED_DUMP, nullptr);
62 if (reader_present)
63 this->start_print_();
64}
65
66// A new reader gets the whole dump: a print cut short by the port closing starts over
68 if (!this->stored_ || this->printing_)
69 return;
70 this->printing_ = true;
71 this->offset_ = 0;
72 this->set_timeout(START_DELAY_ID, START_DELAY_MS, [this]() { this->enable_loop(); });
73}
74
76 this->printing_ = false;
77 this->cancel_timeout(START_DELAY_ID);
78 this->disable_loop();
79}
80
83 if (now - this->last_line_ms_ < LINE_INTERVAL_MS)
84 return;
85 this->last_line_ms_ = now;
86 if (this->verify_ != 1) {
87 ESP_LOGE(TAG, "Stored coredump verification failed: %d", this->verify_);
88 this->finish_(0);
89 return;
90 }
91 if (this->offset_ == 0) {
92 ESP_LOGE(TAG, "#CD:BEGIN#");
93 }
94 uint8_t buf[CHUNK_SIZE];
95 char hex[format_hex_size(CHUNK_SIZE)];
96 coredump_cmd_copy_arg arg{static_cast<off_t>(this->offset_), buf,
97 std::min<size_t>(CHUNK_SIZE, this->total_ - this->offset_)};
98 const int ret = coredump_cmd(COREDUMP_CMD_COPY_STORED_DUMP, &arg);
99 if (ret > 0) {
100 ESP_LOGE(TAG, "#CD:%s", format_hex_to(hex, buf, ret));
101 this->offset_ += ret;
102 }
103 if (ret <= 0 || this->offset_ >= this->total_) {
104 ESP_LOGE(TAG, "#CD:END#");
105 this->finish_(ret < 0 ? ret : 0);
106 }
107}
108
109// Erase so the dump is not reported again; the next crash writes a new one. Erasing the partition halts the
110// CPU for about 85 ms per 4 KB page, so this one call takes most of a second
111void Coredump::finish_(int err) {
112 if (err < 0) {
113 ESP_LOGE(TAG, "Failed to copy coredump: %d", err);
114 }
115 const int erase = coredump_cmd(COREDUMP_CMD_ERASE_STORED_DUMP, nullptr);
116 if (erase < 0) {
117 ESP_LOGE(TAG, "Failed to erase coredump: %d", erase);
118 }
119 this->stored_ = false;
120 this->total_ = 0;
121 this->verify_ = 0;
122 this->printing_ = false;
123 this->disable_loop();
124}
125
127 ESP_LOGCONFIG(TAG,
128 "Coredump:\n"
129 " Stored dump: %s\n"
130 " Size: %" PRIu32 "\n"
131 " Verified: %s\n"
132 " Error: %d",
133 YESNO(this->stored_), this->total_, YESNO(this->verify_ == 1),
134 coredump_query(COREDUMP_QUERY_GET_ERROR, nullptr));
135}
136
137} // namespace esphome::zephyr_coredump
138
139// Backport of https://github.com/zephyrproject-rtos/zephyr/pull/79622 (Zephyr 4.0): give GDB the stack
140// pointer from before the exception frame so it can unwind. The Python side links it in with --wrap.
141#ifdef USE_ZEPHYR_COREDUMP_SP_FIX
142// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
143extern "C" {
144
145#if KERNEL_VERSION_NUMBER >= 0x030700
146using esf_t = struct arch_esf;
147#else
148using esf_t = z_arch_esf_t;
149#endif
150
151static constexpr uint8_t EXC_RETURN_STACK_FRAME_TYPE_POS = 4;
152static constexpr uint32_t EXC_RETURN_STACK_FRAME_TYPE_EXTENDED = 0;
153
154static uint32_t global_exc_return;
155
156void __real_z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t *callee_regs);
157void __wrap_z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t *callee_regs) {
158 global_exc_return = exc_return;
159 __real_z_arm_fault(msp, psp, exc_return, callee_regs);
160}
161
162// Faults from z_arm_fault() come here with the frame z_arm_fault() saved; a spurious interrupt has none.
163// Software fatal errors (k_panic, k_oops) reach z_arm_fatal_error() inside its own object, which --wrap cannot
164// intercept, so their dumps keep Zephyr's stack pointer.
165void __real_z_arm_fatal_error(unsigned int reason, const esf_t *esf);
166void __wrap_z_arm_fatal_error(unsigned int reason, const esf_t *esf) {
167#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) || defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
168 if (esf == nullptr) {
169 __real_z_arm_fatal_error(reason, esf);
170 return;
171 }
172 z_arm_coredump_fault_sp += sizeof(esf->basic);
173#if defined(CONFIG_FPU) && defined(CONFIG_FPU_SHARING)
174 // The thread used the FP registers, so the frame also holds them
175 if ((global_exc_return & BIT(EXC_RETURN_STACK_FRAME_TYPE_POS)) == EXC_RETURN_STACK_FRAME_TYPE_EXTENDED) {
176 z_arm_coredump_fault_sp += sizeof(esf->fpu);
177 }
178#endif
179#ifndef CONFIG_ARMV8_M_MAINLINE
180 // PSR bit 9 set: the frame was padded for alignment
181 if ((esf->basic.xpsr & SCB_CCR_STKALIGN_Msk) == SCB_CCR_STKALIGN_Msk) {
182 z_arm_coredump_fault_sp |= 0x4;
183 }
184#endif
185#endif
186 __real_z_arm_fatal_error(reason, esf);
187}
188}
189// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
190#endif
191#endif
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void enable_loop()
Enable this component's loop.
Definition component.h:248
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void disable_loop()
Disable this component's loop.
void start_print_()
Start printing after a short delay, one line per loop so the USB CDC transmit buffer and the main loo...
Definition coredump.cpp:67
struct arch_esf esf_t
Definition coredump.cpp:146
void __real_z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t *callee_regs)
void __wrap_z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t *callee_regs)
Definition coredump.cpp:157
void __real_z_arm_fatal_error(unsigned int reason, const esf_t *esf)
void __wrap_z_arm_fatal_error(unsigned int reason, const esf_t *esf)
Definition coredump.cpp:166
int ret
bool coredump_pending()
Whether a dump from before this boot is waiting to be printed.
Definition coredump.cpp:35
void print_coredump()
A log reader is present: serial at boot, USB CDC when the port opens, an API or MQTT client subscribi...
Definition coredump.cpp:37
ESPHOME_LOG_TAG(TAG, "zephyr_coredump")
void stop_coredump()
The reader went away (the USB port was closed); the print stops and the dump stays for the next one.
Definition coredump.cpp:43
uint16_t size
Definition helpers.cpp:34
constexpr size_t format_hex_size(size_t byte_count)
Calculate buffer size needed for format_hex_to: "XXXXXXXX...\0" = bytes * 2 + 1.
Definition helpers.h:1583
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
Definition helpers.cpp:428
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t