Merge branch 'feature/multi_heap_poisoning' into 'master'

Heap tracing & poisoning features

See merge request !749
This commit is contained in:
Ivan Grokhotkov 2017-09-07 17:47:51 +08:00
commit 40df6546c0
30 changed files with 1620 additions and 110 deletions

View file

@ -84,6 +84,7 @@ SECTIONS
*(.iram1 .iram1.*)
*libfreertos.a:(.literal .text .literal.* .text.*)
*libheap.a:multi_heap.o(.literal .text .literal.* .text.*)
*libheap.a:multi_heap_poisoning.o(.literal .text .literal.* .text.*)
*libesp32.a:panic.o(.literal .text .literal.* .text.*)
*libesp32.a:core_dump.o(.literal .text .literal.* .text.*)
*libapp_trace.a:(.literal .text .literal.* .text.*)
@ -93,6 +94,7 @@ SECTIONS
*librtc.a:(.literal .text .literal.* .text.*)
*libsoc.a:(.literal .text .literal.* .text.*)
*libhal.a:(.literal .text .literal.* .text.*)
*libgcc.a:lib2funcs.o(.literal .text .literal.* .text.*)
*libspi_flash.a:spi_flash_rom_patch.o(.literal .text .literal.* .text.*)
_iram_text_end = ABSOLUTE(.);
} > iram0_0_seg
@ -116,6 +118,7 @@ SECTIONS
*libphy.a:(.rodata .rodata.*)
*libapp_trace.a:(.rodata .rodata.*)
*libheap.a:multi_heap.o(.rodata .rodata.*)
*libheap.a:multi_heap_poisoning.o(.rodata .rodata.*)
_data_end = ABSOLUTE(.);
. = ALIGN(4);
} >dram0_0_seg

View file

@ -189,11 +189,7 @@ extern "C" {
#endif
#ifndef INCLUDE_pcTaskGetTaskName
#if ( configENABLE_MEMORY_DEBUG == 1)
#define INCLUDE_pcTaskGetTaskName 1
#else
#define INCLUDE_pcTaskGetTaskName 0
#endif
#endif
#ifndef configUSE_APPLICATION_TASK_TAG

View file

@ -231,12 +231,6 @@
#define INCLUDE_pcTaskGetTaskName 1
#define INCLUDE_xTaskGetIdleTaskHandle 1
#if CONFIG_ENABLE_MEMORY_DEBUG
#define configENABLE_MEMORY_DEBUG 1
#else
#define configENABLE_MEMORY_DEBUG 0
#endif
#define INCLUDE_xSemaphoreGetMutexHolder 1
/* The priority at which the tick interrupt runs. This should probably be

View file

@ -123,6 +123,7 @@ extern "C" {
#endif
#include "mpu_wrappers.h"
#include "esp_system.h"
/*
* Setup the stack of a new task so it is ready to be placed under the
@ -143,11 +144,10 @@ extern "C" {
* non-FreeRTOS-specific code, and behave the same as
* pvPortMalloc()/vPortFree().
*/
void *pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;
void vPortFree( void *pv ) PRIVILEGED_FUNCTION;
void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION;
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION;
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION;
#define pvPortMalloc malloc
#define vPortFree free
#define xPortGetFreeHeapSize esp_get_free_heap_size
#define xPortGetMinimumEverFreeHeapSize esp_get_minimum_free_heap_size
/*
* Setup the hardware ready for the scheduler to take control. This generally

View file

@ -382,30 +382,3 @@ void vPortSetStackWatchpoint( void* pxStackStart ) {
uint32_t xPortGetTickRateHz(void) {
return (uint32_t)configTICK_RATE_HZ;
}
/* Heap functions, wrappers around heap_caps_xxx functions
NB: libc malloc() & free() are also defined & available
for this purpose.
*/
void *pvPortMalloc( size_t xWantedSize )
{
return heap_caps_malloc(xWantedSize, MALLOC_CAP_8BIT);
}
void vPortFree( void *pv )
{
return heap_caps_free(pv);
}
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION
{
return heap_caps_get_free_size( MALLOC_CAP_8BIT );
}
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION
{
return heap_caps_get_minimum_free_size( MALLOC_CAP_8BIT );
}

40
components/heap/Kconfig Normal file
View file

@ -0,0 +1,40 @@
menu "Heap memory debugging"
choice HEAP_CORRUPTION_DETECTION
prompt "Heap corruption detection"
default HEAP_POISONING_DISABLED
help
Enable heap poisoning features to detect heap corruption caused by out-of-bounds access to heap memory.
See the "Heap Memory Debugging" page of the IDF documentation
for a description of each level of heap corruption detection.
config HEAP_POISONING_DISABLED
bool "Basic (no poisoning)"
config HEAP_POISONING_LIGHT
bool "Light impact"
config HEAP_POISONING_COMPREHENSIVE
bool "Comprehensive"
endchoice
config HEAP_TRACING
bool "Enable heap tracing"
help
Enables the heap tracing API defined in esp_heap_trace.h.
This function causes a moderate increase in IRAM code side and a minor increase in heap function
(malloc/free/realloc) CPU overhead, even when the tracing feature is not used. So it's best to keep it disabled
unless tracing is being used.
config HEAP_TRACING_STACK_DEPTH
int "Heap tracing stack depth"
range 0 10
default 2
depends on HEAP_TRACING
help
Number of stack frames to save when tracing heap operation callers.
More stack frames uses more memory in the heap trace buffer (and slows down allocation), but
can provide useful information.
endmenu

View file

@ -1,3 +1,18 @@
#
# Component Makefile
#
COMPONENT_OBJS := heap_caps_init.o heap_caps.o multi_heap.o heap_trace.o
ifndef CONFIG_HEAP_POISONING_DISABLED
COMPONENT_OBJS += multi_heap_poisoning.o
endif
ifdef CONFIG_HEAP_TRACING
WRAP_FUNCTIONS = calloc malloc free realloc heap_caps_malloc heap_caps_free heap_caps_realloc
WRAP_ARGUMENT := -Wl,--wrap=
COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME) $(addprefix $(WRAP_ARGUMENT),$(WRAP_FUNCTIONS))
endif

View file

@ -288,3 +288,18 @@ void heap_caps_print_heap_info( uint32_t caps )
printf(" free %d allocated %d min_free %d largest_free_block %d\n", info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes, info.largest_free_block);
}
bool heap_caps_check_integrity(uint32_t caps, bool print_errors)
{
bool all_heaps = caps & MALLOC_CAP_INVALID;
bool valid = true;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL
&& (all_heaps || (get_all_caps(heap) & caps) == caps)) {
valid = multi_heap_check(heap->heap, print_errors) && valid;
}
}
return valid;
}

View file

@ -0,0 +1,406 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sys/param.h>
#include <sdkconfig.h>
#define HEAP_TRACE_SRCFILE /* don't warn on inclusion here */
#include "esp_heap_trace.h"
#undef HEAP_TRACE_SRCFILE
#include "esp_heap_caps.h"
#include "esp_attr.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_memory_layout.h"
#define STACK_DEPTH CONFIG_HEAP_TRACING_STACK_DEPTH
static portMUX_TYPE trace_mux = portMUX_INITIALIZER_UNLOCKED;
static bool tracing;
static heap_trace_mode_t mode;
/* Buffer used for records, starting at offset 0
*/
static heap_trace_record_t *buffer;
static size_t total_records;
/* Count of entries logged in the buffer.
Maximum total_records
*/
static size_t count;
/* Actual number of allocations logged */
static size_t total_allocations;
/* Actual number of frees logged */
static size_t total_frees;
/* Has the buffer overflowed and lost trace entries? */
static bool has_overflowed = false;
esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records)
{
#ifndef CONFIG_HEAP_TRACING
return ESP_ERR_NOT_SUPPORTED;
#endif
if (tracing) {
return ESP_ERR_INVALID_STATE;
}
buffer = record_buffer;
total_records = num_records;
memset(buffer, 0, num_records * sizeof(heap_trace_record_t));
return ESP_OK;
}
esp_err_t heap_trace_start(heap_trace_mode_t mode_param)
{
#ifndef CONFIG_HEAP_TRACING
return ESP_ERR_NOT_SUPPORTED;
#endif
if (buffer == NULL || total_records == 0) {
return ESP_ERR_INVALID_STATE;
}
taskENTER_CRITICAL(&trace_mux);
tracing = false;
mode = mode_param;
count = 0;
total_allocations = 0;
total_frees = 0;
has_overflowed = false;
heap_trace_resume();
taskEXIT_CRITICAL(&trace_mux);
return ESP_OK;
}
static esp_err_t set_tracing(bool enable)
{
#ifndef CONFIG_HEAP_TRACING
return ESP_ERR_NOT_SUPPORTED;
#endif
if (tracing == enable) {
return ESP_ERR_INVALID_STATE;
}
tracing = enable;
return ESP_OK;
}
esp_err_t heap_trace_stop(void)
{
return set_tracing(false);
}
esp_err_t heap_trace_resume(void)
{
return set_tracing(true);
}
size_t heap_trace_get_count(void)
{
return count;
}
esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record)
{
#ifndef CONFIG_HEAP_TRACING
return ESP_ERR_NOT_SUPPORTED;
#endif
if (record == NULL) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t result = ESP_OK;
taskENTER_CRITICAL(&trace_mux);
if (index >= count) {
result = ESP_ERR_INVALID_ARG; /* out of range for 'count' */
} else {
memcpy(record, &buffer[index], sizeof(heap_trace_record_t));
}
taskEXIT_CRITICAL(&trace_mux);
return result;
}
void heap_trace_dump(void)
{
#ifndef CONFIG_HEAP_TRACING
printf("no data, heap tracing is disabled.\n");
return;
#endif
size_t delta_size = 0;
size_t delta_allocs = 0;
printf("%u allocations trace (%u entry buffer)\n",
count, total_records);
size_t start_count = count;
for (int i = 0; i < count; i++) {
heap_trace_record_t *rec = &buffer[i];
if (rec->address != NULL) {
printf("%d bytes (@ %p) allocated CPU %d ccount 0x%08x caller ",
rec->size, rec->address, rec->ccount & 1, rec->ccount & ~3);
for (int j = 0; j < STACK_DEPTH && rec->alloced_by[j] != 0; j++) {
printf("%p%s", rec->alloced_by[j],
(j < STACK_DEPTH - 1) ? ":" : "");
}
if (mode != HEAP_TRACE_ALL || STACK_DEPTH == 0 || rec->freed_by[0] == NULL) {
delta_size += rec->size;
delta_allocs++;
printf("\n");
} else {
printf("\nfreed by ");
for (int j = 0; j < STACK_DEPTH; j++) {
printf("%p%s", rec->freed_by[j],
(j < STACK_DEPTH - 1) ? ":" : "\n");
}
}
}
}
if (mode == HEAP_TRACE_ALL) {
printf("%u bytes alive in trace (%u/%u allocations)\n",
delta_size, delta_allocs, heap_trace_get_count());
} else {
printf("%u bytes 'leaked' in trace (%u allocations)\n", delta_size, delta_allocs);
}
printf("total allocations %u total frees %u\n", total_allocations, total_frees);
if (start_count != count) { // only a problem if trace isn't stopped before dumping
printf("(NB: New entries were traced while dumping, so trace dump may have duplicate entries.)\n");
}
if (has_overflowed) {
printf("(NB: Buffer has overflowed, so trace data is incomplete.)\n");
}
}
/* Add a new allocation to the heap trace records */
static IRAM_ATTR void record_allocation(const heap_trace_record_t *record)
{
taskENTER_CRITICAL(&trace_mux);
if (tracing) {
if (count == total_records) {
has_overflowed = true;
/* Move the whole buffer back one slot.
This is a bit slow, compared to treating this buffer as a ringbuffer and rotating a head pointer.
However, ringbuffer code gets tricky when we remove elements in mid-buffer (for leak trace mode) while
trying to keep track of an item count that may overflow.
*/
memmove(&buffer[0], &buffer[1], sizeof(heap_trace_record_t) * (total_records -1));
count--;
}
// Copy new record into place
memcpy(&buffer[count], record, sizeof(heap_trace_record_t));
count++;
total_allocations++;
}
taskEXIT_CRITICAL(&trace_mux);
}
// remove a record, used when freeing
static void remove_record(int index);
/* record a free event in the heap trace log
For HEAP_TRACE_ALL, this means filling in the freed_by pointer.
For HEAP_TRACE_LEAKS, this means removing the record from the log.
*/
static IRAM_ATTR void record_free(void *p, void **callers)
{
taskENTER_CRITICAL(&trace_mux);
if (tracing && count > 0) {
total_frees++;
/* search backwards for the allocation record matching this free */
int i;
for (i = count - 1; i >= 0; i--) {
if (buffer[i].address == p) {
break;
}
}
if (i >= 0) {
if (mode == HEAP_TRACE_ALL) {
memcpy(buffer[i].freed_by, callers, sizeof(void *) * STACK_DEPTH);
} else { // HEAP_TRACE_LEAKS
// Leak trace mode, once an allocation is freed we remove it from the list
remove_record(i);
}
}
}
taskEXIT_CRITICAL(&trace_mux);
}
/* remove the entry at 'index' from the ringbuffer of saved records */
static IRAM_ATTR void remove_record(int index)
{
if (index < count - 1) {
// Remove the buffer entry from the list
memmove(&buffer[index], &buffer[index+1],
sizeof(heap_trace_record_t) * (total_records - index - 1));
} else {
// For last element, just zero it out to avoid ambiguity
memset(&buffer[index], 0, sizeof(heap_trace_record_t));
}
count--;
}
/* Encode the CPU ID in the LSB of the ccount value */
inline static uint32_t get_ccount(void)
{
uint32_t ccount = xthal_get_ccount() & ~3;
#ifndef CONFIG_FREERTOS_UNICORE
ccount |= xPortGetCoreID();
#endif
return ccount;
}
#define TEST_STACK(N) do { \
if (STACK_DEPTH == N) { \
return; \
} \
callers[N] = __builtin_return_address(N+offset); \
if (!esp_ptr_executable(callers[N])) { \
return; \
} \
} while(0);
/* Static function to read the call stack for a traced heap call.
Calls to __builtin_return_address are "unrolled" via TEST_STACK macro as gcc requires the
argument to be a compile-time constant.
*/
static IRAM_ATTR __attribute__((noinline)) void get_call_stack(void **callers)
{
const int offset = 2; // Caller is 2 stack frames deeper than we care about
bzero(callers, sizeof(void *) * STACK_DEPTH);
TEST_STACK(0);
TEST_STACK(1);
TEST_STACK(2);
TEST_STACK(3);
TEST_STACK(4);
TEST_STACK(5);
TEST_STACK(6);
TEST_STACK(7);
TEST_STACK(8);
TEST_STACK(9);
}
_Static_assert(STACK_DEPTH >= 0 && STACK_DEPTH <= 10, "CONFIG_HEAP_TRACING_STACK_DEPTH must be in range 0-10");
void *__real_heap_caps_malloc(size_t size, uint32_t caps);
/* trace any 'malloc' event */
static IRAM_ATTR __attribute__((noinline)) void *trace_malloc(size_t size, uint32_t caps)
{
uint32_t ccount = get_ccount();
void *p = __real_heap_caps_malloc(size, caps);
if (tracing && p != NULL) {
heap_trace_record_t rec = {
.address = p,
.ccount = ccount,
.size = size,
};
get_call_stack(rec.alloced_by);
record_allocation(&rec);
}
return p;
}
void __real_heap_caps_free(void *p);
/* trace any 'free' event */
static IRAM_ATTR __attribute__((noinline)) void trace_free(void *p)
{
if (tracing && p != NULL) {
void *callers[STACK_DEPTH];
get_call_stack(callers);
record_free(p, callers);
}
__real_heap_caps_free(p);
}
void * __real_heap_caps_realloc(void *p, size_t size, uint32_t caps);
/* trace any 'realloc' event */
static IRAM_ATTR __attribute__((noinline)) void *trace_realloc(void *p, size_t size, uint32_t caps)
{
void *callers[STACK_DEPTH];
uint32_t ccount = get_ccount();
if (tracing && p != NULL && size == 0) {
get_call_stack(callers);
record_free(p, callers);
}
void *r = __real_heap_caps_realloc(p, size, caps);
if (tracing && r != NULL) {
get_call_stack(callers);
if (p != NULL) {
/* trace realloc as free-then-alloc */
record_free(p, callers);
}
heap_trace_record_t rec = {
.address = p,
.ccount = ccount,
.size = size,
};
memcpy(rec.alloced_by, callers, sizeof(heap_trace_record_t) * STACK_DEPTH);
record_allocation(&rec);
}
return r;
}
/* Note: this changes the behaviour of libc malloc/realloc/free a bit,
as they no longer go via the libc functions in ROM. But more or less
the same in the end. */
IRAM_ATTR void *__wrap_malloc(size_t size)
{
return trace_malloc(size, MALLOC_CAP_8BIT);
}
IRAM_ATTR void __wrap_free(void *p)
{
trace_free(p);
}
IRAM_ATTR void *__wrap_realloc(void *p, size_t size)
{
return trace_realloc(p, size, MALLOC_CAP_8BIT);
}
IRAM_ATTR void *__wrap_calloc(size_t nmemb, size_t size)
{
size = size * nmemb;
void *result = trace_malloc(size, MALLOC_CAP_8BIT);
if (result != NULL) {
memset(result, 0, size);
}
return result;
}
IRAM_ATTR void *__wrap_heap_caps_malloc(size_t size, uint32_t caps)
{
return trace_malloc(size, caps);
}
IRAM_ATTR void __wrap_heap_caps_free(void *p) __attribute__((alias("__wrap_free")));
IRAM_ATTR void *__wrap_heap_caps_realloc(void *p, size_t size, uint32_t caps)
{
return trace_realloc(p, size, caps);
}

View file

@ -27,7 +27,7 @@ void *pvPortMallocCaps(size_t xWantedSize, uint32_t caps) asm("heap_caps_malloc"
/* Please use heap_caps_get_minimum_free_heap_size() instead of this function */
size_t xPortGetMinimumEverFreeHeapSizeCaps( uint32_t caps ) asm("heap_caps_get_minimum_free_heap_size") __attribute__((deprecated));
/* Please use heap_caps_get_free_heap_size() instead of this function */
/* Please use heap_caps_get_free_size() instead of this function */
size_t xPortGetFreeHeapSizeCaps( uint32_t caps ) asm("heap_caps_get_free_heap_size") __attribute__((deprecated));
#ifdef __cplusplus

View file

@ -38,7 +38,7 @@
*
* Equivalent semantics to libc malloc(), for capability-aware memory.
*
* In IDF, malloc(p) is equivalent to heaps_caps_malloc(p, MALLOC_CAP_8BIT);
* In IDF, ``malloc(p)`` is equivalent to ``heaps_caps_malloc(p, MALLOC_CAP_8BIT)``.
*
* @param size Size, in bytes, of the amount of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
@ -53,7 +53,7 @@ void *heap_caps_malloc(size_t size, uint32_t caps);
*
* Equivalent semantics to libc free(), for capability-aware memory.
*
* In IDF, free(p) is equivalent to heap_caps_free(p).
* In IDF, ``free(p)`` is equivalent to ``heap_caps_free(p)``.
*
* @param ptr Pointer to memory previously returned from heap_caps_malloc() or heap_caps_realloc(). Can be NULL.
*/
@ -64,10 +64,10 @@ void heap_caps_free( void *ptr);
*
* Equivalent semantics to libc realloc(), for capability-aware memory.
*
* In IDF, realloc(p, s) is equivalent to heap_caps_realloc(p, s, MALLOC_CAP_8BIT).
* In IDF, ``realloc(p, s)`` is equivalent to ``heap_caps_realloc(p, s, MALLOC_CAP_8BIT)``.
*
* 'caps' parameter can be different to the capabilities that any original 'ptr' was allocated with. In this way,
* realloc can be used to "move" a buffer if necessary to ensure it meets new set of capabilities.
* realloc can be used to "move" a buffer if necessary to ensure it meets a new set of capabilities.
*
* @param ptr Pointer to previously allocated memory, or NULL for a new allocation.
* @param size Size of the new buffer requested, or 0 to free the buffer.
@ -103,8 +103,8 @@ size_t heap_caps_get_free_size( uint32_t caps );
* with the given capabilities.
*
* Note the result may be less than the global all-time minimum available heap of this kind, as "low water marks" are
* tracked per-heap. Individual heaps may have reached their "low water marks" at different points in time. However
* this result still gives a "worst case" indication for all-time free heap.
* tracked per-region. Individual regions' heaps may have reached their "low water marks" at different points in time. However
* this result still gives a "worst case" indication for all-time minimum free heap.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
@ -116,7 +116,7 @@ size_t heap_caps_get_minimum_free_size( uint32_t caps );
/**
* @brief Get the largest free block of memory able to be allocated with the given capabilities.
*
* Returns the largest value of 's' for which heap_caps_malloc(s, caps) will succeed.
* Returns the largest value of ``s`` for which ``heap_caps_malloc(s, caps)`` will succeed.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
@ -131,7 +131,7 @@ size_t heap_caps_get_largest_free_block( uint32_t caps );
*
* Calls multi_heap_info() on all heaps which share the given capabilities. The information returned is an aggregate
* across all matching heaps. The meanings of fields are the same as defined for multi_heap_info_t, except that
* minimum_free_bytes has the same caveats described in heap_caps_get_minimum_free_size().
* ``minimum_free_bytes`` has the same caveats described in heap_caps_get_minimum_free_size().
*
* @param info Pointer to a structure which will be filled with relevant
* heap metadata.
@ -154,3 +154,19 @@ void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps );
*/
void heap_caps_print_heap_info( uint32_t caps );
/**
* @brief Check integrity of all heaps with the given capabilities.
*
* Calls multi_heap_check() on all heaps which share the given capabilities. Optionally
* print errors if the heaps are corrupt.
*
* Call ``heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors)`` to check
* all regions' heaps.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
* @param print_errors Print specific errors if heap corruption is found.
*
* @return True if all heaps are valid, False if at least one heap is corrupt.
*/
bool heap_caps_check_integrity(uint32_t caps, bool print_errors);

View file

@ -0,0 +1,136 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "sdkconfig.h"
#include <stdint.h>
#include <esp_err.h>
#if !defined(CONFIG_HEAP_TRACING) && !defined(HEAP_TRACE_SRCFILE)
#warning "esp_heap_trace.h is included but heap tracing is disabled in menuconfig, functions are no-ops"
#endif
#ifndef CONFIG_HEAP_TRACING_STACK_DEPTH
#define CONFIG_HEAP_TRACING_STACK_DEPTH 0
#endif
typedef enum {
HEAP_TRACE_ALL,
HEAP_TRACE_LEAKS,
} heap_trace_mode_t;
/**
* @brief Trace record data type. Stores information about an allocated region of memory.
*/
typedef struct {
uint32_t ccount; ///< CCOUNT of the CPU when the allocation was made. LSB (bit value 1) is the CPU number (0 or 1). */
void *address; ///< Address which was allocated
size_t size; ///< Size of the allocation
void *alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which allocated the memory.
void *freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which freed the memory (all zero if not freed.)
} heap_trace_record_t;
/**
* @brief Initialise heap tracing in standalone mode.
* @note Standalone mode is the only mode currently supported.
*
* This function must be called before any other heap tracing functions.
*
* To disable heap tracing and allow the buffer to be freed, stop tracing and then call heap_trace_init_standalone(NULL, 0);
*
* @param record_buffer Provide a buffer to use for heap trace data. Must remain valid any time heap tracing is enabled, meaning
* it must be allocated from internal memory not in PSRAM.
* @param num_records Size of the heap trace buffer, as number of record structures.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing is currently in progress.
* - ESP_OK Heap tracing initialised successfully.
*/
esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records);
/**
* @brief Start heap tracing. All heap allocations & frees will be traced, until heap_trace_stop() is called.
*
* @note heap_trace_init_standalone() must be called to provide a valid buffer, before this function is called.
*
* @note Calling this function while heap tracing is running will reset the heap trace state and continue tracing.
*
* @param mode Mode for tracing.
* - HEAP_TRACE_ALL means all heap allocations and frees are traced.
* - HEAP_TRACE_LEAKS means only suspected memory leaks are traced. (When memory is freed, the record is removed from the trace buffer.)
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE A non-zero-length buffer has not been set via heap_trace_init_standalone().
* - ESP_OK Tracing is started.
*/
esp_err_t heap_trace_start(heap_trace_mode_t mode);
/**
* @brief Stop heap tracing.
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not in progress.
* - ESP_OK Heap tracing stopped..
*/
esp_err_t heap_trace_stop(void);
/**
* @brief Resume heap tracing which was previously stopped.
*
* Unlike heap_trace_start(), this function does not clear the
* buffer of any pre-existing trace records.
*
* The heap trace mode is the same as when heap_trace_start() was
* last called (or HEAP_TRACE_ALL if heap_trace_start() was never called).
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was already started.
* - ESP_OK Heap tracing resumed.
*/
esp_err_t heap_trace_resume(void);
/**
* @brief Return number of records in the heap trace buffer
*
* It is safe to call this function while heap tracing is running.
*/
size_t heap_trace_get_count(void);
/**
* @brief Return a raw record from the heap trace buffer
*
* @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode record indexing may
* skip entries unless heap tracing is stopped first.
*
* @param index Index (zero-based) of the record to return.
* @param[out] record Record where the heap trace record will be copied.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not initialised.
* - ESP_ERR_INVALID_ARG Index is out of bounds for current heap trace record count.
* - ESP_OK Record returned successfully.
*/
esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record);
/**
* @brief Dump heap trace record data to stdout
*
* @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode the dump may skip
* entries unless heap tracing is stopped first.
*
*
*/
void heap_trace_dump(void);

View file

@ -19,11 +19,43 @@
#include <stddef.h>
#include <stdio.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
/* Defines compile-time configuration macros */
#include "multi_heap_config.h"
#ifndef MULTI_HEAP_POISONING
/* if no heap poisoning, public API aliases directly to these implementations */
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
__attribute__((alias("multi_heap_malloc_impl")));
void multi_heap_free(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_free_impl")));
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
__attribute__((alias("multi_heap_realloc_impl")));
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_get_allocated_size_impl")));
multi_heap_handle_t multi_heap_register(void *start, size_t size)
__attribute__((alias("multi_heap_register_impl")));
void multi_get_heap_info(multi_heap_handle_t heap, multi_heap_info_t *info)
__attribute__((alias("multi_heap_get_info_impl")));
size_t multi_heap_free_size(multi_heap_handle_t heap)
__attribute__((alias("multi_heap_free_size_impl")));
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
__attribute__((alias("multi_heap_minimum_free_size_impl")));
#endif
#define ALIGN(X) ((X) & ~(sizeof(void *)-1))
#define ALIGN_UP(X) ALIGN((X)+sizeof(void *)-1)
@ -194,6 +226,11 @@ static heap_block_t *merge_adjacent(heap_t *heap, heap_block_t *a, heap_block_t
heap->free_bytes += sizeof(a->header);
}
#ifdef MULTI_HEAP_POISONING_SLOW
/* b's former block header needs to be replaced with a fill pattern */
multi_heap_internal_poison_fill_region(b, sizeof(heap_block_t), free);
#endif
return a;
}
@ -235,7 +272,7 @@ static void split_if_necessary(heap_t *heap, heap_block_t *block, size_t size, h
heap->free_bytes += block_data_size(new_block);
}
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p)
{
heap_block_t *pb = get_block(p);
@ -244,7 +281,7 @@ size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
return block_data_size(pb);
}
multi_heap_handle_t multi_heap_register(void *start, size_t size)
multi_heap_handle_t multi_heap_register_impl(void *start, size_t size)
{
heap_t *heap = (heap_t *)ALIGN_UP((intptr_t)start);
uintptr_t end = ALIGN((uintptr_t)start + size);
@ -285,7 +322,7 @@ void multi_heap_set_lock(multi_heap_handle_t heap, void *lock)
heap->lock = lock;
}
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
{
heap_block_t *best_block = NULL;
heap_block_t *prev_free = NULL;
@ -336,7 +373,7 @@ void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
return best_block->data;
}
void multi_heap_free(multi_heap_handle_t heap, void *p)
void multi_heap_free_impl(multi_heap_handle_t heap, void *p)
{
heap_block_t *pb = get_block(p);
@ -378,7 +415,7 @@ void multi_heap_free(multi_heap_handle_t heap, void *p)
}
void *multi_heap_realloc(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)
{
heap_block_t *pb = get_block(p);
void *result;
@ -387,14 +424,16 @@ void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
assert(heap != NULL);
if (p == NULL) {
return multi_heap_malloc(heap, size);
return multi_heap_malloc_impl(heap, size);
}
assert_valid_block(heap, pb);
assert(!is_free(pb) && "realloc arg should be allocated");
if (size == 0) {
multi_heap_free(heap, p);
/* note: calling multi_free_impl() here as we've already been
through any poison-unwrapping */
multi_heap_free_impl(heap, p);
return NULL;
}
@ -449,10 +488,13 @@ void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
if (result == NULL) {
// Need to allocate elsewhere and copy data over
result = multi_heap_malloc(heap, size);
//
// (Calling _impl versions here as we've already been through any
// unwrapping for heap poisoning features.)
result = multi_heap_malloc_impl(heap, size);
if (result != NULL) {
memcpy(result, pb->data, block_data_size(pb));
multi_heap_free(heap, pb->data);
multi_heap_free_impl(heap, pb->data);
}
}
@ -511,10 +553,26 @@ bool multi_heap_check(multi_heap_handle_t heap, bool print_errors)
total_free_bytes += block_data_size(b);
}
}
#ifdef MULTI_HEAP_POISONING
if (!is_last_block(b)) {
/* For slow heap poisoning, any block should contain correct poisoning patterns and/or fills */
bool poison_ok;
if (is_free(b) && b != heap->last_block) {
uint32_t block_len = (intptr_t)get_next_block(b) - (intptr_t)b - sizeof(heap_block_t);
poison_ok = multi_heap_internal_check_block_poisoning(&b[1], block_len, true, print_errors);
}
else {
poison_ok = multi_heap_internal_check_block_poisoning(b->data, block_data_size(b), false, print_errors);
}
valid = poison_ok && valid;
}
#endif
} /* for(heap_block_t b = ... */
if (prev != heap->last_block) {
FAIL_PRINT("CORRUPT HEAP: Ended at %p not %p\n", prev, heap->last_block);
FAIL_PRINT("CORRUPT HEAP: Last block %p not %p\n", prev, heap->last_block);
}
if (!is_free(heap->last_block)) {
FAIL_PRINT("CORRUPT HEAP: Expected prev block %p to be free\n", heap->last_block);
@ -547,7 +605,7 @@ void multi_heap_dump(multi_heap_handle_t heap)
MULTI_HEAP_UNLOCK(heap->lock);
}
size_t multi_heap_free_size(multi_heap_handle_t heap)
size_t multi_heap_free_size_impl(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
@ -555,7 +613,7 @@ size_t multi_heap_free_size(multi_heap_handle_t heap)
return heap->free_bytes;
}
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
@ -563,7 +621,7 @@ size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
return heap->minimum_free_bytes;
}
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info)
{
memset(info, 0, sizeof(multi_heap_info_t));

View file

@ -0,0 +1,37 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef ESP_PLATFORM
#include "sdkconfig.h"
/* Configuration macros for multi-heap */
#ifdef CONFIG_HEAP_POISONING_LIGHT
#define MULTI_HEAP_POISONING
#endif
#ifdef CONFIG_HEAP_POISONING_COMPREHENSIVE
#define MULTI_HEAP_POISONING
#define MULTI_HEAP_POISONING_SLOW
#endif
#else /* !ESP_PLATFORM */
/* Host-side tests, enable full poisoning */
#define MULTI_HEAP_POISONING
#define MULTI_HEAP_POISONING_SLOW
#endif

View file

@ -0,0 +1,40 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
/* Internal definitions for the "implementation" of the multi_heap API,
as defined in multi_heap.c.
If heap poisioning is disabled, these are aliased directly to the public API.
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);
multi_heap_handle_t multi_heap_register_impl(void *start, size_t size);
void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info);
size_t multi_heap_free_size_impl(multi_heap_handle_t heap);
size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap);
size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p);
/* Some internal functions for heap poisoning use */
/* Check an allocated block's poison bytes are correct. Called by multi_heap_check(). */
bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors);
/* Fill a region of memory with the free or malloced pattern.
Called when merging blocks, to overwrite the old block header.
*/
void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free);

View file

@ -0,0 +1,338 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/param.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
/* Defines compile-time configuration macros */
#include "multi_heap_config.h"
#ifdef MULTI_HEAP_POISONING
/* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readabilty */
#ifdef SLOW
#error "external header has defined SLOW"
#endif
#ifdef MULTI_HEAP_POISONING_SLOW
#define SLOW 1
#endif
#define MALLOC_FILL_PATTERN 0xce
#define FREE_FILL_PATTERN 0xfe
#define HEAD_CANARY_PATTERN 0xABBA1234
#define TAIL_CANARY_PATTERN 0xBAAD5678
typedef struct {
uint32_t head_canary;
size_t alloc_size;
} poison_head_t;
typedef struct {
uint32_t tail_canary;
} poison_tail_t;
#define POISON_OVERHEAD (sizeof(poison_head_t) + sizeof(poison_tail_t))
/* Given a "poisoned" region with pre-data header 'head', and actual data size 'alloc_size', fill in the head and tail
region checks.
Returns the pointer to the actual usable data buffer (ie after 'head')
*/
static uint8_t *poison_allocated_region(poison_head_t *head, size_t alloc_size)
{
uint8_t *data = (uint8_t *)(&head[1]); /* start of data ie 'real' allocated buffer */
poison_tail_t *tail = (poison_tail_t *)(data + alloc_size);
head->alloc_size = alloc_size;
head->head_canary = HEAD_CANARY_PATTERN;
uint32_t tail_canary = TAIL_CANARY_PATTERN;
if ((intptr_t)tail % sizeof(void *) == 0) {
tail->tail_canary = tail_canary;
} else {
/* unaligned tail_canary */
memcpy(&tail->tail_canary, &tail_canary, sizeof(uint32_t));
}
return data;
}
/* Given a pointer to some allocated data, check the head & tail poison structures (before & after it) that were
previously injected by poison_allocated_region().
Returns a pointer to the poison header structure, or NULL if the poison structures are corrupt.
*/
static poison_head_t *verify_allocated_region(void *data, bool print_errors)
{
poison_head_t *head = (poison_head_t *)((intptr_t)data - sizeof(poison_head_t));
poison_tail_t *tail = (poison_tail_t *)((intptr_t)data + head->alloc_size);
/* check if the beginning of the data was overwritten */
if (head->head_canary != HEAD_CANARY_PATTERN) {
if (print_errors) {
printf("CORRUPT HEAP: Bad head at %p. Expected 0x%08x got 0x%08x\n", &head->head_canary,
HEAD_CANARY_PATTERN, head->head_canary);
}
return NULL;
}
/* check if the end of the data was overrun */
uint32_t canary;
if ((intptr_t)tail % sizeof(void *) == 0) {
canary = tail->tail_canary;
} else {
/* tail is unaligned */
memcpy(&canary, &tail->tail_canary, sizeof(canary));
}
if (canary != TAIL_CANARY_PATTERN) {
if (print_errors) {
printf("CORRUPT HEAP: Bad tail at %p. Expected 0x%08x got 0x%08x\n", &tail->tail_canary,
TAIL_CANARY_PATTERN, canary);
}
return NULL;
}
return head;
}
#ifdef SLOW
/* Go through a region that should have the specified fill byte 'pattern',
verify it.
if expect_free is true, expect FREE_FILL_PATTERN otherwise MALLOC_FILL_PATTERN.
if swap_pattern is true, swap patterns in the buffer (ie replace MALLOC_FILL_PATTERN with FREE_FILL_PATTERN, and vice versa.)
Returns true if verification checks out.
*/
static bool verify_fill_pattern(void *data, size_t size, bool print_errors, bool expect_free, bool swap_pattern)
{
const uint32_t FREE_FILL_WORD = (FREE_FILL_PATTERN << 24) | (FREE_FILL_PATTERN << 16) | (FREE_FILL_PATTERN << 8) | FREE_FILL_PATTERN;
const uint32_t MALLOC_FILL_WORD = (MALLOC_FILL_PATTERN << 24) | (MALLOC_FILL_PATTERN << 16) | (MALLOC_FILL_PATTERN << 8) | MALLOC_FILL_PATTERN;
const uint32_t EXPECT_WORD = expect_free ? FREE_FILL_WORD : MALLOC_FILL_WORD;
const uint32_t REPLACE_WORD = expect_free ? MALLOC_FILL_WORD : FREE_FILL_WORD;
bool valid = true;
/* Use 4-byte operations as much as possible */
if ((intptr_t)data % 4 == 0) {
uint32_t *p = data;
while (size >= 4) {
if (*p != EXPECT_WORD) {
if (print_errors) {
printf("Invalid data at %p. Expected 0x%08x got 0x%08x\n", p, EXPECT_WORD, *p);
}
valid = false;
}
if (swap_pattern) {
*p = REPLACE_WORD;
}
p++;
size -= 4;
}
data = p;
}
uint8_t *p = data;
for (int i = 0; i < size; i++) {
if (p[i] != (uint8_t)EXPECT_WORD) {
if (print_errors) {
printf("Invalid data at %p. Expected 0x%02x got 0x%02x\n", p, (uint8_t)EXPECT_WORD, *p);
}
valid = false;
}
if (swap_pattern) {
p[i] = (uint8_t)REPLACE_WORD;
}
}
return valid;
}
#endif
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
{
poison_head_t *head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
if (head == NULL) {
return NULL;
}
uint8_t *data = poison_allocated_region(head, size);
#ifdef SLOW
/* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
assert( verify_fill_pattern(data, size, true, true, true) );
#endif
return data;
}
void multi_heap_free(multi_heap_handle_t heap, void *p)
{
if (p == NULL) {
return;
}
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
#ifdef SLOW
/* replace everything with FREE_FILL_PATTERN, including the poison head/tail */
memset(head, FREE_FILL_PATTERN,
head->alloc_size + POISON_OVERHEAD);
#endif
multi_heap_free_impl(heap, head);
}
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
{
poison_head_t *head = NULL;
if (p == NULL) {
return multi_heap_malloc(heap, size);
}
if (size == 0) {
multi_heap_free(heap, p);
return NULL;
}
/* p != NULL, size != 0 */
head = verify_allocated_region(p, true);
assert(head != NULL);
#ifndef SLOW
poison_head_t *new_head = multi_heap_realloc_impl(heap, head, size + POISON_OVERHEAD);
if (new_head == NULL) { // new allocation failed, everything stays as-is
return NULL;
}
return poison_allocated_region(new_head, size);
#else // SLOW
/* When slow poisoning is enabled, it becomes very fiddly to try and correctly fill memory when reallocing in place
(where the buffer may be moved (including to an overlapping address with the old buffer), grown, or shrunk in
place.)
For now we just malloc a new buffer, copy, and free. :|
*/
size_t orig_alloc_size = head->alloc_size;
poison_head_t *new_head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
if (new_head == NULL) {
return NULL;
}
void *new_data = poison_allocated_region(new_head, size);
memcpy(new_data, p, MIN(size, orig_alloc_size));
multi_heap_free(heap, p);
return new_data;
#endif
}
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
{
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
size_t result = multi_heap_get_allocated_size_impl(heap, head);
if (result > 0) {
return result - POISON_OVERHEAD;
}
return 0;
}
multi_heap_handle_t multi_heap_register(void *start, size_t size)
{
if (start != NULL) {
memset(start, FREE_FILL_PATTERN, size);
}
return multi_heap_register_impl(start, size);
}
static inline void subtract_poison_overhead(size_t *arg) {
if (*arg > POISON_OVERHEAD) {
*arg -= POISON_OVERHEAD;
} else {
*arg = 0;
}
}
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
{
multi_heap_get_info_impl(heap, info);
/* don't count the heap poison head & tail overhead in the allocated bytes size */
info->total_allocated_bytes -= info->allocated_blocks * POISON_OVERHEAD;
/* trim largest_free_block to account for poison overhead */
subtract_poison_overhead(&info->largest_free_block);
/* similarly, trim total_free_bytes so there's no suggestion that
a block this big may be available. */
subtract_poison_overhead(&info->total_free_bytes);
subtract_poison_overhead(&info->minimum_free_bytes);
}
size_t multi_heap_free_size(multi_heap_handle_t heap)
{
size_t r = multi_heap_free_size_impl(heap);
subtract_poison_overhead(&r);
return r;
}
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
{
size_t r = multi_heap_minimum_free_size_impl(heap);
subtract_poison_overhead(&r);
return r;
}
/* Internal hooks used by multi_heap to manage poisoning, while keeping some modularity */
bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors)
{
if (is_free) {
#ifdef SLOW
return verify_fill_pattern(start, size, print_errors, true, false);
#else
return true; /* can only verify empty blocks in SLOW mode */
#endif
} else {
void *data = (void *)((intptr_t)start + sizeof(poison_head_t));
poison_head_t *head = verify_allocated_region(data, print_errors);
if (head != NULL && head->alloc_size > size - POISON_OVERHEAD) {
/* block can be bigger than alloc_size, for reasons of alignment & fragmentation,
but block can never be smaller than head->alloc_size... */
if (print_errors) {
printf("CORRUPT HEAP: Size at %p expected <=0x%08x got 0x%08x\n", &head->alloc_size,
size - POISON_OVERHEAD, head->alloc_size);
}
return false;
}
return head != NULL;
}
}
void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free)
{
memset(start, is_free ? FREE_FILL_PATTERN : MALLOC_FILL_PATTERN, size);
}
#else // !MULTI_HEAP_POISONING
#ifdef MULTI_HEAP_POISONING_SLOW
#error "MULTI_HEAP_POISONING_SLOW requires MULTI_HEAP_POISONING"
#endif
#endif // MULTI_HEAP_POISONING

View file

@ -0,0 +1,124 @@
/*
Generic test for heap tracing support
Only compiled in if CONFIG_HEAP_TRACING is set
*/
#include <esp_types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_heap_trace.h"
#include "sdkconfig.h"
#include "unity.h"
#ifdef CONFIG_HEAP_TRACING
// only compile in heap tracing tests if tracing is enabled
TEST_CASE("heap trace leak check", "[heap]")
{
heap_trace_record_t recs[8];
heap_trace_init_standalone(recs, 8);
printf("Leak check test\n"); // Print something before trace starts, or stdout allocations skew total counts
fflush(stdout);
heap_trace_start(HEAP_TRACE_LEAKS);
void *a = malloc(64);
memset(a, '3', 64);
void *b = malloc(96);
memset(b, '4', 11);
printf("a.address %p vs %p b.address %p vs %p\n", a, recs[0].address, b, recs[1].address);
heap_trace_dump();
TEST_ASSERT_EQUAL(2, heap_trace_get_count());
heap_trace_record_t trace_a, trace_b;
heap_trace_get(0, &trace_a);
heap_trace_get(1, &trace_b);
printf("trace_a.address %p trace_bb.address %p\n", trace_a.address, trace_b.address);
TEST_ASSERT_EQUAL_PTR(a, trace_a.address);
TEST_ASSERT_EQUAL_PTR(b, trace_b.address);
TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_a.address);
TEST_ASSERT_EQUAL_PTR(recs[1].address, trace_b.address);
free(a);
TEST_ASSERT_EQUAL(1, heap_trace_get_count());
heap_trace_get(0, &trace_b);
TEST_ASSERT_EQUAL_PTR(b, trace_b.address);
/* buffer deletes trace_a when freed,
so trace_b at head of buffer */
TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_b.address);
heap_trace_stop();
}
TEST_CASE("heap trace wrapped buffer check", "[heap]")
{
const size_t N = 8;
heap_trace_record_t recs[N];
heap_trace_init_standalone(recs, N);
heap_trace_start(HEAP_TRACE_LEAKS);
void *ptrs[N+1];
for (int i = 0; i < N+1; i++) {
ptrs[i] = malloc(i*3);
}
// becuase other mallocs happen as part of this control flow,
// we can't guarantee N entries of ptrs[] are in the heap check buffer.
// but we should guarantee at least the last one is
bool saw_last_ptr = false;
for (int i = 0; i < N; i++) {
heap_trace_record_t rec;
heap_trace_get(i, &rec);
if (rec.address == ptrs[N-1]) {
saw_last_ptr = true;
}
}
TEST_ASSERT(saw_last_ptr);
void *other = malloc(6);
heap_trace_dump();
for (int i = 0; i < N+1; i++) {
free(ptrs[i]);
}
heap_trace_dump();
bool saw_other = false;
for (int i = 0; i < heap_trace_get_count(); i++) {
heap_trace_record_t rec;
heap_trace_get(i, &rec);
// none of ptr[]s should be in the heap trace any more
for (int j = 0; j < N+1; j++) {
TEST_ASSERT_NOT_EQUAL(ptrs[j], rec.address);
}
if (rec.address == other) {
saw_other = true;
}
}
// 'other' pointer should be somewhere in the leak dump
TEST_ASSERT(saw_other);
heap_trace_stop();
}
#endif

View file

@ -3,6 +3,7 @@ all: $(TEST_PROGRAM)
SOURCE_FILES = $(abspath \
../multi_heap.c \
../multi_heap_poisoning.c \
test_multi_heap.cpp \
main.cpp \
)
@ -12,7 +13,7 @@ INCLUDE_FLAGS = -I../include -I../../../tools/catch
GCOV ?= gcov
CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL -g -fstack-protector-all -m32
CFLAGS += -fprofile-arcs -ftest-coverage
CFLAGS += -Wall -Werror -fprofile-arcs -ftest-coverage
CXXFLAGS += -std=c++11 -Wall -Werror -fprofile-arcs -ftest-coverage
LDFLAGS += -lstdc++ -fprofile-arcs -ftest-coverage -m32

View file

@ -1,7 +1,10 @@
#include "catch.hpp"
#include "multi_heap.h"
#include "../multi_heap_config.h"
#include <string.h>
#include <assert.h>
/* Insurance against accidentally using libc heap functions in tests */
#undef free
@ -25,11 +28,7 @@ TEST_CASE("multi_heap simple allocations", "[multi_heap]")
multi_heap_dump(heap);
printf("*********************\n");
void *buf = multi_heap_malloc(heap, test_alloc_size);
printf("First malloc:\n");
multi_heap_dump(heap);
printf("*********************\n");
uint8_t *buf = (uint8_t *)multi_heap_malloc(heap, test_alloc_size);
printf("small_heap %p buf %p\n", small_heap, buf);
REQUIRE( buf != NULL );
@ -50,7 +49,7 @@ TEST_CASE("multi_heap simple allocations", "[multi_heap]")
printf("*********************\n");
/* Now there should be space for another allocation */
buf = multi_heap_malloc(heap, test_alloc_size);
buf = (uint8_t *)multi_heap_malloc(heap, test_alloc_size);
REQUIRE( buf != NULL );
multi_heap_free(heap, buf);
@ -60,15 +59,10 @@ TEST_CASE("multi_heap simple allocations", "[multi_heap]")
TEST_CASE("multi_heap fragmentation", "[multi_heap]")
{
uint8_t small_heap[200];
uint8_t small_heap[256];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
/* allocate enough that we can't fit 6 alloc_size blocks in the heap (due to
per-allocation block overhead. This calculation works for 32-bit pointers,
probably needs tweaking for 64-bit. */
size_t alloc_size = ((multi_heap_free_size(heap)) / 6) & ~(sizeof(void *) - 1);
printf("alloc_size %zu\n", alloc_size);
const size_t alloc_size = 24;
void *p[4];
for (int i = 0; i < 4; i++) {
@ -116,6 +110,8 @@ TEST_CASE("multi_heap many random allocations", "[multi_heap]")
uint8_t big_heap[1024];
const int NUM_POINTERS = 64;
printf("Running multi-allocation test...\n");
void *p[NUM_POINTERS] = { 0 };
size_t s[NUM_POINTERS] = { 0 };
multi_heap_handle_t heap = multi_heap_register(big_heap, sizeof(big_heap));
@ -139,16 +135,17 @@ TEST_CASE("multi_heap many random allocations", "[multi_heap]")
*/
size_t new_size = rand() % 1024;
void *new_p = multi_heap_realloc(heap, p[n], new_size);
printf("realloc %p -> %p (%zu -> %zu)\n", p[n], new_p, s[n], new_size);
multi_heap_check(heap, true);
if (new_size == 0 || new_p != NULL) {
p[n] = new_p;
s[n] = new_size;
if (new_size > 0) {
REQUIRE( p[n] >= big_heap );
REQUIRE( p[n] < big_heap + sizeof(big_heap) );
memset(p[n], n, new_size);
}
s[n] = new_size;
memset(p[n], n, s[n]);
}
REQUIRE( multi_heap_check(heap, true) );
continue;
}
@ -157,10 +154,11 @@ TEST_CASE("multi_heap many random allocations", "[multi_heap]")
/* Verify pre-existing contents of p[n] */
uint8_t compare[s[n]];
memset(compare, n, s[n]);
REQUIRE( memcmp(compare, p[n], s[n]) == 0 );
/*REQUIRE*/assert( memcmp(compare, p[n], s[n]) == 0 );
}
//printf("free %zu bytes %p\n", s[n], p[n]);
REQUIRE( multi_heap_check(heap, true) );
multi_heap_free(heap, p[n]);
printf("freed %p (%zu)\n", p[n], s[n]);
if (!multi_heap_check(heap, true)) {
printf("FAILED iteration %d after freeing %p\n", i, p[n]);
multi_heap_dump(heap);
@ -169,7 +167,9 @@ TEST_CASE("multi_heap many random allocations", "[multi_heap]")
}
s[n] = rand() % 1024;
REQUIRE( multi_heap_check(heap, true) );
p[n] = multi_heap_malloc(heap, s[n]);
printf("malloc %p (%zu)\n", p[n], s[n]);
if (p[n] != NULL) {
REQUIRE( p[n] >= big_heap );
REQUIRE( p[n] < big_heap + sizeof(big_heap) );
@ -294,7 +294,7 @@ TEST_CASE("multi_heap minimum-size allocations", "[multi_heap]")
TEST_CASE("multi_heap_realloc()", "[multi_heap]")
{
const uint32_t PATTERN = 0xABABDADA;
uint8_t small_heap[256];
uint8_t small_heap[300];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
uint32_t *a = (uint32_t *)multi_heap_malloc(heap, 64);
@ -311,6 +311,10 @@ TEST_CASE("multi_heap_realloc()", "[multi_heap]")
REQUIRE( c > b ); /* 'a' moves, 'c' takes the block after 'b' */
REQUIRE( *c == PATTERN );
#ifndef MULTI_HEAP_POISONING_SLOW
// "Slow" poisoning implementation doesn't reallocate in place, so these
// test will fail...
uint32_t *d = (uint32_t *)multi_heap_realloc(heap, c, 36);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( c == d ); /* 'c' block should be shrunk in-place */
@ -333,6 +337,7 @@ TEST_CASE("multi_heap_realloc()", "[multi_heap]")
g = (uint32_t *)multi_heap_realloc(heap, e, 128);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( e == g ); /* 'g' extends 'e' in place, into the space formerly held by 'f' */
#endif
}
TEST_CASE("corrupt heap block", "[multi_heap]")

View file

@ -696,10 +696,8 @@ tcp_new_port(void)
again:
#if ESP_RANDOM_TCP_PORT
tcp_port = system_get_time();
if (tcp_port < 0)
tcp_port = LWIP_RAND() - tcp_port;
tcp_port %= TCP_LOCAL_PORT_RANGE_START;
tcp_port = abs(LWIP_RAND()) % (TCP_LOCAL_PORT_RANGE_END - TCP_LOCAL_PORT_RANGE_START);
tcp_port += TCP_LOCAL_PORT_RANGE_START;
#else
if (tcp_port++ == TCP_LOCAL_PORT_RANGE_END) {
tcp_port = TCP_LOCAL_PORT_RANGE_START;

View file

@ -67,7 +67,6 @@ typedef struct sys_mbox_s {
#define sys_sem_valid( x ) ( ( ( *x ) == NULL) ? pdFALSE : pdTRUE )
#define sys_sem_set_invalid( x ) ( ( *x ) = NULL )
uint32_t system_get_time(void);
void sys_delay_ms(uint32_t ms);
sys_sem_t* sys_thread_sem_init(void);
void sys_thread_sem_deinit(void);

View file

@ -37,6 +37,7 @@
#include <sys/time.h>
#include <sys/fcntl.h>
#include "esp_task.h"
#include "esp_system.h"
#include "sdkconfig.h"
/* Enable all Espressif-only options */
@ -65,7 +66,7 @@
*/
#define SMEMCPY(dst,src,len) memcpy(dst,src,len)
#define LWIP_RAND rand
#define LWIP_RAND esp_random
/*
------------------------------------

View file

@ -229,7 +229,11 @@ uint32_t system_get_current_time(void) __attribute__((alias("system_get_time")))
uint32_t system_relative_time(uint32_t current_time)
{
return system_get_time() - current_time;
#if defined( WITH_FRC1 ) || defined( WITH_RTC )
return get_time_since_boot() - current_time;
#else
return 0;
#endif
}
uint64_t system_get_rtc_time(void)

View file

@ -58,7 +58,15 @@ typedef struct
extern const soc_reserved_region_t soc_reserved_regions[];
extern const size_t soc_reserved_region_count;
inline bool esp_ptr_dma_capable(const void *p)
inline static bool esp_ptr_dma_capable(const void *p)
{
return (intptr_t)p >= SOC_DMA_LOW && (intptr_t)p < SOC_DMA_HIGH;
}
inline static bool esp_ptr_executable(const void *p)
{
intptr_t ip = (intptr_t) p;
return (ip >= SOC_IROM_LOW && ip < SOC_IROM_HIGH)
|| (ip >= SOC_IRAM_LOW && ip < SOC_IRAM_HIGH)
|| (ip >= SOC_RTC_IRAM_LOW && ip < SOC_RTC_IRAM_HIGH);
}

View file

@ -109,6 +109,7 @@ INPUT = \
##
## Memory Allocation #
../components/heap/include/esp_heap_caps.h \
../components/heap/include/esp_heap_trace.h \
../components/heap/include/esp_heap_caps_init.h \
../components/heap/include/multi_heap.h \
## Interrupt Allocation

View file

@ -0,0 +1,200 @@
Heap Memory Debugging
=====================
Overview
--------
ESP-IDF integrates tools for requesting `heap information`_, `detecting heap corruption <heap corruption detection>`_, and `tracing memory leaks <heap tracing>`_. These can help track down memory-related bugs.
For general information about the heap memory allocator, see the :doc:`Heap Memory Allocation </api-reference/system/mem_alloc>` page.
.. _heap-information:
Heap Information
----------------
To obtain information about the state of the heap:
- ``xPortGetFreeHeapSize()`` is a FreeRTOS function which returns the number of free bytes in the (data memory) heap. This is equivalent to ``heap_caps_get_free_size(MALLOC_CAP_8BIT)``.
- ``heap_caps_get_free_size()`` can also be used to return the current free memory for different memory capabilities.
- ``heap_caps_get_largest_free_block()`` can be used to return the largest free block in the heap. This is the largest single allocation which is currently possible. Tracking this value and comparing to total free heap allows you to detect heap fragmentation.
- ``xPortGetMinimumEverFreeHeapSize()`` and the related ``heap_caps_get_minimum_free_size()`` can be used to track the heap "low water mark" since boot.
- ``heap_caps_get_info`` returns a ``multi_heap_info_t`` structure which contains the information from the above functions, plus some additional heap-specific data (number of allocations, etc.)
.. _heap-corruption:
Heap Corruption Detection
-------------------------
Heap corruption detection allows you to detect various types of heap memory errors:
- Out of bounds writes & buffer overflow.
- Writes to freed memory.
- Reads from freed or uninitialized memory,
Assertions
^^^^^^^^^^
The heap implementation (``multi_heap.c``, etc.) includes a lot of assertions which will fail if the heap memory is corrupted. To detect heap corruption most effectively, ensure that assertions are enabled in ``make menuconfig`` under ``Compiler options``.
It's also possible to manually check heap integrity by calling the ``heap_caps_check_integrity()`` function (see below). This function checks all of requested heap memory for integrity, and can be used even if assertions are disabled.
Configuration
^^^^^^^^^^^^^
In ``make menuconfig``, under ``Component config`` there is a menu ``Heap memory debugging``. The setting ``Heap corruption detection`` can be set to one of three levels:
Basic (no poisoning)
^^^^^^^^^^^^^^^^^^^^
This is the default level. No special heap corruption features are enabled, but checks will fail if any of the heap's internal data structures are overwritten or corrupted. This usually indicates a buffer overrun or out of bounds write.
If assertions are enabled, an assertion will also trigger if a double-free occurs (ie the same memory is freed twice).
Light impact
^^^^^^^^^^^^
At this level, heap memory is additionally "poisoned" with head and tail "canary bytes" before and after each block which is allocated. If an application writes outside the bounds of the allocated buffer, the canary bytes will be corrupted and the integrity check will fail.
"Basic" heap corruption checks can also detect out of bounds writes, but this setting is more precise as even a single byte overrun will always be detected. With Basic heap checks, the number of overrun bytes before a failure is detected will depend on the properties of the heap.
Similar to other heap checks, these "canary bytes" are checked via assertion whenever memory is freed and can also be checked manually via ``heap_caps_check_integrity()``.
This level increases memory usage, each individual allocation will use 9 to 12 additional bytes of memory (depending on alignment).
Comprehensive
^^^^^^^^^^^^^
This level incorporates the "light impact" detection features plus additional checks for uninitialised-access and use-after-free bugs. In this mode, all freshly allocated memory is filled with the pattern 0xCE, and all freed memory is filled with the pattern 0xFE.
If an application crashes reading/writing an address related to 0xCECECECE when this setting is enabled, this indicates it has read uninitialized memory. The application should be changed to either use calloc() (which zeroes memory), or initialize the memory before using it. The value 0xCECECECE may also be seen in stack-allocated automatic variables, because in IDF most task stacks are originally allocated from the heap and in C stack memory is uninitialized by default.
If an application crashes reading/writing an address related to 0xFEFEFEFE, this indicates it is reading heap memory after it has been freed (a "use after free bug".) The application should be changed to not access heap memory after it has been freed.
If the IDF heap allocator fails because the pattern 0xFEFEFEFE was not found in freed memory then this indicates the app has a use-after-free bug where it is writing to memory which has already been freed.
Enabling "Comprehensive" detection has a substantial runtime performance impact (as all memory needs to be set to the allocation patterns each time a malloc/free completes, and the memory also needs to be checked each time.)
Finding Heap Corruption
^^^^^^^^^^^^^^^^^^^^^^^
Memory corruption can be one of the hardest classes of bugs to find and fix, as one area of memory can be corrupted from a totally different place. Some tips:
- If you can find the address (in memory) which is being corrupted, you can set a watchpoint on this address via JTAG to have the CPU halt when it is written to.
- If you don't have JTAG, but you do know roughly when the corruption happens, then you can set a watchpoint in software. A fatal exception will occur when the watchpoint triggers. For example ``esp_set_watchpoint(0, (void *)addr, 4, ESP_WATCHPOINT_STORE``. Note that the watchpoint is set on the current running CPU only, so if you don't know which CPU is corrupting memory then you will need to call this function on both CPUs.
- For buffer overflows, `heap tracing`_ in ``HEAP_TRACE_ALL`` mode lets you see which callers allocate the memory address(es) immediately before the address which is being corrupted. There is a strong chance this is the code which overflows the buffer.
.. _heap-tracing:
Heap Tracing
------------
Heap Tracing allows tracing of code which allocates/frees memory.
Note: Heap tracing "standalone" mode is currently implemented, meaning that tracing does not require any external hardware but uses internal memory to hold trace data. Heap tracing via JTAG trace port is also planned.
Heap tracing can perform two functions:
- Leak checking: find memory which is allocated and never freed.
- Heap use analysis: show all functions that are allocating/freeing memory while the trace is running.
How To Diagnose Memory Leaks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you suspect a memory leak, the first step is to figure out which part of the program is leaking memory. Use the ``xPortGetFreeHeapSize()``, ``heap_caps_get_free()``, and related functions to track memory use over the life of the application. Try to narrow the leak down to a single function or sequence of functions where free memory always decreases and never recovers.
Once you've identified the code which you think is leaking:
- Under ``make menuconfig``, navigate to ``Component settings`` -> ``Heap Memory Debugging`` and set ``Enable heap tracing``.
- Call the function ``heap_trace_init_standalone()`` early in the program, to register a buffer which can be used to record the memory trace.
- Call the function ``heap_trace_start()`` to begin recording all mallocs/frees in the system. Call this immediately before the piece of code which you suspect is leaking memory.
- Call the function ``heap_trace_stop()`` to stop the trace once the suspect piece of code has finished executing.
- Call the function ``heap_trace_dump()`` to dump the results of the heap trace.
An example::
#include "esp_heap_trace.h"
#define NUM_RECORDS 100
static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM
...
void app_main()
{
...
ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) );
...
}
void some_function()
{
ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
do_something_you_suspect_is_leaking();
ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
...
}
The output from the heap trace will look something like this::
2 allocations trace (100 entry buffer)
32 bytes (@ 0x3ffaf214) allocated CPU 0 ccount 0x2e9b7384 caller 0x400d276d:0x400d27c1
0x400d276d: leak_some_memory at /path/to/idf/examples/get-started/blink/main/./blink.c:27
0x400d27c1: blink_task at /path/to/idf/examples/get-started/blink/main/./blink.c:52
8 bytes (@ 0x3ffaf804) allocated CPU 0 ccount 0x2e9b79c0 caller 0x400d2776:0x400d27c1
0x400d2776: leak_some_memory at /path/to/idf/examples/get-started/blink/main/./blink.c:29
0x400d27c1: blink_task at /path/to/idf/examples/get-started/blink/main/./blink.c:52
40 bytes 'leaked' in trace (2 allocations)
total allocations 2 total frees 0
(Above example output is using :doc:`IDF Monitor </get-started/idf-monitor>` to automatically decode PC addresses to their source files & line number.)
The first line indicates how many allocation entries are in the buffer, compared to its total size.
In ``HEAP_TRACE_LEAKS`` mode, for each traced memory allocation which has not already been freed a line is printed with:
- ``XX bytes`` is number of bytes allocated
- ``@ 0x...`` is the heap address returned from malloc/calloc.
- ``CPU x`` is the CPU (0 or 1) running when the allocation was made.
- ``ccount 0x...`` is the CCOUNT (CPU cycle count) register value when the allocation was mode. Is different for CPU 0 vs CPU 1.
- ``caller 0x...`` gives the call stack of the call to malloc()/free(), as a list of PC addresses.
These can be decoded to source files and line numbers, as shown above.
The depth of the call stack recorded for each trace entry can be configured in ``make menuconfig``, under ``Heap Memory Debugging`` -> ``Enable heap tracing`` -> ``Heap tracing stack depth``. Up to 10 stack frames can be recorded for each allocation (the default is 2). Each additional stack frame increases the memory usage of each ``heap_trace_record_t`` record by eight bytes.
Finally, the total number of 'leaked' bytes (bytes allocated but not freed while trace was running) is printed, and the total number of allocations this represents.
A warning will be printed if the trace buffer was not large enough to hold all the allocations which happened. If you see this warning, consider either shortening the tracing period or increasing the number of records in the trace buffer.
Performance Impact
^^^^^^^^^^^^^^^^^^
Enabling heap tracing in menuconfig increases the code size of your program, and has a very small negative impact on performance of heap allocation/free operations even when heap tracing is not running.
When heap tracing is running, heap allocation/free operations are substantially slower than when heap tracing is stopped. Increasing the depth of stack frames recorded for each allocation (see above) will also increase this performance impact.
False-Positive Memory Leaks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Not everything printed by ``heap_trace_dump()`` is necessarily a memory leak. Among things which may show up here, but are not memory leaks:
- Any memory which is allocated after ``heap_trace_start()`` but then freed after ``heap_trace_stop()`` will appear in the leak dump.
- Allocations may be made by other tasks in the system. Depending on the timing of these tasks, it's quite possible this memory is freed after ``heap_trace_stop()`` is called.
- The first time a task uses stdio - for example, when it calls printf() - a lock (RTOS mutex semaphore) is allocated by the libc. This allocation lasts until the task is deleted.
- The Bluetooth, WiFi, and TCP/IP libraries will allocate heap memory buffers to handle incoming or outgoing data. These memory buffers are usually short lived, but some may be shown in the heap leak trace if the data was received/transmitted by the lower levels of the network while the leak trace was running.
- TCP connections will continue to use some memory after they are closed, because of the ``TIME_WAIT`` state. After the ``TIME_WAIT`` period has completed, this memory will be freed.
One way to differentiate between "real" and "false positive" memory leaks is to call the suspect code multiple times while tracing is running, and look for patterns (multiple matching allocations) in the heap trace output.
API Reference - Heap Tracing
----------------------------
.. include:: /_build/inc/esp_heap_trace.inc

View file

@ -4,7 +4,8 @@ System API
.. toctree::
:maxdepth: 1
Memory Allocation <mem_alloc>
Heap Memory Allocation <mem_alloc>
Heap Memory Debugging <heap_debug>
Interrupt Allocation <intr_alloc>
Watchdogs <wdts>
Over The Air Updates (OTA) <ota>

View file

@ -1,43 +1,77 @@
Memory allocation
=================
Heap Memory Allocation
======================
Overview
--------
The ESP32 has multiple types of RAM. Internally, there's IRAM, DRAM as well as RAM that can be used as both. It's also
possible to connect external SPI flash to the ESP32; it's memory can be integrated into the ESP32s memory map using
possible to connect external SPI RAM to the ESP32 - external RAM can be integrated into the ESP32's memory map using
the flash cache.
In order to make use of all this memory, esp-idf has a capabilities-based memory allocator. Basically, if you want to have
memory with certain properties (for example, DMA-capable, or capable of executing code), you
can create an OR-mask of the required capabilities and pass that to pvPortMallocCaps. For instance, the normal malloc
code internally allocates memory with ```heap_caps_malloc(size, MALLOC_CAP_8BIT)``` in order to get data memory that is
byte-addressable.
For most purposes, the standard libc ``malloc()`` and ``free()`` functions can be used for heap allocation without any
issues.
Because malloc uses this allocation system as well, memory allocated using ```heap_caps_malloc()``` can be freed by calling
the standard ```free()``` function.
However, in order to fully make use of all of the memory types and their characteristics, esp-idf also has a
capabilities-based heap memory allocator. If you want to have memory with certain properties (for example, DMA-capable
memory, or executable memory), you can create an OR-mask of the required capabilities and pass that to
``heap_caps_malloc()``. For instance, the standard ``malloc()`` implementation internally allocates memory via
``heap_caps_malloc(size, MALLOC_CAP_8BIT)`` in order to get data memory that is byte-addressable.
Because malloc uses this allocation system as well, memory allocated using ``heap_caps_malloc()`` can be freed by calling
the standard ``free()`` function.
The "soc" component contains a list of memory regions for the chip, along with the type of each memory (aka its tag) and the associated capabilities for that memory type. On startup, a separate heap is initialised for each contiguous memory region. The capabilities-based allocator chooses the best heap for each allocation, based on the requested capabilities.
Special Uses
------------
DMA-Capable Memory
^^^^^^^^^^^^^^^^^^
Use the MALLOC_CAP_DMA flag to allocate memory which is suitable for use with hardware DMA engines (for example SPI and I2S). This capability flag excludes any external PSRAM.
32-Bit Accessible Memory
^^^^^^^^^^^^^^^^^^^^^^^^
If a certain memory structure is only addressed in 32-bit units, for example an array of ints or pointers, it can be
useful to allocate it with the MALLOC_CAP_32BIT flag. This also allows the allocator to give out IRAM memory; something
which it can't do for a normal malloc() call. This can help to use all the available memory in the ESP32.
Memory allocated with MALLOC_CAP_32BIT can *only* be accessed via 32-bit reads and writes, any other type of access will
generate a fatal LoadStoreError exception.
API Reference - Heap Allocation
-------------------------------
.. include:: /_build/inc/esp_heap_caps.inc
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 Corruption Detection <heap-corruption>`
- :ref:`Heap Tracing <heap-tracing>` (memory leak detection, monitoring, etc.)
API Reference - Initialisation
------------------------------
.. include:: /_build/inc/esp_heap_caps_init.inc
API Reference - Heap Regions
----------------------------
Implementation Notes
--------------------
Knowledge about the regions of memory in the chip comes from the "soc" component, which contains memory layout information for the chip.
Each contiguous region of memory contains its own memory heap. The heaps are created using the `multi_heap <API Reference - Multi Heap API>`_ functionality. multi_heap allows any contiguous region of memory to be used as a heap.
The heap capabilities allocator uses knowledge of the memory regions to initialize each individual heap. When you call a function in the heap capabilities API, it will find the most appropriate heap for the allocation (based on desired capabilities, available space, and preferences for each region's use) and then call the multi_heap function to use the heap situation in that particular region.
API Reference - Multi Heap API
------------------------------
(Note: The multi heap API is used internally by the heap capabilities allocator. Most IDF programs will never need to call this API directly.)
.. include:: /_build/inc/multi_heap.inc

View file

@ -9,6 +9,7 @@
#include "freertos/task.h"
#include "esp_log.h"
#include "soc/cpu.h"
#include "esp_heap_caps.h"
#define unity_printf ets_printf
@ -19,6 +20,63 @@ static struct test_desc_t* s_unity_tests_last = NULL;
// Inverse of the filter
static bool s_invert = false;
static size_t before_free_8bit;
static size_t before_free_32bit;
/* Each unit test is allowed to "leak" this many bytes.
TODO: Make this value editable by the test.
Will always need to be some value here, as fragmentation can reduce free space even when no leak is occuring.
*/
const size_t WARN_LEAK_THRESHOLD = 256;
const size_t CRITICAL_LEAK_THRESHOLD = 4096;
/* setUp runs before every test */
void setUp(void)
{
printf("%s", ""); /* sneakily lazy-allocate the reent structure for this test task */
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
if (before_free <= after_free) {
return;
}
size_t leaked = before_free - after_free;
if (leaked < WARN_LEAK_THRESHOLD) {
return;
}
printf("MALLOC_CAP_%s %s leak: Before %u bytes free, After %u bytes free (delta %u)\n",
type,
leaked < CRITICAL_LEAK_THRESHOLD ? "potential" : "critical",
before_free, after_free, leaked);
fflush(stdout);
TEST_ASSERT(leaked < CRITICAL_LEAK_THRESHOLD); /* fail the test if it leaks too much */
}
/* tearDown runs after every test */
void tearDown(void)
{
/* some FreeRTOS stuff is cleaned up by idle task */
vTaskDelay(5);
/* check if unit test has caused heap corruption in any heap */
TEST_ASSERT( heap_caps_check_integrity(MALLOC_CAP_INVALID, true) );
/* check for leaks */
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void unity_putc(int c)
{
if (c == '\n')

View file

@ -269,6 +269,15 @@ CONFIG_TIMER_TASK_STACK_DEPTH=2048
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_FREERTOS_DEBUG_INTERNALS is not set
#
# Heap memory debugging
#
# CONFIG_HEAP_POISONING_DISABLED is not set
# CONFIG_HEAP_POISONING_LIGHT is not set
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_HEAP_TRACING=y
CONFIG_HEAP_TRACING_STACK_DEPTH=2
#
# Log output
#