warzone2100/lib/framework/mem.c

521 lines
12 KiB
C

/*
* mem.c
*
* Replacements for malloc and free to track memory usage.
*
* Also allows pointer validity checking.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* Allow frame header files to be singly included */
#define FRAME_LIB_INCLUDE
#include "types.h"
#include "debug.h"
#include "mem.h"
#include "heap.h"
#include "treap.h"
#include "treapint.h"
#include "memint.h"
#include "block.h"
#include <assert.h>
/* Whether allocated memory is initialised to a value and whether the memory
* is trashed before it is freed.
* This is done automatically by Visual C's memory routines.
*/
#define MEMORY_SET TRUE
/* Number of bytes after which memory amounts are displayed in Kb */
#define SHOW_KB_LIMIT (0x400)
/* What functions to use for the real malloc and free */
#define RMALLOC malloc
#define RFREE free
/* The root of the memory treap */
static MEM_NODE *psMemRoot = NULL;
/* The current block heap to use instead of MALLOC */
static BLOCK_HEAP *psCurrBlockHeap;
void memMemoryDump(MEM_NODE *Node);
/* Initialise the memory system */
BOOL memInitialise(void)
{
if (psMemRoot != NULL)
{
debug(LOG_MEMORY, "memInitialise: *** WARNING *** : memory still allocated??");
}
psMemRoot = NULL;
psCurrBlockHeap = NULL;
return TRUE;
}
/* Release the memory treap */
static void memTreapDestroy(TREAP_NODE *psRoot)
{
if (psRoot)
{
// Destroy the sub trees
memTreapDestroy(psRoot->psLeft);
memTreapDestroy(psRoot->psRight);
// Free the root
RFREE(psRoot);
}
}
/* Shutdown the memory system */
void memShutDown(void)
{
// Report any memory still allocated
memMemoryReport(NULL);
// Free up the allocated memory
memTreapDestroy((TREAP_NODE *)psMemRoot);
}
/* Set a block heap to use for all memory allocation rather than standard malloc/free */
void memSetBlockHeap(BLOCK_HEAP *psHeap)
{
psCurrBlockHeap = psHeap;
}
/* Get the current block heap */
BLOCK_HEAP *memGetBlockHeap(void)
{
return psCurrBlockHeap;
}
/* compare two memory blocks
* NOTE: key1 is always the block passed into the treap code
* and therefore not necessarily to be trusted
*/
SDWORD memBlockCmp(UDWORD key1, UDWORD key2)
{
UDWORD start1,start2, end1,end2;
// Calculate the edges of the memory blocks
start1 = (UDWORD)((UBYTE *)((MEM_NODE *)key1)->pObj + sizeof(MEM_NODE)
+ SAFETY_ZONE_SIZE);
start2 = (UDWORD)((UBYTE *)((MEM_NODE *)key2)->pObj + sizeof(MEM_NODE)
+ SAFETY_ZONE_SIZE);
end1 = start1 + ((MEM_NODE *)key1)->size;
end2 = start2 + ((MEM_NODE *)key2)->size;
// see if one block is inside another
if ((start1 >= start2 && end1 <= end2))// || // block 1 inside block 2
// (start2 >= start1 && end2 <= end1)) // block 2 inside block 1
{
return 0;
}
else if (start1 < start2)
{
// less than
return -1;
}
// greater than
return 1;
}
#ifdef DEBUG_MALLOC
/* Replacement for malloc that records where the memory was requested.
* All allocated memory is initialised to INITIALISE_BYTE
* A buffer is also allocated at the top and bottom of the memory to check for
* overwrites.
*/
void *memMalloc(STRING *pFileName, SDWORD LineNumber, size_t Size)
{
void *pMemBase;
MEM_NODE *psNode;
ASSERT(((pFileName != NULL), "No filename passed to mem_Malloc"));
ASSERT((Size != 0, "Cannot allocate 0 bytes of memory."));
if (psCurrBlockHeap != NULL)
{
// use a block heap rather than normal malloc
blkCallPos(pFileName, LineNumber);
return blkAlloc(psCurrBlockHeap, Size);
}
pMemBase = RMALLOC( Size + sizeof(MEM_NODE) + 2 * SAFETY_ZONE_SIZE );
if (!pMemBase)
{
ASSERT((FALSE, "Warning: malloc returning NULL - [%s - %d]",pFileName,LineNumber));
DBPRINTF(("[%s - %d] %d bytes\n",pFileName,LineNumber,Size));
return NULL;
}
/* Got the main bit of memory - set up the node entry */
psNode = (MEM_NODE *)pMemBase;
psNode->pFile = (STRING *)RMALLOC( strlen(pFileName)+1 );
if (!psNode->pFile)
{
RFREE(pMemBase);
DBMB(("Warning: malloc returning NULL"));
return NULL;
}
strcpy(psNode->pFile, pFileName);
psNode->line = LineNumber;
psNode->size = Size;
/* Store the new entry in the memory treap */
psNode->priority = (UDWORD)rand();
psNode->key = (UDWORD)psNode;
psNode->pObj = psNode;
psNode->psLeft = NULL;
psNode->psRight = NULL;
treapAddNode((TREAP_NODE **)&psMemRoot, (TREAP_NODE *)psNode, memBlockCmp);
/* Now initialise the memory - try to catch unitialised variables */
memset((UBYTE *)(pMemBase) + sizeof(MEM_NODE),
SAFETY_ZONE_BYTE, SAFETY_ZONE_SIZE);
memset((UBYTE *)(pMemBase) + sizeof(MEM_NODE) + SAFETY_ZONE_SIZE + Size,
SAFETY_ZONE_BYTE, SAFETY_ZONE_SIZE);
#ifdef MEMORY_SET
/* The PC initialises malloc'ed memory already, no need to do it again */
memset((UBYTE *)(pMemBase) + sizeof(MEM_NODE) + SAFETY_ZONE_SIZE,
INITIALISE_BYTE, Size);
#endif
return ((UBYTE *)(pMemBase) + sizeof(MEM_NODE) + SAFETY_ZONE_SIZE);
}
#endif
/* Replacement for malloc for release builds. */
void *memMallocRelease(size_t Size)
{
if (psCurrBlockHeap != NULL)
{
// use a block heap rather than normal malloc
return blkAlloc(psCurrBlockHeap, Size);
}
return RMALLOC(Size);
}
#ifdef DEBUG_MALLOC
/* Replacement for free
*
* Checks whether the pointer is valid before freeing it.
* If the pointer is invalid, it asserts.
* The buffer zones around the allocated memory are also checked for
* overwrites.
* All memory is reset to FREE_BYTE before freeing to avoid using
* freed memory.
*/
void memFree(STRING *pFileName, SDWORD LineNumber, void *pMemToFree)
{
MEM_NODE sNode, *psDeleted;
SDWORD i, InvalidBottom, InvalidTop;
UBYTE *pMemBase;
BLOCK_HEAP *psBlock;
#ifdef MEMORY_SET
SDWORD Size;
#endif
(void)LineNumber;
(void)pFileName;
ASSERT(((pFileName != NULL), "No filename passed to mem_Free"));
ASSERT(((pMemToFree != NULL), "Attempt to free NULL pointer, called by:\n"
"File: %s\nLine: %d", pFileName, LineNumber));
// see if the pointer was allocated in a block
psBlock = blkFind(pMemToFree);
if (psBlock != NULL)
{
// use a block heap rather than normal free
blkCallPos(pFileName, LineNumber);
blkFree(psBlock, pMemToFree);
return;
}
// Create a dummy node for the search
// This is only looked at by memBlockCmp so only need to set the object and size
sNode.pObj = ((UBYTE *)pMemToFree) - sizeof(MEM_NODE) - SAFETY_ZONE_SIZE;
sNode.size = 1;
/* Get the node for the memory block */
psDeleted = (MEM_NODE *)treapDelRec((TREAP_NODE **)&psMemRoot,
(UDWORD)&sNode, memBlockCmp);
ASSERT((psDeleted != NULL,
"Invalid pointer passed to mem_Free by:\n"
"File: %s\nLine: %d\n\n"
"Attempt to free already freed pointer?",
pFileName, LineNumber));
if (psDeleted)
{
/* The pointer is valid, check the buffer zones */
pMemBase = (UBYTE *)(pMemToFree) - SAFETY_ZONE_SIZE;
InvalidBottom = InvalidTop = 0;
for(i=0; i<SAFETY_ZONE_SIZE; i++)
{
if (pMemBase[i] != SAFETY_ZONE_BYTE)
{
InvalidBottom++;
}
if (pMemBase[i + psDeleted->size + SAFETY_ZONE_SIZE] != SAFETY_ZONE_BYTE)
{
InvalidTop++;
}
}
ASSERT(( !InvalidBottom && !InvalidTop,
"Safety zone on memory overwritten.\n"
"%d Invalid bytes (of %d) found below memory buffer.\n"
"%d Invalid bytes (of %d) found above memory buffer.\n\n"
"Memory allocated by:\nFile: %s\nLine: %d\n"
"Memory freed by:\nFile: %s\nLine: %d\n",
InvalidBottom, SAFETY_ZONE_SIZE, InvalidTop, SAFETY_ZONE_SIZE,
psDeleted->pFile, psDeleted->line,
pFileName, LineNumber));
/* Trash the memory before it is freed (The PC already does this) */
#ifdef MEMORY_SET
Size = psDeleted->size;
memset(pMemToFree, FREE_BYTE, Size);
#endif
/* Now free the memory */
RFREE(psDeleted->pFile);
RFREE(psDeleted);
}
}
#endif
/* Replacement for Free for release builds */
void memFreeRelease(void *pMemToFree)
{
BLOCK_HEAP *psBlock;
// see if the pointer was allocated in a block
psBlock = blkFind(pMemToFree);
if (psBlock != NULL)
{
// use a block heap rather than normal free
blkFree(psBlock, pMemToFree);
return;
}
RFREE(pMemToFree);
}
/* Checks whether the memory buffer pointed to by pPtr of size Size
* is contained in any of the memory blocks allocated.
*/
BOOL memPointerValid(void *pPtr, size_t size)
{
MEM_NODE sNode;
ASSERT((size, "memPointerValid: cannot check a pointer with zero size"));
if (pPtr == NULL)
{
return FALSE;
}
// Create a dummy node for the search
// This is only looked at by memBlockCmp so only need to set the object and size
sNode.pObj = ((UBYTE *)pPtr) - sizeof(MEM_NODE) - SAFETY_ZONE_SIZE;
sNode.size = size;
// See if the block is in the treap
if (treapFindRec((TREAP_NODE *)psMemRoot, (UDWORD)&sNode, memBlockCmp))
{
return TRUE;
}
// check the block heaps as well (if the code is there)
#ifdef DEBUG_BLOCK
return blkPointerValidAll(pPtr, size);
#else
return FALSE;
#endif
}
/* Recursive function to print out the list of memory blocks */
SDWORD memRecReport(MEM_NODE *psRoot)
{
#ifdef REALLY_DEBUG_MALLOC
if (psRoot)
{
if (psRoot->size < SHOW_KB_LIMIT)
{
debug(LOG_MEMORY, "memRecReport for %s line %d: \t%d bytes",
psRoot->pFile, psRoot->line, psRoot->size);
}
else
{
debug(LOG_MEMORY, "memRecReport for %s line %d: \t%d kilobytes",
psRoot->pFile, psRoot->line, (int) psRoot->size / 1024);
}
return memRecReport((MEM_NODE *)psRoot->psLeft) +
memRecReport((MEM_NODE *)psRoot->psRight) +
psRoot->size;
}
#endif
return 0;
}
#ifdef DEBUG_MALLOC
#define MAXMODULES (32)
typedef struct
{
char pFile[128];
int Count;
int Total;
} MEMMOD;
static MEMMOD MemModuleInfo[MAXMODULES];
static UDWORD MemTotalEntries;
static UDWORD MemTotalModules;
static UDWORD MemTotalAllocated;
#endif
/* Recursive function to total up the amount of mem allocated */
void memSummary(MEM_NODE *psRoot)
{
#ifdef DEBUG_MALLOC
// bsort
if (psRoot)
{
int i;
BOOL FoundModule;
MemTotalEntries++;
MemTotalAllocated+=psRoot->size;
FoundModule=FALSE;
for (i=0;i<(SDWORD)MemTotalModules;i++)
{
if (strcmp(psRoot->pFile,MemModuleInfo[i].pFile)==0)
{
MemModuleInfo[i].Count++;
MemModuleInfo[i].Total+=psRoot->size;
FoundModule=TRUE;
}
}
if (FoundModule==FALSE)
{
if (MemTotalModules <MAXMODULES)
{
strcpy(MemModuleInfo[MemTotalModules].pFile,psRoot->pFile);
MemModuleInfo[MemTotalModules].Count=1;
MemModuleInfo[MemTotalModules].Total=psRoot->size;
MemTotalModules++;
}
}
memSummary((MEM_NODE *)psRoot->psLeft);
memSummary((MEM_NODE *)psRoot->psRight);
}
return ;
#endif
}
void memMemorySummary(void)
{
memMemoryDump(psMemRoot);
}
void memMemoryDump(MEM_NODE *Node)
{
#ifdef DEBUG_MALLOC
int i;
MemTotalEntries=0;
MemTotalModules=0;
MemTotalAllocated=0;
memSummary(Node);
debug(LOG_MEMORY, "Memory Summary: %d bytes allocated in %d handy size chunks", MemTotalAllocated, MemTotalEntries);
for (i=0;i<(SDWORD)MemTotalModules;i++)
{
debug(LOG_MEMORY, "memMemoryDump: %d) [%s] %d allocations totalling %d bytes",
i, MemModuleInfo[i].pFile, MemModuleInfo[i].Count, MemModuleInfo[i].Total);
}
#endif
}
/* Report on currently allocated memory.
* If pFileName is not NULL send the report to the specified file.
* If pFileName is NULL the report goes to DBPRINTF
*/
void memMemoryReport(STRING *pFileName)
{
#ifdef DEBUG_MALLOC
SDWORD TotMem;
if (!psMemRoot)
{
debug(LOG_MEMORY, "memMemoryReport: No memory allocated");
}
else
{
TotMem = memRecReport(psMemRoot);
if (TotMem < SHOW_KB_LIMIT)
{
debug(LOG_MEMORY, "memMemoryReport: Total memory allocated is %d bytes", TotMem);
}
else
{
debug(LOG_MEMORY, "memMemoryReport: Total memory allocated is %d kilobytes", (int) TotMem / 1024);
}
}
#endif
}
/* Display the memory treap */
void memDisplayTreap(STRING *pFileName)
{
#ifdef DEBUG_MALLOC
debug(LOG_MEMORY, "Memory Allocation Treap:");
treapDisplayRec((TREAP_NODE *)psMemRoot, 0);
#endif
}