mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 06:44:33 -05:00
nRF52: say more when an assert resets
The assert line alone can't tell an out-of-memory crash apart from heap corruption or a stack overflow, and for a failed operator new it always names alloc.cpp. - After the assert line, log the FreeRTOS task (the log's [thread] tag is the last OSThread to run), its stack high-water mark, free heap and the largest free block, or that the free list failed a bounds-checked walk. - A failed operator new logs the requested size and the caller's address first, for addr2line. - MemGet::getMaxAllocHeap() walks newlib-nano's free list on nRF52, so the periodic heap line reports the largest block there. That line no longer prints "min 0" where the minimum isn't tracked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5a8f7e424a
commit
c77feed4a7
5 files changed
+95
-3
No files matched your search
+3
-1
@@ -1327,8 +1327,10 @@ void Power::logHeapUsage()
|
||||
char detail[64] = "";
|
||||
const uint32_t minFree = memGet.getMinFreeHeap();
|
||||
const uint32_t maxAlloc = memGet.getMaxAllocHeap();
|
||||
if (minFree || maxAlloc)
|
||||
if (minFree)
|
||||
snprintf(detail, sizeof(detail), ", min %u, largest block %u", minFree, maxAlloc);
|
||||
else if (maxAlloc)
|
||||
snprintf(detail, sizeof(detail), ", largest block %u", maxAlloc);
|
||||
|
||||
const uint32_t psramTotal = memGet.getPsramSize();
|
||||
if (psramTotal)
|
||||
|
||||
@@ -37,6 +37,43 @@ static uint32_t sbrkHeadroom()
|
||||
|
||||
MemGet memGet;
|
||||
|
||||
#ifdef ARCH_NRF52
|
||||
#include <unistd.h> // sbrk
|
||||
|
||||
extern "C" char __HeapBase[], __HeapLimit[];
|
||||
// newlib-nano's free list: each chunk starts with its size (header included) and the next free chunk
|
||||
struct NanoFreeChunk {
|
||||
long size;
|
||||
NanoFreeChunk *next;
|
||||
};
|
||||
extern "C" NanoFreeChunk *__malloc_free_list;
|
||||
|
||||
/// Walk newlib-nano's free list for the largest free chunk; false if a link leaves the heap or the list doesn't end.
|
||||
static bool walkNrf52FreeList(uint32_t &largest)
|
||||
{
|
||||
largest = 0;
|
||||
bool intact = true;
|
||||
uint32_t chunks = 0;
|
||||
vTaskSuspendAll();
|
||||
for (const NanoFreeChunk *c = __malloc_free_list; c; c = c->next) {
|
||||
const char *p = reinterpret_cast<const char *>(c);
|
||||
if (p < __HeapBase || p >= __HeapLimit || (reinterpret_cast<uintptr_t>(p) & 3) || c->size <= 0 ||
|
||||
c->size > __HeapLimit - p || ++chunks > 2048) {
|
||||
intact = false;
|
||||
break;
|
||||
}
|
||||
if ((uint32_t)c->size > largest)
|
||||
largest = c->size;
|
||||
}
|
||||
const char *brk = static_cast<const char *>(sbrk(0));
|
||||
(void)xTaskResumeAll();
|
||||
// The never-claimed space above the break is one more block
|
||||
if (brk >= __HeapBase && brk <= __HeapLimit && (uint32_t)(__HeapLimit - brk) > largest)
|
||||
largest = __HeapLimit - brk;
|
||||
return intact;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns the amount of free heap memory in bytes.
|
||||
* @return uint32_t The amount of free heap memory in bytes.
|
||||
@@ -100,11 +137,29 @@ uint32_t MemGet::getMaxAllocHeap()
|
||||
{
|
||||
#ifdef ARCH_ESP32
|
||||
return ESP.getMaxAllocHeap();
|
||||
#elif defined(ARCH_NRF52)
|
||||
uint32_t largest;
|
||||
walkNrf52FreeList(largest);
|
||||
return largest;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity-walks the allocator's free list, where the platform allows it.
|
||||
* @return false if the list looks corrupt, true if it is intact or can't be checked.
|
||||
*/
|
||||
bool MemGet::heapLooksIntact()
|
||||
{
|
||||
#ifdef ARCH_NRF52
|
||||
uint32_t largest;
|
||||
return walkNrf52FreeList(largest);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of free psram memory in bytes.
|
||||
*
|
||||
|
||||
@@ -13,6 +13,8 @@ class MemGet
|
||||
uint32_t getMinFreeHeap();
|
||||
// Largest block malloc() could still return, or 0 where the platform can't report it
|
||||
uint32_t getMaxAllocHeap();
|
||||
// False if the allocator's free list fails a sanity walk (heap corruption); true where it can't be checked
|
||||
bool heapLooksIntact();
|
||||
uint32_t getFreePsram();
|
||||
uint32_t getPsramSize();
|
||||
};
|
||||
|
||||
@@ -7,16 +7,26 @@
|
||||
* Custom new/delete to panic if out out memory
|
||||
*/
|
||||
|
||||
void *operator new(size_t size)
|
||||
// The assert below can only name this file, so log the request and who made it first
|
||||
static void __attribute__((noinline)) logAllocFailure(size_t size, void *caller)
|
||||
{
|
||||
LOG_ERROR("new(%u) failed, caller 0x%08lx", (unsigned)size, (unsigned long)caller);
|
||||
}
|
||||
|
||||
void *__attribute__((noinline)) operator new(size_t size)
|
||||
{
|
||||
auto p = rtos_malloc(size);
|
||||
if (!p)
|
||||
logAllocFailure(size, __builtin_return_address(0));
|
||||
assert(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void *operator new[](size_t size)
|
||||
void *__attribute__((noinline)) operator new[](size_t size)
|
||||
{
|
||||
auto p = rtos_malloc(size);
|
||||
if (!p)
|
||||
logAllocFailure(size, __builtin_return_address(0));
|
||||
assert(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "UptimeClock.h"
|
||||
#include "configuration.h"
|
||||
#include "memGet.h"
|
||||
#include "mesh/Throttle.h"
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#include <Adafruit_nRFCrypto.h>
|
||||
@@ -197,10 +198,32 @@ static void drainConsoleBeforeReset()
|
||||
delay(20);
|
||||
}
|
||||
|
||||
// What file:line can't say: the task that asserted (the log's [thread] tag is the last OSThread to run), how close
|
||||
// its stack came to overflowing, and whether the heap was exhausted, fragmented or corrupt.
|
||||
static void logAssertContext()
|
||||
{
|
||||
if (__get_IPSR() != 0) {
|
||||
LOG_ERROR("assert context: exception %lu", (unsigned long)__get_IPSR());
|
||||
return;
|
||||
}
|
||||
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING)
|
||||
return;
|
||||
const char *task = pcTaskGetName(NULL);
|
||||
const unsigned stackFree = uxTaskGetStackHighWaterMark(NULL) * sizeof(StackType_t);
|
||||
// mallinfo() behind getFreeHeap() walks the same list unchecked, so only call it once the list checks out
|
||||
if (!memGet.heapLooksIntact()) {
|
||||
LOG_ERROR("assert context: task %s, stack free %u B, heap free list corrupt", task, stackFree);
|
||||
return;
|
||||
}
|
||||
LOG_ERROR("assert context: task %s, stack free %u B, heap free %u, largest block %u", task, stackFree, memGet.getFreeHeap(),
|
||||
memGet.getMaxAllocHeap());
|
||||
}
|
||||
|
||||
// handle standard gcc assert failures
|
||||
void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr)
|
||||
{
|
||||
LOG_ERROR("assert failed %s: %d, %s, test=%s", file, line, func, failedexpr);
|
||||
logAssertContext();
|
||||
drainConsoleBeforeReset();
|
||||
// debugger_break(); FIXME doesn't work, possibly for segger
|
||||
// Reboot cpu
|
||||
|
||||
Reference in new issue
Block a user