From 5a0febc64ef515fd3873b3f6b842ac4d669fda55 Mon Sep 17 00:00:00 2001 From: Hadinger Date: Sat, 7 Dec 2019 10:41:29 +0100 Subject: [PATCH] Add save call stack in RTC memory in case of crash --- tasmota/CHANGELOG.md | 1 + tasmota/i18n.h | 3 ++ tasmota/my_user_config.h | 3 ++ tasmota/support_command.ino | 15 +++++- tasmota/support_crash_recorder.ino | 80 ++++++++++++++++++++++++++++++ tasmota/support_wifi.ino | 1 + tasmota/tasmota.h | 2 +- 7 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 tasmota/support_crash_recorder.ino diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 4bb5447ad..10c3c6941 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 7.1.2.2 20191206 - Add command ``SerialConfig 0..23`` or ``SerialConfig 8N1`` to select Serial Config (#7108) +- Add save call stack in RTC memory in case of crash, command ``Status 12`` to dump the stack ### 7.1.2.1 20191206 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index dfa998474..30fcf173e 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -208,6 +208,7 @@ #define D_STATUS9_MARGIN "PTH" #define D_STATUS10_SENSOR "SNS" #define D_STATUS11_STATUS "STS" + #define D_STATUS12_STATUS "STK" #define D_CMND_STATE "State" #define D_CMND_POWER "Power" #define D_CMND_FANSPEED "FanSpeed" @@ -292,6 +293,8 @@ #define D_JSON_FLAG "FLAG" #define D_JSON_BASE "BASE" #define D_CMND_TEMPOFFSET "TempOffset" +#define D_CMND_CRASH "Crash" + #define D_JSON_ONE_TO_CRASH "1 to crash" // Commands xdrv_01_mqtt.ino #define D_CMND_MQTTLOG "MqttLog" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 7ef154d53..46fe15def 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -268,6 +268,9 @@ //#define MY_LANGUAGE zh-CN // Chinese (Simplified) in China //#define MY_LANGUAGE zh-TW // Chinese (Traditional) in Taiwan +// -- Crash generator --------------------------- +//#define USE_CRASH // add a `Crash` command to test the crash recorder (+48 bytes) + // -- Wifi Config tools --------------------------- #define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 13 as used by Wifi Manager web GUI diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 070409566..020e57b9d 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -30,7 +30,10 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix #ifdef USE_I2C D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|" #endif - D_CMND_SENSOR "|" D_CMND_DRIVER; +#ifdef USE_CRASH + D_CMND_CRASH "|" +#endif + D_CMND_SENSOR "|" D_CMND_DRIVER ; void (* const TasmotaCommand[])(void) PROGMEM = { &CmndBacklog, &CmndDelay, &CmndPower, &CmndStatus, &CmndState, &CmndSleep, &CmndUpgrade, &CmndUpgrade, &CmndOtaUrl, @@ -44,6 +47,9 @@ void (* const TasmotaCommand[])(void) PROGMEM = { &CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset, #ifdef USE_I2C &CmndI2cScan, CmndI2cDriver, +#endif +#ifdef USE_CRASH + &CmndCrash, #endif &CmndSensor, &CmndDriver }; @@ -483,6 +489,13 @@ void CmndStatus(void) MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "11")); } + if ((0 == payload) || (12 == payload)) { + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS12_STATUS "\":")); + CrashDump(); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "12")); + } + #ifdef USE_SCRIPT_STATUS if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data); #endif diff --git a/tasmota/support_crash_recorder.ino b/tasmota/support_crash_recorder.ino new file mode 100644 index 000000000..be0cde11b --- /dev/null +++ b/tasmota/support_crash_recorder.ino @@ -0,0 +1,80 @@ +/* + support_crash_recorder.ino - record the call stack in RTC in cas of crash + + Copyright (C) 2019 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 . +*/ + +const uint32_t dump_max_len = 64; // dump only 64 call addresses + +/** + * 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(addr_written, (uint32_t*)&value, sizeof(value)); + addr_written++; + if (addr_written >= dump_max_len) { break; } // we store only 64 addresses + } + } + // fill the rest of RTC with zeros + value = 0; + while (addr_written < dump_max_len) { + ESP.rtcUserMemoryWrite(addr_written++, (uint32_t*)&value, sizeof(value)); + } +} + +// Generate a crash to test the crash recorder +void CmndCrash(void) +{ + if (1 == XdrvMailbox.payload) { + volatile uint32_t dummy; + dummy = *((uint32_t*) 0x00000000); // invalid address + } else { + ResponseCmndChar(D_JSON_ONE_TO_CRASH); + } +} + +// Clear the RTC dump area when we do a normal reboot, this avoids garbage data to stay in RTC +void CrashDumpClear(void) { + uint32_t value = 0; + for (uint32_t i = 0; i < dump_max_len; i++) { + ESP.rtcUserMemoryWrite(i, (uint32_t*)&value, sizeof(value)); + } +} + +/*********************************************************************************************\ + * CmndCrashDump - dump the crash history - called by `Status 12` +\*********************************************************************************************/ +void CrashDump(void) +{ + ResponseAppend_P(PSTR("{\"call_chain\":[")); + for (uint32_t i = 0; i < dump_max_len; i++) { + uint32_t value; + ESP.rtcUserMemoryRead(i, (uint32_t*)&value, sizeof(value)); + if ((value >= 0x40000000) && (value < 0x40300000)) { + if (i > 0) { ResponseAppend_P(PSTR(",")); } + ResponseAppend_P(PSTR("\"%08x\""), value); + } + } + ResponseAppend_P(PSTR("]}")); +} diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index 49faa0663..06d922b26 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -617,6 +617,7 @@ void WifiShutdown(void) void EspRestart(void) { WifiShutdown(); + CrashDumpClear(); // Clear the stack dump in RTC // ESP.restart(); // This results in exception 3 on restarts on core 2.3.0 ESP.reset(); } diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index cefef3a06..c31bfad83 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -131,7 +131,7 @@ const uint32_t SOFT_BAUDRATE = 9600; // Default software serial baudrate const uint32_t APP_BAUDRATE = 115200; // Default serial baudrate const uint32_t SERIAL_POLLING = 100; // Serial receive polling in ms const uint32_t ZIGBEE_POLLING = 100; // Serial receive polling in ms -const uint8_t MAX_STATUS = 11; // Max number of status lines +const uint8_t MAX_STATUS = 12; // Max number of status lines const uint32_t START_VALID_TIME = 1451602800; // Time is synced and after 2016-01-01