diff --git a/src/SomfyController.ino b/src/SomfyController.ino index fb1dbc2..f753b49 100644 --- a/src/SomfyController.ino +++ b/src/SomfyController.ino @@ -78,16 +78,15 @@ void setup() { #define CRASH_LOG(fmt, ...) do { if(newCrash) ESP_LOGW(TAG, fmt, ##__VA_ARGS__); else ESP_LOGI(TAG, fmt, ##__VA_ARGS__); } while(0) if(newCrash) ESP_LOGW(TAG, "*** Crash coredump from the last boot ***"); else ESP_LOGI(TAG, "Stored coredump from an earlier crash"); - CRASH_LOG(" Task: %s", summary.exc_task); + // exc_task isn't always terminated. + CRASH_LOG(" Task: %.*s", (int)sizeof(summary.exc_task), summary.exc_task); CRASH_LOG(" PC: 0x%08x", summary.exc_pc); #ifdef CONFIG_IDF_TARGET_ARCH_XTENSA CRASH_LOG(" Cause: %d", summary.ex_info.exc_cause); - char bt_buf[256] = {0}; - int pos = 0; - for (int i = 0; i < summary.exc_bt_info.depth; i++) { - pos += snprintf(bt_buf + pos, sizeof(bt_buf) - pos, " 0x%08x", summary.exc_bt_info.bt[i]); - } - CRASH_LOG(" Backtrace:%s", bt_buf); + char bt_buf[256]; + formatBacktrace(bt_buf, sizeof(bt_buf), summary.exc_bt_info.bt, + sizeof(summary.exc_bt_info.bt) / sizeof(summary.exc_bt_info.bt[0]), summary.exc_bt_info.depth); + CRASH_LOG(" Backtrace:%s%s", bt_buf, summary.exc_bt_info.corrupted ? " (corrupted)" : ""); #elif CONFIG_IDF_TARGET_ARCH_RISCV CRASH_LOG(" Cause: %d", summary.ex_info.mcause); CRASH_LOG(" MTVAL: 0x%08x RA: 0x%08x SP: 0x%08x", diff --git a/src/Utils.cpp b/src/Utils.cpp index f329951..dbe4e78 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -147,6 +147,18 @@ void RssiBroadcast::sent(bool broadcast, int rssi, int channel, uint32_t now) { bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain) { return !connected || apRssi >= currentRssi + minGain; } +void formatBacktrace(char *buf, size_t size, const uint32_t *bt, size_t maxFrames, uint32_t depth) { + // depth comes from flash. A half-written dump reads back as 0xa5a5a5a5, + // so never trust it past bt[] or the buffer. + buf[0] = '\0'; + size_t pos = 0; + size_t frames = depth < maxFrames ? depth : maxFrames; + for (size_t i = 0; i < frames && pos < size - 1; i++) { + int n = snprintf(buf + pos, size - pos, " 0x%08x", (unsigned int)bt[i]); + if (n < 0) break; + pos += (size_t)n; + } +} const char *wifiAuthModeName(uint8_t authMode) { switch(authMode) { case 0: return "OPEN"; diff --git a/src/Utils.h b/src/Utils.h index e182535..3f5814b 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -82,6 +82,9 @@ enum class heap_emit_t : uint8_t { none, client, broadcast, room }; // num is the socket client asked for, 255 for everyone. A broadcast goes out // every intervalMs; in between only the clients in room 0 get one. heap_emit_t heapEmitTarget(uint8_t num, uint32_t sinceLastMs, uint32_t intervalMs, bool roomHasClients); +// A coredump backtrace as " 0x%08x" per frame. bt holds maxFrames entries and +// depth is how many the dump claims are valid. +void formatBacktrace(char *buf, size_t size, const uint32_t *bt, size_t maxFrames, uint32_t depth); // How well loop() keeps up: the passes it made per second and its longest // pass, over the window since the last memStatus broadcast. Only the loop // task calls record(), rate(), maxUs() and publish(). publish() closes the diff --git a/test/test_utils/test_utils.cpp b/test/test_utils/test_utils.cpp index f3c2c2c..513c8b9 100644 --- a/test/test_utils/test_utils.cpp +++ b/test/test_utils/test_utils.cpp @@ -257,6 +257,35 @@ static void test_worthRoaming_any_ap_while_disconnected() { TEST_ASSERT_TRUE(worthRoaming(false, 0, -90, 6)); } +/********************************************************************* + * formatBacktrace + ********************************************************************/ +static void test_formatBacktrace_frames_the_dump_holds() { + const uint32_t bt[16] = {0x40083c19, 0x4008e42d, 0x00000011}; + char buf[256]; + formatBacktrace(buf, sizeof(buf), bt, 16, 3); + TEST_ASSERT_EQUAL_STRING(" 0x40083c19 0x4008e42d 0x00000011", buf); +} +static void test_formatBacktrace_garbage_depth_stays_in_bounds() { + // A half-written dump reads back as the 0xa5 stack fill: depth 0xa5a5a5a5. + // setup() looped that many times and overran bt[] and its buffer. + struct { uint32_t bt[16]; uint32_t depth; } summary; + memset(&summary, 0xa5, sizeof(summary)); + struct { char buf[256]; char guard[16]; } out; + memset(out.guard, 'G', sizeof(out.guard)); + formatBacktrace(out.buf, sizeof(out.buf), summary.bt, 16, summary.depth); + TEST_ASSERT_EQUAL(16 * 11, strlen(out.buf)); + for (size_t i = 0; i < sizeof(out.guard); i++) TEST_ASSERT_EQUAL_CHAR('G', out.guard[i]); +} +static void test_formatBacktrace_stops_when_the_buffer_is_full() { + const uint32_t bt[16] = {0}; + struct { char buf[30]; char guard[16]; } out; + memset(out.guard, 'G', sizeof(out.guard)); + formatBacktrace(out.buf, sizeof(out.buf), bt, 16, 16); + TEST_ASSERT_EQUAL(sizeof(out.buf) - 1, strlen(out.buf)); + for (size_t i = 0; i < sizeof(out.guard); i++) TEST_ASSERT_EQUAL_CHAR('G', out.guard[i]); +} + /********************************************************************* * wifiAuthModeName ********************************************************************/ @@ -384,6 +413,9 @@ int main(int argc, char **argv) { RUN_TEST(test_worthRoaming_needs_the_full_gain); RUN_TEST(test_worthRoaming_never_to_a_weaker_ap); RUN_TEST(test_worthRoaming_any_ap_while_disconnected); + RUN_TEST(test_formatBacktrace_frames_the_dump_holds); + RUN_TEST(test_formatBacktrace_garbage_depth_stays_in_bounds); + RUN_TEST(test_formatBacktrace_stops_when_the_buffer_is_full); RUN_TEST(test_wifiAuthModeName_matches_the_idf_enum); RUN_TEST(test_wifiAuthModeName_unknown_past_the_shared_values); RUN_TEST(test_heapEmitTarget_one_client_always_gets_it);