## Summary * **What is the goal of this PR?** On a cold boot (or after a crash that corrupts RTC RAM), logHead contains garbage. Then addToLogRingBuffer does: ``strncpy(logMessages[logHead], message, MAX_ENTRY_LEN - 1); `` With garbage logHead, this computes a completely invalid address. The % MAX_LOG_LINES guard on line 16 only runs after the bad store, which is too late. The fix is to clamp logHead before use. ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ (did use claude for the magic hash value)
149 lines
4.1 KiB
C++
149 lines
4.1 KiB
C++
#include "HalSystem.h"
|
|
|
|
#include <string>
|
|
|
|
#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" {
|
|
|
|
void __real_panic_abort(const char* message);
|
|
void __real_panic_print_backtrace(const void* frame, int core);
|
|
|
|
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();
|
|
} else {
|
|
// Panic reboot: preserve logs and panic info, but clamp logHead in case the
|
|
// panic occurred before begin() ever ran (e.g. in a static constructor).
|
|
// If logHead was out of range, logMessages is also garbage — clear it so
|
|
// getLastLogs() does not dump corrupt data into the crash report.
|
|
if (sanitizeLogHead()) {
|
|
clearLastLogs();
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|