*(.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.*)
*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
--- /dev/null
+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.
+
+ "Basic" heap corruption detection disables poisoning, but in Debug mode an assertion will trigger if an
+ application overwrites the heap's internal block headers and corrupts the heap structure.
+
+ "Light impact" detection "poisons" memory allocated from the heap with 4-byte head and tail "canaries". If an
+ application overruns its bounds at all, these canaries will be compromised. This option increases memory usage,
+ each allocated buffer will use an extra 9-12 bytes from the heap.
+
+ "Comprehensive" detection incorporates the "light impact" detection features plus additional checks for
+ uinitialised-access and use-after-free bugs. All freshly allocated memory is set to the pattern 0xce, and all
+ freed memory is set to the pattern 0xfe. These options have a noticeable additional performance impact.
+
+ To check the integrity of all heap memory at runtime, see the function heap_caps_check_integrity().
+
+config HEAP_POISONING_DISABLED
+ bool "Basic (no poisoning)"
+config HEAP_POISONING_LIGHT
+ bool "Light impact"
+config HEAP_POISONING_COMPREHENSIVE
+ bool "Comprehensive"
+endchoice
+
+endmenu
#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_get_heap_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)
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;
}
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);
-
+
assert_valid_block(heap, pb);
assert(!is_free(pb));
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);
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;
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);
}
-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;
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;
}
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);
}
}
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);
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;
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;
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));
--- /dev/null
+// 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
--- /dev/null
+// 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);
--- /dev/null
+// 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
SOURCE_FILES = $(abspath \
../multi_heap.c \
+ ../multi_heap_poisoning.c \
test_multi_heap.cpp \
main.cpp \
)
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
#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
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 );
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);
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++) {
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));
*/
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;
}
/* 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);
}
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) );
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);
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 */
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]")