safemalloc.c   [plain text]


/* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB 
This file is public domain and comes with NO WARRANTY of any kind */

/*
 * [This posting refers to an article entitled "oops, corrupted memory
 * again!" in net.lang.c.  I am posting it here because it is source.]
 *
 * My tool for approaching this problem is to build another level of data
 * abstraction on top of malloc() and free() that implements some checking.
 * This does a number of things for you:
 *	- Checks for overruns and underruns on allocated data
 *	- Keeps track of where in the program the memory was malloc'ed
 *	- Reports on pieces of memory that were not free'ed
 *	- Records some statistics such as maximum memory used
 *	- Marks newly malloc'ed and newly free'ed memory with special values
 * You can use this scheme to:
 *	- Find bugs such as overrun, underrun, etc because you know where
 *	  a piece of data was malloc'ed and where it was free'ed
 *	- Find bugs where memory was not free'ed
 *	- Find bugs where newly malloc'ed memory is used without initializing
 *	- Find bugs where newly free'ed memory is still used
 *	- Determine how much memory your program really uses
 *	- and other things
 */

/*
 * To implement my scheme you must have a C compiler that has __LINE__ and
 * __FILE__ macros.  If your compiler doesn't have these then (a) buy another:
 * compilers that do are available on UNIX 4.2bsd based systems and the PC,
 * and probably on other machines; or (b) change my scheme somehow.  I have
 * recomendations on both these points if you would like them (e-mail please).
 *
 * There are 4 functions in my package:
 *	char *NEW( uSize )	Allocate memory of uSize bytes
 *				(equivalent to malloc())
 *	char *REA( pPtr, uSize) Allocate memory of uSize bytes, move data and
 *				free pPtr.
 *				(equivalent to realloc())
 *	FREE( pPtr )		Free memory allocated by NEW
 *				(equivalent to free())
 *	TERMINATE(file)		End system, report errors and stats on file
 * I personally use two more functions, but have not included them here:
 *	char *STRSAVE( sPtr )	Save a copy of the string in dynamic memory
 *	char *RENEW( pPtr, uSize )
 *				(equivalent to realloc())
 */

/*
 * Memory sub-system, written by Bjorn Benson
   Fixed to use my_sys scheme by Michael Widenius
 */

#ifndef SAFEMALLOC
#define SAFEMALLOC			/* Get protos from my_sys */
#endif

#include "mysys_priv.h"
#include <m_string.h>
#include "my_static.h"
#include "mysys_err.h"

ulonglong safemalloc_mem_limit = ~(ulonglong)0;

#define pNext		tInt._pNext
#define pPrev		tInt._pPrev
#define sFileName	tInt._sFileName
#define uLineNum	tInt._uLineNum
#define uDataSize	tInt._uDataSize
#define lSpecialValue	tInt._lSpecialValue

	/* Static functions prototypes */

static int check_ptr(const char *where, byte *ptr, const char *sFile,
		     uint uLine);
static int _checkchunk(struct remember *pRec, const char *sFile, uint uLine);

/*
 *	Note: both these refer to the NEW'ed
 *	data only.  They do not include
 *	malloc() roundoff or the extra
 *	space required by the remember
 *	structures.
 */

#define ALLOC_VAL	(uchar) 0xA5	/* NEW'ed memory is filled with this */
				/* value so that references to it will	 */
				/* end up being very strange.		 */
#define FREE_VAL	(uchar) 0x8F	/* FREE'ed memory is filled with this */
				/* value so that references to it will	 */
				/* also end up being strange.		 */

#define MAGICKEY	0x14235296	/* A magic value for underrun key */
#define MAGICEND0	0x68		/* Magic values for overrun keys  */
#define MAGICEND1	0x34		/*		"		  */
#define MAGICEND2	0x7A		/*		"		  */
#define MAGICEND3	0x15		/*		"		  */

 /* Warning: do not change the MAGICEND? values to */
 /* something with the high bit set.  Various C    */
 /* compilers (like the 4.2bsd one) do not do the  */
 /* sign extension right later on in this code and */
 /* you will get erroneous errors.		  */


/*
 * gptr _mymalloc( uint uSize, my_string sFile, uint uLine, MyFlags )
 *	Allocate some memory.
 */

gptr _mymalloc (uint uSize, const char *sFile, uint uLine, myf MyFlags)
{
    struct remember *pTmp;
    DBUG_ENTER("_mymalloc");
    DBUG_PRINT("enter",("Size: %u",uSize));


    if (!sf_malloc_quick)
      (void) _sanity (sFile, uLine);

    if(uSize + lCurMemory > safemalloc_mem_limit)
      pTmp = 0;
    else
       /* Allocate the physical memory */
       pTmp = (struct remember *) malloc (
		sizeof (struct irem)			/* remember data  */
		+ sf_malloc_prehunc
		+ uSize					/* size requested */
		+ 4					/* overrun mark   */
		+ sf_malloc_endhunc
		);

    /* Check if there isn't anymore memory avaiable */
    if (pTmp == NULL)
    {
      if (MyFlags & MY_FAE)
	error_handler_hook=fatal_error_handler_hook;
      if (MyFlags & (MY_FAE+MY_WME))
      {
	char buff[SC_MAXWIDTH];
	my_errno=errno;
	sprintf(buff,"Out of memory at line %d, '%s'", uLine, sFile);
	my_message(EE_OUTOFMEMORY,buff,MYF(ME_BELL+ME_WAITTANG));
	sprintf(buff,"needed %d byte (%ldk), memory in use: %ld bytes (%ldk)",
		uSize, (uSize + 1023L) / 1024L,
		lMaxMemory, (lMaxMemory + 1023L) / 1024L);
	my_message(EE_OUTOFMEMORY,buff,MYF(ME_BELL+ME_WAITTANG));
      }
      DBUG_PRINT("error",("Out of memory, in use: %ld at line %d, '%s'",
			  lMaxMemory,uLine, sFile));
      if (MyFlags & MY_FAE)
	exit(1);
      DBUG_RETURN ((gptr) NULL);
    }

    /* Fill up the structure */
    *((long*) ((char*) &pTmp -> lSpecialValue+sf_malloc_prehunc)) = MAGICKEY;
    pTmp -> aData[uSize + sf_malloc_prehunc+0] = MAGICEND0;
    pTmp -> aData[uSize + sf_malloc_prehunc+1] = MAGICEND1;
    pTmp -> aData[uSize + sf_malloc_prehunc+2] = MAGICEND2;
    pTmp -> aData[uSize + sf_malloc_prehunc+3] = MAGICEND3;
    pTmp -> sFileName = (my_string) sFile;
    pTmp -> uLineNum = uLine;
    pTmp -> uDataSize = uSize;
    pTmp -> pPrev = NULL;

    /* Add this remember structure to the linked list */
    pthread_mutex_lock(&THR_LOCK_malloc);
    if ((pTmp->pNext=pRememberRoot))
    {
      pRememberRoot -> pPrev = pTmp;
    }
    pRememberRoot = pTmp;

    /* Keep the statistics */
    lCurMemory += uSize;
    if (lCurMemory > lMaxMemory) {
	lMaxMemory = lCurMemory;
    }
    cNewCount++;
    pthread_mutex_unlock(&THR_LOCK_malloc);

    /* Set the memory to the aribtrary wierd value */
#ifdef HAVE_purify
    if (MyFlags & MY_ZEROFILL)
#endif
      bfill(&pTmp -> aData[sf_malloc_prehunc],uSize,
	    (char) (MyFlags & MY_ZEROFILL ? 0 : ALLOC_VAL));
    /* Return a pointer to the real data */
    DBUG_PRINT("exit",("ptr: %lx",&(pTmp -> aData[sf_malloc_prehunc])));
    if (sf_min_adress > &(pTmp -> aData[sf_malloc_prehunc]))
      sf_min_adress = &(pTmp -> aData[sf_malloc_prehunc]);
    if (sf_max_adress < &(pTmp -> aData[sf_malloc_prehunc]))
      sf_max_adress = &(pTmp -> aData[sf_malloc_prehunc]);
    DBUG_RETURN ((gptr) &(pTmp -> aData[sf_malloc_prehunc]));
}

/*
 *  Allocate some new memory and move old memoryblock there.
 *  Free then old memoryblock
 */

gptr _myrealloc (register gptr pPtr, register uint uSize,
		 const char *sFile, uint uLine, myf MyFlags)
{
  struct remember *pRec;
  gptr ptr;
  DBUG_ENTER("_myrealloc");

  if (!pPtr && (MyFlags & MY_ALLOW_ZERO_PTR))
    DBUG_RETURN(_mymalloc(uSize,sFile,uLine,MyFlags));

  if (!sf_malloc_quick)
    (void) _sanity (sFile, uLine);

  if (check_ptr("Reallocating",(byte*) pPtr,sFile,uLine))
    DBUG_RETURN((gptr) NULL);

  pRec = (struct remember *) ((char*) pPtr - sizeof (struct irem)-
			      sf_malloc_prehunc);
  if (*((long*) ((char*) &pRec -> lSpecialValue+sf_malloc_prehunc))
      != MAGICKEY)
  {
    fprintf (stderr, "Reallocating unallocated data at line %d, '%s'\n",
	     uLine, sFile);
    DBUG_PRINT("safe",("Reallocating unallocated data at line %d, '%s'",
		       uLine, sFile));
    (void) fflush(stderr);
    DBUG_RETURN((gptr) NULL);
  }

  if ((ptr=_mymalloc(uSize,sFile,uLine,MyFlags)))	/* Allocate new area */
  {
    uSize=min(uSize,pRec-> uDataSize);		/* Move as much as possibly */
    memcpy((byte*) ptr,pPtr,(size_t) uSize);	/* Copy old data */
    _myfree(pPtr,sFile,uLine,0);		/* Free not needed area */
  }
  else
  {
    if (MyFlags & MY_HOLD_ON_ERROR)
      DBUG_RETURN(pPtr);
    if (MyFlags & MY_FREE_ON_ERROR)
      _myfree(pPtr,sFile,uLine,0);
  }
  DBUG_RETURN(ptr);
} /* _myrealloc */


/*
 * void _myfree( my_string pPtr, my_string sFile, uint uLine, myf myflags)
 *	Deallocate some memory.
 */

void _myfree (gptr pPtr, const char *sFile, uint uLine, myf myflags)
{
  struct remember *pRec;
  DBUG_ENTER("_myfree");
  DBUG_PRINT("enter",("ptr: %lx",pPtr));

  if (!sf_malloc_quick)
    (void) _sanity (sFile, uLine);

  if ((!pPtr && (myflags & MY_ALLOW_ZERO_PTR)) ||
      check_ptr("Freeing",(byte*) pPtr,sFile,uLine))
    DBUG_VOID_RETURN;

  /* Calculate the address of the remember structure */
  pRec = (struct remember *) ((byte*) pPtr-sizeof(struct irem)-
			      sf_malloc_prehunc);

  /* Check to make sure that we have a real remember structure	*/
  /* Note: this test could fail for four reasons:		*/
  /*	(1) The memory was already free'ed			*/
  /*	(2) The memory was never new'ed				*/
  /*	(3) There was an underrun				*/
  /*	(4) A stray pointer hit this location			*/

  if (*((long*) ((char*) &pRec -> lSpecialValue+sf_malloc_prehunc))
      != MAGICKEY)
  {
    fprintf (stderr, "Freeing unallocated data at line %d, '%s'\n",
	     uLine, sFile);
    DBUG_PRINT("safe",("Unallocated data at line %d, '%s'",uLine,sFile));
    (void) fflush(stderr);
    DBUG_VOID_RETURN;
  }

  /* Remove this structure from the linked list */
  pthread_mutex_lock(&THR_LOCK_malloc);
  if (pRec -> pPrev) {
    pRec -> pPrev -> pNext = pRec -> pNext;
  } else {
    pRememberRoot = pRec -> pNext;
  }
  if (pRec -> pNext) {
    pRec -> pNext -> pPrev = pRec -> pPrev;
  }
  /* Handle the statistics */
  lCurMemory -= pRec -> uDataSize;
  cNewCount--;
  pthread_mutex_unlock(&THR_LOCK_malloc);

#ifndef HAVE_purify
  /* Mark this data as free'ed */
  bfill(&pRec->aData[sf_malloc_prehunc],pRec->uDataSize,(pchar) FREE_VAL);
#endif
  *((long*) ((char*) &pRec -> lSpecialValue+sf_malloc_prehunc)) = ~MAGICKEY;

  /* Actually free the memory */
  free ((my_string ) pRec);
  DBUG_VOID_RETURN;
}

	/* Check if we have a wrong  pointer */

static int check_ptr(const char *where, byte *ptr, const char *sFile,
		     uint uLine)
{
  if (!ptr)
  {
    fprintf (stderr, "%s NULL pointer at line %d, '%s'\n",
	     where,uLine, sFile);
    DBUG_PRINT("safe",("Null pointer at line %d '%s'", uLine, sFile));
    (void) fflush(stderr);
    return 1;
  }
#ifndef _MSC_VER
  if ((long) ptr & (MY_ALIGN(1,sizeof(char *))-1))
  {
    fprintf (stderr, "%s wrong aligned pointer at line %d, '%s'\n",
	     where,uLine, sFile);
    DBUG_PRINT("safe",("Wrong aligned pointer at line %d, '%s'",
		       uLine,sFile));
    (void) fflush(stderr);
    return 1;
  }
#endif
  if (ptr < sf_min_adress || ptr > sf_max_adress)
  {
    fprintf (stderr, "%s pointer out of range at line %d, '%s'\n",
	     where,uLine, sFile);
    DBUG_PRINT("safe",("Pointer out of range at line %d '%s'",
		       uLine,sFile));
    (void) fflush(stderr);
    return 1;
  }
  return 0;
}


/*
 * TERMINATE(FILE *file)
 *	Report on all the memory pieces that have not been
 *	free'ed as well as the statistics.
 */

void TERMINATE (FILE *file)
{
  struct remember *pPtr;
  DBUG_ENTER("TERMINATE");
  pthread_mutex_lock(&THR_LOCK_malloc);

  /* Report the difference between number of calls to  */
  /* NEW and the number of calls to FREE.  >0 means more	 */
  /* NEWs than FREEs.  <0, etc.				 */

  if (cNewCount)
  {
    if (file)
    {
      fprintf (file, "cNewCount: %d\n", cNewCount);
      (void) fflush(file);
    }
    DBUG_PRINT("safe",("cNewCount: %d",cNewCount));
  }

  /* Report on all the memory that was allocated with NEW	 */
  /* but not free'ed with FREE.				 */

  if ((pPtr=pRememberRoot))
  {
    if (file)
    {
      fprintf(file, "Memory that was not free'ed (%ld bytes):\n",lCurMemory);
      (void) fflush(file);
    }
    DBUG_PRINT("safe",("Memory that was not free'ed (%ld bytes):",lCurMemory));
    while (pPtr)
    {
      if (file)
      {
	fprintf (file,
		 "\t%6u bytes at 0x%09lx, allocated at line %4u in '%s'\n",
		 pPtr -> uDataSize,
		 (ulong) &(pPtr -> aData[sf_malloc_prehunc]),
		 pPtr -> uLineNum, pPtr -> sFileName);
	(void) fflush(file);
      }
      DBUG_PRINT("safe",
		 ("%6u bytes at 0x%09lx, allocated at line %4d in '%s'",
		  pPtr -> uDataSize, &(pPtr -> aData[sf_malloc_prehunc]),
		  pPtr -> uLineNum, pPtr -> sFileName));
      pPtr = pPtr -> pNext;
    }
  }
  /* Report the memory usage statistics */
  if (file)
  {
    fprintf (file, "Maximum memory usage: %ld bytes (%ldk)\n",
	     lMaxMemory, (lMaxMemory + 1023L) / 1024L);
    (void) fflush(file);
  }
  DBUG_PRINT("safe",("Maximum memory usage: %ld bytes (%ldk)",
		     lMaxMemory, (lMaxMemory + 1023L) / 1024L));
  pthread_mutex_unlock(&THR_LOCK_malloc);
  DBUG_VOID_RETURN;
}


	/* Returns 0 if chunk is ok */

static int _checkchunk (register struct remember *pRec, const char *sFile,
			uint uLine)
{
  reg1 uint uSize;
  reg2 my_string magicp;
  reg3 int flag=0;

  /* Check for a possible underrun */
  if (*((long*) ((char*) &pRec -> lSpecialValue+sf_malloc_prehunc))
      != MAGICKEY)
  {
    fprintf (stderr, "Memory allocated at %s:%d was underrun,",
	     pRec -> sFileName, pRec -> uLineNum);
    fprintf (stderr, " discovered at %s:%d\n", sFile, uLine);
    (void) fflush(stderr);
    DBUG_PRINT("safe",("Underrun at %lx, allocated at %s:%d",
		       &(pRec -> aData[sf_malloc_prehunc]),
		       pRec -> sFileName,
		       pRec -> uLineNum));
    flag=1;
  }

  /* Check for a possible overrun */
  uSize = pRec -> uDataSize;
  magicp = &(pRec -> aData[uSize+sf_malloc_prehunc]);
  if (*magicp++ != MAGICEND0 ||
      *magicp++ != MAGICEND1 ||
      *magicp++ != MAGICEND2 ||
      *magicp++ != MAGICEND3)
  {
    fprintf (stderr, "Memory allocated at %s:%d was overrun,",
	     pRec -> sFileName, pRec -> uLineNum);
    fprintf (stderr, " discovered at '%s:%d'\n", sFile, uLine);
    (void) fflush(stderr);
    DBUG_PRINT("safe",("Overrun at %lx, allocated at %s:%d",
		       &(pRec -> aData[sf_malloc_prehunc]),
		       pRec -> sFileName,
		       pRec -> uLineNum));
    flag=1;
  }
  return(flag);
}


	/* Returns how many wrong chunks */

int _sanity (const char *sFile, uint uLine)
{
  reg1 struct remember *pTmp;
  reg2 int flag=0;
  uint count=0;

  pthread_mutex_lock(&THR_LOCK_malloc);
  count=cNewCount;
  for (pTmp = pRememberRoot; pTmp != NULL && count-- ; pTmp = pTmp -> pNext)
    flag+=_checkchunk (pTmp, sFile, uLine);
  pthread_mutex_unlock(&THR_LOCK_malloc);
  if (count || pTmp)
  {
    const char *format="Safemalloc link list destroyed, discovered at '%s:%d'";
    fprintf (stderr, format, sFile, uLine); fputc('\n',stderr);
    (void) fflush(stderr);
    DBUG_PRINT("safe",(format, sFile, uLine));
    flag=1;
  }
  return flag;
} /* _sanity */


	/* malloc and copy */

gptr _my_memdup(const byte *from, uint length, const char *sFile, uint uLine,
		myf MyFlags)
{
  gptr ptr;
  if ((ptr=_mymalloc(length,sFile,uLine,MyFlags)) != 0)
    memcpy((byte*) ptr, (byte*) from,(size_t) length);
  return(ptr);
} /*_my_memdup */


my_string _my_strdup(const char *from, const char *sFile, uint uLine,
		     myf MyFlags)
{
  gptr ptr;
  uint length=(uint) strlen(from)+1;
  if ((ptr=_mymalloc(length,sFile,uLine,MyFlags)) != 0)
    memcpy((byte*) ptr, (byte*) from,(size_t) length);
  return((my_string) ptr);
} /* _my_strdup */