diff --git a/centrallix-lib/include/newmalloc.h b/centrallix-lib/include/newmalloc.h index 6022e72e..3fb75258 100644 --- a/centrallix-lib/include/newmalloc.h +++ b/centrallix-lib/include/newmalloc.h @@ -32,7 +32,7 @@ typedef struct _ov int Magic; struct _ov *Next; } - Overlay,*pOverlay; + Overlay, *pOverlay; #ifdef NMMALLOC_DEBUG #define BLK_LEAK_CHECK 1 @@ -44,11 +44,10 @@ typedef struct _ov #define SIZED_BLK_COUNTING 1 #endif -/** nmMalloc block caching causes Valgrind to lose track of what call - ** stack actually allocated the block to begin with. So if we're using - ** valgrind, turn off block caching altogether, and make the nmSysXyz() calls - ** just pass-throughs. - **/ +/*** nmMalloc block caching causes Valgrind to lose track of the call stack + *** where the developer allocated the block. If we are using Valgrind, this + *** caching is disabled. ALso, the nmSysXyz() call are simply pass-throughs. + ***/ #ifdef USING_VALGRIND #define NO_BLK_CACHE 1 #undef NM_USE_SYSMALLOC @@ -70,16 +69,18 @@ void nmSetErrFunction(int (*error_fn)()); void nmClear(); void nmCheckAll(); // checks for buffer overflows void* nmMalloc(int size); -void nmFree(void* ptr,int size); +void nmFree(void* ptr, int size); void nmStats(); -void nmRegister(int size,char* name); +void nmRegister(int size, char* name); +void nmPrintNames(int size); void nmDebug(); void nmDeltas(); void* nmSysMalloc(int size); void nmSysFree(void* ptr); -void* nmSysRealloc(void* ptr, int newsize); -char* nmSysStrdup(const char* ptr); +void* nmSysRealloc(void* ptr, int new_size); +char* nmSysStrdup(const char* str); +/** Tagging system (not implemented). **/ void nmEnableTagging(); void nmRegisterTagID(int tag_id, char* name); void nmSetTag(void* ptr, int tag_id, void* tag); diff --git a/centrallix-lib/src/newmalloc.c b/centrallix-lib/src/newmalloc.c index 32c02b85..dd0684ac 100644 --- a/centrallix-lib/src/newmalloc.c +++ b/centrallix-lib/src/newmalloc.c @@ -1,13 +1,3 @@ -#ifdef HAVE_CONFIG_H -#include "cxlibconfig-internal.h" -#endif -#include -#include -#include -#include -#include "magic.h" -#include "newmalloc.h" - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ @@ -18,8 +8,8 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: NewMalloc memory manager (newmalloc.c, .h) */ -/* Author: Greg Beeley (GRB) */ +/* Module: NewMalloc memory manager (newmalloc.c, .h) */ +/* Author: Greg Beeley (GRB) */ /* */ /* Description: This module provides block-caching memory allocation */ /* and debugging services, to help find memory leaks as */ @@ -36,19 +26,31 @@ /* all.... */ /************************************************************************/ +#ifdef HAVE_CONFIG_H +#include "cxlibconfig-internal.h" +#endif + +#include +#include +#include +#include +#include + +#include "magic.h" +#include "newmalloc.h" -/** define BUFFER_OVERFLOW_CHECKING for buffer overflow checking -*** this works off of magic numbers in the 4 bytes on either end -*** of the buffer that is returned to the user, at the cost of -*** 16 bytes of memory per buffer, and a full scan of the list -*** of allocated memory twice per nmMalloc() or nmFree() call -*** -*** the check can be made at any time from normal code by calling: -*** nmCheckAll() -*** -- this functions is still defined if BUFFER_OVERFLOW_CHECKING is -*** not defined, but it becomes a NOOP -**/ +/*** BUFFER_OVERFLOW_CHECKING adds 4 bytes of magic data to either end of + *** the memory buffer returned to the user by nmMalloc(). This allows us + *** to detect clobbered memory at the cost of increasing memory overhead + *** by 16 bytes per allocated buffer, and requires a full scan of the + *** allocated memory list twice in each nmMalloc() or nmFree() call. + *** + *** This check can also be run manually by calling nmCheckAll(). + *** + *** Note: nmCheckAll() is still defined if BUFFER_OVERFLOW_CHECKING is not + *** defined (it is a NOOP). + ***/ #ifdef BUFFER_OVERFLOW_CHECKING typedef struct _mem { @@ -68,7 +70,8 @@ typedef struct _mem pMemStruct startMemList; #endif -pOverlay lists[MAX_SIZE+1]; +/** List of overlay structs, used for caching allocated memory. **/ +pOverlay lists[MAX_SIZE+1]; /* TODO: Greg - Is this 65KB global variable a problem? (On the stack, it would be...) */ int listcnt[MAX_SIZE+1]; int outcnt[MAX_SIZE+1]; int outcnt_delta[MAX_SIZE+1]; @@ -84,7 +87,7 @@ void* blks[MAX_BLOCKS]; int blksiz[MAX_BLOCKS]; #endif -int isinit=0; +bool is_init = false; int (*err_fn)() = NULL; int nmsys_outcnt[MAX_SIZE+1]; @@ -101,130 +104,186 @@ typedef struct _RB pRegisteredBlockType blknames[MAX_SIZE+1]; + +/** Initialize the NewMalloc subsystem. **/ void nmInitialize() { - int i; - - for(i=0;i<=MAX_SIZE;i++) lists[i]=NULL; - for(i=0;i<=MAX_SIZE;i++) listcnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) outcnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) outcnt_delta[i] = 0; - for(i=0;i<=MAX_SIZE;i++) blknames[i] = NULL; - for(i=0;i<=MAX_SIZE;i++) usagecnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) nmsys_outcnt[i] = 0; - for(i=0;i<=MAX_SIZE;i++) nmsys_outcnt_delta[i] = 0; -#ifdef BLK_LEAK_CHECK - for(i=0;imagic_start!=MGK_MEMSTART) + int ret = 0; + + /** Check the starting magic value. **/ + if (mem->magic_start != MGK_MEMSTART) { - printf("bad magic_start at %p (%p) -- 0x%08x != 0x%08x\n",MEMDATA(mem),mem,mem->magic_start,MGK_MEMSTART); + fprintf(stderr, + "Bad magic_start at %p (%p) -- 0x%08x != 0x%08x\n", + MEMDATA(mem), mem, mem->magic_start, MGK_MEMSTART + ); ret = -1; } - if(ENDMAGIC(mem)!=MGK_MEMEND) + + /** Check the ending magic value. **/ + if (ENDMAGIC(mem) != MGK_MEMEND) { - printf("bad magic_end at %p (%p) -- 0x%08x != 0x%08x\n",MEMDATA(mem),mem,ENDMAGIC(mem),MGK_MEMEND); + fprintf(stderr, + "Bad magic_end at %p (%p) -- 0x%08x != 0x%08x\n", + MEMDATA(mem), mem, ENDMAGIC(mem), MGK_MEMEND + ); ret = -1; } + return ret; } #endif +/*** Check the before and after magic values on all MemStructs to detect memory + *** buffer overflows. Causes a seg fault if such an overflow is detected. + ***/ void nmCheckAll() { -#ifdef BUFFER_OVERFLOW_CHECKING - pMemStruct mem; - int ret=0; - mem=startMemList; - while(mem) - { - if(nmCheckItem(mem)==-1) - ret=-1; - mem=mem->next; - } - if(ret==-1) + #ifdef BUFFER_OVERFLOW_CHECKING + int ret = 0; + + /** Traverse the memory list and check each item. **/ + for (pMemStruct mem = startMemList; mem != NULL; mem = mem->next) + if (nmCheckItem(mem) == -1) ret = -1; + + /** Handle error. **/ + if (ret == -1) { printf("causing segfault to halt.......\n"); - *(int*)NULL=0; + *(int*)NULL = 0; } -#endif + #endif } + #ifdef BUFFER_OVERFLOW_CHECKING +/*** Allocate new memory, using before and after magic values. + *** + *** @param size The size of the memory buffer to be allocated. + *** @returns A pointer to the start of the allocated memory buffer. + ***/ void* nmDebugMalloc(int size) { - pMemStruct tmp; - - tmp = (pMemStruct)malloc(size+EXTRA_MEM); - if(!tmp) - return NULL; + /** Allocate space for the data. **/ + pMemStruct tmp = (pMemStruct)malloc(size + EXTRA_MEM); + if(tmp == NULL) return NULL; + + /** Initialize data in the memory struct (including magic values). **/ + tmp->size = size; + tmp->magic_start = MGK_MEMSTART; + ENDMAGIC(tmp) = MGK_MEMEND; + + /** Prepend this mem struct to the MemList linked list. **/ tmp->next = startMemList; - startMemList=tmp; - tmp->size=size; - tmp->magic_start=MGK_MEMSTART; - ENDMAGIC(tmp)=MGK_MEMEND; + startMemList = tmp; + /** Return the memory data for the user to use. **/ return (void*)MEMDATA(tmp); } + +/*** Free a memory buffer allocated using `nmDebugMalloc()`. The before and + *** after magic values are checked and a warning is displayed if an overflow + *** has occurred (although this does not halt the program or function). + *** + *** @param ptr A pointer to the memory to be freed. + ***/ void -nmDebugFree(void *ptr) +nmDebugFree(void* ptr) { - pMemStruct tmp; - pMemStruct prev; - - tmp = MEMDATATOSTRUCT(ptr); + /** Get the mem struct for the item being freed. **/ + pMemStruct tmp = MEMDATATOSTRUCT(ptr); + + /** Verify that our data hasn't been clobbered. **/ nmCheckItem(tmp); - if(tmp==startMemList) - { - startMemList=tmp->next; + + /** Remove the item from the linked list. **/ + if (tmp == startMemList) + { /* Item is at the start. */ + startMemList = tmp->next; } else - { - prev = startMemList; - while(prev->next != tmp) - prev=prev->next; - prev->next=tmp->next; + { /* Item is not at the start. */ + /** Traverse the linked list to find the previous item. **/ + pMemStruct prev = startMemList; + while (prev->next != tmp) + prev = prev->next; + + /** Fix the gap that will be left by freeing this item. **/ + prev->next = tmp->next; } + + /** Free the item. **/ free(tmp); } -void* -nmDebugRealloc(void *ptr,int newsize) + +/*** Reallocates a memory block from `nmDebugMalloc()` (or `nmDebugRealloc()`) + *** to a new size, maintaining as much data as possible. Data loss only + *** occurs if the new size is smaller, in which case bits are lost starting + *** at the end of the buffer. + *** + *** @param ptr A pointer to the current buffer (deallocated by this call). + *** @param new_size The size that the buffer should be after this call. + *** @returns The new buffer, or NULL if an error occurs. + ***/ +void* +nmDebugRealloc(void* ptr, int new_size) { - void *newptr; - int oldsize; - - if(!ptr) - return nmDebugMalloc(newsize); - newptr=(void*)nmDebugMalloc(newsize); - if(!newptr) - return NULL; - oldsize=MEMDATATOSTRUCT(ptr)->size; - memmove(newptr,ptr,oldsize); + /** Behaves as nmDebugMalloc() if there is no target pointer. **/ + if (ptr == NULL) return nmDebugMalloc(new_size); + + /** Allocate new data. **/ + void* new_ptr = (void*)nmDebugMalloc(new_size); + if (new_ptr == NULL) return NULL; + + /** Move the old data. **/ + int old_size = MEMDATATOSTRUCT(ptr)->size; + memmove(new_ptr, ptr, old_size); + + /** Free the old allocation. **/ nmDebugFree(ptr); - return newptr; + + return new_ptr; } #else #define nmDebugMalloc(size) malloc(size) @@ -232,383 +291,534 @@ nmDebugRealloc(void *ptr,int newsize) #define nmDebugRealloc(ptr,size) realloc(ptr,size) #endif + void nmSetErrFunction(int (*error_fn)()) { err_fn = error_fn; - return; } + +/*** Clear the allocated memory block cache. This deallocates all memory + *** blocks that were marked as unused by a call to `nmFree()`, but were + *** moved to the cache instead of being freed to the OS memory pool. + ***/ void nmClear() { - int i; - pOverlay ov,del; - - if (!isinit) nmInitialize(); - - for(i=MIN_SIZE;i<=MAX_SIZE;i++) + if (!is_init) nmInitialize(); + + /** Iterate over each overlay list in the cache and clear it. **/ + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) + { + pOverlay ov = lists[size]; + while (ov != NULL) { - ov = lists[i]; - while(ov) - { - del = ov; - ov = ov->Next; - nmDebugFree(del); - } - lists[i] = NULL; + pOverlay del = ov; + ov = ov->Next; + nmDebugFree(del); } - - return; + lists[size] = NULL; + } } + +/*** Allocate memory using block caching. The allocated block may be supplied + *** by the cache (if available), or requested from the operating system. + *** + *** @attention Should only be used for memory that will benefit from caching + *** (e.g. a struct of a constant size). Dynamically sized memory (such as + *** variable-length strings) should be allocated using nmSysMalloc() to + *** avoid overhead from unnecessary caching. + *** + *** @param size The size of the memory block to be allocated. + *** @returns A pointer to the start of the allocated memory block. + ***/ void* nmMalloc(int size) { void* tmp; -#ifdef BLK_LEAK_CHECK - int i; -#endif - - if (!isinit) nmInitialize(); - -#ifdef GLOBAL_BLK_COUNTING - nmMallocCnt++; -#endif - -#ifdef BUFFER_OVERFLOW_CHECKING - nmCheckAll(); -#endif - - if (size <= MAX_SIZE && size >= MIN_SIZE) - { -#ifdef SIZED_BLK_COUNTING - outcnt[size]++; - usagecnt[size]++; -#endif - if (lists[size] == NULL) - { - tmp = (void*)nmDebugMalloc(size); - } - else - { -#ifdef GLOBAL_BLK_COUNTING - nmMallocHits++; -#endif - tmp = lists[size]; - ASSERTMAGIC(tmp,MGK_FREEMEM); - lists[size]=lists[size]->Next; -#ifdef SIZED_BLK_COUNTING - listcnt[size]--; -#endif - } - } - else - { -#ifdef GLOBAL_BLK_COUNTING - nmMallocTooBig++; - if (size > nmMallocLargest) nmMallocLargest = size; -#endif + if (!is_init) nmInitialize(); + + /** Handle counting. **/ + #ifdef GLOBAL_BLK_COUNTING + nmMallocCnt++; + #endif + + /** Handle buffer overflow check. **/ + #ifdef BUFFER_OVERFLOW_CHECKING + nmCheckAll(); + #endif + + /** Use caching if the size can be cached. **/ + if (MIN_SIZE <= size && size <= MAX_SIZE) + { + /** Handle counting. **/ + #ifdef SIZED_BLK_COUNTING + outcnt[size]++; + usagecnt[size]++; + #endif + + /** Cache check. **/ + if (lists[size] == NULL) + { /* Miss. */ tmp = (void*)nmDebugMalloc(size); } - - if (!tmp) - { - if (err_fn) err_fn("Insufficient system memory for operation."); - } else - { - if (size >= MIN_SIZE) - OVERLAY(tmp)->Magic = MGK_ALLOCMEM; + { /* Hit. */ + /** Handle counting. **/ + #ifdef GLOBAL_BLK_COUNTING + nmMallocHits++; + #endif + + /** Pop allocated memory off of the start of the cache list. **/ + tmp = lists[size]; + ASSERTMAGIC(tmp, MGK_FREEMEM); + lists[size] = lists[size]->Next; + + /** Handle counting. **/ + #ifdef SIZED_BLK_COUNTING + listcnt[size]--; + #endif } - -#ifdef BUFFER_OVERFLOW_CHECKING - nmCheckAll(); -#endif - -#ifdef BLK_LEAK_CHECK - for(i=0;i nmMallocLargest) nmMallocLargest = size; + #endif + + /** Caching isn't supported for this size: Allocate memory normally. **/ + tmp = (void*)nmDebugMalloc(size); + } + + /** TODO: Greg - We might need more docs for overlays. **/ + /*** It seems like this code block is doing too many different things. It + *** uses overlays, which I don't fully understand, so I wasn't able to + *** simplify it. + ***/ + if (tmp == NULL) + { + if (err_fn) err_fn("Insufficient system memory for operation."); + } + else + { + if (size >= MIN_SIZE) + OVERLAY(tmp)->Magic = MGK_ALLOCMEM; + } + + /** Handle overflow check. **/ + #ifdef BUFFER_OVERFLOW_CHECKING + nmCheckAll(); + #endif + + /** Handle memory leak checks. **/ + #ifdef BLK_LEAK_CHECK + /** Find the next open index in the blocks array and record this block. **/ + for (int i = 0; i < MAX_BLOCKS; i++) + { + if (blks[i] == NULL) { - if (blks[i] == NULL) - { - blks[i] = tmp; - blksiz[i] = size; - break; - } + blks[i] = tmp; + blksiz[i] = size; + break; } -#endif - + } + #endif + + /** Return the allocated memory. **/ return tmp; } +/*** Free a memory buffer allocated using `nmMalloc()`. This buffer may be + *** deallocated into the operating system memory pool, or it may be cached + *** for reuse with `nmMalloc()`. + *** + *** @attention Be EXTREMELY careful not to provide an incorrect size value. + *** This can lead to memory blocks being cached incorrectly, which causes + *** errors to occur when they are reallocated, possibly FAR AWAY from the + *** original source of the bug. + *** + *** @param ptr A pointer to the memory to be freed. + *** @param size The size of the memory block to be freed. (Note: Even though + *** the OS does store this value, the C memory manager does not make it + *** available to us, so it must be provided for this function to run.) + ***/ void nmFree(void* ptr, int size) { -#ifndef NO_BLK_CACHE -#ifdef DUP_FREE_CHECK - pOverlay tmp; -#endif -#endif -#ifdef BLK_LEAK_CHECK - int i; -#endif - - if (size >= MIN_SIZE) - ASSERTNOTMAGIC(ptr,MGK_FREEMEM); - - if (!ptr) return; - - if (!isinit) nmInitialize(); - -#ifdef GLOBAL_BLK_COUNTING - nmFreeCnt++; -#endif - -#ifdef BUFFER_OVERFLOW_CHECKING - nmCheckAll(); -#endif - -#ifdef BLK_LEAK_CHECK - for(i=0;i= MIN_SIZE) + ASSERTNOTMAGIC(ptr, MGK_FREEMEM); + + /** If the pointer is null, no work needed. **/ + if (ptr == NULL) return; + + /** Handle counting. **/ + #ifdef GLOBAL_BLK_COUNTING + nmFreeCnt++; + #endif + + /** Handle overflow check. **/ + #ifdef BUFFER_OVERFLOW_CHECKING + nmCheckAll(); + #endif + + /** Handle memory leak check. **/ + #ifdef BLK_LEAK_CHECK + /** Find this block in the blocks array and remove it. **/ + for (int i = 0; i < MAX_BLOCKS; i++) + { + if (blks[i] == ptr) { - if (blks[i] == ptr) - { - blks[i] = NULL; - blksiz[i] = 0; - break; - } + blks[i] = NULL; + blksiz[i] = 0; + break; } -#endif - -#ifndef NO_BLK_CACHE - if (size <= MAX_SIZE && size >= MIN_SIZE) + } + #endif + + /** Check for the block cache if it is enabled. **/ + #ifndef NO_BLK_CACHE + if (size <= MAX_SIZE && size >= MIN_SIZE) + { + /** Handle duplicate free check. **/ + #ifdef DUP_FREE_CHECK + /** Search the freed memory cache to see if this memory is there. **/ + for (pOverlay tmp = lists[size]; tmp != NULL; tmp = OVERLAY(tmp)->Next) { -#ifdef DUP_FREE_CHECK - tmp = lists[size]; - while(tmp) - { - ASSERTMAGIC(OVERLAY(tmp),MGK_FREEMEM); - if (OVERLAY(tmp) == OVERLAY(ptr)) - { - printf("Duplicate nmFree()!!! Size = %d, Address = %p\n",size,ptr); - if (err_fn) err_fn("Internal error - duplicate nmFree() occurred."); - return; - } - tmp = OVERLAY(tmp)->Next; + ASSERTMAGIC(OVERLAY(tmp),MGK_FREEMEM); + if (OVERLAY(tmp) == OVERLAY(ptr)) + { + printf("Duplicate nmFree()!!! Size = %d, Address = %p\n", size, ptr); + if (err_fn) err_fn("Internal error - duplicate nmFree() occurred."); + return; } -#endif -#ifdef SIZED_BLK_COUNTING - outcnt[size]--; -#endif - OVERLAY(ptr)->Next = lists[size]; - lists[size] = OVERLAY(ptr); -#ifdef SIZED_BLK_COUNTING - listcnt[size]++; -#endif - OVERLAY(ptr)->Magic = MGK_FREEMEM; } - else - { -#endif - nmDebugFree(ptr); -#ifndef NO_BLK_CACHE - } -#endif - -#ifdef BUFFER_OVERFLOW_CHECKING - nmCheckAll(); -#endif - - return; + #endif + + /** Add the freed memory to the cache. **/ + OVERLAY(ptr)->Magic = MGK_FREEMEM; + OVERLAY(ptr)->Next = lists[size]; + lists[size] = OVERLAY(ptr); + ptr = NULL; + + /** Handle counting. **/ + #ifdef SIZED_BLK_COUNTING + outcnt[size]--; + listcnt[size]++; + #endif + } + #endif + + /** Free the block if it was not consumed by the cache. **/ + if (ptr != NULL) + { + nmDebugFree(ptr); + ptr = NULL; + } + + /** Handle overflow check. **/ + #ifdef BUFFER_OVERFLOW_CHECKING + nmCheckAll(); + #endif } +/** Print stats about the NewMalloc subsystem, for debugging. **/ void nmStats() { - - if (!isinit) nmInitialize(); - - printf("NewMalloc subsystem statistics:\n"); - printf(" nmMalloc: %d calls, %d hits (%3.3f%%)\n", - nmMallocCnt, - nmMallocHits, - (float)nmMallocHits/(float)nmMallocCnt*100.0); - printf(" nmFree: %d calls\n", nmFreeCnt); - printf(" bigblks: %d too big, %d largest size\n\n", - nmMallocTooBig, - nmMallocLargest); - - return; + if (!is_init) nmInitialize(); + + /** Warn if statistics are not being tracked. **/ + #ifndef GLOBAL_BLK_COUNTING + printf("Warning: GLOBAL_BLK_COUNTING is disabled.\n"); + #endif + + /** Print subsystem stats. **/ + printf( + "NewMalloc subsystem statistics:\n" + " nmMalloc: %d calls, %d hits (%3.3f%%)\n" + " nmFree: %d calls\n" + " bigblks: %d too big, %d largest size\n\n", + nmMallocCnt, nmMallocHits, (float)nmMallocHits / (float)nmMallocCnt * 100.0, + nmFreeCnt, + nmMallocTooBig, nmMallocLargest + ); } +/** Register a new memory size with a name, for debugging. **/ void -nmRegister(int size,char* name) +nmRegister(int size, char* name) { pRegisteredBlockType blk; + + /** Ignore blocks too large to be cached. **/ + if (size > MAX_SIZE) return; + + /** Prepend a new RegisteredBlockType record to the list of records for this size. **/ + blk = (pRegisteredBlockType)malloc(sizeof(RegisteredBlockType)); + if (blk == NULL) return; + blk->Next = blknames[size]; + blknames[size] = blk; + + /** Initialize values for this record. **/ + blk->Magic = MGK_REGISBLK; + blk->Size = size; + strcpy(blk->Name, name); + } - if (size > MAX_SIZE) return; - - blk = (pRegisteredBlockType)malloc(sizeof(RegisteredBlockType)); - blk->Next = blknames[size]; - blk->Size = size; - strcpy(blk->Name,name); - blknames[size] = blk; - blk->Magic = MGK_REGISBLK; - - return; +/*** Print the registered names for a block of data of a given size to stdout. + *** + *** @param size The size of block to query. + ***/ +void +nmPrintNames(int size) + { + /*** Traverse the linked list that holds all registered names for the given + *** size and print each one. + **/ + for (pRegisteredBlockType blk = blknames[size]; blk != NULL; blk = blk->Next) + { + ASSERTMAGIC(blk, MGK_REGISBLK); + printf("%s ", blk->Name); + } } +/** Print debug information about the newmalloc system. **/ void nmDebug() { - int i; - pRegisteredBlockType blk; - - printf("size\tout\tcache\tusage\tnames\n"); - for(i=MIN_SIZE;iName); - blk = blk->Next; - } - printf("\n"); - } - } - printf("\n-----\n"); - printf("size\toutcnt\n-------\t-------\n"); - for(i=MIN_SIZE;i<=MAX_SIZE;i++) - { - if (nmsys_outcnt[i]) printf("%d\t%d\n",i,nmsys_outcnt[i]); - } + /** Print the header for the block sizes table. **/ + printf("size\tout\tcache\tusage\tnames\n"); + + /** Iterate through each possible block size. **/ + for (size_t size = MIN_SIZE; size < MAX_SIZE; size++) + { + /** Skip unused block sizes. **/ + if (usagecnt[size] == 0) continue; + + /** Print stats about this block size. **/ + printf("%ld\t%d\t%d\t%d\t", size, outcnt[size], listcnt[size], usagecnt[size]); + + /** Print each name for this block size. **/ + nmPrintNames(size); + printf("\n"); - - return; + } + + /** Print the header for the nmSysXYZ() info table. **/ + printf("\n-----\n"); + printf("size\toutcnt\n-------\t-------\n"); + + /** Iterate through each possible block size. **/ + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) + { + /** Skip unused block sizes. **/ + if (nmsys_outcnt[size] == 0) continue; + + /** Print the nmSysXYZ() block information. **/ + printf("%ld\t%d\n", size, nmsys_outcnt[size]); + } + printf("\n"); } +/** Print debug information about the newmalloc system. **/ void nmDeltas() { - int i, total; - pRegisteredBlockType blk; - - total = 0; - printf("size\tdelta\tnames\n-------\t-------\t-------\n"); - for(i=MIN_SIZE;i<=MAX_SIZE;i++) - { - if (outcnt[i] != outcnt_delta[i]) - { - printf("%d\t%d\t",i,outcnt[i] - outcnt_delta[i]); - total += (i * (outcnt[i] - outcnt_delta[i])); - blk = blknames[i]; - while(blk) - { - ASSERTMAGIC(blk,MGK_REGISBLK); - printf("%s ", blk->Name); - blk = blk->Next; - } - printf("\n"); - outcnt_delta[i] = outcnt[i]; - } - } - printf("\nsize\tdelta\n-------\t-------\n"); - for(i=MIN_SIZE;i<=MAX_SIZE;i++) - { - if (nmsys_outcnt[i] != nmsys_outcnt_delta[i]) - { - printf("%d\t%d\n",i,nmsys_outcnt[i] - nmsys_outcnt_delta[i]); - total += (i * (nmsys_outcnt[i] - nmsys_outcnt_delta[i])); - nmsys_outcnt_delta[i] = nmsys_outcnt[i]; - } - } + /** Print the header for the block size deltas table. **/ + printf("size\tdelta\tnames\n-------\t-------\t-------\n"); + + /** Iterate through each possible block size. **/ + int total_delta = 0; + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) + { + /** Skip entries where there is no change. **/ + if (outcnt[size] == outcnt_delta[size]) continue; + + /** Print the change and add it to the total_delta. **/ + printf("%ld\t%d\t", size, outcnt[size] - outcnt_delta[size]); + total_delta += (size * (outcnt[size] - outcnt_delta[size])); + + /** Print each name for this block size from the linked list. **/ + nmPrintNames(size); + + /** End of line. **/ printf("\n"); - printf("delta %d total bytes\n", total); - - return; + + /** Reset the delta. **/ + outcnt_delta[size] = outcnt[size]; + } + + /** Print the header for the nmSysXYZ() info table. **/ + printf("\nsize\tdelta\n-------\t-------\n"); + for (size_t size = MIN_SIZE; size <= MAX_SIZE; size++) + { + if (nmsys_outcnt[size] != nmsys_outcnt_delta[size]) continue; + + printf("%ld\t%d\n", size, nmsys_outcnt[size] - nmsys_outcnt_delta[size]); + total_delta += (size * (nmsys_outcnt[size] - nmsys_outcnt_delta[size])); + nmsys_outcnt_delta[size] = nmsys_outcnt[size]; + } + printf("\n"); + + /** Print the total delta. **/ + /** TODO: Israel - Change this to use snprint_bytes() once that function is available. **/ + printf("delta %d total bytes\n", total_delta); } +/*** Allocate memory without using block caching. The size of the allocated + *** memory is stored at the start of the memory block for debugging. + *** + *** @attention Should be used for memory that will NOT benefit from caching + *** (e.g. a variable length string). Consistently sized memory (such as + *** a struct of a constant size) should be allocated using nmMalloc() to + *** improve performance. + *** + *** @param size The size of the memory block to be allocated. + *** @returns A pointer to the start of the allocated memory block. + ***/ void* nmSysMalloc(int size) { -#ifdef NM_USE_SYSMALLOC - char* ptr; - ptr = (char*)nmDebugMalloc(size+sizeof(int)); - if (!ptr) return NULL; + /** Fallback if sysMalloc() is disabled. **/ + #ifndef NM_USE_SYSMALLOC + return (void*)nmDebugMalloc(size); + #else + + /** Allocate the requested space, plus the initial size int. **/ + char* ptr = (char*)nmDebugMalloc(sizeof(int) + size); + if (ptr == NULL) return NULL; + + /** Set the size int. **/ *(int*)ptr = size; -#ifdef SIZED_BLK_COUNTING + + /** Update sized block counting, if necessary. **/ + #ifdef SIZED_BLK_COUNTING if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]++; -#endif - return (void*)(ptr+sizeof(int)); -#else - return (void*)nmDebugMalloc(size); -#endif + #endif + + /** Return the allocated memory (starting after the size int). **/ + return (void*)(sizeof(int) + ptr); + #endif } + +/*** Free a memory buffer allocated using `nmSysMalloc()` (or similar). + *** + *** @param ptr A pointer to the memory to be freed. + ***/ void nmSysFree(void* ptr) { -#ifdef NM_USE_SYSMALLOC -#ifdef SIZED_BLK_COUNTING + /** Fallback if sysMalloc() is disabled. **/ + #ifndef NM_USE_SYSMALLOC + nmDebugFree(ptr); + #else + + /** Count sized blocks, if enabled. **/ + #ifdef SIZED_BLK_COUNTING int size; size = *(int*)(((char*)ptr)-sizeof(int)); if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]--; -#endif - nmDebugFree(((char*)ptr)-sizeof(int)); -#else - nmDebugFree(ptr); -#endif + #endif + + /** Free the initial size int, as well as the rest of the allocated memory. **/ + nmDebugFree(((char*)ptr) - sizeof(int)); + #endif return; } + +/*** Reallocates a memory block from `nmSysMalloc()` (or similar) to a new + *** size, maintaining as much data as possible. Data loss only occurs if the + *** new size is smaller, in which case bits are lost starting at the end of + *** the buffer. + *** + *** @param ptr A pointer to the current buffer (deallocated by this call). + *** @param new_size The size that the buffer should be after this call. + *** @returns The new buffer, or NULL if an error occurs. + ***/ void* -nmSysRealloc(void* ptr, int newsize) +nmSysRealloc(void* ptr, int new_size) { -#ifdef NM_USE_SYSMALLOC -#ifdef SIZED_BLK_COUNTING - int size; -#endif - char* newptr; - if (!ptr) return nmSysMalloc(newsize); -#ifdef SIZED_BLK_COUNTING - size = *(int*)(((char*)ptr)-sizeof(int)); -#endif - newptr = (char*)nmDebugRealloc((((char*)ptr)-sizeof(int)), newsize+sizeof(int)); - if (!newptr) return NULL; -#ifdef SIZED_BLK_COUNTING - if (size > 0 && size <= MAX_SIZE) nmsys_outcnt[size]--; -#endif - *(int*)newptr = newsize; -#ifdef SIZED_BLK_COUNTING - if (newsize > 0 && newsize <= MAX_SIZE) nmsys_outcnt[newsize]++; -#endif - return (void*)(newptr+sizeof(int)); -#else - return (void*)nmDebugRealloc(ptr,newsize); -#endif + /** Fallback if sysMalloc() is disabled. **/ + #ifndef NM_USE_SYSMALLOC + return (void*)nmDebugRealloc(ptr, new_size); + #else + + /** Behaves as nmSysMalloc() if there is no target pointer. **/ + if (ptr == NULL) return nmSysMalloc(new_size); + + /** If no work needs to be done, do nothing. **/ + void* buffer_ptr = ((char*)ptr) - sizeof(int); + const int size = *(int*)buffer_ptr; + if (size == new_size) return ptr; + + /** Realloc the given memory, taking the initial size int into account into account. **/ + char* new_ptr = (char*)nmDebugRealloc(buffer_ptr, sizeof(int) + new_size); + if (new_ptr == NULL) return NULL; + + /** Update the initial size int. **/ + *(int*)new_ptr = new_size; + + /** Handle counting. **/ + #ifdef SIZED_BLK_COUNTING + if (0 < size && size <= MAX_SIZE) nmsys_outcnt[size]--; + if (0 < new_size && new_size <= MAX_SIZE) nmsys_outcnt[new_size]++; + #endif + + /** Return the pointer to the new memory. **/ + return (void*)(sizeof(int) + new_ptr); + #endif } + +/*** Duplicate a string into a new memory buffer, which is allocated by using + *** `nmSysMalloc()` (and thus, it should be freed with `nmSysFree()` to avoid + *** causing a memory leak). + *** + *** Note: The string is not deallocated by this function call. Thus, it can + *** be stack allocated, heap allocated, or even a string literal without + *** causing an error. + *** + *** @attention The str pointer is _assumed_ to point to a null-terminated + *** string. If this is not the case, the behavior is undefined, as with + *** the C standard `strdup()` function. + *** + *** @param str The string to be duplicated. + *** @returns A pointer to the new string buffer containing the string, or + *** NULL if an error occurs. + ***/ char* -nmSysStrdup(const char* ptr) +nmSysStrdup(const char* str) { -#ifdef NM_USE_SYSMALLOC - char* newptr; - int n = strlen(ptr); - newptr = (char*)nmSysMalloc(n+1); - if (!newptr) return NULL; - memcpy(newptr,ptr,n+1); - return newptr; -#else - return strdup(ptr); -#endif + /** Fallback if sysMalloc() is disabled. **/ + #ifndef NM_USE_SYSMALLOC + return strdup(str); + #else + + /** Allocate space for the string. **/ + size_t n = strlen(str) + 1u; /* Length, including the null terminator. */ + char* new_str = (char*)nmSysMalloc(n); + if (new_str == NULL) return NULL; + + /** Copy the string into the new memory. **/ + memcpy(new_str, str, n); + + return new_str; + #endif }