Merge branch 'feature/wider_alignment_range_malloc' into 'master'

feature/wider alignment range malloc

Closes IDF-648

See merge request espressif/esp-idf!6691
This commit is contained in:
Angus Gratton 2020-01-13 12:02:35 +08:00
commit ec20cf01b5
8 changed files with 411 additions and 0 deletions

View File

@ -502,3 +502,81 @@ size_t heap_caps_get_allocated_size( void *ptr )
size_t size = multi_heap_get_allocated_size(heap->heap, ptr); size_t size = multi_heap_get_allocated_size(heap->heap, ptr);
return size; 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, &registered_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);
}

View File

@ -85,6 +85,51 @@ void heap_caps_free( void *ptr);
*/ */
void *heap_caps_realloc( void *ptr, size_t size, int caps); 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. * @brief Allocate a chunk of memory which has the given capabilities. The initialized value in the memory is set to zero.
* *

View File

@ -29,6 +29,17 @@ extern "C" {
/** @brief Opaque handle to a registered heap */ /** @brief Opaque handle to a registered heap */
typedef struct multi_heap_info *multi_heap_handle_t; 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 /** @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. * 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); 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. /** @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. * Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the specified heap.

View File

@ -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. 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_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_free_impl(multi_heap_handle_t heap, void *p);
void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size); void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size);

View File

@ -45,6 +45,9 @@
#define HEAD_CANARY_PATTERN 0xABBA1234 #define HEAD_CANARY_PATTERN 0xABBA1234
#define TAIL_CANARY_PATTERN 0xBAAD5678 #define TAIL_CANARY_PATTERN 0xBAAD5678
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
typedef struct { typedef struct {
uint32_t head_canary; uint32_t head_canary;
MULTI_HEAP_BLOCK_OWNER MULTI_HEAP_BLOCK_OWNER
@ -182,6 +185,59 @@ static bool verify_fill_pattern(void *data, size_t size, bool print_errors, bool
} }
#endif #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) void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
{ {
if(size > SIZE_MAX - POISON_OVERHEAD) { if(size > SIZE_MAX - POISON_OVERHEAD) {
@ -203,6 +259,30 @@ void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
return data; 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) void multi_heap_free(multi_heap_handle_t heap, void *p)
{ {
if (p == NULL) { if (p == NULL) {

View File

@ -0,0 +1,139 @@
/*
Tests for the capabilities-based memory allocator.
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_spi_flash.h"
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
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
}

View File

@ -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

View File

@ -94,6 +94,7 @@ void* memalign(size_t alignment, size_t n)
extern void memalign_function_was_linked_but_unsupported_in_esp_idf(void); extern void memalign_function_was_linked_but_unsupported_in_esp_idf(void);
memalign_function_was_linked_but_unsupported_in_esp_idf(); memalign_function_was_linked_but_unsupported_in_esp_idf();
return NULL; return NULL;
} }
int malloc_trim(size_t pad) int malloc_trim(size_t pad)