heap: added alloc failed hook and configuration options

heap/test: added alloc failed hook tests

docs: added alloc failed hook documentation

heap: add function to register allocation failed hook

docs: allocation failed hook docs improvements
This commit is contained in:
Felipe Neves 2020-04-27 17:09:15 -03:00
parent 1ab26f85ec
commit 6f5e43e26a
5 changed files with 152 additions and 0 deletions

View file

@ -63,4 +63,10 @@ menu "Heap memory debugging"
This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block
allocated.
config HEAP_ABORT_WHEN_ALLOCATION_FAILS
bool "Abort if memory allocation fails"
default n
help
When enabled, if a memory allocation operation fails it will cause a system abort.
endmenu

View file

@ -21,6 +21,7 @@
#include "multi_heap.h"
#include "esp_log.h"
#include "heap_private.h"
#include "esp_system.h"
/*
This file, combined with a region allocator that supports multiple heaps, solves the problem that the ESP32 has RAM
@ -31,6 +32,8 @@ its knowledge of how the memory is configured along with a priority scheme to al
possible. This should optimize the amount of RAM accessible to the code without hardwiring addresses.
*/
static esp_alloc_failed_hook_t alloc_failed_callback;
/*
This takes a memory chunk in a region that can be addressed as both DRAM as well as IRAM. It will convert it to
IRAM in such a way that it can be later freed. It assumes both the address as well as the length to be word-aligned.
@ -53,6 +56,29 @@ IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len)
return iptr + 1;
}
static void heap_caps_alloc_failed(size_t requested_size, uint32_t caps, const char *function_name)
{
if (alloc_failed_callback) {
alloc_failed_callback(requested_size, caps, function_name);
}
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
esp_system_abort("Memory allocation failed");
#endif
}
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback)
{
if (callback == NULL) {
return ESP_ERR_INVALID_ARG;
}
alloc_failed_callback = callback;
return ESP_OK;
}
bool heap_caps_match(const heap_t *heap, uint32_t caps)
{
return heap->heap != NULL && ((get_all_caps(heap) & caps) == caps);
@ -68,6 +94,8 @@ IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps )
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
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
@ -77,6 +105,8 @@ IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps )
//NULL directly, even although our heap capabilities (based on soc_memory_tags & soc_memory_regions) would
//indicate there is a tag for this.
if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) {
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
caps |= MALLOC_CAP_32BIT; // IRAM is 32-bit accessible RAM
@ -121,6 +151,9 @@ IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps )
}
}
}
heap_caps_alloc_failed(size, caps, __func__);
//Nothing usable found.
return NULL;
}
@ -288,6 +321,8 @@ IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, int caps)
}
if (size > HEAP_SIZE_MAX) {
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
@ -342,6 +377,9 @@ IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, int caps)
heap_caps_free(ptr);
return new_p;
}
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
@ -518,12 +556,15 @@ IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, int caps)
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
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
//aligned alloc for now only supports default allocator or external
//allocator.
if((caps & (MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM)) == 0) {
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
@ -550,6 +591,9 @@ IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, int caps)
}
}
}
heap_caps_alloc_failed(size, caps, __func__);
//Nothing usable found.
return NULL;
}

View file

@ -17,6 +17,7 @@
#include <stdlib.h>
#include "multi_heap.h"
#include <sdkconfig.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
@ -42,6 +43,21 @@ extern "C" {
#define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker
/**
* @brief callback called when a allocation operation fails, if registered
* @param size in bytes of failed allocation
* @param caps capabillites requested of failed allocation
* @param function_name function which generated the failure
*/
typedef void (*esp_alloc_failed_hook_t) (size_t size, uint32_t caps, const char * function_name);
/**
* @brief registers a callback function to be invoked if a memory allocation operation fails
* @param callback caller defined callback to be invoked
* @return ESP_OK if callback was registered.
*/
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback);
/**
* @brief Allocate a chunk of memory which has the given capabilities
*

View file

@ -182,3 +182,62 @@ TEST_CASE("heap_caps_xxx functions work with flash cache disabled", "[heap]")
{
TEST_ASSERT( iram_malloc_test() );
}
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
TEST_CASE("When enabled, allocation operation failure generates an abort", "[heap][reset=abort,SW_CPU_RESET]")
{
const size_t stupid_allocation_size = (128 * 1024 * 1024);
void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
(void)ptr;
TEST_FAIL_MESSAGE("should not be reached");
}
#endif
static bool called_user_failed_hook = false;
void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name)
{
printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",function_name, requested_size, caps);
called_user_failed_hook = true;
}
TEST_CASE("user provided alloc failed hook must be called when allocation fails", "[heap]")
{
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
const size_t stupid_allocation_size = (128 * 1024 * 1024);
void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_realloc(ptr, stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_aligned_alloc(0x200, stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
(void)ptr;
}
TEST_CASE("allocation with invalid capability should also trigger the alloc failed hook", "[heap]")
{
const size_t allocation_size = 64;
const uint32_t invalid_cap = MALLOC_CAP_INVALID;
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
called_user_failed_hook = false;
void *ptr = heap_caps_malloc(allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_realloc(ptr, allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_aligned_alloc(0x200, allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
(void)ptr;
}

View file

@ -44,6 +44,33 @@ If a heap integrity assertion fails, a line will be printed like ``CORRUPT HEAP:
It's also possible to manually check heap integrity by calling :cpp:func:`heap_caps_check_integrity_all` or related functions. This function checks all of requested heap memory for integrity, and can be used even if assertions are disabled. If the integrity check prints an error, it will also contain the address(es) of corrupt heap structures.
Memory Allocation Failed Hook
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Users can use :cpp:func:`heap_caps_register_failed_alloc_callback` to register a callback that will be invoked every time a allocation
operation fails.
Additionaly user can enable a generation of a system abort if allocation operation fails by following the steps below:
- In the project configuration menu, navigate to ``Component config`` -> ``Heap Memory Debugging`` and select ``Abort if memory allocation fails`` option (see :ref:`CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS`).
The example below show how to register a allocation failure callback::
#include "esp_heap_caps.h"
void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name)
{
printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",function_name, requested_size, caps);
}
void app_main()
{
...
esp_err_t error = heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook);
...
void *ptr = heap_caps_malloc(allocation_size, MALLOC_CAP_DEFAULT);
...
}
Finding Heap Corruption
^^^^^^^^^^^^^^^^^^^^^^^