diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c index 54018b0dec..1d73b0087f 100644 --- a/components/heap/heap_caps.c +++ b/components/heap/heap_caps.c @@ -502,3 +502,81 @@ size_t heap_caps_get_allocated_size( void *ptr ) size_t size = multi_heap_get_allocated_size(heap->heap, ptr); return size; } + +IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, int caps) +{ + void *ret = NULL; + + if(!alignment) { + return NULL; + } + + //Alignment must be a power of two: + if((alignment & (alignment - 1)) != 0) { + return NULL; + } + + if (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; + } + + //aligned alloc for now only supports default allocator or external + //allocator. + if((caps & (MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM)) == 0) { + return NULL; + } + + //if caps requested are supported, clear undesired others: + caps &= (MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM); + + for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) { + //Iterate over heaps and check capabilities at this priority + heap_t *heap; + SLIST_FOREACH(heap, ®istered_heaps, next) { + if (heap->heap == NULL) { + continue; + } + if ((heap->caps[prio] & caps) != 0) { + //Heap has at least one of the caps requested. If caps has other bits set that this prio + //doesn't cover, see if they're available in other prios. + if ((get_all_caps(heap) & caps) == caps) { + //Just try to alloc, nothing special. + ret = multi_heap_aligned_alloc(heap->heap, size, alignment); + if (ret != NULL) { + return ret; + } + } + } + } + } + //Nothing usable found. + return NULL; +} + +void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps) +{ + size_t size_bytes; + if (__builtin_mul_overflow(n, size, &size_bytes)) { + return NULL; + } + + void *ptr = heap_caps_aligned_alloc(alignment,size_bytes, caps); + if(ptr != NULL) { + memset(ptr, 0, size_bytes); + } + + return ptr; +} + +IRAM_ATTR void heap_caps_aligned_free(void *ptr) +{ + if (ptr == NULL) { + return; + } + + heap_t *heap = find_containing_heap(ptr); + assert(heap != NULL && "free() target pointer is outside heap areas"); + multi_heap_aligned_free(heap->heap, ptr); +} diff --git a/components/heap/include/esp_heap_caps.h b/components/heap/include/esp_heap_caps.h index cd91445227..61db74e1ce 100644 --- a/components/heap/include/esp_heap_caps.h +++ b/components/heap/include/esp_heap_caps.h @@ -85,6 +85,51 @@ void heap_caps_free( void *ptr); */ void *heap_caps_realloc( void *ptr, size_t size, int caps); +/** + * @brief Allocate a aligned chunk of memory which has the given capabilities + * + * Equivalent semantics to libc aligned_alloc(), for capability-aware memory. + * @param alignment How the pointer received needs to be aligned + * must be a power of two + * @param size Size, in bytes, of the amount of memory to allocate + * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type + * of memory to be returned + * + * @return A pointer to the memory allocated on success, NULL on failure + * + * @note Any memory allocated with heaps_caps_aligned_alloc() MUST + * be freed with heap_caps_aligned_free() and CANNOT be passed to free() + * + */ +void *heap_caps_aligned_alloc(size_t alignment, size_t size, int caps); + +/** + * @brief Allocate a aligned chunk of memory which has the given capabilities. The initialized value in the memory is set to zero. + * + * @param alignment How the pointer received needs to be aligned + * must be a power of two + * @param n Number of continuing chunks of memory to allocate + * @param size Size, in bytes, of a chunk of memory to allocate + * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type + * of memory to be returned + * + * @return A pointer to the memory allocated on success, NULL on failure + * + * @note Any memory allocated with heap_caps_aligned_calloc() MUST + * be freed with heap_caps_aligned_free() and CANNOT be passed to free() + */ +void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps); + +/** + * @brief Used to deallocate memory previously allocated with heap_caps_aligned_alloc + * + * @param ptr Pointer to the memory allocated + * @note This function is aimed to deallocate only memory allocated with + * heap_caps_aligned_alloc, memory allocated with heap_caps_malloc + * MUST not be passed to this function + */ +void heap_caps_aligned_free(void *ptr); + /** * @brief Allocate a chunk of memory which has the given capabilities. The initialized value in the memory is set to zero. * diff --git a/components/heap/include/multi_heap.h b/components/heap/include/multi_heap.h index dbd0cae864..d07d418023 100644 --- a/components/heap/include/multi_heap.h +++ b/components/heap/include/multi_heap.h @@ -29,6 +29,17 @@ extern "C" { /** @brief Opaque handle to a registered heap */ typedef struct multi_heap_info *multi_heap_handle_t; +/** + * @brief allocate a chunk of memory with specific alignment + * + * @param heap Handle to a registered heap. + * @param size size in bytes of memory chunk + * @param alignment how the memory must be aligned + * + * @return pointer to the memory allocated, NULL on failure + */ +void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment); + /** @brief malloc() a buffer in a given heap * * Semantics are the same as standard malloc(), only the returned buffer will be allocated in the specified heap. @@ -40,6 +51,14 @@ typedef struct multi_heap_info *multi_heap_handle_t; */ void *multi_heap_malloc(multi_heap_handle_t heap, size_t size); +/** @brief free() a buffer aligned in a given heap. + * + * @param heap Handle to a registered heap. + * @param p NULL, or a pointer previously returned from multi_heap_aligned_alloc() for the same heap. + */ +void multi_heap_aligned_free(multi_heap_handle_t heap, void *p); + + /** @brief free() a buffer in a given heap. * * Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the specified heap. diff --git a/components/heap/multi_heap_internal.h b/components/heap/multi_heap_internal.h index a69a052590..0f6a520096 100644 --- a/components/heap/multi_heap_internal.h +++ b/components/heap/multi_heap_internal.h @@ -23,6 +23,7 @@ typedef const struct heap_block *multi_heap_block_handle_t; If heap poisoning is enabled, wrapper functions call each of these. */ + void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size); void multi_heap_free_impl(multi_heap_handle_t heap, void *p); void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size); diff --git a/components/heap/multi_heap_poisoning.c b/components/heap/multi_heap_poisoning.c index ffe885150d..8a356e6495 100644 --- a/components/heap/multi_heap_poisoning.c +++ b/components/heap/multi_heap_poisoning.c @@ -45,6 +45,9 @@ #define HEAD_CANARY_PATTERN 0xABBA1234 #define TAIL_CANARY_PATTERN 0xBAAD5678 + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + typedef struct { uint32_t head_canary; MULTI_HEAP_BLOCK_OWNER @@ -182,6 +185,59 @@ static bool verify_fill_pattern(void *data, size_t size, bool print_errors, bool } #endif +void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment) +{ + if(heap == NULL) { + return NULL; + } + + if(!size) { + return NULL; + } + + if(!alignment) { + return NULL; + } + + //Alignment must be a power of two... + if((alignment & (alignment - 1)) != 0) { + return NULL; + } + + if(size > SIZE_MAX - POISON_OVERHEAD) { + return NULL; + } + + uint32_t overhead = (sizeof(uint32_t) + (alignment - 1) + POISON_OVERHEAD); + + multi_heap_internal_lock(heap); + poison_head_t *head = multi_heap_malloc_impl(heap, size + overhead); + uint8_t *data = NULL; + if (head != NULL) { + data = poison_allocated_region(head, size + (overhead - POISON_OVERHEAD)); +#ifdef SLOW + /* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */ + bool ret = verify_fill_pattern(data, size, true, true, true); + assert( ret ); +#else + (void)data; +#endif + } else { + multi_heap_internal_unlock(heap); + return NULL; + } + + //Lets align our new obtained block address: + //and save information to recover original block pointer + //to allow us to deallocate the memory when needed + void *ptr = (void *)ALIGN_UP((uintptr_t)head + sizeof(uint32_t) + sizeof(poison_head_t), alignment); + *((uint32_t *)ptr - 1) = (uint32_t)((uintptr_t)ptr - (uintptr_t)head); + + multi_heap_internal_unlock(heap); + + return ptr; +} + void *multi_heap_malloc(multi_heap_handle_t heap, size_t size) { if(size > SIZE_MAX - POISON_OVERHEAD) { @@ -203,6 +259,30 @@ void *multi_heap_malloc(multi_heap_handle_t heap, size_t size) return data; } +void multi_heap_aligned_free(multi_heap_handle_t heap, void *p) +{ + if(p == NULL) { + return; + } + + multi_heap_internal_lock(heap); + + uint32_t offset = *((uint32_t *)p - 1); + void *block_head = (void *)((uint8_t *)p - offset); + block_head += sizeof(poison_head_t); + + poison_head_t *head = verify_allocated_region(block_head, true); + assert(head != NULL); + block_head -= sizeof(poison_head_t); +#ifdef SLOW + /* replace everything with FREE_FILL_PATTERN, including the poison head/tail */ + memset(block_head, FREE_FILL_PATTERN, head->alloc_size + POISON_OVERHEAD); +#endif + + multi_heap_free_impl(heap, block_head); + multi_heap_internal_unlock(heap); +} + void multi_heap_free(multi_heap_handle_t heap, void *p) { if (p == NULL) { diff --git a/components/heap/test/test_aligned_alloc_caps.c b/components/heap/test/test_aligned_alloc_caps.c new file mode 100644 index 0000000000..613eb1eb29 --- /dev/null +++ b/components/heap/test/test_aligned_alloc_caps.c @@ -0,0 +1,139 @@ +/* + Tests for the capabilities-based memory allocator. +*/ + +#include +#include +#include "unity.h" +#include "esp_attr.h" +#include "esp_heap_caps.h" +#include "esp_spi_flash.h" +#include +#include +#include + +TEST_CASE("Capabilities aligned allocator test", "[heap]") +{ + uint32_t alignments = 0; + + printf("[ALIGNED_ALLOC] Allocating from default CAP: \n"); + + for(;alignments <= 1024; alignments++) { + uint8_t *buf = (uint8_t *)heap_caps_aligned_alloc(alignments, (alignments + 137), MALLOC_CAP_DEFAULT); + if(((alignments & (alignments - 1)) != 0) || (!alignments)) { + TEST_ASSERT( buf == NULL ); + //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); + } else { + TEST_ASSERT( buf != NULL ); + printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); + printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); + //Address of obtained block must be aligned with selected value + TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); + + //Write some data, if it corrupts memory probably the heap + //canary verification will fail: + memset(buf, 0xA5, (alignments + 137)); + + heap_caps_aligned_free(buf); + } + } + + //Alloc from a non permitted area: + uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_alloc(alignments, (alignments + 137), MALLOC_CAP_EXEC | MALLOC_CAP_32BIT); + TEST_ASSERT( not_permitted_buf == NULL ); + +#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_ESP32S2_SPIRAM_SUPPORT + alignments = 0; + printf("[ALIGNED_ALLOC] Allocating from external memory: \n"); + + for(;alignments <= 1024 * 1024; alignments++) { + //Now try to take aligned memory from IRAM: + uint8_t *buf = (uint8_t *)heap_caps_aligned_alloc(alignments, 10*1024, MALLOC_CAP_SPIRAM); + if(((alignments & (alignments - 1)) != 0) || (!alignments)) { + TEST_ASSERT( buf == NULL ); + //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); + } else { + TEST_ASSERT( buf != NULL ); + printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); + printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); + //Address of obtained block must be aligned with selected value + TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); + + //Write some data, if it corrupts memory probably the heap + //canary verification will fail: + memset(buf, 0xA5, (10*1024)); + heap_caps_aligned_free(buf); + } + } +#endif + +} + +TEST_CASE("Capabilities aligned calloc test", "[heap]") +{ + uint32_t alignments = 0; + + printf("[ALIGNED_ALLOC] Allocating from default CAP: \n"); + + for(;alignments <= 1024; alignments++) { + uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_DEFAULT); + if(((alignments & (alignments - 1)) != 0) || (!alignments)) { + TEST_ASSERT( buf == NULL ); + //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); + } else { + TEST_ASSERT( buf != NULL ); + printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); + printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); + //Address of obtained block must be aligned with selected value + TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); + + //Write some data, if it corrupts memory probably the heap + //canary verification will fail: + memset(buf, 0xA5, (alignments + 137)); + + heap_caps_aligned_free(buf); + } + } + + //Check if memory is initialized with zero: + uint8_t byte_array[1024]; + memset(&byte_array, 0, sizeof(byte_array)); + uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1, 1024, MALLOC_CAP_DEFAULT); + TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0); + heap_caps_aligned_free(buf); + + //Same size, but different chunk: + buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1024, 1, MALLOC_CAP_DEFAULT); + TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0); + heap_caps_aligned_free(buf); + + //Alloc from a non permitted area: + uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_32BIT); + TEST_ASSERT( not_permitted_buf == NULL ); + +#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_ESP32S2_SPIRAM_SUPPORT + alignments = 0; + printf("[ALIGNED_ALLOC] Allocating from external memory: \n"); + + for(;alignments <= 1024 * 1024; alignments++) { + //Now try to take aligned memory from IRAM: + uint8_t *buf = (uint8_t *)(uint8_t *)heap_caps_aligned_calloc(alignments, 1, 10*1024, MALLOC_CAP_SPIRAM);; + if(((alignments & (alignments - 1)) != 0) || (!alignments)) { + TEST_ASSERT( buf == NULL ); + //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); + } else { + TEST_ASSERT( buf != NULL ); + printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); + printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); + //Address of obtained block must be aligned with selected value + TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); + + //Write some data, if it corrupts memory probably the heap + //canary verification will fail: + memset(buf, 0xA5, (10*1024)); + heap_caps_aligned_free(buf); + } + } +#endif + +} \ No newline at end of file diff --git a/components/heap/test_multi_heap_host/test_multi_heap.cpp b/components/heap/test_multi_heap_host/test_multi_heap.cpp index 310ee9dcf1..f668dd9533 100644 --- a/components/heap/test_multi_heap_host/test_multi_heap.cpp +++ b/components/heap/test_multi_heap_host/test_multi_heap.cpp @@ -494,3 +494,51 @@ TEST_CASE("unaligned heaps", "[multi_heap]") } } } + +#ifndef CONFIG_HEAP_POISONING_NONE + +TEST_CASE("multi_heap aligned allocations", "[multi_heap]") +{ + uint8_t test_heap[1024 * 1024]; + multi_heap_handle_t heap = multi_heap_register(test_heap, sizeof(test_heap)); + uint32_t aligments = 0; // starts from alignment by 4-byte boundary + size_t old_size = multi_heap_free_size(heap); + size_t leakage = 1024; + printf("[ALIGNED_ALLOC] heap_size before: %d \n", old_size); + + printf("New heap:\n"); + multi_heap_dump(heap); + printf("*********************\n"); + + for(;aligments < 500 * 1024; aligments++) { + + //Use some stupid size value to test correct alignment even in strange + //memory layout objects: + uint8_t *buf = (uint8_t *)multi_heap_aligned_alloc(heap, (aligments + 137), aligments ); + if(((aligments & (aligments - 1)) != 0) || (!aligments)) { + REQUIRE( buf == NULL ); + //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); + } else { + REQUIRE( buf != NULL ); + REQUIRE((intptr_t)buf >= (intptr_t)test_heap); + REQUIRE((intptr_t)buf < (intptr_t)(test_heap + sizeof(test_heap))); + + printf("[ALIGNED_ALLOC] alignment required: %u \n", aligments); + //printf("[ALIGNED_ALLOC] allocated size: %d \n", multi_heap_get_allocated_size(heap, buf)); + printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); + //Address of obtained block must be aligned with selected value + REQUIRE(((intptr_t)buf & (aligments - 1)) == 0); + + //Write some data, if it corrupts memory probably the heap + //canary verification will fail: + memset(buf, 0xA5, (aligments + 137)); + + multi_heap_aligned_free(heap, buf); + } + } + + printf("[ALIGNED_ALLOC] heap_size after: %d \n", multi_heap_free_size(heap)); + REQUIRE((old_size - multi_heap_free_size(heap)) <= leakage); +} + +#endif \ No newline at end of file diff --git a/components/newlib/heap.c b/components/newlib/heap.c index 7203d81907..9dbc635064 100644 --- a/components/newlib/heap.c +++ b/components/newlib/heap.c @@ -94,6 +94,7 @@ void* memalign(size_t alignment, size_t n) extern void memalign_function_was_linked_but_unsupported_in_esp_idf(void); memalign_function_was_linked_but_unsupported_in_esp_idf(); return NULL; + } int malloc_trim(size_t pad)