19
19
* either a "thin wrapper" or a "thick wrapper" to capture Heap debug info and
20
20
* diagnostics.
21
21
*
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 .
25
25
*
26
- * Iventory of debug options supported by this modules
26
+ * Inventory of debug options supported by this modules
27
27
*
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.
35
36
*
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.
40
56
*
41
57
* * UMM_POISON_CHECK_LITE - A much lighter version of UMM_POISON_CHECK.
42
58
* Adds and presets an extra 4 bytes of poison at the beginning and end of
43
59
* each allocation. On each call to free or realloc, test the current
44
60
* 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.
50
63
*
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.
56
78
*
57
79
* As the number of active heap allocations grows, this option will cause
58
80
* increasingly long periods with interrupts disabled, adversely affecting
59
81
* time-critical sketches.
60
82
*
61
83
* Enable via a build option define.
62
84
*
63
- * UMM_POISON_CHECK_LITE is a better alternative.
64
- *
65
85
* * UMM_INTEGRITY_CHECK - will verify that the Heap is semantically correct
66
86
* and that all the block indexes make sense. While it can catch errors
67
87
* quickly, the check runs with interrupts disabled and may affect WiFi
77
97
* rather than general debugging. It will detect heap corruption; however, it
78
98
* offers little aid in determining who did it.
79
99
*
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
+ *
82
104
*/
83
105
84
106
#include < stdlib.h>
@@ -120,10 +142,9 @@ extern "C" {
120
142
#undef STATIC_ALWAYS_INLINE
121
143
#undef ENABLE_THICK_DEBUG_WRAPPERS
122
144
123
- #if defined(UMM_POISON_CHECK) || defined( UMM_POISON_CHECK_LITE)
145
+ #if defined(UMM_POISON_CHECK_LITE)
124
146
/*
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.
127
148
*
128
149
* Support debug wrappers that need to include handling poison
129
150
*/
@@ -136,7 +157,22 @@ extern "C" {
136
157
#undef realloc
137
158
#undef free
138
159
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)
140
176
// All other debug wrappers that do not require handling poison
141
177
#define UMM_MALLOC_FL (s,f,l,c ) umm_malloc(s)
142
178
#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
317
353
#endif
318
354
319
355
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 (" \n In-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
+
320
372
#ifdef ENABLE_THICK_DEBUG_WRAPPERS
321
373
// /////////////////////////////////////////////////////////////////////////////
322
374
// Thick Heap API wrapper for debugging: malloc, pvPortMalloc, "new", and
323
375
// _malloc_r families of heap APIs.
324
376
//
325
377
// While UMM_INTEGRITY_CHECK and UMM_POISON_CHECK are included, the Arduino IDE
326
378
// 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.
331
382
//
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.
334
386
//
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.
348
390
//
349
391
#undef malloc
350
392
#undef calloc
@@ -384,20 +426,20 @@ void* IRAM_ATTR _heap_pvPortCalloc(size_t count, size_t size, const char* file,
384
426
void * IRAM_ATTR _heap_pvPortRealloc (void *ptr, size_t size, const char * file, int line, const void *caller)
385
427
{
386
428
INTEGRITY_CHECK__PANIC_FL (file, line, caller);
429
+ POISON_CHECK__PANIC_FL (file, line, caller);
387
430
ptr = _HEAP_DEBUG_PROBE_PSFLC_CB (heap_realloc_in_cb_id, ptr, size, file, line, caller);
388
431
void * ret = UMM_REALLOC_FL (ptr, size, file, line, caller);
389
432
ret = _HEAP_DEBUG_PROBE_PSFLC_CB (heap_realloc_out_cb_id, ret, size, file, line, caller);
390
- POISON_CHECK__PANIC_FL (file, line, caller);
391
433
OOM_CHECK__LOG_LAST_FAIL_FL (ret, size, file, line, caller);
392
434
return ret;
393
435
}
394
436
395
437
void IRAM_ATTR _heap_vPortFree (void *ptr, const char * file, int line, [[maybe_unused]] const void *caller)
396
438
{
397
439
INTEGRITY_CHECK__PANIC_FL (file, line, caller);
440
+ POISON_CHECK__PANIC_FL (file, line, caller);
398
441
ptr = _HEAP_DEBUG_PROBE_PSFLC_CB (heap_free_cb_id, ptr, 0 , file, line, caller);
399
442
UMM_FREE_FL (ptr, file, line, caller);
400
- POISON_CHECK__PANIC_FL (file, line, caller);
401
443
}
402
444
403
445
@@ -499,31 +541,39 @@ void IRAM_ATTR _heap_vPortFree(void *ptr, const char* file, int line, const void
499
541
void * _malloc_r (struct _reent * unused, size_t size)
500
542
{
501
543
(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);
504
548
return ret;
505
549
}
506
550
507
551
void * _calloc_r (struct _reent * unused, size_t count, size_t size)
508
552
{
509
553
(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);
512
558
return ret;
513
559
}
514
560
515
561
void * _realloc_r (struct _reent * unused, void * ptr, size_t size)
516
562
{
517
563
(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);
520
568
return ret;
521
569
}
522
570
523
571
void _free_r (struct _reent * unused, void * ptr)
524
572
{
525
573
(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);
527
577
}
528
578
529
579
@@ -602,9 +652,11 @@ void* _heap_abi_malloc(size_t size, bool unhandled, const void* caller)
602
652
[[maybe_unused]] const int line = 0 ;
603
653
604
654
#ifdef ENABLE_THICK_DEBUG_WRAPPERS
655
+ ISR_CHECK__LOG_NOT_SAFE (caller);
605
656
INTEGRITY_CHECK__PANIC_FL (file, line, caller);
606
657
POISON_CHECK__PANIC_FL (file, line, caller);
607
658
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);
608
660
bool ok = OOM_CHECK__LOG_LAST_FAIL_FL (ret, size, file, line, caller);
609
661
#else
610
662
void * ret = UMM_MALLOC (size);
@@ -639,11 +691,15 @@ void* _heap_abi_malloc(size_t size, bool unhandled, const void* caller)
639
691
// These function replace their weak counterparts tagged with _GLIBCXX_WEAK_DEFINITION
640
692
void operator delete (void * ptr) noexcept
641
693
{
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);
643
697
}
644
698
645
699
void operator delete (void * ptr, std::size_t ) noexcept
646
700
{
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);
648
704
}
649
705
#endif
0 commit comments