/* support_crash_recorder.ino - record the call stack in RTC in case of crash Copyright (C) 2021 Stephan Hadinger, Theo Arends, This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Generate a crash to test the crash recorder void CmndCrash(void) { volatile uint32_t dummy; dummy = *((uint32_t*) 0x00000000); (void)dummy; } // Do an infinite loop to trigger WDT watchdog void CmndWDT(void) { volatile uint32_t dummy = 0; while (1) { dummy++; } } // This will trigger the os watch after OSWATCH_RESET_TIME (=120) seconds void CmndBlockedLoop(void) { while (1) { delay(1000); } } #ifdef ESP8266 const uint32_t crash_magic = 0x53415400; // Stack trace magic number (TASx) const uint32_t crash_rtc_offset = 32; // Offset in RTC memory skipping OTA used block const uint32_t crash_dump_max_len = 31; // Dump only 31 call addresses to satisfy max JSON length of about 600 characters /** * Save crash information in RTC memory * This function is called automatically if ESP8266 suffers an exception * It should be kept quick / consise to be able to execute before hardware wdt may kick in */ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end ) { uint32_t addr_written = 0; // how many addresses have we already written in RTC uint32_t value; // 4 bytes buffer to write to RTC for (uint32_t i = stack; i < stack_end; i += 4) { value = *((uint32_t*) i); // load value from stack if ((value >= 0x40000000) && (value < 0x40300000)) { // keep only addresses in code area ESP.rtcUserMemoryWrite(crash_rtc_offset + addr_written, (uint32_t*)&value, sizeof(value)); addr_written++; if (addr_written >= crash_dump_max_len) { break; } // we store only 31 addresses } } value = crash_magic + addr_written; ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); } // Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC void CrashDumpClear(void) { uint32_t value = 0; ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); } /*********************************************************************************************\ * CmndCrashDump - dump the crash history - called by `Status 12` \*********************************************************************************************/ bool CrashFlag(void) { return ((ResetReason() == REASON_EXCEPTION_RST) || (ResetReason() == REASON_SOFT_WDT_RST) || oswatch_blocked_loop); } void CrashDump(void) { ResponseAppend_P(PSTR("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":[\"%08x\",\"%08x\",\"%08x\"],\"EXCVADDR\":\"%08x\",\"DEPC\":\"%08x\""), resetInfo.exccause, // Exception Cause GetResetReason().c_str(), // Reset Reason resetInfo.epc1, // Exception Progam Counter resetInfo.epc2, // Exception Progam Counter - High-Priority Interrupt 1 resetInfo.epc3, // Exception Progam Counter - High-Priority Interrupt 2 resetInfo.excvaddr, // Exception Virtual Address Register - Virtual address that caused last fetch, load, or store exception resetInfo.depc); // Double Exception Program Counter uint32_t value; ESP.rtcUserMemoryRead(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); if (crash_magic == (value & 0xFFFFFF00)) { ResponseAppend_P(PSTR(",\"CallChain\":[")); uint32_t count = value & 0x3F; for (uint32_t i = 0; i < count; i++) { ESP.rtcUserMemoryRead(crash_rtc_offset +i, (uint32_t*)&value, sizeof(value)); if (i > 0) { ResponseAppend_P(PSTR(",")); } ResponseAppend_P(PSTR("\"%08x\""), value); } ResponseAppend_P(PSTR("]")); } ResponseJsonEnd(); } #endif // ESP8266 #ifdef ESP32 const uint32_t crash_magic = 0x53415400; // Stack trace magic number (TASx) const uint32_t crash_dump_max_len = 48; // Dump only 48 call addresses // RTC_NOINIT_ATTR: store in RTC slow memory that survices a boot/crash // needs to be volatile and have no intializer RTC_NOINIT_ATTR volatile struct { uint32_t magic; uint32_t stack[crash_dump_max_len]; uint32_t pc; uint32_t exccause; uint32_t excvaddr; } crash_recorder; bool CrashFlag(void) { return crash_recorder.magic == crash_magic; } // Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC void CrashDumpClear(void) { crash_recorder.magic = 0; for (uint32_t i=0; i= 4 #include "esp_debug_helpers.h" #else // IDF 3.x #include "esp_panic.h" #endif extern "C" { // esp-idf 3.x void __real_panicHandler(XtExcFrame *frame); void __real_xt_unhandled_exception(XtExcFrame *frame); } extern "C" IRAM_ATTR void custom_crash_recorder(XtExcFrame *exc_frame) { crash_recorder.magic = crash_magic; // crash occured crash_recorder.pc = exc_frame->pc; crash_recorder.exccause = exc_frame->exccause; crash_recorder.excvaddr = exc_frame->excvaddr; for (uint32_t i=0; ipc, .sp = exc_frame->a1, .next_pc = exc_frame->a0}; esp_backtrace_frame_t stk_frame; stk_frame.pc = exc_frame->pc; stk_frame.sp = exc_frame->a1; stk_frame.next_pc = exc_frame->a0; crash_recorder.stack[idx++] = esp_cpu_process_stack_pc(stk_frame.pc); static const uint32_t depth = 100; //Check if first frame is valid bool corrupted = (esp_stack_ptr_is_sane(stk_frame.sp) && esp_ptr_executable((void*)esp_cpu_process_stack_pc(stk_frame.pc))) ? false : true; uint32_t i = ((depth <= 0) ? INT32_MAX : depth) - 1; //Account for stack frame that's already printed while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) { if (!esp_backtrace_get_next_frame(&stk_frame)) { //Get next stack frame corrupted = true; } crash_recorder.stack[idx++] = esp_cpu_process_stack_pc(stk_frame.pc); if (idx >= crash_dump_max_len) break; // no more slots } } extern "C" IRAM_ATTR void __wrap_panicHandler(XtExcFrame *frame) { custom_crash_recorder(frame); __real_panicHandler(frame); // call the actual panic handler } extern "C" IRAM_ATTR void __wrap_xt_unhandled_exception(XtExcFrame *frame) { crash_recorder.magic = crash_magic; // crash occured custom_crash_recorder(frame); __real_xt_unhandled_exception(frame); // call the actual panic handler } // taken in panic.c, but orginal array is 'static' so can't be called static const char *edesc[] = { "IllegalInstruction", "Syscall", "InstructionFetchError", "LoadStoreError", "Level1Interrupt", "Alloca", "IntegerDivideByZero", "PCValue", "Privileged", "LoadStoreAlignment", "res", "res", "InstrPDAddrError", "LoadStorePIFDataError", "InstrPIFAddrError", "LoadStorePIFAddrError", "InstTLBMiss", "InstTLBMultiHit", "InstFetchPrivilege", "res", "InstrFetchProhibited", "res", "res", "res", "LoadStoreTLBMiss", "LoadStoreTLBMultihit", "LoadStorePrivilege", "res", "LoadProhibited", "StoreProhibited", "res", "res", "Cp0Dis", "Cp1Dis", "Cp2Dis", "Cp3Dis", "Cp4Dis", "Cp5Dis", "Cp6Dis", "Cp7Dis" }; #define NUM_EDESCS (sizeof(edesc) / sizeof(char *)) void CrashDump(void) { if (crash_recorder.magic == crash_magic) { ResponseAppend_P("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":\"%08x\",\"EXCVADDR\":\"%08x\"", crash_recorder.exccause, // Exception Cause crash_recorder.exccause < NUM_EDESCS ? edesc[crash_recorder.exccause] : "Unknown", crash_recorder.pc, // Exception Progam Counter crash_recorder.excvaddr // Exception Virtual Address Register - Virtual address that caused last fetch, load, or store exception ); // crash dump present ResponseAppend_P(PSTR(",\"CallChain\":[")); for (uint32_t i = 0; i < crash_dump_max_len; i++) { uint32_t return_addr = crash_recorder.stack[i]; if (!return_addr) { break; } if (i > 0) { ResponseAppend_P(PSTR(",")); } ResponseAppend_P(PSTR("\"%08x\""), return_addr); } ResponseAppend_P(PSTR("]")); } ResponseJsonEnd(); } #elif CONFIG_IDF_TARGET_ESP32C3 extern "C" { // esp-idf 3.x void __real_panicHandler(void *frame); void __real_xt_unhandled_exception(void *frame); } // from panic_arch.c const char *esp32c3_crash_reason[] = { "Instruction address misaligned", "Instruction access fault", "Illegal instruction", "Breakpoint", "Load address misaligned", "Load access fault", "Store address misaligned", "Store access fault", "Environment call from U-mode", "Environment call from S-mode", "", "Environment call from M-mode", "Instruction page fault", "Load page fault", "", "Store page fault", }; #define NUM_C3_REASONS (sizeof(esp32c3_crash_reason) / sizeof(char *)) #include extern "C" IRAM_ATTR void custom_crash_recorder(void *exc_frame) { RvExcFrame *regs = (RvExcFrame *)exc_frame; crash_recorder.magic = crash_magic; // crash occured crash_recorder.pc = regs->mepc; crash_recorder.exccause = regs->mcause; crash_recorder.excvaddr = regs->mtval; for (uint32_t i=0; ira; // push return address as first value // // code copied from panic_print_basic_backtrace() uint32_t * sp = (uint32_t*) regs->sp; uint32_t i = 0; for (uint32_t i = 0; ((uint32_t) sp) < 0x3FCDFFF0 && i < 320 && idx < crash_dump_max_len; i++, sp++) { uint32_t value = *sp; if ((value >= 0x40000000) && (value < 0x42800000)) { // keep only addresses in code area crash_recorder.stack[idx++] = value; } } } extern "C" IRAM_ATTR void __wrap_panicHandler(void *frame) { custom_crash_recorder(frame); __real_panicHandler(frame); // call the actual panic handler } extern "C" IRAM_ATTR void __wrap_xt_unhandled_exception(void *frame) { crash_recorder.magic = crash_magic; // crash occured custom_crash_recorder(frame); __real_xt_unhandled_exception(frame); // call the actual panic handler } void CrashDump(void) { if (crash_recorder.magic == crash_magic) { ResponseAppend_P("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":\"%08x\",\"EXCVADDR\":\"%08x\"", crash_recorder.exccause, // Exception Cause crash_recorder.exccause < NUM_C3_REASONS ? esp32c3_crash_reason[crash_recorder.exccause] : "Unknown", crash_recorder.pc, // Exception Progam Counter crash_recorder.excvaddr // Exception Virtual Address Register - Virtual address that caused last fetch, load, or store exception ); // crash dump present ResponseAppend_P(PSTR(",\"CallChain\":[")); for (uint32_t i = 0; i < crash_dump_max_len; i++) { uint32_t return_addr = crash_recorder.stack[i]; if (!return_addr) { break; } if (i > 0) { ResponseAppend_P(PSTR(",")); } ResponseAppend_P(PSTR("\"%08x\""), return_addr); } ResponseAppend_P(PSTR("]")); } ResponseJsonEnd(); } #else // unknown bool CrashFlag(void) { return false; } // Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC void CrashDumpClear(void) { } void CrashDump(void) { } #endif #endif