4#ifdef USE_ESP32_CRASH_HANDLER
13#include <esp_private/panic_internal.h>
16#if CONFIG_IDF_TARGET_ARCH_XTENSA
17#include <esp_cpu_utils.h>
18#include <esp_debug_helpers.h>
19#include <xtensa_context.h>
20#elif CONFIG_IDF_TARGET_ARCH_RISCV
21#include <riscv/rvruntime-frames.h>
24static constexpr uint32_t CRASH_MAGIC = 0xDEADBEEF;
25static constexpr size_t MAX_BACKTRACE = 16;
29static inline bool IRAM_ATTR is_code_addr(
uint32_t addr) {
30 return (addr >= SOC_IROM_LOW && addr < SOC_IROM_HIGH) || (addr >= SOC_IRAM_LOW && addr < SOC_IRAM_HIGH);
33#if CONFIG_IDF_TARGET_ARCH_RISCV
37static inline bool is_return_addr(
uint32_t addr) {
38 if (!is_code_addr(addr) || addr < 4)
46 memcpy(&inst, (
const void *) (addr - 4),
sizeof(inst));
51 if ((opcode == 0x6f || opcode == 0x67) && rd == 0x80)
57 uint16_t c_inst = *(uint16_t *) (addr - 2);
58 if ((c_inst & 0xf07f) == 0x9002 && (c_inst & 0x0f80) != 0)
68#if CONFIG_IDF_TARGET_ARCH_XTENSA
71static uint8_t IRAM_ATTR walk_xtensa_backtrace(XtExcFrame *frame,
uint32_t *
out, uint8_t max) {
72 esp_backtrace_frame_t bt_frame = {
79 uint32_t first_pc = esp_cpu_process_stack_pc(bt_frame.pc);
80 if (is_code_addr(first_pc)) {
83 while (
count < max && bt_frame.next_pc != 0) {
84 if (!esp_backtrace_get_next_frame(&bt_frame))
86 uint32_t pc = esp_cpu_process_stack_pc(bt_frame.pc);
87 if (is_code_addr(pc)) {
95#if CONFIG_IDF_TARGET_ARCH_RISCV
98static uint8_t IRAM_ATTR capture_riscv_backtrace(RvExcFrame *frame,
uint32_t *
out, uint8_t max, uint8_t *reg_count) {
100 if (is_code_addr(frame->mepc)) {
103 if (is_code_addr(frame->ra) && frame->ra != frame->mepc) {
111 if (is_code_addr(
val) &&
val != frame->mepc &&
val != frame->ra) {
126static constexpr uint32_t CRASH_DATA_VERSION = 4;
127#if CONFIG_IDF_TARGET_ARCH_XTENSA
130static constexpr uint32_t XTENSA_EXCCAUSE_COUNT = XCHAL_EXCCAUSE_NUM;
131#elif CONFIG_IDF_TARGET_ARCH_RISCV
134static constexpr uint32_t RISCV_EXCEPTION_CAUSE_COUNT = 32;
140 uint8_t backtrace_count;
141 uint8_t reg_frame_count;
143 uint8_t pseudo_excause;
148 uint8_t crashed_core;
149#if SOC_CPU_CORES_NUM > 1
150 static_assert(SOC_CPU_CORES_NUM == 2,
"Dual-core logic assumes exactly 2 cores");
151 uint8_t other_backtrace_count;
152 uint8_t other_reg_frame_count;
153 uint32_t other_backtrace[MAX_BACKTRACE];
160static
bool s_crash_data_valid = false;
174static uint32_t s_current_build_time =
static_cast<uint32_t>(ESPHOME_BUILD_TIME);
180static void read_crash_data() {
181 if (s_raw_crash_data.magic == CRASH_MAGIC && s_raw_crash_data.version == CRASH_DATA_VERSION) {
182 s_crash_data_valid =
true;
184 if (s_raw_crash_data.backtrace_count > MAX_BACKTRACE)
185 s_raw_crash_data.backtrace_count = MAX_BACKTRACE;
186 if (s_raw_crash_data.reg_frame_count > s_raw_crash_data.backtrace_count)
187 s_raw_crash_data.reg_frame_count = s_raw_crash_data.backtrace_count;
188 if (s_raw_crash_data.exception > 4)
189 s_raw_crash_data.exception = 4;
190 if (s_raw_crash_data.pseudo_excause > 1)
191 s_raw_crash_data.pseudo_excause = 0;
192 if (s_raw_crash_data.crashed_core >= SOC_CPU_CORES_NUM)
193 s_raw_crash_data.crashed_core = 0;
194#if SOC_CPU_CORES_NUM > 1
195 if (s_raw_crash_data.other_backtrace_count > MAX_BACKTRACE)
196 s_raw_crash_data.other_backtrace_count = MAX_BACKTRACE;
197 if (s_raw_crash_data.other_reg_frame_count > s_raw_crash_data.other_backtrace_count)
198 s_raw_crash_data.other_reg_frame_count = s_raw_crash_data.other_backtrace_count;
205 return s_crash_data_valid;
212 s_raw_crash_data.magic = 0;
216static bool cause_slot_was_written() {
217#if CONFIG_IDF_TARGET_ARCH_XTENSA
218 return s_raw_crash_data.cause < XTENSA_EXCCAUSE_COUNT;
220 return s_raw_crash_data.cause < RISCV_EXCEPTION_CAUSE_COUNT;
227static const char *get_exception_reason() {
228 uint8_t exception = s_raw_crash_data.exception;
229 if (exception == PANIC_EXCEPTION_ABORT || exception == PANIC_EXCEPTION_TWDT) {
233 if (!cause_slot_was_written()) {
237#if CONFIG_IDF_TARGET_ARCH_XTENSA
238 if (s_raw_crash_data.pseudo_excause) {
241 static const char *
const PSEUDO_REASON[] = {
243 "Unhandled debug exception",
245 "Unhandled kernel exception",
246 "Coprocessor exception",
247 "Interrupt wdt timeout on CPU0",
248 "Interrupt wdt timeout on CPU1",
251 uint32_t cause = s_raw_crash_data.cause;
252 if (cause <
sizeof(PSEUDO_REASON) /
sizeof(PSEUDO_REASON[0]))
253 return PSEUDO_REASON[cause];
254 return PSEUDO_REASON[0];
257 static const char *
const REASON[] = {
258 "IllegalInstruction",
260 "InstructionFetchError",
264 "IntegerDivideByZero",
267 "LoadStoreAlignment",
271 "LoadStorePIFDataError",
273 "LoadStorePIFAddrError",
276 "InstFetchPrivilege",
278 "InstrFetchProhibited",
283 "LoadStoreTLBMultihit",
284 "LoadStorePrivilege",
299 uint32_t cause = s_raw_crash_data.cause;
300 if (cause <
sizeof(REASON) /
sizeof(REASON[0]) && REASON[cause] !=
nullptr)
301 return REASON[cause];
302#elif CONFIG_IDF_TARGET_ARCH_RISCV
306 if (s_raw_crash_data.pseudo_excause)
308 static const char *
const REASON[] = {
309 "Instruction address misaligned",
310 "Instruction access fault",
311 "Illegal instruction",
313 "Load address misaligned",
315 "Store address misaligned",
316 "Store access fault",
317 "Environment call from U-mode",
318 "Environment call from S-mode",
320 "Environment call from M-mode",
321 "Instruction page fault",
326 uint32_t cause = s_raw_crash_data.cause;
327 if (cause <
sizeof(REASON) /
sizeof(REASON[0]) && REASON[cause] !=
nullptr)
328 return REASON[cause];
334static const char *get_exception_type() {
335 static const char *
const TYPES[] = {
342 uint8_t exc = s_raw_crash_data.exception;
343 if (exc <
sizeof(TYPES) /
sizeof(TYPES[0]))
349static void log_backtrace(
const uint32_t *addrs, uint8_t
count, uint8_t reg_frame_count) {
351 for (uint8_t i = 0; i <
count; i++) {
353#if CONFIG_IDF_TARGET_ARCH_RISCV
354 if (i >= reg_frame_count && !is_return_addr(addr))
356 const char *source = (i < reg_frame_count) ?
"backtrace" :
"stack scan";
358 const char *source =
"backtrace";
360 ESP_LOGE(TAG,
" BT%d: 0x%08" PRIX32
" (%s)", bt_num++, addr, source);
365static int append_addrs_to_hint(
char *buf,
int size,
int pos,
const uint32_t *addrs, uint8_t
count,
366 uint8_t reg_frame_count) {
367 for (uint8_t i = 0; i <
count &&
pos <
size - 12; i++) {
369#if CONFIG_IDF_TARGET_ARCH_RISCV
370 if (i >= reg_frame_count && !is_return_addr(addr))
373 pos += snprintf(buf + pos, size - pos,
" 0x%08" PRIX32, addr);
381#if CONFIG_IDF_TARGET_ARCH_XTENSA
382static const char *
const FAULT_ADDR_REG =
"EXCVADDR";
383static const char *
const FAULT_ADDR_REG_LOWER =
"excvaddr";
384#elif CONFIG_IDF_TARGET_ARCH_RISCV
385static const char *
const FAULT_ADDR_REG =
"MTVAL";
386static const char *
const FAULT_ADDR_REG_LOWER =
"mtval";
391static bool has_fault_addr() {
392 return s_raw_crash_data.exception == PANIC_EXCEPTION_FAULT && !s_raw_crash_data.pseudo_excause &&
393 cause_slot_was_written();
403static uint8_t log_foreign_backtrace(
const uint32_t *addrs, uint8_t
count, uint8_t bt_num) {
404 for (uint8_t i = 0; i <
count; i++) {
405 ESP_LOGE(TAG,
" bt%d: 0x%08" PRIX32, bt_num++, addrs[i]);
410static void log_foreign_addresses() {
411 ESP_LOGE(TAG,
" Captured by a different firmware build; addresses belong to that build's ELF");
412 ESP_LOGE(TAG,
" pc: 0x%08" PRIX32, s_raw_crash_data.pc);
413 if (has_fault_addr()) {
414 ESP_LOGE(TAG,
" %s: 0x%08" PRIX32, FAULT_ADDR_REG_LOWER, s_raw_crash_data.fault_addr);
416 uint8_t bt_num = log_foreign_backtrace(s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count, 0);
417#if SOC_CPU_CORES_NUM > 1
418 if (s_raw_crash_data.other_backtrace_count > 0) {
420 ESP_LOGE(TAG,
" other core (%d):", 1 - s_raw_crash_data.crashed_core);
421 log_foreign_backtrace(s_raw_crash_data.other_backtrace, s_raw_crash_data.other_backtrace_count, bt_num);
437 ESP_LOGE(TAG,
"*** CRASH DETECTED ON PREVIOUS BOOT ***");
438 const char *reason = get_exception_reason();
439 if (reason !=
nullptr) {
440 ESP_LOGE(TAG,
" Reason: %s - %s (cause %" PRIu32
")", get_exception_type(), reason, s_raw_crash_data.cause);
442 ESP_LOGE(TAG,
" Reason: %s", get_exception_type());
444 ESP_LOGE(TAG,
" Crashed core: %d", s_raw_crash_data.crashed_core);
445 if (s_raw_crash_data.build_time != s_current_build_time) {
448 log_foreign_addresses();
451 ESP_LOGE(TAG,
" PC: 0x%08" PRIX32
" (fault location)", s_raw_crash_data.pc);
454 if (has_fault_addr()) {
455 ESP_LOGE(TAG,
" %s: 0x%08" PRIX32
" (faulting address)", FAULT_ADDR_REG, s_raw_crash_data.fault_addr);
457 log_backtrace(s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count, s_raw_crash_data.reg_frame_count);
459#if SOC_CPU_CORES_NUM > 1
460 if (s_raw_crash_data.other_backtrace_count > 0) {
461 int other_core = 1 - s_raw_crash_data.crashed_core;
462 ESP_LOGE(TAG,
" Other core (%d) backtrace:", other_core);
463 log_backtrace(s_raw_crash_data.other_backtrace, s_raw_crash_data.other_backtrace_count,
464 s_raw_crash_data.other_reg_frame_count);
471 static const char *
const ADDR2LINE_CMD =
"addr2line -pfiaC -e firmware.elf";
473 int pos = snprintf(hint,
sizeof(hint),
"Use: %s 0x%08" PRIX32, ADDR2LINE_CMD, s_raw_crash_data.pc);
474 append_addrs_to_hint(hint,
sizeof(hint),
pos, s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count,
475 s_raw_crash_data.reg_frame_count);
476 ESP_LOGE(TAG,
"%s", hint);
477#if SOC_CPU_CORES_NUM > 1
478 if (s_raw_crash_data.other_backtrace_count > 0) {
479 pos = snprintf(hint,
sizeof(hint),
"Other core: %s", ADDR2LINE_CMD);
480 append_addrs_to_hint(hint,
sizeof(hint),
pos, s_raw_crash_data.other_backtrace,
481 s_raw_crash_data.other_backtrace_count, s_raw_crash_data.other_reg_frame_count);
482 ESP_LOGE(TAG,
"%s", hint);
504 s_raw_crash_data.pc = (
uint32_t) info->addr;
505 s_raw_crash_data.backtrace_count = 0;
506 s_raw_crash_data.reg_frame_count = 0;
507 s_raw_crash_data.exception = (uint8_t) info->exception;
508 s_raw_crash_data.pseudo_excause = info->pseudo_excause ? 1 : 0;
509 s_raw_crash_data.crashed_core = (uint8_t) info->core;
515 bool is_twdt = &g_twdt_isr !=
nullptr && g_twdt_isr;
516 s_raw_crash_data.exception = (uint8_t) (is_twdt ? PANIC_EXCEPTION_TWDT : PANIC_EXCEPTION_ABORT);
519 s_raw_crash_data.cause = 0;
520 s_raw_crash_data.fault_addr = 0;
525 s_raw_crash_data.build_time = esphome::esp32::s_current_build_time;
526#if SOC_CPU_CORES_NUM > 1
527 s_raw_crash_data.other_backtrace_count = 0;
528 s_raw_crash_data.other_reg_frame_count = 0;
531#if CONFIG_IDF_TARGET_ARCH_XTENSA
533 if (info->frame !=
nullptr) {
534 auto *xt_frame = (XtExcFrame *) info->frame;
536 if (info->pseudo_excause)
537 s_raw_crash_data.pc = xt_frame->pc;
538 if (!g_panic_abort) {
541 s_raw_crash_data.cause = xt_frame->exccause;
542 s_raw_crash_data.fault_addr = xt_frame->excvaddr;
544 s_raw_crash_data.backtrace_count = walk_xtensa_backtrace(xt_frame, s_raw_crash_data.backtrace, MAX_BACKTRACE);
547#if SOC_CPU_CORES_NUM > 1
551 if (info->core >= 0 && info->core < SOC_CPU_CORES_NUM) {
552 int other_core = 1 - info->core;
553 auto *other_frame = (XtExcFrame *) g_exc_frames[other_core];
554 if (other_frame !=
nullptr) {
555 s_raw_crash_data.other_backtrace_count =
556 walk_xtensa_backtrace(other_frame, s_raw_crash_data.other_backtrace, MAX_BACKTRACE);
561#elif CONFIG_IDF_TARGET_ARCH_RISCV
563 if (info->frame !=
nullptr) {
564 auto *rv_frame = (RvExcFrame *) info->frame;
565 if (!g_panic_abort) {
567 s_raw_crash_data.cause = rv_frame->mcause;
568 s_raw_crash_data.fault_addr = rv_frame->mtval;
570 s_raw_crash_data.backtrace_count =
571 capture_riscv_backtrace(rv_frame, s_raw_crash_data.backtrace, MAX_BACKTRACE, &s_raw_crash_data.reg_frame_count);
574#if SOC_CPU_CORES_NUM > 1
576 if (info->core >= 0 && info->core < SOC_CPU_CORES_NUM) {
577 int other_core = 1 - info->core;
578 auto *other_frame = (RvExcFrame *) g_exc_frames[other_core];
579 if (other_frame !=
nullptr) {
580 s_raw_crash_data.other_backtrace_count = capture_riscv_backtrace(
581 other_frame, s_raw_crash_data.other_backtrace, MAX_BACKTRACE, &s_raw_crash_data.other_reg_frame_count);
588 s_raw_crash_data.version = CRASH_DATA_VERSION;
589 s_raw_crash_data.magic = CRASH_MAGIC;
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
void __real_esp_panic_handler(panic_info_t *info)
void IRAM_ATTR __wrap_esp_panic_handler(panic_info_t *info)
std::vector< uint8_t > & out
ESPHOME_LOG_TAG(TAG, "hw_timer_esp_idf")
bool crash_handler_has_data()
Returns true if crash data was found this boot, reading it first if needed.
void crash_handler_log()
Log crash data if a crash was detected on previous boot.
void crash_handler_clear()
Clear the magic marker and mark crash data as consumed.