瀏覽代碼

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
Mahavir Jain 2 年之前
父節點
當前提交
55fd7d43c1

+ 5 - 0
components/heap/Kconfig

@@ -56,6 +56,11 @@ menu "Heap memory debugging"
             More stack frames uses more memory in the heap trace buffer (and slows down allocation), but
             More stack frames uses more memory in the heap trace buffer (and slows down allocation), but
             can provide useful information.
             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
     config HEAP_TASK_TRACKING
         bool "Enable heap task tracking"
         bool "Enable heap task tracking"
         depends on !HEAP_POISONING_DISABLED
         depends on !HEAP_POISONING_DISABLED

+ 25 - 7
components/heap/heap_caps.c

@@ -15,6 +15,16 @@
 #include "heap_private.h"
 #include "heap_private.h"
 #include "esp_system.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.
 /* Forward declaration for base function, put in IRAM.
  * These functions don't check for errors after trying to allocate memory. */
  * 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 );
 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;
 static esp_alloc_failed_hook_t alloc_failed_callback;
 
 
-
 #ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
 #ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
 IRAM_ATTR static void hex_to_str(char buf[8], uint32_t n)
 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;
     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
         // Avoids int overflow when adding small numbers to size, or
         // calculating 'end' from start+size, by limiting 'size' to the possible range
         // calculating 'end' from start+size, by limiting 'size' to the possible range
         return NULL;
         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
                         ret = multi_heap_malloc(heap->heap, size + 4);  // int overflow checked above
 
 
                         if (ret != NULL) {
                         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 {
                     } else {
                         //Just try to alloc, nothing special.
                         //Just try to alloc, nothing special.
                         ret = multi_heap_malloc(heap->heap, size);
                         ret = multi_heap_malloc(heap->heap, size);
                         if (ret != NULL) {
                         if (ret != NULL) {
+                            CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
                             return ret;
                             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);
     void* ptr = heap_caps_malloc_base(size, caps);
 
 
+
     if (!ptr && size > 0){
     if (!ptr && size > 0){
         heap_caps_alloc_failed(size, caps, __func__);
         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;
             break;
         }
         }
     }
     }
+
     if (r == NULL && size > 0){
     if (r == NULL && size > 0){
         heap_caps_alloc_failed(size, caps, __func__);
         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;
             break;
         }
         }
     }
     }
+
     if (r == NULL && size > 0){
     if (r == NULL && size > 0){
         heap_caps_alloc_failed(size, caps, __func__);
         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;
             break;
         }
         }
     }
     }
+
     if (r == NULL && size > 0){
     if (r == NULL && size > 0){
         heap_caps_alloc_failed(size, caps, __func__);
         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);
     heap_t *heap = find_containing_heap(ptr);
     assert(heap != NULL && "free() target pointer is outside heap areas");
     assert(heap != NULL && "free() target pointer is outside heap areas");
     multi_heap_free(heap->heap, ptr);
     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)
         // (which will resize the block if it can)
         void *r = multi_heap_realloc(heap->heap, ptr, size);
         void *r = multi_heap_realloc(heap->heap, ptr, size);
         if (r != NULL) {
         if (r != NULL) {
+            CALL_HOOK(esp_heap_trace_alloc_hook, r, size, caps);
             return r;
             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);
     ptr = heap_caps_realloc_base(ptr, size, caps);
 
 
+
     if (ptr == NULL && size > 0){
     if (ptr == NULL && size > 0){
         heap_caps_alloc_failed(size, caps, __func__);
         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);
     void* ptr = heap_caps_calloc_base(n, size, caps);
 
 
+
     if (!ptr && size > 0){
     if (!ptr && size > 0){
         heap_caps_alloc_failed(size, caps, __func__);
         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.
                     //Just try to alloc, nothing special.
                     ret = multi_heap_aligned_alloc(heap->heap, size, alignment);
                     ret = multi_heap_aligned_alloc(heap->heap, size, alignment);
                     if (ret != NULL) {
                     if (ret != NULL) {
+                        CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
                         return ret;
                         return ret;
                     }
                     }
                 }
                 }

+ 21 - 0
components/heap/include/esp_heap_caps.h

@@ -11,6 +11,7 @@
 #include "multi_heap.h"
 #include "multi_heap.h"
 #include <sdkconfig.h>
 #include <sdkconfig.h>
 #include "esp_err.h"
 #include "esp_err.h"
+#include "esp_attr.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 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);
 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
  * @brief Allocate a chunk of memory which has the given capabilities
  *
  *

+ 1 - 1
components/heap/multi_heap_poisoning.c

@@ -370,7 +370,7 @@ multi_heap_handle_t multi_heap_register(void *start, size_t size)
     return multi_heap_register_impl(start, 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) {
     if (*arg > POISON_OVERHEAD) {
         *arg -= POISON_OVERHEAD;
         *arg -= POISON_OVERHEAD;
     } else {
     } else {

+ 79 - 11
components/heap/test_apps/main/test_malloc.c

@@ -162,22 +162,90 @@ TEST_CASE("malloc/calloc(0) should not call failure callback", "[heap]")
 
 
 TEST_CASE("test get allocated size", "[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++) {
     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
         // 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)
         // 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_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;
+        }
+    }
+}
 
 
-        heap_caps_free(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

+ 14 - 0
components/heap/test_apps/pytest_heap.py

@@ -102,3 +102,17 @@ def test_memory_protection(dut: Dut) -> None:
     dut.expect_exact('Press ENTER to see the list of tests')
     dut.expect_exact('Press ENTER to see the list of tests')
     dut.write('[heap][mem_prot]')
     dut.write('[heap][mem_prot]')
     dut.expect_unity_test_output(timeout=300)
     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 - 0
components/heap/test_apps/sdkconfig.ci.func_hooks

@@ -0,0 +1 @@
+CONFIG_HEAP_USE_HOOKS=y

+ 13 - 0
docs/en/api-reference/system/heap_debug.rst

@@ -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.
 - :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:
 
 
 Heap Corruption Detection
 Heap Corruption Detection

+ 1 - 0
docs/en/api-reference/system/mem_alloc.rst

@@ -158,6 +158,7 @@ Heap Tracing & Debugging
 The following features are documented on the :doc:`Heap Memory Debugging </api-reference/system/heap_debug>` page:
 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 Information <heap-information>` (free space, etc.)
+- :ref:`Heap allocation and free function hooks <heap-allocation-free>`
 - :ref:`Heap Corruption Detection <heap-corruption>`
 - :ref:`Heap Corruption Detection <heap-corruption>`
 - :ref:`Heap Tracing <heap-tracing>` (memory leak detection, monitoring, etc.)
 - :ref:`Heap Tracing <heap-tracing>` (memory leak detection, monitoring, etc.)