From e497d2446d2b622b63c2239d0353a1d8d6a6a71b Mon Sep 17 00:00:00 2001 From: Dmitry Frank Date: Tue, 26 Jan 2016 22:32:21 +0300 Subject: [PATCH] Added poisoning, integrity check, tests To enable integrity check, build with `-DUMM_INTEGRITY_CHECK`. To enable poisoning, build with `-DUMM_POISON`. You may see other possible adjustments in `umm_malloc_cfg_example.h` Plus, added tests, build and run with: `make -C test` --- test/Makefile | 33 + test/umm_malloc_cfg.h | 140 +++++ test/umm_malloc_test.c | 202 +++++++ umm_malloc.c | 601 ++++++++++++++----- umm_malloc.h | 2 + umm_malloc_cfg.h => umm_malloc_cfg_example.h | 51 ++ 6 files changed, 892 insertions(+), 137 deletions(-) create mode 100644 test/Makefile create mode 100644 test/umm_malloc_cfg.h create mode 100644 test/umm_malloc_test.c rename umm_malloc_cfg.h => umm_malloc_cfg_example.h (58%) diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..a0bcdad --- /dev/null +++ b/test/Makefile @@ -0,0 +1,33 @@ + +all: test test_poison test_integrity test_poison_integrity + +INCDIRS = -I.. -I. + +test: + @echo NORMAL + gcc --std=c99 $(CFLAGS) $(INCDIRS) -g3 -m32 \ + ../umm_malloc.c umm_malloc_test.c \ + -o test_umm + ./test_umm + +test_poison: + @echo POISON + gcc --std=c99 $(CFLAGS) $(INCDIRS) -DUMM_POISON -g3 -m32 \ + ../umm_malloc.c umm_malloc_test.c \ + -o test_umm + ./test_umm + +test_integrity: + @echo INTEGRITY + gcc --std=c99 $(CFLAGS) $(INCDIRS) -DUMM_INTEGRITY_CHECK -g3 -m32 \ + ../umm_malloc.c umm_malloc_test.c \ + -o test_umm + ./test_umm + +test_poison_integrity: + @echo POISON + INTEGRITY + gcc --std=c99 $(CFLAGS) $(INCDIRS) -DUMM_POISON -DUMM_INTEGRITY_CHECK -g3 -m32 \ + ../umm_malloc.c umm_malloc_test.c \ + -o test_umm + ./test_umm + diff --git a/test/umm_malloc_cfg.h b/test/umm_malloc_cfg.h new file mode 100644 index 0000000..a168e5d --- /dev/null +++ b/test/umm_malloc_cfg.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +/* + * Smartjs-specific configuration for umm_malloc + */ + +#ifndef _UMM_MALLOC_CFG_H +#define _UMM_MALLOC_CFG_H + +/* + * There are a number of defines you can set at compile time that affect how + * the memory allocator will operate. + * You can set them in your config file umm_malloc_cfg.h. + * In GNU C, you also can set these compile time defines like this: + * + * -D UMM_TEST_MAIN + * + * Set this if you want to compile in the test suite at the end of this file. + * + * If you leave this define unset, then you might want to set another one: + * + * -D UMM_REDEFINE_MEM_FUNCTIONS + * + * If you leave this define unset, then the function names are left alone as + * umm_malloc() umm_free() and umm_realloc() so that they cannot be confused + * with the C runtime functions malloc() free() and realloc() + * + * If you do set this define, then the function names become malloc() + * free() and realloc() so that they can be used as the C runtime functions + * in an embedded environment. + * + * -D UMM_BEST_FIT (defualt) + * + * Set this if you want to use a best-fit algorithm for allocating new + * blocks + * + * -D UMM_FIRST_FIT + * + * Set this if you want to use a first-fit algorithm for allocating new + * blocks + * + * -D UMM_DBG_LOG_LEVEL=n + * + * Set n to a value from 0 to 6 depending on how verbose you want the debug + * log to be + * + * ---------------------------------------------------------------------------- + * + * Support for this library in a multitasking environment is provided when + * you add bodies to the UMM_CRITICAL_ENTRY and UMM_CRITICAL_EXIT macros + * (see below) + * + * ---------------------------------------------------------------------------- + */ + +extern char test_umm_heap[]; +extern void umm_corruption(void); + +/* Start and end addresses of the heap */ +#define UMM_MALLOC_CFG__HEAP_ADDR (test_umm_heap) +#define UMM_MALLOC_CFG__HEAP_SIZE 0x10000 + +/* A couple of macros to make packing structures less compiler dependent */ + +#define UMM_H_ATTPACKPRE +#define UMM_H_ATTPACKSUF __attribute__((__packed__)) + +/* + * Callback that is called whenever a heap corruption is detected + */ +#define UMM_HEAP_CORRUPTION_CB() umm_corruption(); + +/* + * A couple of macros to make it easier to protect the memory allocator + * in a multitasking system. You should set these macros up to use whatever + * your system uses for this purpose. You can disable interrupts entirely, or + * just disable task switching - it's up to you + * + * NOTE WELL that these macros MUST be allowed to nest, because umm_free() is + * called from within umm_malloc() + */ + +#define UMM_CRITICAL_ENTRY() +#define UMM_CRITICAL_EXIT() + +/* + * -D UMM_INTEGRITY_CHECK : + * + * Enables heap integrity check before any heap operation. It affects + * performance, but does NOT consume extra memory. + * + * If integrity violation is detected, the message is printed and user-provided + * callback is called: `UMM_HEAP_CORRUPTION_CB()` + * + * Note that not all buffer overruns are detected: each buffer is aligned by + * 4 bytes, so there might be some trailing "extra" bytes which are not checked + * for corruption. + */ +/* +#define UMM_INTEGRITY_CHECK +*/ + +/* + * -D UMM_POISON : + * + * Enables heap poisoning: add predefined value (poison) before and after each + * allocation, and check before each heap operation that no poison is + * corrupted. + * + * Other than the poison itself, we need to store exact user-requested length + * for each buffer, so that overrun by just 1 byte will be always noticed. + * + * Customizations: + * + * UMM_POISON_SIZE_BEFORE: + * Number of poison bytes before each block, e.g. 2 + * UMM_POISON_SIZE_AFTER: + * Number of poison bytes after each block e.g. 2 + * UMM_POISONED_BLOCK_LEN_TYPE + * Type of the exact buffer length, e.g. `short` + * + * NOTE: each allocated buffer is aligned by 4 bytes. But when poisoning is + * enabled, actual pointer returned to user is shifted by + * `(sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE)`. + * It's your responsibility to make resulting pointers aligned appropriately. + * + * If poison corruption is detected, the message is printed and user-provided + * callback is called: `UMM_HEAP_CORRUPTION_CB()` + */ +/* +#define UMM_POISON +*/ +#define UMM_POISON_SIZE_BEFORE 4 +#define UMM_POISON_SIZE_AFTER 4 +#define UMM_POISONED_BLOCK_LEN_TYPE short + +#endif /* _UMM_MALLOC_CFG_H */ diff --git a/test/umm_malloc_test.c b/test/umm_malloc_test.c new file mode 100644 index 0000000..67da9a9 --- /dev/null +++ b/test/umm_malloc_test.c @@ -0,0 +1,202 @@ + +#include +#include +#include +#include + +#include "umm_malloc.h" + +#define TRY(v) do { \ + bool res = v;\ + if (!res) {\ + printf("assert failed: " #v "\n");\ + abort();\ + }\ +} while (0) + +char test_umm_heap[UMM_MALLOC_CFG__HEAP_SIZE]; +static int corruption_cnt = 0; + +void umm_corruption(void) { + corruption_cnt++; +} + +#if defined(UMM_POISON) +bool test_poison(void) { + + size_t size; + for (size = 1; size <= 16; size++) { + + { + umm_init(); + corruption_cnt = 0; + char *ptr = umm_malloc(size); + ptr[size]++; + + umm_free(ptr); + + if (corruption_cnt == 0) { + printf("corruption_cnt should not be 0, but it is\n"); + return false; + } + } + + { + umm_init(); + corruption_cnt = 0; + char *ptr = umm_calloc(1, size); + ptr[-1]++; + + umm_free(ptr); + + if (corruption_cnt == 0) { + printf("corruption_cnt should not be 0, but it is\n"); + return false; + } + } + } + + return true; +} +#endif + +#if defined(UMM_INTEGRITY_CHECK) +bool test_integrity_check(void) { + + size_t size; + for (size = 1; size <= 16; size++) { + + { + umm_init(); + corruption_cnt = 0; + char *ptr = umm_malloc(size); + memset(ptr, 0xfe, size + 8/* size of umm_block*/); + + umm_free(ptr); + + if (corruption_cnt == 0) { + printf("corruption_cnt should not be 0, but it is\n"); + return false; + } + } + + { + umm_init(); + corruption_cnt = 0; + char *ptr = umm_calloc(1, size); + ptr[-1]++; + + umm_free(ptr); + + if (corruption_cnt == 0) { + printf("corruption_cnt should not be 0, but it is\n"); + return false; + } + } + } + + return true; +} +#endif + +bool random_stress(void) { + void * ptr_array[256]; + size_t i; + int idx; + + corruption_cnt = 0; + + printf( "Size of umm_heap is %u\n", (unsigned int)sizeof(test_umm_heap) ); + + umm_init(); + + umm_info( NULL, 1 ); + + for( idx=0; idx<256; ++idx ) + ptr_array[idx] = (void *)NULL; + + for( idx=0; idx<100000; ++idx ) { + i = rand()%256; + + switch( rand() % 16 ) { + + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + { + ptr_array[i] = umm_realloc(ptr_array[i], 0); + break; + } + case 7: + case 8: + { + size_t size = rand()%40; + ptr_array[i] = umm_realloc(ptr_array[i], size ); + memset(ptr_array[i], 0xfe, size); + break; + } + + case 9: + case 10: + case 11: + case 12: + { + size_t size = rand()%100; + ptr_array[i] = umm_realloc(ptr_array[i], size ); + memset(ptr_array[i], 0xfe, size); + break; + } + + case 13: + case 14: + { + size_t size = rand()%200; + umm_free(ptr_array[i]); + ptr_array[i] = umm_calloc( 1, size ); + if (ptr_array[i] != NULL){ + int a; + for (a = 0; a < size; a++) { + if (((char *)ptr_array[i])[a] != 0x00) { + printf("calloc returned non-zeroed memory\n"); + return false; + } + } + } + memset(ptr_array[i], 0xfe, size); + break; + } + + default: + { + size_t size = rand()%400; + umm_free(ptr_array[i]); + ptr_array[i] = umm_malloc( size ); + memset(ptr_array[i], 0xfe, size); + break; + } + } + + } + + + return (corruption_cnt == 0); +} + +int main(void) { +#if defined(UMM_INTEGRITY_CHECK) + TRY(test_integrity_check()); +#endif + +#if defined(UMM_POISON) + TRY(test_poison()); +#endif + + TRY(random_stress()); + + return 0; +} + diff --git a/umm_malloc.c b/umm_malloc.c index 696e261..cb2ec3c 100644 --- a/umm_malloc.c +++ b/umm_malloc.c @@ -609,11 +609,10 @@ UMM_H_ATTPACKPRE typedef struct umm_block_t { /* ------------------------------------------------------------------------- */ -#ifndef UMM_TEST_MAIN - #ifdef UMM_REDEFINE_MEM_FUNCTIONS # define umm_free free # define umm_malloc malloc +# define umm_calloc calloc # define umm_realloc realloc #endif @@ -622,15 +621,6 @@ unsigned short int umm_numblocks = 0; #define UMM_NUMBLOCKS (umm_numblocks) -#else - -umm_block umm_heap[8192]; -const unsigned short int umm_numblocks = sizeof(umm_heap)/sizeof(umm_block); - -#define UMM_NUMBLOCKS (umm_numblocks) - -#endif - /* ------------------------------------------------------------------------ */ #define UMM_BLOCK(b) (umm_heap[b]) @@ -641,6 +631,320 @@ const unsigned short int umm_numblocks = sizeof(umm_heap)/sizeof(umm_block); #define UMM_PFREE(b) (UMM_BLOCK(b).body.free.prev) #define UMM_DATA(b) (UMM_BLOCK(b).body.data) +/* integrity check (UMM_INTEGRITY_CHECK) {{{ */ +#if defined(UMM_INTEGRITY_CHECK) +/* + * Perform integrity check of the whole heap data. Returns 1 in case of + * success, 0 otherwise. + * + * First of all, iterate through all free blocks, and check that all backlinks + * match (i.e. if block X has next free block Y, then the block Y should have + * previous free block set to X). + * + * Additionally, we check that each free block is correctly marked with + * `UMM_FREELIST_MASK` on the `next` pointer: during iteration through free + * list, we mark each free block by the same flag `UMM_FREELIST_MASK`, but + * on `prev` pointer. We'll check and unmark it later. + * + * Then, we iterate through all blocks in the heap, and similarly check that + * all backlinks match (i.e. if block X has next block Y, then the block Y + * should have previous block set to X). + * + * But before checking each backlink, we check that the `next` and `prev` + * pointers are both marked with `UMM_FREELIST_MASK`, or both unmarked. + * This way, we ensure that the free flag is in sync with the free pointers + * chain. + */ +static int integrity_check(void) { + int ok = 1; + unsigned short int prev; + unsigned short int cur; + + if (umm_heap == NULL) { + umm_init(); + } + + /* Iterate through all free blocks */ + prev = 0; + while(1) { + cur = UMM_NFREE(prev); + + /* Check that next free block number is valid */ + if (cur >= UMM_NUMBLOCKS) { + printf("heap integrity broken: too large next free num: %d " + "(in block %d, addr 0x%lx)\n", cur, prev, + (unsigned long)&UMM_NBLOCK(prev)); + ok = 0; + goto clean; + } + if (cur == 0) { + /* No more free blocks */ + break; + } + + /* Check if prev free block number matches */ + if (UMM_PFREE(cur) != prev) { + printf("heap integrity broken: free links don't match: " + "%d -> %d, but %d -> %d\n", + prev, cur, cur, UMM_PFREE(cur)); + ok = 0; + goto clean; + } + + UMM_PBLOCK(cur) |= UMM_FREELIST_MASK; + + prev = cur; + } + + /* Iterate through all blocks */ + prev = 0; + while(1) { + cur = UMM_NBLOCK(prev) & UMM_BLOCKNO_MASK; + + /* Check that next block number is valid */ + if (cur >= UMM_NUMBLOCKS) { + printf("heap integrity broken: too large next block num: %d " + "(in block %d, addr 0x%lx)\n", cur, prev, + (unsigned long)&UMM_NBLOCK(prev)); + ok = 0; + goto clean; + } + if (cur == 0) { + /* No more blocks */ + break; + } + + /* make sure the free mark is appropriate, and unmark it */ + if ((UMM_NBLOCK(cur) & UMM_FREELIST_MASK) + != (UMM_PBLOCK(cur) & UMM_FREELIST_MASK)) + { + printf("heap integrity broken: mask wrong at addr 0x%lx: n=0x%x, p=0x%x\n", + (unsigned long)&UMM_NBLOCK(cur), + (UMM_NBLOCK(cur) & UMM_FREELIST_MASK), + (UMM_PBLOCK(cur) & UMM_FREELIST_MASK) + ); + ok = 0; + goto clean; + } + + /* unmark */ + UMM_PBLOCK(cur) &= UMM_BLOCKNO_MASK; + + /* Check if prev block number matches */ + if (UMM_PBLOCK(cur) != prev) { + printf("heap integrity broken: block links don't match: " + "%d -> %d, but %d -> %d\n", + prev, cur, cur, UMM_PBLOCK(cur)); + ok = 0; + goto clean; + } + + prev = cur; + } + +clean: + if (!ok){ + UMM_HEAP_CORRUPTION_CB(); + } + return ok; +} + +#define INTEGRITY_CHECK() integrity_check() +#else +/* + * Integrity check is disabled, so just define stub macro + */ +#define INTEGRITY_CHECK() 1 +#endif +/* }}} */ + +/* poisoning (UMM_POISON) {{{ */ +#if defined(UMM_POISON) +#define POISON_BYTE (0xa5) + +/* + * Yields a size of the poison for the block of size `s`. + * If `s` is 0, returns 0. + */ +#define POISON_SIZE(s) ( \ + (s) ? \ + (UMM_POISON_SIZE_BEFORE + UMM_POISON_SIZE_AFTER + \ + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) \ + ) : 0 \ + ) + +/* + * Print memory contents starting from given `ptr` + */ +static void dump_mem ( const unsigned char *ptr, size_t len ) { + while (len--) { + printf(" 0x%.2x", (unsigned int)(*ptr++)); + } +} + +/* + * Put poison data at given `ptr` and `poison_size` + */ +static void put_poison( unsigned char *ptr, size_t poison_size ) { + memset(ptr, POISON_BYTE, poison_size); +} + +/* + * Check poison data at given `ptr` and `poison_size`. `where` is a pointer to + * a string, either "before" or "after", meaning, before or after the block. + * + * If poison is there, returns 1. + * Otherwise, prints the appropriate message, and returns 0. + */ +static int check_poison( const unsigned char *ptr, size_t poison_size, + const char *where) { + size_t i; + int ok = 1; + + for (i = 0; i < poison_size; i++) { + if (ptr[i] != POISON_BYTE) { + ok = 0; + break; + } + } + + if (!ok) { + printf("there is no poison %s the block. " + "Expected poison address: 0x%lx, actual data:", + where, (unsigned long)ptr); + dump_mem(ptr, poison_size); + printf("\n"); + } + + return ok; +} + +/* + * Check if a block is properly poisoned. Must be called only for non-free + * blocks. + */ +static int check_poison_block( umm_block *pblock ) { + int ok = 1; + + if (pblock->header.used.next & UMM_FREELIST_MASK) { + printf("check_poison_block is called for free block 0x%lx\n", + (unsigned long)pblock); + } else { + /* the block is used; let's check poison */ + unsigned char *pc = (unsigned char *)pblock->body.data; + unsigned char *pc_cur; + + pc_cur = pc + sizeof(UMM_POISONED_BLOCK_LEN_TYPE); + if (!check_poison(pc_cur, UMM_POISON_SIZE_BEFORE, "before")) { + UMM_HEAP_CORRUPTION_CB(); + ok = 0; + goto clean; + } + + pc_cur = pc + *((UMM_POISONED_BLOCK_LEN_TYPE *)pc) - UMM_POISON_SIZE_AFTER; + if (!check_poison(pc_cur, UMM_POISON_SIZE_AFTER, "after")) { + UMM_HEAP_CORRUPTION_CB(); + ok = 0; + goto clean; + } + } + +clean: + return ok; +} + +/* + * Iterates through all blocks in the heap, and checks poison for all used + * blocks. + */ +static int check_poison_all_blocks(void) { + int ok = 1; + unsigned short int blockNo = 0; + + if (umm_heap == NULL) { + umm_init(); + } + + /* Now iterate through the blocks list */ + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + + while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) { + if ( !(UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK) ) { + /* This is a used block (not free), so, check its poison */ + ok = check_poison_block(&UMM_BLOCK(blockNo)); + if (!ok){ + break; + } + } + + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + } + + return ok; +} + +/* + * Takes a pointer returned by actual allocator function (`_umm_malloc` or + * `_umm_realloc`), puts appropriate poison, and returns adjusted pointer that + * should be returned to the user. + * + * `size_w_poison` is a size of the whole block, including a poison. + */ +static void *get_poisoned( unsigned char *ptr, size_t size_w_poison ) { + if (size_w_poison != 0 && ptr != NULL) { + + /* Put exact length of the user's chunk of memory */ + memcpy(ptr, &size_w_poison, sizeof(UMM_POISONED_BLOCK_LEN_TYPE)); + + /* Poison beginning and the end of the allocated chunk */ + put_poison(ptr + sizeof(UMM_POISONED_BLOCK_LEN_TYPE), + UMM_POISON_SIZE_BEFORE); + put_poison(ptr + size_w_poison - UMM_POISON_SIZE_AFTER, + UMM_POISON_SIZE_AFTER); + + /* Return pointer at the first non-poisoned byte */ + return ptr + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE; + } else { + return ptr; + } +} + +/* + * Takes "poisoned" pointer (i.e. pointer returned from `get_poisoned()`), + * and checks that the poison of this particular block is still there. + * + * Returns unpoisoned pointer, i.e. actual pointer to the allocated memory. + */ +static void *get_unpoisoned( unsigned char *ptr ) { + if (ptr != NULL) { + unsigned short int c; + + ptr -= (sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE); + + /* Figure out which block we're in. Note the use of truncated division... */ + c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); + + check_poison_block(&UMM_BLOCK(c)); + } + + return ptr; +} + +#define CHECK_POISON_ALL_BLOCKS() check_poison_all_blocks() +#define GET_POISONED(ptr, size) get_poisoned(ptr, size) +#define GET_UNPOISONED(ptr) get_unpoisoned(ptr) + +#else +/* + * Integrity check is disabled, so just define stub macros + */ +#define POISON_SIZE(s) 0 +#define CHECK_POISON_ALL_BLOCKS() 1 +#define GET_POISONED(ptr, size) (ptr) +#define GET_UNPOISONED(ptr) (ptr) +#endif +/* }}} */ + /* ---------------------------------------------------------------------------- * One of the coolest things about this little library is that it's VERY * easy to get debug information about the memory heap by simply iterating @@ -673,8 +977,8 @@ void *umm_info( void *ptr, int force ) { DBG_LOG_FORCE( force, "\n\nDumping the umm_heap...\n" ); - DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", - (unsigned int)(&UMM_BLOCK(blockNo)), + DBG_LOG_FORCE( force, "|0x%08lx|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), @@ -706,12 +1010,12 @@ void *umm_info( void *ptr, int force ) { ummHeapInfo.maxFreeContiguousBlocks = curBlocks; } - DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", - (unsigned int)(&UMM_BLOCK(blockNo)), + DBG_LOG_FORCE( force, "|0x%08lx|B %5i|NB %5i|PB %5i|Z %5u|NF %5i|PF %5i|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), - curBlocks, + (unsigned int)curBlocks, UMM_NFREE(blockNo), UMM_PFREE(blockNo) ); @@ -728,12 +1032,12 @@ void *umm_info( void *ptr, int force ) { ++ummHeapInfo.usedEntries; ummHeapInfo.usedBlocks += curBlocks; - DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|\n", - (unsigned int)(&UMM_BLOCK(blockNo)), + DBG_LOG_FORCE( force, "|0x%08lx|B %5i|NB %5i|PB %5i|Z %5u|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), - curBlocks ); + (unsigned int)curBlocks ); } blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; @@ -754,8 +1058,8 @@ void *umm_info( void *ptr, int force ) { } } - DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", - (unsigned int)(&UMM_BLOCK(blockNo)), + DBG_LOG_FORCE( force, "|0x%08lx|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", + (unsigned long)(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), @@ -779,61 +1083,6 @@ void *umm_info( void *ptr, int force ) { return( NULL ); } -/* ------------------------------------------------------------------------- */ - -static void umm_init( void ) { - /* init heap pointer and size, and memset it to 0 */ - umm_heap = (umm_block *)UMM_MALLOC_CFG__HEAP_ADDR; - umm_numblocks = (UMM_MALLOC_CFG__HEAP_SIZE / sizeof(umm_block)); - memset(umm_heap, 0x00, UMM_MALLOC_CFG__HEAP_SIZE); - - /* setup initial blank heap structure */ - { - /* index of the 0th `umm_block` */ - const unsigned short int block_0th = 0; - /* index of the 1st `umm_block` */ - const unsigned short int block_1th = 1; - /* index of the latest `umm_block` */ - const unsigned short int block_last = UMM_NUMBLOCKS - 1; - - /* setup the 0th `umm_block`, which just points to the 1st */ - UMM_NBLOCK(block_0th) = block_1th; - UMM_NFREE(block_0th) = block_1th; - - /* - * Now, we need to set the whole heap space as a huge free block. We should - * not touch the 0th `umm_block`, since it's special: the 0th `umm_block` - * is the head of the free block list. It'a a part of the heap invariant. - * - * See the detailed explanation at the beginning of the file. - */ - - /* - * 1th `umm_block` has pointers: - * - * - next `umm_block`: the latest one - * - prev `umm_block`: the 0th - * - * Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK` - */ - UMM_NBLOCK(block_1th) = block_last | UMM_FREELIST_MASK; - UMM_NFREE(block_1th) = block_last; - UMM_PBLOCK(block_1th) = block_0th; - UMM_PFREE(block_1th) = block_0th; - - /* - * latest `umm_block` has pointers: - * - * - next `umm_block`: 0 (meaning, there are no more `umm_blocks`) - * - prev `umm_block`: the 1st - */ - UMM_NBLOCK(block_last) = 0; - UMM_NFREE(block_last) = 0; - UMM_PBLOCK(block_last) = block_1th; - UMM_PFREE(block_last) = block_1th; - } -} - /* ------------------------------------------------------------------------ */ static unsigned short int umm_blocks( size_t size ) { @@ -929,9 +1178,66 @@ static unsigned short int umm_assimilate_down( unsigned short int c, unsigned sh return( UMM_PBLOCK(c) ); } +/* ------------------------------------------------------------------------- */ + +void umm_init( void ) { + /* init heap pointer and size, and memset it to 0 */ + umm_heap = (umm_block *)UMM_MALLOC_CFG__HEAP_ADDR; + umm_numblocks = (UMM_MALLOC_CFG__HEAP_SIZE / sizeof(umm_block)); + memset(umm_heap, 0x00, UMM_MALLOC_CFG__HEAP_SIZE); + + /* setup initial blank heap structure */ + { + /* index of the 0th `umm_block` */ + const unsigned short int block_0th = 0; + /* index of the 1st `umm_block` */ + const unsigned short int block_1th = 1; + /* index of the latest `umm_block` */ + const unsigned short int block_last = UMM_NUMBLOCKS - 1; + + /* setup the 0th `umm_block`, which just points to the 1st */ + UMM_NBLOCK(block_0th) = block_1th; + UMM_NFREE(block_0th) = block_1th; + + /* + * Now, we need to set the whole heap space as a huge free block. We should + * not touch the 0th `umm_block`, since it's special: the 0th `umm_block` + * is the head of the free block list. It's a part of the heap invariant. + * + * See the detailed explanation at the beginning of the file. + */ + + /* + * 1th `umm_block` has pointers: + * + * - next `umm_block`: the latest one + * - prev `umm_block`: the 0th + * + * Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK` + * + * And it's the last free block, so the next free block is 0. + */ + UMM_NBLOCK(block_1th) = block_last | UMM_FREELIST_MASK; + UMM_NFREE(block_1th) = 0; + UMM_PBLOCK(block_1th) = block_0th; + UMM_PFREE(block_1th) = block_0th; + + /* + * latest `umm_block` has pointers: + * + * - next `umm_block`: 0 (meaning, there are no more `umm_blocks`) + * - prev `umm_block`: the 1st + * + * It's not a free block, so we don't touch NFREE / PFREE at all. + */ + UMM_NBLOCK(block_last) = 0; + UMM_PBLOCK(block_last) = block_1th; + } +} + /* ------------------------------------------------------------------------ */ -void umm_free( void *ptr ) { +static void _umm_free( void *ptr ) { unsigned short int c; @@ -1017,8 +1323,7 @@ void umm_free( void *ptr ) { /* ------------------------------------------------------------------------ */ -void *umm_malloc( size_t size ) { - +static void *_umm_malloc( size_t size ) { unsigned short int blocks; unsigned short int blockSize = 0; @@ -1148,7 +1453,7 @@ void *umm_malloc( size_t size ) { /* ------------------------------------------------------------------------ */ -void *umm_realloc( void *ptr, size_t size ) { +static void *_umm_realloc( void *ptr, size_t size ) { unsigned short int blocks; unsigned short int blockSize; @@ -1172,7 +1477,7 @@ void *umm_realloc( void *ptr, size_t size ) { if( ((void *)NULL == ptr) ) { DBG_LOG_DEBUG( "realloc the NULL pointer - call malloc()\n" ); - return( umm_malloc(size) ); + return( _umm_malloc(size) ); } /* @@ -1184,7 +1489,7 @@ void *umm_realloc( void *ptr, size_t size ) { if( 0 == size ) { DBG_LOG_DEBUG( "realloc to 0 size, just free the block\n" ); - umm_free( ptr ); + _umm_free( ptr ); return( (void *)NULL ); } @@ -1297,7 +1602,7 @@ void *umm_realloc( void *ptr, size_t size ) { DBG_LOG_DEBUG( "realloc %i to a smaller block %i, shrink and free the leftover bits\n", blockSize, blocks ); umm_make_new_block( c, blocks, 0, 0 ); - umm_free( (void *)&UMM_DATA(c+blocks) ); + _umm_free( (void *)&UMM_DATA(c+blocks) ); } else { /* New block is bigger than the old block... */ @@ -1306,15 +1611,15 @@ void *umm_realloc( void *ptr, size_t size ) { DBG_LOG_DEBUG( "realloc %i to a bigger block %i, make new, copy, and free the old\n", blockSize, blocks ); /* - * Now umm_malloc() a new/ one, copy the old data to the new block, and + * Now _umm_malloc() a new/ one, copy the old data to the new block, and * free up the old block, but only if the malloc was sucessful! */ - if( (ptr = umm_malloc( size )) ) { + if( (ptr = _umm_malloc( size )) ) { memcpy( ptr, oldptr, curSize ); } - umm_free( oldptr ); + _umm_free( oldptr ); } /* Release the critical section... */ @@ -1325,80 +1630,102 @@ void *umm_realloc( void *ptr, size_t size ) { /* ------------------------------------------------------------------------ */ -void * umm_calloc( size_t num, size_t size ) { - size_t s = size * num; - void *ret = umm_malloc(s); +void *umm_malloc( size_t size ) { + void *ret; - memset(ret, 0x00, s); - return ret; -} + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return NULL; + } -size_t umm_free_heap_size( void ) { - umm_info(NULL, 0); - return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block); + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return NULL; + } + + size += POISON_SIZE(size); + + ret = _umm_malloc( size ); + + ret = GET_POISONED(ret, size); + + return ret; } /* ------------------------------------------------------------------------ */ -#ifdef UMM_TEST_MAIN +void *umm_calloc( size_t num, size_t item_size ) { + void *ret; + size_t size = item_size * num; -main() { + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return NULL; + } - void * ptr_array[256]; + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return NULL; + } - size_t i; + size += POISON_SIZE(size); + ret = _umm_malloc(size); + memset(ret, 0x00, size); - int idx; + ret = GET_POISONED(ret, size); - printf( "Size of umm_heap is %i\n", sizeof(umm_heap) ); - printf( "Size of header is %i\n", sizeof(umm_heap[0]) ); - printf( "Size of nblock is %i\n", sizeof(umm_heap[0].header.used.next) ); - printf( "Size of pblock is %i\n", sizeof(umm_heap[0].header.used.prev) ); - printf( "Size of nfree is %i\n", sizeof(umm_heap[0].body.free.next) ); - printf( "Size of pfree is %i\n", sizeof(umm_heap[0].body.free.prev) ); + return ret; +} + +/* ------------------------------------------------------------------------ */ - memset( umm_heap, 0, sizeof(umm_heap) ); +void *umm_realloc( void *ptr, size_t size ) { + void *ret; + + ptr = GET_UNPOISONED(ptr); + + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return NULL; + } - umm_info( NULL, 1 ); + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return NULL; + } - for( idx=0; idx<256; ++idx ) - ptr_array[idx] = (void *)NULL; + size += POISON_SIZE(size); + ret = _umm_realloc( ptr, size ); - for( idx=0; idx<6553500; ++idx ) { - i = rand()%256; + ret = GET_POISONED(ret, size); - switch( rand() % 16 ) { + return ret; +} - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: ptr_array[i] = umm_realloc(ptr_array[i], 0); - break; - case 7: - case 8: ptr_array[i] = umm_realloc(ptr_array[i], rand()%40 ); - break; +/* ------------------------------------------------------------------------ */ - case 9: - case 10: - case 11: - case 12: ptr_array[i] = umm_realloc(ptr_array[i], rand()%100 ); - break; +void umm_free( void *ptr ) { - case 13: - case 14: ptr_array[i] = umm_realloc(ptr_array[i], rand()%200 ); - break; + ptr = GET_UNPOISONED(ptr); - default: ptr_array[i] = umm_realloc(ptr_array[i], rand()%400 ); - break; - } + /* check poison of each blocks, if poisoning is enabled */ + if (!CHECK_POISON_ALL_BLOCKS()) { + return; + } + /* check full integrity of the heap, if this check is enabled */ + if (!INTEGRITY_CHECK()) { + return; } - umm_info( NULL, 1 ); + _umm_free( ptr ); +} + +/* ------------------------------------------------------------------------ */ +size_t umm_free_heap_size( void ) { + umm_info(NULL, 0); + return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block); } -#endif +/* ------------------------------------------------------------------------ */ diff --git a/umm_malloc.h b/umm_malloc.h index 54a22e2..7f1c058 100644 --- a/umm_malloc.h +++ b/umm_malloc.h @@ -27,6 +27,8 @@ UMM_HEAP_INFO; extern UMM_HEAP_INFO ummHeapInfo; +void umm_init( void ); + void *umm_info( void *ptr, int force ); void *umm_malloc( size_t size ); diff --git a/umm_malloc_cfg.h b/umm_malloc_cfg_example.h similarity index 58% rename from umm_malloc_cfg.h rename to umm_malloc_cfg_example.h index f6f2636..4c625ad 100644 --- a/umm_malloc_cfg.h +++ b/umm_malloc_cfg_example.h @@ -73,4 +73,55 @@ #define UMM_CRITICAL_ENTRY() #define UMM_CRITICAL_EXIT() +/* + * -D UMM_INTEGRITY_CHECK : + * + * Enables heap integrity check before any heap operation. It affects + * performance, but does NOT consume extra memory. + * + * If integrity violation is detected, the message is printed and user-provided + * callback is called: `UMM_HEAP_CORRUPTION_CB()` + * + * Note that not all buffer overruns are detected: each buffer is aligned by + * 4 bytes, so there might be some trailing "extra" bytes which are not checked + * for corruption. + */ +/* +#define UMM_INTEGRITY_CHECK +*/ + +/* + * -D UMM_POISON : + * + * Enables heap poisoning: add predefined value (poison) before and after each + * allocation, and check before each heap operation that no poison is + * corrupted. + * + * Other than the poison itself, we need to store exact user-requested length + * for each buffer, so that overrun by just 1 byte will be always noticed. + * + * Customizations: + * + * UMM_POISON_SIZE_BEFORE: + * Number of poison bytes before each block, e.g. 2 + * UMM_POISON_SIZE_AFTER: + * Number of poison bytes after each block e.g. 2 + * UMM_POISONED_BLOCK_LEN_TYPE + * Type of the exact buffer length, e.g. `short` + * + * NOTE: each allocated buffer is aligned by 4 bytes. But when poisoning is + * enabled, actual pointer returned to user is shifted by + * `(sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE)`. + * It's your responsibility to make resulting pointers aligned appropriately. + * + * If poison corruption is detected, the message is printed and user-provided + * callback is called: `UMM_HEAP_CORRUPTION_CB()` + */ +/* +#define UMM_POISON +*/ +#define UMM_POISON_SIZE_BEFORE 2 +#define UMM_POISON_SIZE_AFTER 2 +#define UMM_POISONED_BLOCK_LEN_TYPE short + #endif /* _UMM_MALLOC_CFG_H */