doc: Add docs for heap trace & poisoning

This commit is contained in:
Angus Gratton 2017-07-19 09:07:17 +10:00 committed by Angus Gratton
parent ec498ad86d
commit 72995bfcec
7 changed files with 273 additions and 44 deletions

View file

@ -6,18 +6,8 @@ choice HEAP_CORRUPTION_DETECTION
help
Enable heap poisoning features to detect heap corruption caused by out-of-bounds access to heap memory.
"Basic" heap corruption detection disables poisoning, but in Debug mode an assertion will trigger if an
application overwrites the heap's internal block headers and corrupts the heap structure.
"Light impact" detection "poisons" memory allocated from the heap with 4-byte head and tail "canaries". If an
application overruns its bounds at all, these canaries will be compromised. This option increases memory usage,
each allocated buffer will use an extra 9-12 bytes from the heap.
"Comprehensive" detection incorporates the "light impact" detection features plus additional checks for
uinitialised-access and use-after-free bugs. All freshly allocated memory is set to the pattern 0xce, and all
freed memory is set to the pattern 0xfe. These options have a noticeable additional performance impact.
To check the integrity of all heap memory at runtime, see the function heap_caps_check_integrity().
See the "Heap Memory Debugging" page of the IDF documentation
for a description of each level of heap corruption detection.
config HEAP_POISONING_DISABLED
bool "Basic (no poisoning)"

View file

@ -38,7 +38,7 @@
*
* Equivalent semantics to libc malloc(), for capability-aware memory.
*
* In IDF, malloc(p) is equivalent to heaps_caps_malloc(p, MALLOC_CAP_8BIT);
* In IDF, ``malloc(p)`` is equivalent to ``heaps_caps_malloc(p, MALLOC_CAP_8BIT)``.
*
* @param size Size, in bytes, of the amount of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
@ -53,7 +53,7 @@ void *heap_caps_malloc(size_t size, uint32_t caps);
*
* Equivalent semantics to libc free(), for capability-aware memory.
*
* In IDF, free(p) is equivalent to heap_caps_free(p).
* In IDF, ``free(p)`` is equivalent to ``heap_caps_free(p)``.
*
* @param ptr Pointer to memory previously returned from heap_caps_malloc() or heap_caps_realloc(). Can be NULL.
*/
@ -64,10 +64,10 @@ void heap_caps_free( void *ptr);
*
* Equivalent semantics to libc realloc(), for capability-aware memory.
*
* In IDF, realloc(p, s) is equivalent to heap_caps_realloc(p, s, MALLOC_CAP_8BIT).
* In IDF, ``realloc(p, s)`` is equivalent to ``heap_caps_realloc(p, s, MALLOC_CAP_8BIT)``.
*
* 'caps' parameter can be different to the capabilities that any original 'ptr' was allocated with. In this way,
* realloc can be used to "move" a buffer if necessary to ensure it meets new set of capabilities.
* realloc can be used to "move" a buffer if necessary to ensure it meets a new set of capabilities.
*
* @param ptr Pointer to previously allocated memory, or NULL for a new allocation.
* @param size Size of the new buffer requested, or 0 to free the buffer.
@ -103,8 +103,8 @@ size_t heap_caps_get_free_size( uint32_t caps );
* with the given capabilities.
*
* Note the result may be less than the global all-time minimum available heap of this kind, as "low water marks" are
* tracked per-heap. Individual heaps may have reached their "low water marks" at different points in time. However
* this result still gives a "worst case" indication for all-time free heap.
* tracked per-region. Individual regions' heaps may have reached their "low water marks" at different points in time. However
* this result still gives a "worst case" indication for all-time minimum free heap.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
@ -116,7 +116,7 @@ size_t heap_caps_get_minimum_free_size( uint32_t caps );
/**
* @brief Get the largest free block of memory able to be allocated with the given capabilities.
*
* Returns the largest value of 's' for which heap_caps_malloc(s, caps) will succeed.
* Returns the largest value of ``s`` for which ``heap_caps_malloc(s, caps)`` will succeed.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
@ -131,7 +131,7 @@ size_t heap_caps_get_largest_free_block( uint32_t caps );
*
* Calls multi_heap_info() on all heaps which share the given capabilities. The information returned is an aggregate
* across all matching heaps. The meanings of fields are the same as defined for multi_heap_info_t, except that
* minimum_free_bytes has the same caveats described in heap_caps_get_minimum_free_size().
* ``minimum_free_bytes`` has the same caveats described in heap_caps_get_minimum_free_size().
*
* @param info Pointer to a structure which will be filled with relevant
* heap metadata.
@ -160,7 +160,8 @@ void heap_caps_print_heap_info( uint32_t caps );
* Calls multi_heap_check() on all heaps which share the given capabilities. Optionally
* print errors if the heaps are corrupt.
*
* Pass caps == MALLOC_CAP_INVALID to test all registered heaps.
* Call ``heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors)`` to check
* all regions' heaps.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory

View file

@ -30,6 +30,9 @@ typedef enum {
HEAP_TRACE_LEAKS,
} heap_trace_mode_t;
/**
* @brief Trace record data type. Stores information about an allocated region of memory.
*/
typedef struct {
uint32_t ccount; ///< CCOUNT of the CPU when the allocation was made. LSB (bit value 1) is the CPU number (0 or 1). */
void *address; ///< Address which was allocated
@ -46,7 +49,8 @@ typedef struct {
*
* To disable heap tracing and allow the buffer to be freed, stop tracing and then call heap_trace_init_standalone(NULL, 0);
*
* @param record_buffer Provide a buffer to use for heap trace data. Must remain valid any time heap tracing is enabled.
* @param record_buffer Provide a buffer to use for heap trace data. Must remain valid any time heap tracing is enabled, meaning
* it must be allocated from internal memory not in PSRAM.
* @param num_records Size of the heap trace buffer, as number of record structures.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
@ -62,9 +66,9 @@ esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t
*
* @note Calling this function while heap tracing is running will reset the heap trace state and continue tracing.
*
* @param mode_param Mode for tracing.
* @param mode Mode for tracing.
* - HEAP_TRACE_ALL means all heap allocations and frees are traced.
* - HEAP_TRACE_LEAKS means only suspected memory leaks are traced (freeed memory is removed from the trace buffer.)
* - HEAP_TRACE_LEAKS means only suspected memory leaks are traced. (When memory is freed, the record is removed from the trace buffer.)
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE A non-zero-length buffer has not been set via heap_trace_init_standalone().
@ -86,7 +90,7 @@ esp_err_t heap_trace_stop(void);
* @brief Resume heap tracing which was previously stopped.
*
* Unlike heap_trace_start(), this function does not clear the
* buffer of any pre-existing trace data.
* buffer of any pre-existing trace records.
*
* The heap trace mode is the same as when heap_trace_start() was
* last called (or HEAP_TRACE_ALL if heap_trace_start() was never called).
@ -108,11 +112,11 @@ size_t heap_trace_get_count(void);
/**
* @brief Return a raw record from the heap trace buffer
*
* It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode record indexing may
* @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode record indexing may
* skip entries unless heap tracing is stopped first.
*
* @param index Index (zero-based) of the record to return.
* @param[out] record Record where the heap trace data will be copied.
* @param[out] record Record where the heap trace record will be copied.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not initialised.
@ -122,13 +126,11 @@ size_t heap_trace_get_count(void);
esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record);
/**
* @brief Dump heap trace data to stdout
* @brief Dump heap trace record data to stdout
*
* It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode the dump may skip
* @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode the dump may skip
* entries unless heap tracing is stopped first.
*
* Record data is dumped in summary form. Use a tool like :doc:`idf_monitor </get-started/idf-monitor>` to decode stack
* trace addresses to the line of code they represent.
*
*/
void heap_trace_dump(void);

View file

@ -109,6 +109,7 @@ INPUT = \
##
## Memory Allocation #
../components/heap/include/esp_heap_caps.h \
../components/heap/include/esp_heap_trace.h \
../components/heap/include/esp_heap_caps_init.h \
../components/heap/include/multi_heap.h \
## Interrupt Allocation

View file

@ -0,0 +1,200 @@
Heap Memory Debugging
=====================
Overview
--------
ESP-IDF integrates tools for requesting `heap information`_, `detecting heap corruption <heap corruption detection>`_, and `tracing memory leaks <heap tracing>`_. These can help track down memory-related bugs.
For general information about the heap memory allocator, see the :doc:`Heap Memory Allocation </api-reference/system/mem_alloc>` page.
.. _heap-information:
Heap Information
----------------
To obtain information about the state of the heap:
- ``xPortGetFreeHeapSize()`` is a FreeRTOS function which returns the number of free bytes in the (data memory) heap. This is equivalent to ``heap_caps_get_free_size(MALLOC_CAP_8BIT)``.
- ``heap_caps_get_free_size()`` can also be used to return the current free memory for different memory capabilities.
- ``heap_caps_get_largest_free_block()`` can be used to return the largest free block in the heap. This is the largest single allocation which is currently possible. Tracking this value and comparing to total free heap allows you to detect heap fragmentation.
- ``xPortGetMinimumEverFreeHeapSize()`` and the related ``heap_caps_get_minimum_free_size()`` can be used to track the heap "low water mark" since boot.
- ``heap_caps_get_info`` returns a ``multi_heap_info_t`` structure which contains the information from the above functions, plus some additional heap-specific data (number of allocations, etc.)
.. _heap-corruption:
Heap Corruption Detection
-------------------------
Heap corruption detection allows you to detect various types of heap memory errors:
- Out of bounds writes & buffer overflow.
- Writes to freed memory.
- Reads from freed or uninitialized memory,
Assertions
^^^^^^^^^^
The heap implementation (``multi_heap.c``, etc.) includes a lot of assertions which will fail if the heap memory is corrupted. To detect heap corruption most effectively, ensure that assertions are enabled in ``make menuconfig`` under ``Compiler options``.
It's also possible to manually check heap integrity by calling the ``heap_caps_check_integrity()`` function (see below). This function checks all of requested heap memory for integrity, and can be used even if assertions are disabled.
Configuration
^^^^^^^^^^^^^
In ``make menuconfig``, under ``Component config`` there is a menu ``Heap memory debugging``. The setting ``Heap corruption detection`` can be set to one of three levels:
Basic (no poisoning)
^^^^^^^^^^^^^^^^^^^^
This is the default level. No special heap corruption features are enabled, but checks will fail if any of the heap's internal data structures are overwritten or corrupted. This usually indicates a buffer overrun or out of bounds write.
If assertions are enabled, an assertion will also trigger if a double-free occurs (ie the same memory is freed twice).
Light impact
^^^^^^^^^^^^
At this level, heap memory is additionally "poisoned" with head and tail "canary bytes" before and after each block which is allocated. If an application writes outside the bounds of the allocated buffer, the canary bytes will be corrupted and the integrity check will fail.
"Basic" heap corruption checks can also detect out of bounds writes, but this setting is more precise as even a single byte overrun will always be detected. With Basic heap checks, the number of overrun bytes before a failure is detected will depend on the properties of the heap.
Similar to other heap checks, these "canary bytes" are checked via assertion whenever memory is freed and can also be checked manually via ``heap_caps_check_integrity()``.
This level increases memory usage, each individual allocation will use 9 to 12 additional bytes of memory (depending on alignment).
Comprehensive
^^^^^^^^^^^^^
This level incorporates the "light impact" detection features plus additional checks for uninitialised-access and use-after-free bugs. In this mode, all freshly allocated memory is filled with the pattern 0xCE, and all freed memory is filled with the pattern 0xFE.
If an application crashes reading/writing an address related to 0xCECECECE when this setting is enabled, this indicates it has read uninitialized memory. The application should be changed to either use calloc() (which zeroes memory), or initialize the memory before using it. The value 0xCECECECE may also be seen in stack-allocated automatic variables, because in IDF most task stacks are originally allocated from the heap and in C stack memory is uninitialized by default.
If an application crashes reading/writing an address related to 0xFEFEFEFE, this indicates it is reading heap memory after it has been freed (a "use after free bug".) The application should be changed to not access heap memory after it has been freed.
If the IDF heap allocator fails because the pattern 0xFEFEFEFE was not found in freed memory then this indicates the app has a use-after-free bug where it is writing to memory which has already been freed.
Enabling "Comprehensive" detection has a substantial runtime performance impact (as all memory needs to be set to the allocation patterns each time a malloc/free completes, and the memory also needs to be checked each time.)
Finding Heap Corruption
^^^^^^^^^^^^^^^^^^^^^^^
Memory corruption can be one of the hardest classes of bugs to find and fix, as one area of memory can be corrupted from a totally different place. Some tips:
- If you can find the address (in memory) which is being corrupted, you can set a watchpoint on this address via JTAG to have the CPU halt when it is written to.
- If you don't have JTAG, but you do know roughly when the corruption happens, then you can set a watchpoint in software. A fatal exception will occur when the watchpoint triggers. For example ``esp_set_watchpoint(0, (void *)addr, 4, ESP_WATCHPOINT_STORE``. Note that the watchpoint is set on the current running CPU only, so if you don't know which CPU is corrupting memory then you will need to call this function on both CPUs.
- For buffer overflows, `heap tracing`_ in ``HEAP_TRACE_ALL`` mode lets you see which callers allocate the memory address(es) immediately before the address which is being corrupted. There is a strong chance this is the code which overflows the buffer.
.. _heap-tracing:
Heap Tracing
------------
Heap Tracing allows tracing of code which allocates/frees memory.
Note: Heap tracing "standalone" mode is currently implemented, meaning that tracing does not require any external hardware but uses internal memory to hold trace data. Heap tracing via JTAG trace port is also planned.
Heap tracing can perform two functions:
- Leak checking: find memory which is allocated and never freed.
- Heap use analysis: show all functions that are allocating/freeing memory while the trace is running.
How To Diagnose Memory Leaks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you suspect a memory leak, the first step is to figure out which part of the program is leaking memory. Use the ``xPortGetFreeHeapSize()``, ``heap_caps_get_free()``, and related functions to track memory use over the life of the application. Try to narrow the leak down to a single function or sequence of functions where free memory always decreases and never recovers.
Once you've identified the code which you think is leaking:
- Under ``make menuconfig``, navigate to ``Component settings`` -> ``Heap Memory Debugging`` and set ``Enable heap tracing``.
- Call the function ``heap_trace_init_standalone()`` early in the program, to register a buffer which can be used to record the memory trace.
- Call the function ``heap_trace_start()`` to begin recording all mallocs/frees in the system. Call this immediately before the piece of code which you suspect is leaking memory.
- Call the function ``heap_trace_stop()`` to stop the trace once the suspect piece of code has finished executing.
- Call the function ``heap_trace_dump()`` to dump the results of the heap trace.
An example::
#include "esp_heap_trace.h"
#define NUM_RECORDS 100
static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM
...
void app_main()
{
...
ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
...
}
void some_function()
{
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
do_something_you_suspect_is_leaking();
ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
...
}
The output from the heap trace will look something like this::
2 allocations trace (100 entry buffer)
32 bytes (@ 0x3ffaf214) allocated CPU 0 ccount 0x2e9b7384 caller 0x400d276d:0x400d27c1
0x400d276d: leak_some_memory at /path/to/idf/examples/get-started/blink/main/./blink.c:27
0x400d27c1: blink_task at /path/to/idf/examples/get-started/blink/main/./blink.c:52
8 bytes (@ 0x3ffaf804) allocated CPU 0 ccount 0x2e9b79c0 caller 0x400d2776:0x400d27c1
0x400d2776: leak_some_memory at /path/to/idf/examples/get-started/blink/main/./blink.c:29
0x400d27c1: blink_task at /path/to/idf/examples/get-started/blink/main/./blink.c:52
40 bytes 'leaked' in trace (2 allocations)
total allocations 2 total frees 0
(Above example output is using :doc:`IDF Monitor </get-started/idf-monitor>` to automatically decode PC addresses to their source files & line number.)
The first line indicates how many allocation entries are in the buffer, compared to its total size.
In ``HEAP_TRACE_LEAKS`` mode, for each traced memory allocation which has not already been freed a line is printed with:
- ``XX bytes`` is number of bytes allocated
- ``@ 0x...`` is the heap address returned from malloc/calloc.
- ``CPU x`` is the CPU (0 or 1) running when the allocation was made.
- ``ccount 0x...`` is the CCOUNT (CPU cycle count) register value when the allocation was mode. Is different for CPU 0 vs CPU 1.
- ``caller 0x...`` gives the call stack of the call to malloc()/free(), as a list of PC addresses.
These can be decoded to source files and line numbers, as shown above.
The depth of the call stack recorded for each trace entry can be configured in ``make menuconfig``, under ``Heap Memory Debugging`` -> ``Enable heap tracing`` -> ``Heap tracing stack depth``. Up to 10 stack frames can be recorded for each allocation (the default is 2). Each additional stack frame increases the memory usage of each ``heap_trace_record_t`` record by eight bytes.
Finally, the total number of 'leaked' bytes (bytes allocated but not freed while trace was running) is printed, and the total number of allocations this represents.
A warning will be printed if the trace buffer was not large enough to hold all the allocations which happened. If you see this warning, consider either shortening the tracing period or increasing the number of records in the trace buffer.
Performance Impact
^^^^^^^^^^^^^^^^^^
Enabling heap tracing in menuconfig increases the code size of your program, and has a very small negative impact on performance of heap allocation/free operations even when heap tracing is not running.
When heap tracing is running, heap allocation/free operations are substantially slower than when heap tracing is stopped. Increasing the depth of stack frames recorded for each allocation (see above) will also increase this performance impact.
False-Positive Memory Leaks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Not everything printed by ``heap_trace_dump()`` is necessarily a memory leak. Among things which may show up here, but are not memory leaks:
- Any memory which is allocated after ``heap_trace_start()`` but then freed after ``heap_trace_stop()`` will appear in the leak dump.
- Allocations may be made by other tasks in the system. Depending on the timing of these tasks, it's quite possible this memory is freed after ``heap_trace_stop()`` is called.
- The first time a task uses stdio - for example, when it calls printf() - a lock (RTOS mutex semaphore) is allocated by the libc. This allocation lasts until the task is deleted.
- The Bluetooth, WiFi, and TCP/IP libraries will allocate heap memory buffers to handle incoming or outgoing data. These memory buffers are usually short lived, but some may be shown in the heap leak trace if the data was received/transmitted by the lower levels of the network while the leak trace was running.
- TCP connections will continue to use some memory after they are closed, because of the ``TIME_WAIT`` state. After the ``TIME_WAIT`` period has completed, this memory will be freed.
One way to differentiate between "real" and "false positive" memory leaks is to call the suspect code multiple times while tracing is running, and look for patterns (multiple matching allocations) in the heap trace output.
API Reference - Heap Tracing
----------------------------
.. include:: /_build/inc/esp_heap_trace.inc

View file

@ -4,7 +4,8 @@ System API
.. toctree::
:maxdepth: 1
Memory Allocation <mem_alloc>
Heap Memory Allocation <mem_alloc>
Heap Memory Debugging <heap_debug>
Interrupt Allocation <intr_alloc>
Watchdogs <wdts>
Over The Air Updates (OTA) <ota>

View file

@ -1,43 +1,77 @@
Memory allocation
=================
Heap Memory Allocation
======================
Overview
--------
The ESP32 has multiple types of RAM. Internally, there's IRAM, DRAM as well as RAM that can be used as both. It's also
possible to connect external SPI flash to the ESP32; it's memory can be integrated into the ESP32s memory map using
possible to connect external SPI RAM to the ESP32 - external RAM can be integrated into the ESP32's memory map using
the flash cache.
In order to make use of all this memory, esp-idf has a capabilities-based memory allocator. Basically, if you want to have
memory with certain properties (for example, DMA-capable, or capable of executing code), you
can create an OR-mask of the required capabilities and pass that to pvPortMallocCaps. For instance, the normal malloc
code internally allocates memory with ```heap_caps_malloc(size, MALLOC_CAP_8BIT)``` in order to get data memory that is
byte-addressable.
For most purposes, the standard libc ``malloc()`` and ``free()`` functions can be used for heap allocation without any
issues.
Because malloc uses this allocation system as well, memory allocated using ```heap_caps_malloc()``` can be freed by calling
the standard ```free()``` function.
However, in order to fully make use of all of the memory types and their characteristics, esp-idf also has a
capabilities-based heap memory allocator. If you want to have memory with certain properties (for example, DMA-capable
memory, or executable memory), you can create an OR-mask of the required capabilities and pass that to
``heap_caps_malloc()``. For instance, the standard ``malloc()`` implementation internally allocates memory via
``heap_caps_malloc(size, MALLOC_CAP_8BIT)`` in order to get data memory that is byte-addressable.
Because malloc uses this allocation system as well, memory allocated using ``heap_caps_malloc()`` can be freed by calling
the standard ``free()`` function.
The "soc" component contains a list of memory regions for the chip, along with the type of each memory (aka its tag) and the associated capabilities for that memory type. On startup, a separate heap is initialised for each contiguous memory region. The capabilities-based allocator chooses the best heap for each allocation, based on the requested capabilities.
Special Uses
------------
DMA-Capable Memory
^^^^^^^^^^^^^^^^^^
Use the MALLOC_CAP_DMA flag to allocate memory which is suitable for use with hardware DMA engines (for example SPI and I2S). This capability flag excludes any external PSRAM.
32-Bit Accessible Memory
^^^^^^^^^^^^^^^^^^^^^^^^
If a certain memory structure is only addressed in 32-bit units, for example an array of ints or pointers, it can be
useful to allocate it with the MALLOC_CAP_32BIT flag. This also allows the allocator to give out IRAM memory; something
which it can't do for a normal malloc() call. This can help to use all the available memory in the ESP32.
Memory allocated with MALLOC_CAP_32BIT can *only* be accessed via 32-bit reads and writes, any other type of access will
generate a fatal LoadStoreError exception.
API Reference - Heap Allocation
-------------------------------
.. include:: /_build/inc/esp_heap_caps.inc
Heap Tracing & Debugging
------------------------
The following features are documented on the :doc:`Heap Memory Debugging </api-reference/system/heap_debug>` page:
- :ref:`Heap Information <heap-information>` (free space, etc.)
- :ref:`Heap Corruption Detection <heap-corruption>`
- :ref:`Heap Tracing <heap-tracing>` (memory leak detection, monitoring, etc.)
API Reference - Initialisation
------------------------------
.. include:: /_build/inc/esp_heap_caps_init.inc
API Reference - Heap Regions
----------------------------
Implementation Notes
--------------------
Knowledge about the regions of memory in the chip comes from the "soc" component, which contains memory layout information for the chip.
Each contiguous region of memory contains its own memory heap. The heaps are created using the `multi_heap <API Reference - Multi Heap API>`_ functionality. multi_heap allows any contiguous region of memory to be used as a heap.
The heap capabilities allocator uses knowledge of the memory regions to initialize each individual heap. When you call a function in the heap capabilities API, it will find the most appropriate heap for the allocation (based on desired capabilities, available space, and preferences for each region's use) and then call the multi_heap function to use the heap situation in that particular region.
API Reference - Multi Heap API
------------------------------
(Note: The multi heap API is used internally by the heap capabilities allocator. Most IDF programs will never need to call this API directly.)
.. include:: /_build/inc/multi_heap.inc