Skip to content

Commit 1102256

Browse files
committed
improve comments
Added DEBUG_ESP_WITHINISR to provide notification for ISRs using C++ 'new'/'delete' operators or LIBC calls using _malloc_r, ... Moved block of port related changes for UMM_POISON... from `umm_malloc_cfg.h` to `umm_malloc_cfgport.h`. Support for specifying both UMM_POISON_CHECK and UMM_POISON_CHECK_LITE at the same time did not work as expected. UMM_POISON_CHECK_LITE dominated in the build. Fix: Keep things simple prevent combining UMM_POISON_CHECK and UMM_POISON_CHECK_LITE. Defines for UMM_POISON_CHECK, UMM_POISON_CHECK_LITE, and UMM_POISON_NONE are checked for mutual exclusivity. For sketches with extremely limited resources, added UMM_POISON_NONE to prevent UMM_POISON_CHECK_LITE from being added to debug builds. Make UMM_POISON_CHECK_LITE fail messages more specific about the location. At free distinguishes between neighbor and allocation. And, identify the caller's address.
1 parent a716193 commit 1102256

File tree

8 files changed

+292
-167
lines changed

8 files changed

+292
-167
lines changed

cores/esp8266/abi.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extern "C" void *_heap_abi_malloc(size_t size, bool unhandled, const void* const
2929
extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));
3030
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
3131

32-
#if defined(__cpp_exceptions) && (defined(DEBUG_ESP_OOM) || defined(DEBUG_ESP_PORT))
32+
#if defined(__cpp_exceptions) && (defined(DEBUG_ESP_OOM) || defined(DEBUG_ESP_PORT) || defined(DEBUG_ESP_WITHINISR))
3333
/*
3434
When built with C++ Exceptions: "enabled", track caller address of Last OOM.
3535
* For debug build, force enable Last OOM tracking.

cores/esp8266/heap.cpp

Lines changed: 119 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -19,49 +19,69 @@
1919
* either a "thin wrapper" or a "thick wrapper" to capture Heap debug info and
2020
* diagnostics.
2121
*
22-
* Protect ISR call paths to malloc APIs and pvPortMalloc APIs with IRAM_ATTR.
23-
* "new" and _malloc_r (LIBC) are not expected to be called from an ISR and are
24-
* not safe.
22+
* Even though ISRs should not perform Heap API calls, we protect the call path
23+
* with IRAM_ATTR for malloc APIs and pvPortMalloc APIs. "new" and _malloc_r
24+
* (LIBC) are unprotected.
2525
*
26-
* Iventory of debug options supported by this modules
26+
* Inventory of debug options supported by this modules
2727
*
28-
* * DEBUG_ESP_OOM - Monitors all "allocating" Heap API families for failure
29-
* and saves the last failure for postmortem to display. Additionally if
30-
* system OS print is enabled (system_get_os_print == true), a diagnostic
31-
* message can be printed at the time of the OOM event. To furthor assist in
32-
* debugging, "fancy macros" redefine malloc, calloc, and realloc to their
33-
* matching cousins in the portable malloc family, These allow the capturing
34-
* of the file name and line number of the caller that had the OOM event.
28+
* * DEBUG_ESP_OOM - Monitors all "allocating" Heap API families for an
29+
* out-of-memory result and saves the Last OOM for Postmortem to display.
30+
* Additionally, if system OS print is enabled (system_get_os_print() ==
31+
* true) , print a diagnostic message at the time of the OOM event. To
32+
* further assist in debugging, "fancy macros" redefine malloc, calloc, and
33+
* realloc to their matching cousins in the portable malloc family.
34+
* Identifies the file name and line number of the caller where the OOM event
35+
* occurred.
3536
*
36-
* When DEBUG_ESP_OOM is not selected, use a minimized version to monitor for
37-
* OOM from LIBC and the C++ operator "new". These wrappers check for an
38-
* out-of-memory result, saving the caller address of the last fail. Report
39-
* last fail at Postmortem.
37+
* When DEBUG_ESP_OOM is not selected, use a minimized Last OOM wrapper to
38+
* track LIBC and the C++ operator "new". These wrappers only save the
39+
* caller address and size of the Last OOM - and report details at
40+
* Postmortem. No Last OOM tracking for the "new" operator with the non-debug
41+
* build and option C++ Exceptions: "enabled".
42+
*
43+
* You may select DEBUG_ESP_OOM through the Arduino IDE, Tools->Debug level:
44+
* "OOM". Or, enable via a build option define.
45+
*
46+
* DEBUG_ESP_WITHINISR - Monitors in-flash Heap APIs for calls from ISRs.
47+
* If they occur, print a message with the address of the caller.
48+
*
49+
* Considerations:
50+
* * There may be some rare special case where 'new', 'delete', or LIBC's
51+
* _malloc_r APIs are called with interrupts disabled.
52+
* * If called from within an ISR, we could have crash before reaching
53+
* this code.
54+
*
55+
* Enable via a build option define.
4056
*
4157
* * UMM_POISON_CHECK_LITE - A much lighter version of UMM_POISON_CHECK.
4258
* Adds and presets an extra 4 bytes of poison at the beginning and end of
4359
* each allocation. On each call to free or realloc, test the current
4460
* allocation's poison areas, then each active allocation's neighbor is
45-
* tested. This option is enabled when Tools->Debug: Serial is selected or
46-
* Tools->Debug level: "CORE" is selected. While coverage is not 100%, a
47-
* sketch is less likely to have strange behavior from heavy heap access with
48-
* interrupts disabled. If needed, continue reading "UMM_POISON_CHECK" for
49-
* more details.
61+
* tested. During each successful malloc/calloc, check the neighbors of the
62+
* free block before resizing to fulfill the request.
5063
*
51-
* * UMM_POISON_CHECK - Adds and presets 4 bytes of poison at the beginning and
52-
* end of each allocation. For each Heap API call, a complete sweep through
53-
* every allocation is performed, verifying that all poison bytes are
54-
* correct. This check runs with interrupts disabled and may affect WiFi
55-
* performance and maybe system stability.
64+
* In the absence of other UMM_POISON_... options, this option assumes
65+
* "enabled" when Tools->Debug: Serial is selected or Tools->Debug level:
66+
* "CORE" is selected. Otherwise, you may enable it via a build option
67+
* definition.
68+
*
69+
* While coverage is not 100%, a sketch is less likely to have strange
70+
* behavior from heavy heap access with interrupts disabled. Also, with
71+
* UMM_POISON_CHECK_LITE, more caller context is available at "poison fail."
72+
* If you need more perspective, continue reading "UMM_POISON_CHECK."
73+
*
74+
* * UMM_POISON_CHECK - Adds and presets 4 bytes of poison at the beginning
75+
* and end of each allocation. At each Heap API call, performs a global Heap
76+
* poison data verification. This check runs with interrupts disabled and may
77+
* affect WiFi performance and possibly system stability.
5678
*
5779
* As the number of active heap allocations grows, this option will cause
5880
* increasingly long periods with interrupts disabled, adversely affecting
5981
* time-critical sketches.
6082
*
6183
* Enable via a build option define.
6284
*
63-
* UMM_POISON_CHECK_LITE is a better alternative.
64-
*
6585
* * UMM_INTEGRITY_CHECK - will verify that the Heap is semantically correct
6686
* and that all the block indexes make sense. While it can catch errors
6787
* quickly, the check runs with interrupts disabled and may affect WiFi
@@ -77,8 +97,10 @@
7797
* rather than general debugging. It will detect heap corruption; however, it
7898
* offers little aid in determining who did it.
7999
*
80-
* UMM_POISON_CHECK_LITE should detect most would-be heap corruptions with
81-
* lower overhead.
100+
* While not as comprehensive as UMM_INTEGRITY_CHECK, using
101+
* UMM_POISON_CHECK_LITE should reveal most heap corruptions with lower
102+
* overhead.
103+
*
82104
*/
83105

84106
#include <stdlib.h>
@@ -120,10 +142,9 @@ extern "C" {
120142
#undef STATIC_ALWAYS_INLINE
121143
#undef ENABLE_THICK_DEBUG_WRAPPERS
122144

123-
#if defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
145+
#if defined(UMM_POISON_CHECK_LITE)
124146
/*
125-
* With either of these defines, umm_malloc will build with umm_poison_*
126-
* wrappers for each Heap API.
147+
* umm_malloc will build with umm_poison_* wrappers for each Heap API.
127148
*
128149
* Support debug wrappers that need to include handling poison
129150
*/
@@ -136,7 +157,22 @@ extern "C" {
136157
#undef realloc
137158
#undef free
138159

139-
#elif defined(DEBUG_ESP_OOM) || defined(UMM_INTEGRITY_CHECK) || defined(HEAP_DEBUG_PROBE_PSFLC_CB)
160+
#elif defined(UMM_POISON_CHECK)
161+
/*
162+
* umm_malloc will build with umm_poison_* wrappers for each Heap API.
163+
*
164+
* Support debug wrappers that need to include handling poison
165+
*/
166+
#define UMM_MALLOC_FL(s,f,l,c) umm_poison_malloc(s)
167+
#define UMM_CALLOC_FL(n,s,f,l,c) umm_poison_calloc(n,s)
168+
#define UMM_REALLOC_FL(p,s,f,l,c) umm_poison_realloc(p,s)
169+
#define UMM_FREE_FL(p,f,l,c) umm_poison_free(p)
170+
#define ENABLE_THICK_DEBUG_WRAPPERS
171+
172+
#undef realloc
173+
#undef free
174+
175+
#elif defined(DEBUG_ESP_OOM) || defined(UMM_INTEGRITY_CHECK) || defined(DEBUG_ESP_WITHINISR) || defined(HEAP_DEBUG_PROBE_PSFLC_CB)
140176
// All other debug wrappers that do not require handling poison
141177
#define UMM_MALLOC_FL(s,f,l,c) umm_malloc(s)
142178
#define UMM_CALLOC_FL(n,s,f,l,c) umm_calloc(n,s)
@@ -317,34 +353,40 @@ static bool oom_check__log_last_fail_psc(void *ptr, size_t size, const void* cal
317353
#endif
318354

319355

356+
///////////////////////////////////////////////////////////////////////////////
357+
// Monitor Heap APIs in flash for calls from ISRs
358+
//
359+
#if DEBUG_ESP_WITHINISR
360+
#define DEBUG_HEAP_PRINTF ets_uart_printf
361+
static void isr_check__flash_not_safe(const void *caller) {
362+
if (ETS_INTR_WITHINISR()) { // Assumes, non-zero INTLEVEL means in ISR
363+
DEBUG_HEAP_PRINTF("\nIn-flash, Heap API call from %p with Interrupts Disabled.\n", caller);
364+
}
365+
}
366+
#define ISR_CHECK__LOG_NOT_SAFE(c) isr_check__flash_not_safe(c)
367+
#else
368+
#define ISR_CHECK__LOG_NOT_SAFE(c) do { (void)c; } while(false)
369+
#endif
370+
371+
320372
#ifdef ENABLE_THICK_DEBUG_WRAPPERS
321373
///////////////////////////////////////////////////////////////////////////////
322374
// Thick Heap API wrapper for debugging: malloc, pvPortMalloc, "new", and
323375
// _malloc_r families of heap APIs.
324376
//
325377
// While UMM_INTEGRITY_CHECK and UMM_POISON_CHECK are included, the Arduino IDE
326378
// has no selection to build with them. Both are CPU intensive and can adversly
327-
// effect the WiFi operation. We use option UMM_POISON_CHECK_LITE instead of
328-
// UMM_POISON_CHECK. This is include in the debug build when you select the
329-
// Debug Port. For completeness they are all included in the list below. Both
330-
// UMM_INTEGRITY_CHECK and UMM_POISON_CHECK can be enabled by a build define.
379+
// effect the WiFi operation. For completeness they are all included in the
380+
// list below. Both UMM_INTEGRITY_CHECK and UMM_POISON_CHECK can be enabled by
381+
// build defines.
331382
//
332-
// The thinking behind the ordering of Integrity Check, Full Poison Check, and
333-
// the specific *alloc function.
383+
// A debug build will use option UMM_POISON_CHECK_LITE by default. If explicitly
384+
// specifying UMM_POISON_CHECK_LITE or UMM_POISON_CHECK, only one is permitted
385+
// in a Build.
334386
//
335-
// 1. Integrity Check - verifies the heap management information is not corrupt.
336-
// This allows any other testing, that walks the heap, to run safely.
337-
//
338-
// 2. Place Full Poison Check before or after a specific *alloc function?
339-
// a. After, when the *alloc function operates on an existing allocation.
340-
// b. Before, when the *alloc function creates a new, not modified, allocation.
341-
//
342-
// In a free() or realloc() call, the focus is on their allocation. It is
343-
// checked 1st and reported on 1ST if an error exists. Full Poison Check is
344-
// done after.
345-
//
346-
// For malloc(), calloc(), and zalloc() Full Poison Check is done 1st since
347-
// these functions do not modify an existing allocation.
387+
// When selected, do Integrity Check first. Verifies the heap management
388+
// information is not corrupt. Followed by Full Poison Check before *alloc
389+
// operation.
348390
//
349391
#undef malloc
350392
#undef calloc
@@ -384,20 +426,20 @@ void* IRAM_ATTR _heap_pvPortCalloc(size_t count, size_t size, const char* file,
384426
void* IRAM_ATTR _heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line, const void *caller)
385427
{
386428
INTEGRITY_CHECK__PANIC_FL(file, line, caller);
429+
POISON_CHECK__PANIC_FL(file, line, caller);
387430
ptr = _HEAP_DEBUG_PROBE_PSFLC_CB(heap_realloc_in_cb_id, ptr, size, file, line, caller);
388431
void* ret = UMM_REALLOC_FL(ptr, size, file, line, caller);
389432
ret = _HEAP_DEBUG_PROBE_PSFLC_CB(heap_realloc_out_cb_id, ret, size, file, line, caller);
390-
POISON_CHECK__PANIC_FL(file, line, caller);
391433
OOM_CHECK__LOG_LAST_FAIL_FL(ret, size, file, line, caller);
392434
return ret;
393435
}
394436

395437
void IRAM_ATTR _heap_vPortFree(void *ptr, const char* file, int line, [[maybe_unused]] const void *caller)
396438
{
397439
INTEGRITY_CHECK__PANIC_FL(file, line, caller);
440+
POISON_CHECK__PANIC_FL(file, line, caller);
398441
ptr = _HEAP_DEBUG_PROBE_PSFLC_CB(heap_free_cb_id, ptr, 0, file, line, caller);
399442
UMM_FREE_FL(ptr, file, line, caller);
400-
POISON_CHECK__PANIC_FL(file, line, caller);
401443
}
402444

403445

@@ -499,31 +541,39 @@ void IRAM_ATTR _heap_vPortFree(void *ptr, const char* file, int line, const void
499541
void* _malloc_r(struct _reent* unused, size_t size)
500542
{
501543
(void) unused;
502-
void* ret = _heap_pvPortMalloc(size, NULL, 0, __builtin_return_address(0));
503-
OOM_CHECK__LOG_LAST_FAIL_LITE_FL(ret, size, NULL, 0, __builtin_return_address(0));
544+
void *caller = __builtin_return_address(0);
545+
ISR_CHECK__LOG_NOT_SAFE(caller);
546+
void* ret = _heap_pvPortMalloc(size, NULL, 0, caller);
547+
OOM_CHECK__LOG_LAST_FAIL_LITE_FL(ret, size, NULL, 0, caller);
504548
return ret;
505549
}
506550

507551
void* _calloc_r(struct _reent* unused, size_t count, size_t size)
508552
{
509553
(void) unused;
510-
void* ret = _heap_pvPortCalloc(count, size, NULL, 0, __builtin_return_address(0));
511-
OOM_CHECK__LOG_LAST_FAIL_LITE_FL(ret, size, NULL, 0, __builtin_return_address(0));
554+
void *caller = __builtin_return_address(0);
555+
ISR_CHECK__LOG_NOT_SAFE(caller);
556+
void* ret = _heap_pvPortCalloc(count, size, NULL, 0, caller);
557+
OOM_CHECK__LOG_LAST_FAIL_LITE_FL(ret, size, NULL, 0, caller);
512558
return ret;
513559
}
514560

515561
void* _realloc_r(struct _reent* unused, void* ptr, size_t size)
516562
{
517563
(void) unused;
518-
void* ret = _heap_pvPortRealloc(ptr, size, NULL, 0, __builtin_return_address(0));
519-
OOM_CHECK__LOG_LAST_FAIL_LITE_FL(ret, size, NULL, 0, __builtin_return_address(0));
564+
void *caller = __builtin_return_address(0);
565+
ISR_CHECK__LOG_NOT_SAFE(caller);
566+
void* ret = _heap_pvPortRealloc(ptr, size, NULL, 0, caller);
567+
OOM_CHECK__LOG_LAST_FAIL_LITE_FL(ret, size, NULL, 0, caller);
520568
return ret;
521569
}
522570

523571
void _free_r(struct _reent* unused, void* ptr)
524572
{
525573
(void) unused;
526-
_heap_vPortFree(ptr, NULL, 0, __builtin_return_address(0));
574+
void *caller = __builtin_return_address(0);
575+
ISR_CHECK__LOG_NOT_SAFE(caller);
576+
_heap_vPortFree(ptr, NULL, 0, caller);
527577
}
528578

529579

@@ -602,9 +652,11 @@ void* _heap_abi_malloc(size_t size, bool unhandled, const void* caller)
602652
[[maybe_unused]] const int line = 0;
603653

604654
#ifdef ENABLE_THICK_DEBUG_WRAPPERS
655+
ISR_CHECK__LOG_NOT_SAFE(caller);
605656
INTEGRITY_CHECK__PANIC_FL(file, line, caller);
606657
POISON_CHECK__PANIC_FL(file, line, caller);
607658
void* ret = UMM_MALLOC_FL(size, file, line, caller);
659+
ret = _HEAP_DEBUG_PROBE_PSFLC_CB(heap_abi_malloc_cb_id, ret, size, file, line, caller);
608660
bool ok = OOM_CHECK__LOG_LAST_FAIL_FL(ret, size, file, line, caller);
609661
#else
610662
void* ret = UMM_MALLOC(size);
@@ -639,11 +691,15 @@ void* _heap_abi_malloc(size_t size, bool unhandled, const void* caller)
639691
// These function replace their weak counterparts tagged with _GLIBCXX_WEAK_DEFINITION
640692
void operator delete(void* ptr) noexcept
641693
{
642-
_heap_vPortFree(ptr, NULL, 0, __builtin_return_address(0));
694+
void *caller = __builtin_return_address(0);
695+
ISR_CHECK__LOG_NOT_SAFE(caller);
696+
_heap_vPortFree(ptr, NULL, 0, caller);
643697
}
644698

645699
void operator delete(void* ptr, std::size_t) noexcept
646700
{
647-
_heap_vPortFree(ptr, NULL, 0, __builtin_return_address(0));
701+
void *caller = __builtin_return_address(0);
702+
ISR_CHECK__LOG_NOT_SAFE(caller);
703+
_heap_vPortFree(ptr, NULL, 0, caller);
648704
}
649705
#endif

cores/esp8266/heap_cb.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@
1010
* ets_uart_printf(...)).
1111
*
1212
* Example:
13-
* extern "C" void my_user_bp(const char* f, int l, const void* c) {
14-
* (void)f; (void)l; (void)c;
15-
* __asm__ __volatile__("break 1, 0;" ::: "memory");
13+
* const char watch_module[] PROGMEM STORE_ATTR = __FILE__; // assigned from file to monitor
14+
*
15+
* extern const char watch_module[];
16+
*
17+
* extern "C" void my_user_bp(heap_api_cb_id id, void *ptr, size_t size,
18+
* const char* file, int line, const void *caller) {
19+
* (void)ptr; (void)size; (void)file; (void)line; (void)caller;
20+
*
21+
* if (watch_module == file && id == heap_poison_lite_cb_id) {
22+
* __asm__ __volatile__("break 1, 0;" ::: "memory");
23+
* }
1624
* }
1725
*
1826
* -DHEAP_DEBUG_PROBE_PSFLC_CB='my_user_bp'

cores/esp8266/umm_malloc/umm_local.c

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ static bool check_poison_neighbors(umm_heap_context_t *_context, uint16_t cur) {
8181

8282
return true;
8383
}
84-
#endif
85-
86-
#if defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
8784

8885
/* ------------------------------------------------------------------------ */
8986
#include "heap_cb.h"
@@ -95,7 +92,6 @@ static void *get_unpoisoned_check_neighbors(const void *vptr, const char *file,
9592

9693
ptr -= (sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE);
9794

98-
#if defined(UMM_POISON_CHECK_LITE)
9995
UMM_CRITICAL_DECL(id_poison);
10096
uint16_t c;
10197
bool poison = true;
@@ -106,13 +102,13 @@ static void *get_unpoisoned_check_neighbors(const void *vptr, const char *file,
106102
c = (ptr - (uintptr_t)(&(_context->heap[0]))) / sizeof(umm_block);
107103

108104
UMM_CRITICAL_ENTRY(id_poison);
109-
if (! check_poison_block(&UMM_BLOCK(c))) {
105+
if (!check_poison_block(&UMM_BLOCK(c))) {
110106
DBGLOG_ERROR("Allocation address %p\n", vptr);
111107
size_t size = *(size_t *)ptr;
112108
_HEAP_DEBUG_PROBE_PSFLC_CB(heap_poison_lite_cb_id, (void *)ptr, size, file, line, caller);
113109
poison = false;
114110
} else
115-
if (! check_poison_neighbors(_context, c)) {
111+
if (!check_poison_neighbors(_context, c)) {
116112
DBGLOG_ERROR("This bad block is in a neighbor allocation near: %p\n", vptr);
117113
_HEAP_DEBUG_PROBE_PSFLC_CB(heap_poison_lite_neighbor_cb_id, (void *)ptr, 0, file, line, caller);
118114
poison = false;
@@ -125,21 +121,13 @@ static void *get_unpoisoned_check_neighbors(const void *vptr, const char *file,
125121
}
126122

127123
if (!poison) {
128-
DBGLOG_ERROR("Called from %p\n", caller);
124+
DBGLOG_ERROR("Caller near %p\n", caller);
129125
if (file) {
130126
__panic_func(file, line, "");
131127
} else {
132128
abort();
133129
}
134130
}
135-
#else
136-
/*
137-
* No need to check poison here. POISON_CHECK() has already done a
138-
* full heap check.
139-
*/
140-
(void)file;
141-
(void)line;
142-
#endif
143131
}
144132

145133
return (void *)ptr;

0 commit comments

Comments
 (0)