From bc5f3469263c7a8411e390b9f36eff95050b2ab1 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 12 Sep 2021 12:24:09 +0200 Subject: [PATCH] Crash recorder ``Status 12`` for ESP32/ESP32S2/ESP32C3, supporting Esp-idf 3.3/4.4 --- CHANGELOG.md | 1 + platformio_tasmota32.ini | 2 + tasmota/support_crash_recorder.ino | 292 ++++++++++++++++++++++++++--- tasmota/support_esp.ino | 38 ---- 4 files changed, 269 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfd6e705..7a6e2f8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Berry class ``webclient`` for HTTP/HTTPS requests - Support for ESP32S2 GPIOs - ESP32 add GPIO 6/7/8/11 to template and remove GPIO 28-31 (remapping so backwards compatible) +- Crash recorder ``Status 12`` for ESP32/ESP32S2/ESP32C3, supporting Esp-idf 3.3/4.4 ### Fixed - OpenTherm invalid JSON (#13028) diff --git a/platformio_tasmota32.ini b/platformio_tasmota32.ini index ad2a50d08..62dfd7739 100644 --- a/platformio_tasmota32.ini +++ b/platformio_tasmota32.ini @@ -23,6 +23,8 @@ build_flags = ${esp_defaults.build_flags} -I$PROJECT_DIR/include -include "sdkconfig.h" -include "esp32x_fixes.h" + ; wrappers for the crash-recorder + -Wl,--wrap=panicHandler -Wl,--wrap=xt_unhandled_exception [core32] diff --git a/tasmota/support_crash_recorder.ino b/tasmota/support_crash_recorder.ino index b9d81b53f..9981a9d7f 100644 --- a/tasmota/support_crash_recorder.ino +++ b/tasmota/support_crash_recorder.ino @@ -17,6 +17,31 @@ 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) @@ -45,31 +70,6 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); } -// 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); - } -} - // Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC void CrashDumpClear(void) { @@ -113,4 +113,244 @@ void CrashDump(void) ResponseJsonEnd(); } -#endif // ESP8266 \ No newline at end of file +#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; ipc; + 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 \ No newline at end of file diff --git a/tasmota/support_esp.ino b/tasmota/support_esp.ino index 5feba48e7..6f94bf3f7 100644 --- a/tasmota/support_esp.ino +++ b/tasmota/support_esp.ino @@ -311,44 +311,6 @@ int32_t EspPartitionMmap(uint32_t action) { } */ -// -// Crash stuff -// - -void CrashDump(void) { -} - -bool CrashFlag(void) { - return false; -} - -void CrashDumpClear(void) { -} - -void CmndCrash(void) { - /* - volatile uint32_t dummy; - dummy = *((uint32_t*) 0x00000000); -*/ -} - -// 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); - } -*/ -} // // ESP32 specific