diff --git a/lib/Logging/Logging.cpp b/lib/Logging/Logging.cpp index 7d72c920..22c14db9 100644 --- a/lib/Logging/Logging.cpp +++ b/lib/Logging/Logging.cpp @@ -1,5 +1,21 @@ #include "Logging.h" +#include + +#define MAX_ENTRY_LEN 256 +#define MAX_LOG_LINES 16 + +// Simple ring buffer log, useful for error reporting when we encounter a crash +RTC_NOINIT_ATTR char logMessages[MAX_LOG_LINES][MAX_ENTRY_LEN]; +RTC_NOINIT_ATTR size_t logHead = 0; + +void addToLogRingBuffer(const char* message) { + // Add the message to the ring buffer, overwriting old messages if necessary + strncpy(logMessages[logHead], message, MAX_ENTRY_LEN - 1); + logMessages[logHead][MAX_ENTRY_LEN - 1] = '\0'; + logHead = (logHead + 1) % MAX_LOG_LINES; +} + // Since logging can take a large amount of flash, we want to make the format string as short as possible. // This logPrintf prepend the timestamp, level and origin to the user-provided message, so that the user only needs to // provide the format string for the message itself. @@ -9,7 +25,7 @@ void logPrintf(const char* level, const char* origin, const char* format, ...) { } va_list args; va_start(args, format); - char buf[256]; + char buf[MAX_ENTRY_LEN]; char* c = buf; // add the timestamp { @@ -43,5 +59,26 @@ void logPrintf(const char* level, const char* origin, const char* format, ...) { // add the user message vsnprintf(c, sizeof(buf) - (c - buf), format, args); va_end(args); - logSerial.print(buf); + if (logSerial) { + logSerial.print(buf); + } + addToLogRingBuffer(buf); +} + +std::string getLastLogs() { + std::string output; + for (size_t i = 0; i < MAX_LOG_LINES; i++) { + size_t idx = (logHead + i) % MAX_LOG_LINES; + if (logMessages[idx][0] != '\0') { + output += logMessages[idx]; + } + } + return output; +} + +void clearLastLogs() { + for (size_t i = 0; i < MAX_LOG_LINES; i++) { + logMessages[i][0] = '\0'; + } + logHead = 0; } diff --git a/lib/Logging/Logging.h b/lib/Logging/Logging.h index cbc7f86c..83cabdc1 100644 --- a/lib/Logging/Logging.h +++ b/lib/Logging/Logging.h @@ -2,6 +2,8 @@ #include +#include + /* Define ENABLE_SERIAL_LOG to enable logging Can be set in platformio.ini build_flags or as a compile definition @@ -53,6 +55,9 @@ void logPrintf(const char* level, const char* origin, const char* format, ...); #define LOG_INF(origin, format, ...) #endif +std::string getLastLogs(); +void clearLastLogs(); + class MySerialImpl : public Print { public: void begin(unsigned long baud) { logSerial.begin(baud); } diff --git a/lib/hal/HalSystem.cpp b/lib/hal/HalSystem.cpp new file mode 100644 index 00000000..4e17d5bd --- /dev/null +++ b/lib/hal/HalSystem.cpp @@ -0,0 +1,137 @@ +#include "HalSystem.h" + +#include + +#include "Arduino.h" +#include "HalStorage.h" +#include "Logging.h" +#include "esp_debug_helpers.h" +#include "esp_private/esp_cpu_internal.h" +#include "esp_private/esp_system_attr.h" +#include "esp_private/panic_internal.h" + +#define MAX_PANIC_STACK_DEPTH 32 + +RTC_NOINIT_ATTR char panicMessage[256]; +RTC_NOINIT_ATTR HalSystem::StackFrame panicStack[MAX_PANIC_STACK_DEPTH]; + +extern "C" { + +static DRAM_ATTR const char PANIC_REASON_UNKNOWN[] = "(unknown panic reason)"; +void IRAM_ATTR __wrap_panic_abort(const char* message) { + if (!message) message = PANIC_REASON_UNKNOWN; + // IRAM-safe bounded copy (strncpy is not IRAM-safe in panic context) + int i = 0; + for (; i < (int)sizeof(panicMessage) - 1 && message[i]; i++) { + panicMessage[i] = message[i]; + } + panicMessage[i] = '\0'; + + __real_panic_abort(message); +} + +void IRAM_ATTR __wrap_panic_print_backtrace(const void* frame, int core) { + if (!frame) { + __real_panic_print_backtrace(frame, core); + return; + } + for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) { + panicStack[i].sp = 0; + } + + // Copied from components/esp_system/port/arch/riscv/panic_arch.c + uint32_t sp = (uint32_t)((RvExcFrame*)frame)->sp; + const int per_line = 8; + int depth = 0; + for (int x = 0; x < 1024; x += per_line * sizeof(uint32_t)) { + uint32_t* spp = (uint32_t*)(sp + x); + // panic_print_hex(sp + x); + // panic_print_str(": "); + panicStack[depth].sp = sp + x; + for (int y = 0; y < per_line; y++) { + // panic_print_str("0x"); + // panic_print_hex(spp[y]); + // panic_print_str(y == per_line - 1 ? "\r\n" : " "); + panicStack[depth].spp[y] = spp[y]; + } + + depth++; + if (depth >= MAX_PANIC_STACK_DEPTH) { + break; + } + } + + __real_panic_print_backtrace(frame, core); +} +} + +namespace HalSystem { + +void begin() { + // This is mostly for the first boot, we need to initialize the panic info and logs to empty state + // If we reboot from a panic state, we want to keep the panic info until we successfully dump it to the SD card, use + // `clearPanic()` to clear it after dumping + if (!isRebootFromPanic()) { + clearPanic(); + } +} + +void checkPanic() { + if (isRebootFromPanic()) { + auto panicInfo = getPanicInfo(true); + auto file = Storage.open("/crash_report.txt", O_WRITE | O_CREAT | O_TRUNC); + if (file) { + file.write(panicInfo.c_str(), panicInfo.size()); + file.close(); + LOG_INF("SYS", "Dumped panic info to SD card"); + } else { + LOG_ERR("SYS", "Failed to open crash_report.txt for writing"); + } + } +} + +void clearPanic() { + panicMessage[0] = '\0'; + for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) { + panicStack[i].sp = 0; + } + clearLastLogs(); +} + +std::string getPanicInfo(bool full) { + if (!full) { + return panicMessage; + } else { + std::string info; + + info += "CrossPoint version: " CROSSPOINT_VERSION; + info += "\n\nPanic reason: " + std::string(panicMessage); + info += "\n\nLast logs:\n" + getLastLogs(); + info += "\n\nStack memory:\n"; + + auto toHex = [](uint32_t value) { + char buffer[9]; + snprintf(buffer, sizeof(buffer), "%08X", value); + return std::string(buffer); + }; + for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) { + if (panicStack[i].sp == 0) { + break; + } + info += "0x" + toHex(panicStack[i].sp) + ": "; + for (size_t j = 0; j < 8; j++) { + info += "0x" + toHex(panicStack[i].spp[j]) + " "; + } + info += "\n"; + } + + return info; + } +} + +bool isRebootFromPanic() { + const auto resetReason = esp_reset_reason(); + return resetReason == ESP_RST_PANIC || resetReason == ESP_RST_CPU_LOCKUP; +} + +} // namespace HalSystem diff --git a/lib/hal/HalSystem.h b/lib/hal/HalSystem.h new file mode 100644 index 00000000..93275082 --- /dev/null +++ b/lib/hal/HalSystem.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +extern "C" { + +void __real_panic_abort(const char* message); +void __wrap_panic_abort(const char* message); + +void __real_panic_print_backtrace(const void* frame, int core); +void __wrap_panic_print_backtrace(const void* frame, int core); +} + +namespace HalSystem { +struct StackFrame { + uint32_t sp; + uint32_t spp[8]; +}; + +void begin(); + +// Dump panic info to SD card if necessary +void checkPanic(); +void clearPanic(); + +std::string getPanicInfo(bool full = false); +bool isRebootFromPanic(); +} // namespace HalSystem diff --git a/platformio.ini b/platformio.ini index 691f1570..8e45ab77 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,6 +35,7 @@ build_flags = # Default is (320*4+1)*2=2562, we need more for larger images -DPNG_MAX_BUFFERED_PIXELS=16416 -Wno-bidi-chars + -Wl,--wrap=panic_print_backtrace,--wrap=panic_abort build_unflags = -std=gnu++11 diff --git a/src/main.cpp b/src/main.cpp index b27eb52c..d629346f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -227,6 +228,7 @@ void setupDisplayAndFonts() { void setup() { t1 = millis(); + HalSystem::begin(); gpio.begin(); powerManager.begin(); @@ -249,6 +251,9 @@ void setup() { return; } + HalSystem::checkPanic(); + HalSystem::clearPanic(); // TODO: move this to an activity when we have one to display the panic info + SETTINGS.loadFromFile(); I18N.loadSettings(); KOREADER_STORE.loadFromFile();