mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/heap-alloc-free-callbacks' into 'master'
heap: Add allocation and free function hooks. Closes IDFGH-9424 See merge request espressif/esp-idf!22456
This commit is contained in:
commit
55fd7d43c1
@ -56,6 +56,11 @@ menu "Heap memory debugging"
|
||||
More stack frames uses more memory in the heap trace buffer (and slows down allocation), but
|
||||
can provide useful information.
|
||||
|
||||
config HEAP_USE_HOOKS
|
||||
bool "Use allocation and free hooks"
|
||||
help
|
||||
Enable the user to implement function hooks triggered for each successful allocation and free.
|
||||
|
||||
config HEAP_TASK_TRACKING
|
||||
bool "Enable heap task tracking"
|
||||
depends on !HEAP_POISONING_DISABLED
|
||||
|
@ -15,6 +15,16 @@
|
||||
#include "heap_private.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
#ifdef CONFIG_HEAP_USE_HOOKS
|
||||
#define CALL_HOOK(hook, ...) { \
|
||||
if (hook != NULL) { \
|
||||
hook(__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define CALL_HOOK(hook, ...) {}
|
||||
#endif
|
||||
|
||||
/* Forward declaration for base function, put in IRAM.
|
||||
* These functions don't check for errors after trying to allocate memory. */
|
||||
static void *heap_caps_realloc_base( void *ptr, size_t size, uint32_t caps );
|
||||
@ -32,7 +42,6 @@ possible. This should optimize the amount of RAM accessible to the code without
|
||||
|
||||
static esp_alloc_failed_hook_t alloc_failed_callback;
|
||||
|
||||
|
||||
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
|
||||
IRAM_ATTR static void hex_to_str(char buf[8], uint32_t n)
|
||||
{
|
||||
@ -113,11 +122,7 @@ IRAM_ATTR static void *heap_caps_malloc_base( size_t size, uint32_t caps)
|
||||
{
|
||||
void *ret = NULL;
|
||||
|
||||
if (size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (size > HEAP_SIZE_MAX) {
|
||||
if (size == 0 || size > HEAP_SIZE_MAX ) {
|
||||
// Avoids int overflow when adding small numbers to size, or
|
||||
// calculating 'end' from start+size, by limiting 'size' to the possible range
|
||||
return NULL;
|
||||
@ -162,12 +167,15 @@ IRAM_ATTR static void *heap_caps_malloc_base( size_t size, uint32_t caps)
|
||||
ret = multi_heap_malloc(heap->heap, size + 4); // int overflow checked above
|
||||
|
||||
if (ret != NULL) {
|
||||
return dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above
|
||||
uint32_t *iptr = dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, iptr, size, caps);
|
||||
return iptr;
|
||||
}
|
||||
} else {
|
||||
//Just try to alloc, nothing special.
|
||||
ret = multi_heap_malloc(heap->heap, size);
|
||||
if (ret != NULL) {
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@ -188,6 +196,7 @@ IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps){
|
||||
|
||||
void* ptr = heap_caps_malloc_base(size, caps);
|
||||
|
||||
|
||||
if (!ptr && size > 0){
|
||||
heap_caps_alloc_failed(size, caps, __func__);
|
||||
}
|
||||
@ -286,6 +295,7 @@ IRAM_ATTR void *heap_caps_malloc_prefer( size_t size, size_t num, ... )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (r == NULL && size > 0){
|
||||
heap_caps_alloc_failed(size, caps, __func__);
|
||||
}
|
||||
@ -309,6 +319,7 @@ IRAM_ATTR void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ..
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (r == NULL && size > 0){
|
||||
heap_caps_alloc_failed(size, caps, __func__);
|
||||
}
|
||||
@ -332,6 +343,7 @@ IRAM_ATTR void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (r == NULL && size > 0){
|
||||
heap_caps_alloc_failed(size, caps, __func__);
|
||||
}
|
||||
@ -374,6 +386,8 @@ IRAM_ATTR void heap_caps_free( void *ptr)
|
||||
heap_t *heap = find_containing_heap(ptr);
|
||||
assert(heap != NULL && "free() target pointer is outside heap areas");
|
||||
multi_heap_free(heap->heap, ptr);
|
||||
|
||||
CALL_HOOK(esp_heap_trace_free_hook, ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -427,6 +441,7 @@ IRAM_ATTR static void *heap_caps_realloc_base( void *ptr, size_t size, uint32_t
|
||||
// (which will resize the block if it can)
|
||||
void *r = multi_heap_realloc(heap->heap, ptr, size);
|
||||
if (r != NULL) {
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, r, size, caps);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@ -458,6 +473,7 @@ IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps)
|
||||
{
|
||||
ptr = heap_caps_realloc_base(ptr, size, caps);
|
||||
|
||||
|
||||
if (ptr == NULL && size > 0){
|
||||
heap_caps_alloc_failed(size, caps, __func__);
|
||||
}
|
||||
@ -489,6 +505,7 @@ IRAM_ATTR void *heap_caps_calloc( size_t n, size_t size, uint32_t caps)
|
||||
{
|
||||
void* ptr = heap_caps_calloc_base(n, size, caps);
|
||||
|
||||
|
||||
if (!ptr && size > 0){
|
||||
heap_caps_alloc_failed(size, caps, __func__);
|
||||
}
|
||||
@ -677,6 +694,7 @@ IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t
|
||||
//Just try to alloc, nothing special.
|
||||
ret = multi_heap_aligned_alloc(heap->heap, size, alignment);
|
||||
if (ret != NULL) {
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "multi_heap.h"
|
||||
#include <sdkconfig.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -53,6 +54,26 @@ typedef void (*esp_alloc_failed_hook_t) (size_t size, uint32_t caps, const char
|
||||
*/
|
||||
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback);
|
||||
|
||||
#ifdef CONFIG_HEAP_USE_HOOKS
|
||||
/**
|
||||
* @brief callback called after every allocation
|
||||
* @param ptr the allocated memory
|
||||
* @param size in bytes of the allocation
|
||||
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type of memory allocated.
|
||||
* @note this hook is called on the same thread as the allocation, which may be within a low level operation.
|
||||
* You should refrain from doing heavy work, logging, flash writes, or any locking.
|
||||
*/
|
||||
__attribute__((weak)) IRAM_ATTR void esp_heap_trace_alloc_hook(void* ptr, size_t size, uint32_t caps);
|
||||
|
||||
/**
|
||||
* @brief callback called after every free
|
||||
* @param ptr the memory that was freed
|
||||
* @note this hook is called on the same thread as the allocation, which may be within a low level operation.
|
||||
* You should refrain from doing heavy work, logging, flash writes, or any locking.
|
||||
*/
|
||||
__attribute__((weak)) IRAM_ATTR void esp_heap_trace_free_hook(void* ptr);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Allocate a chunk of memory which has the given capabilities
|
||||
*
|
||||
|
@ -370,7 +370,7 @@ multi_heap_handle_t multi_heap_register(void *start, size_t size)
|
||||
return multi_heap_register_impl(start, size);
|
||||
}
|
||||
|
||||
static inline void subtract_poison_overhead(size_t *arg) {
|
||||
static inline __attribute__((always_inline)) void subtract_poison_overhead(size_t *arg) {
|
||||
if (*arg > POISON_OVERHEAD) {
|
||||
*arg -= POISON_OVERHEAD;
|
||||
} else {
|
||||
|
@ -162,22 +162,90 @@ TEST_CASE("malloc/calloc(0) should not call failure callback", "[heap]")
|
||||
|
||||
TEST_CASE("test get allocated size", "[heap]")
|
||||
{
|
||||
const size_t iterations = 32;
|
||||
// random values to test, some are 4 bytes aligned, some are not
|
||||
const size_t alloc_sizes[] = { 1035, 1064, 1541 };
|
||||
const size_t iterations = sizeof(alloc_sizes) / sizeof(size_t);
|
||||
void *ptr_array[iterations];
|
||||
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
// minimum block size is 12, so to avoid unecessary logic in the test,
|
||||
// set the minimum requested size to 12.
|
||||
const size_t alloc_size = rand() % 1024 + 12;
|
||||
|
||||
void *ptr = heap_caps_malloc(alloc_size, MALLOC_CAP_DEFAULT);
|
||||
TEST_ASSERT_NOT_NULL(ptr);
|
||||
ptr_array[i] = heap_caps_malloc(alloc_sizes[i], MALLOC_CAP_DEFAULT);
|
||||
TEST_ASSERT_NOT_NULL(ptr_array[i]);
|
||||
|
||||
// test that the heap_caps_get_allocated_size() returns the right number of bytes (aligned to 4 bytes
|
||||
// since the heap component aligns to 4 bytes)
|
||||
const size_t aligned_size = (alloc_size + 3) & ~3;
|
||||
printf("initial size: %d, requested size : %d, allocated size: %d\n", alloc_size, aligned_size, heap_caps_get_allocated_size(ptr));
|
||||
TEST_ASSERT_EQUAL(aligned_size, heap_caps_get_allocated_size(ptr));
|
||||
const size_t aligned_size = (alloc_sizes[i] + 3) & ~3;
|
||||
const size_t real_size = heap_caps_get_allocated_size(ptr_array[i]);
|
||||
printf("initial size: %d, requested size : %d, allocated size: %d\n", alloc_sizes[i], aligned_size, real_size);
|
||||
TEST_ASSERT_EQUAL(aligned_size, real_size);
|
||||
|
||||
heap_caps_free(ptr);
|
||||
heap_caps_free(ptr_array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HEAP_USE_HOOKS
|
||||
// provide the definition of alloc and free hooks
|
||||
static const size_t alloc_size = 1234; // make this size atypical to be able to rely on it in the hook
|
||||
static const size_t expected_calls = 2; // one call for malloc/calloc and one call for realloc
|
||||
static uint32_t *alloc_ptr = NULL;
|
||||
static bool test_success = false;
|
||||
static size_t counter = 0;
|
||||
|
||||
static void reset_static_variables(void) {
|
||||
test_success = false;
|
||||
alloc_ptr = NULL;
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
void esp_heap_trace_alloc_hook(void* ptr, size_t size, uint32_t caps)
|
||||
{
|
||||
if (size == alloc_size) {
|
||||
counter++;
|
||||
if (counter == expected_calls) {
|
||||
alloc_ptr = ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void esp_heap_trace_free_hook(void* ptr)
|
||||
{
|
||||
if (alloc_ptr == ptr && counter == expected_calls) {
|
||||
test_success = true;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("test allocation and free function hooks", "[heap]")
|
||||
{
|
||||
// alloc, realloc and free memory, at the end of the test, test_success will be set
|
||||
// to true if both function hooks are called.
|
||||
uint32_t *ptr = heap_caps_malloc(alloc_size, MALLOC_CAP_DEFAULT);
|
||||
TEST_ASSERT_NOT_NULL(ptr);
|
||||
ptr = heap_caps_realloc(ptr, alloc_size, MALLOC_CAP_32BIT);
|
||||
heap_caps_free(ptr);
|
||||
|
||||
TEST_ASSERT_TRUE(test_success);
|
||||
|
||||
// re-init the static variables
|
||||
reset_static_variables();
|
||||
|
||||
// calloc, realloc and free memory, at the end of the test, test_success will be set
|
||||
// to true if both function hooks are called.
|
||||
ptr = heap_caps_calloc(1, alloc_size, MALLOC_CAP_DEFAULT);
|
||||
TEST_ASSERT_NOT_NULL(ptr);
|
||||
ptr = heap_caps_realloc(ptr, alloc_size, MALLOC_CAP_32BIT);
|
||||
heap_caps_free(ptr);
|
||||
|
||||
TEST_ASSERT_TRUE(test_success);
|
||||
|
||||
// re-init the static variables
|
||||
reset_static_variables();
|
||||
|
||||
// aligned alloc, realloc and aligned free memory, at the end of the test, test_success
|
||||
// will be set to true if both function hooks are called.
|
||||
ptr = heap_caps_aligned_alloc(0x200, alloc_size, MALLOC_CAP_DEFAULT);
|
||||
TEST_ASSERT_NOT_NULL(ptr);
|
||||
ptr = heap_caps_realloc(ptr, alloc_size, MALLOC_CAP_32BIT);
|
||||
heap_caps_free(ptr);
|
||||
|
||||
TEST_ASSERT_TRUE(test_success);
|
||||
}
|
||||
#endif
|
||||
|
@ -102,3 +102,17 @@ def test_memory_protection(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('[heap][mem_prot]')
|
||||
dut.expect_unity_test_output(timeout=300)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'func_hooks'
|
||||
]
|
||||
)
|
||||
def test_heap_func_hooks(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('"test allocation and free function hooks"')
|
||||
dut.expect_unity_test_output(timeout=300)
|
||||
|
1
components/heap/test_apps/sdkconfig.ci.func_hooks
Normal file
1
components/heap/test_apps/sdkconfig.ci.func_hooks
Normal file
@ -0,0 +1 @@
|
||||
CONFIG_HEAP_USE_HOOKS=y
|
@ -24,6 +24,19 @@ To obtain information about the state of the heap:
|
||||
- :cpp:func:`heap_caps_dump` and :cpp:func:`heap_caps_dump_all` will output detailed information about the structure of each block in the heap. Note that this can be large amount of output.
|
||||
|
||||
|
||||
.. _heap-allocation-free:
|
||||
|
||||
Heap allocation and free function hooks
|
||||
---------------------------------------
|
||||
|
||||
Heap allocation and free detection hooks allows you to be notified of every successful allocation and free operations:
|
||||
- Providing a definition of :cpp:func:`esp_heap_trace_alloc_hook` will allow you to be notified of every successful memory allocation operations
|
||||
- Providing a definition of :cpp:func:`esp_heap_trace_free_hook` will allow you to be notified of every memory free operations
|
||||
|
||||
To activate the feature, navigate to ``Component config`` -> ``Heap Memory Debugging`` in the configuration menu and select ``Use allocation and free hooks`` option (see :ref:`CONFIG_HEAP_USE_HOOKS`).
|
||||
:cpp:func:`esp_heap_trace_alloc_hook` and :cpp:func:`esp_heap_trace_free_hook` have weak declarations, it is not necessary to provide a declarations for both hooks.
|
||||
Since allocating and freeing memory is allowed even though strongly recommended against, :cpp:func:`esp_heap_trace_alloc_hook` and :cpp:func:`esp_heap_trace_free_hook` can potentially be called from ISR.
|
||||
|
||||
.. _heap-corruption:
|
||||
|
||||
Heap Corruption Detection
|
||||
|
@ -158,6 +158,7 @@ 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 allocation and free function hooks <heap-allocation-free>`
|
||||
- :ref:`Heap Corruption Detection <heap-corruption>`
|
||||
- :ref:`Heap Tracing <heap-tracing>` (memory leak detection, monitoring, etc.)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user