#define TOMBSTONES 1
//#define _REPORT_ACTIONS 1
#define ERRFILE stderr

/*
    Should be able to make use of these somewhere?
http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html#Function%20Names (DONE)
http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html#Return%20Address

 */

/************************************************************************
 *                                                                      *
 *                      Copyright (c) 1985 by                           *
 *              Digital Equipment Corporation, Maynard, MA              *
 *                      All rights reserved.                            *
 *                                                                      *
 *   The information in this software is subject to change  without     *
 *   notice  and should not be construed as a commitment by Digital     *
 *   Equipment Corporation.                                             *
 *                                                                      *
 *   Digital assumes no responsibility for the use  or  reliability     *
 *   of its software on equipment which is not supplied by Digital.     *
 *                                                                      *
 *   Redistribution and use in source and binary forms are permitted    *
 *   provided that the above copyright notice and this paragraph are    *
 *   duplicated in all such forms and that any documentation,           *
 *   advertising materials, and other materials related to such         *
 *   distribution and use acknowledge that the software was developed   *
 *   by Digital Equipment Corporation. The name of Digital Equipment    *
 *   Corporation may not be used to endorse or promote products derived *
 *   from this software without specific prior written permission.      *
 *   THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR     *
 *   IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED     *
 *   WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.*
 *   Do not take internally. In case of accidental ingestion, contact   *
 *   your physician immediately.                                        *
 *                                                                      *
 ************************************************************************/

/*

     This version of mnemosyne is a major hack by Graham Toal <gtoal@ed.ac.uk>
    of Marcus J. Ranum's (mjr@decuac.dec.com) excellent package.  His code
    was beautifully portable and really well written; mine is not (OK, I
    promise to tidy it up some day but who knows when...); however some of
    the extensions I've added are *so* useful that I'm releasing it (to
    the Archimedes world at least) as it stands.  After a short field trial,
    I'll post this back to the original author.

    My extensions include:

     1) Working out the name of the procedure which called malloc!
        (only on the Archimedes, and only if compiled appropriately (with -gf).)

     2) Checking for overwriting past arrays (either end)

     3) Checking of *all* arrays for overwriting every time you access
        the malloc package.  This has a high overhead, so can be turned
        off. (But it is on by default).  You can also explicitly test
        the heap integrity at any time.

     4) Better diagnostics when you free a pointer malloc didn't give you
        (eg s = malloc(10); strcpy(s, "text"); while (*s++ != '\0') ; free(s) )

     5) Better diagnostics if freeing something that wasn't mallocked; on
        the Archimedes it is possible to tell if the data points to a literal
        in the code area or to a static; neither of which should be freed.
        It may one day also detect autos, but since the stack is implemented
        as chained heap blocks, it is impossible at the moment to tell the
        difference between a variable on the stack, and a mallocked area
        allocated by someone else not using mnemosyne.

     5) Better info in many reports, eg name of variable if known.

 */

/* Pending:
      Because malloc & realloc don't guarantee zeroed memory, it is unwise
    to be 'sociable' and zero it for the user 'to be on the safe side'.  In
    fact the better strategy for detecting bugs is to fill the data with
    a non-zero value.  It would be nice if it were a value that would assist
    the compiler in uninitialised variable checking. (I used a language once
    that treated 0x80808080 as an uninitialised pattern)

      Given that uninitialised checking is not part of the C ethos, we can
    improve our chances of finding bugs by setting the new mallocked areas
    to *random* patterns instead.  Any arbitrary fixed value has a finite
    probability of causing problems which will be spotted by the programmer
    as an unassigned malloc bug; however by using different values on
    different runs, the probability is greatly increased.  Indeed, random
    behaviour of a program traditionally indicates to an alert programmer
    that there is an unassigned variable, so this is a natural extension
    of common practice :-)

    A *very* sneaky way of doing this would be to set the data to a 1-1 hash
    of the address of the data, so that if it turned up somewhere after being
    assigned to a variable, you would work out where it came from!

    (I've extended mnemosyne to include a function which gives you the base
     of a mallocked area from the address of any data inside it...)

    Graham Toal

 */

/*
Modified by Julian Smith 01 Sep 1994, to work with StubsHack - automatic
runtime interception of all malloc calls, even those from within
libraries not compiled with mnemosyne.
*/

#include        <stdio.h>
#include        <string.h>

#ifndef O_BINARY
#define O_BINARY 0
#endif

static char rcsid[] =
 "$Header: RCS.c.mnemosyn 1.10 91/04/17 18:36:31 gtoal Exp Locker: gtoal $";

#define NOFAKEMALLOC 1		/* suppress fake malloc calls, but check
				   headers conform */
#include "mnemosyne.h"

#ifndef TRUE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif

int mnem_noglobals = TRUE;

/*
malloc() realloc() and family wrappers - these functions manage a set
of data files that are updated whenever a pointer is allocated or freed,
as well as gathering stats about dynamic memory use and leakage.

        Marcus J. Ranum, 1990. (mjr@decuac.dec.com)
*/

/*
there is some egregious use of globals, void functions, and whatnot
in this code. it is mostly due to the constraint that nothing must
be visible to the outside, and that the code must be structurally
simple. error checking is pitched out the window in spots, since an
error in the mnemosyne code should not affect whatever is being
instrumented if at all possible. this means no errors, no signals,
nothing. (this message brought to you by my ego, in case you think
I don't know how to write better code than this) :)

mjr, hacking on Christmas, 1990.
*/

#define REC_UNINIT      000
#define REC_INITTED     001
#define REC_ERR         002
#define REC_ON          010
#define REC_ONOFF       020
static int rec_state = REC_UNINIT;

#ifdef TOMBSTONES
#define HEAP_PRE_MAGIC 0x71c8
#define HEAP_POST_MAGIC 0x5ea2
#define HEAP_PRE_DEAD 0x4b81
#define HEAP_POST_DEAD 0x0c11

struct prestruct
{
   unsigned int bytesize;	/* *should* be a size_t */
#if defined(__arm)
   short int padding;
#endif
   short int heappremagic;
   /* take care to keep this record of a size that matches the alignment of
      data returned by malloc, eg 4 or 8 bytes (because of problems of
      fetching long/double on some machines like 68000's) If sizeof(*pre)
      rounds up to those boundaries, you've no problems. (ie if the records
      are padded by the compiler) However, it *is* be nicer to have the
      padding at the front when possible, rather than the back, to catch
      tombstone violations earlier. */
}
   *pre;

struct poststruct
{
   short int heappostmagic;
   /* but don't care about alignment here */
}
   *post;
#endif

/*
this method of storing the symbol maps is not the most efficient, but
then that's not important here, since all we're trying to do is find
memory leaks. if you choose to improve the symbol management to use
bucketed hash tables, please contact the author and the code will be
updated :) - besides, since we do file I/O every time you malloc or
free something, there's no way in hell this is going to set any new
records for speed.
*/

/* storage for code/line # entry */
struct sym
{
   char *labl;
   int lineno;
   int mapno;
   int mallcnt;
   float avsiz;
   struct sym *next;
};

/* static symbol map */
static struct
{
   FILE *fp;
   FILE *log;
   FILE *fdtwo;

   long nalloc;			/* count of allocations */
   long nrlloc;			/* count of re-allocations */
   long nfree;			/* count of frees */
   long nbfree;			/* count of bad frees */
   long ninuse;			/* known allocated memory in use */
   float avgsiz;		/* average malloc size */

   /* one entry per pointer returned by malloc */
   int pmap;			/* current next ptr map to alloc */
   struct s_ptr *phash[HASHSIZ];

   /* one entry per line of code that calls malloc/realloc, etc */
   int lmap;			/* current next line map to alloc */
   struct sym *shash[HASHSIZ];	/* hash access */
}
map;

static int checking_all = (0 == 0);	/* Default is heavy checking! */

				  /* If you call checkheap() explicitly,
				     heavy checking is turned off because we
				     assume you know what you are doing, and
				     can insert checks at more appropriate
				     places (thus reducing the very high
				     overhead of tombstone checking) */

static void checkheap (char *lab, int lin, char *caller);

/*
 * Setup the memory map.
 * stack grows down from high memory.
 *
 * The memory map look like this:
 * +--------------------+ <- low memory
 * | .text              |
 * |        _etext      |
 * |        ctor list   | the ctor and dtor lists are for
 * |        dtor list   | C++ support
 * +--------------------+
 * | .data              | initialized data goes here
 * |        _edata      |
 * +--------------------+
 * | .bss               |
 * |        __bss_start | start of bss, cleared by crt0
 * |        _end        | start of heap, used by sbrk()
 * +--------------------+
 * .                    .
 * .                    .
 * .                    .
 * |        __stack     | top of stack
 * +--------------------+
 */

#define HAVE_SYM_END 1
#define HAVE_SYM_ETEXT 1
#define HAVE_SYM_EDATA 1
#define HAVE_SYM_DATA_START 1
#define HAVE_SYM_BSS_START 1

/* see also getrlimit() */
#ifdef NEVER
#if HAVE_SYM_BSS_START
extern char *__bss_start;
#endif
#if HAVE_SYM_DATA_START
extern char *__data_start;
#endif
#if HAVE_SYM_EDATA
extern char *_edata;
#endif
#if HAVE_SYM_ETEXT
extern char *_etext;
#endif
#if HAVE_SYM_END
extern char *_end;
#endif
#endif

#include <sys/types.h>
#include <time.h>
#include <sys/resource.h>

static int code_start, code_end;
static int something_start, something_end;
static int data_start, data_end;
static int heap_start, heap_end;
static int stack_start, stack_end;

int mnem_init (char *myname)
{
//  extern int _stklen;
   FILE *sizef;
   char sizes[1024];
   char line[1024];
   char name[1024];
   char *s;
   struct rlimit rl;
   int start, length;

#ifdef NEVER
   fprintf (stdout, "__bss_start: %p\n", __bss_start);
   fprintf (stdout, "__data_start: %p\n", __data_start);
   fprintf (stdout, "_edata: %p\n", _edata);
   fprintf (stdout, "_etext: %p\n", _etext);
   fprintf (stdout, "_end: %p\n", _end);
#endif

   mnem_noglobals = FALSE;
   /* don't know why those don't work.  Let's try a hackier solution */
   sprintf (sizes, "size -A %s", myname);
   sizef = popen (sizes, "r");
   s = fgets (line, 1023, sizef);
   if (s == NULL)
      return;
   s = fgets (line, 1023, sizef);
   if (s == NULL)
      return;
   for (;;) {
      s = fgets (line, 1023, sizef);
      if (s == NULL)
	 break;
      if (*s == '.') {
	 s = strchr (line, ' ');
	 *s++ = '\0';
	 strcpy (name, line);
	 sscanf (s, "%d %d", &length, &start);
	 if (start != 0)
	    /* fprintf (stdout, "%15s: %08x to %08x\n", name, start, start +
	       length); */
	    if (strcmp (name, ".bss") == 0) {
	       code_end = something_start = start;
	       something_end = start + length;
	    } else if (strcmp (name, ".interp") == 0) {
	       code_start = start & (~0xfff);
	    } else if (strcmp (name, ".data") == 0) {
	       data_start = start;
	       data_end = start + length;
	    }
      } else {
	 /* fprintf(stdout, "rejecting: %s\n", line); */
      }
      fflush (stdout);
   }
   pclose (sizef);
   stack_start = ((((int) &myname) >> 16) + 1) << 16;
#ifdef NEVER
   fprintf (stdout,
	    "%15s: %08x                /* current end of heap */\n",
	    "sbrk(0)", sbrk (0));
   fprintf (stdout,
	    "%15s: %08x                /* current stack */\n",
	    "stack", &myname);
   fprintf (stdout,
	    "%15s: %08x                /* stacktop estimate */\n",
	    "stacktop", stack_start);
#endif
//  fprintf(stdout,
//    "%15s: %08x                /* stack length */\n",
//    "_stklen", _stklen);

   getrlimit (RLIMIT_STACK, &rl);
#ifdef NEVER
   fprintf (stdout,
	    "%15s:             %08x    /* current stack limit */\n",
	    "rlim_cur", rl.rlim_cur);
#endif
   stack_end = stack_start - rl.rlim_cur;
#ifdef NEVER
   fprintf (stdout,
	    "%15s:             %08x    /* stack max */\n",
	    "rlim_max", rl.rlim_max);
#endif
}

int static_area (void *pointer)
{
   return ((data_start <= (int) pointer) && ((int) pointer <= data_end));
}

int stack_area (void *pointer)
{
   return ((stack_end <= (int) pointer) && ((int) pointer <= stack_start));
}

int prog_area (void *pointer)
{
   return ((code_start <= (int) pointer) && ((int) pointer <= code_end));
}

#ifdef TOMBSTONES
int withinblock (mall_t user, mall_t sys, mall_t * where)
{
struct prestruct *pre;

   *where = NULL;
   pre = (struct prestruct *) (((char *) sys) - sizeof (*pre));
   if (((int) (sys) <= (int) user)
       && ((int) user < ((int) sys) + pre->bytesize)) {
#ifdef _REPORT_ACTIONS
      fprintf (ERRFILE, "info: pointer at %p is within %u-byte heap block at %p\n", user, pre->bytesize, sys);	/* ## 
														   omit 
														 */
#endif
      *where = sys;
      return (0 == 0);
   }
   return (0 != 0);
}
#endif

static int withinheap (mall_t usr, mall_t * start)
{				/* Run down *all* known arrays if possible
				   and check whether this pointer is within
				   any of our heap blocks. Print a message if 
				   you find it. */
#ifdef TOMBSTONES
int x;
struct s_ptr *p;

   for (x = 0; x < HASHSIZ; x++) {
      p = map.phash[x];
      while (p != (struct s_ptr *) 0) {
	 if (p->dsk.siz != 0) {	/* skip freed entries */
	    if (withinblock (usr, p->ptr, start))
	       return (0 == 0);
	 }
	 p = p->next;
      }
   }
#endif
   return (0 != 0);		/* Err on side of safety if not known */
}

/* print a locale record with checks for closed log file */
static void ploc (lab, lin, siz)
     char *lab;
     int lin;
     unsigned int siz;
{
   if (map.log == (FILE *) 0)
      return;
   if (lab == (char *) 0) {
      (void) fprintf (map.log, " unknown");
      fflush (map.log);
   } else if (*lab == '\0') {
      (void) fprintf (map.log, " exit()");
      fflush (map.log);
   } else {
      (void) fprintf (map.log, " \"%s\"", lab);
      fflush (map.log);
   }
   if (lin != -1) {
      (void) fprintf (map.log, " line:%d", lin);
      fflush (map.log);
   }
   if ((sizeof (int) > 2) && (((int) siz) < -1)) {
      if ((int) siz != -1) {
	 (void) fprintf (map.log, " size:%d", (int) siz);
	 fflush (map.log);
      }
   } else {
      if ((int) siz != -1) {
	 (void) fprintf (map.log, " size:%u", siz);
	 fflush (map.log);
      }
   }
}

/* print a symbol map entry with checks for closed log file */
static void psym (s)
     struct sym *s;
{
   if (map.log == (FILE *) 0)
      return;
   (void) fprintf (map.log, " \"%s\"", s->labl);
   if (s->lineno != -1)
      (void) fprintf (map.log, " line:%d", s->lineno);
   fflush (map.log);
}

/* output a warning message with checks for closed log file */
static void pmsg (s)
     char *s;
{
   if ((map.log == NULL) || (s == NULL) || (*s == '\0'))
      return;
   (void) fprintf (map.log, "%s", s);
   fflush (map.log);
}

/* save an entry to the .lines file */
static void savesym (s)
     struct sym *s;
{
   if (map.fp == (FILE *) 0)
      return;

   (void) fprintf (map.fp, "%4d %7d %12.1f %9d    %s\n",
		   s->mapno, s->mallcnt, s->avsiz, s->lineno, s->labl);
   fflush (map.fp);
}

/* save an entry in the pointer map file */
static void saveptr (p)
     register struct s_ptr *p;
{
   if (fseek (map.fdtwo, p->map * sizeof (p->dsk), SEEK_SET) != 0) {	/* non 
									   zero 
									   if 
									   unable 
									   to 
									   satisfy 
									 */
      pmsg ("mnemosyne: cannot seek in pointer map file\n");
      rec_state |= REC_ERR;
      return;
   }
   if (fwrite (&(p->dsk), sizeof (p->dsk), 1, map.fdtwo) != 1) {	/* not 
									   size 
									   of 
									   record 
									   but 
									   num 
									   records 
									   ? */
      pmsg ("mnemosyne: cannot write in pointer map file\n");
		/**rec_state |= REC_ERR;**/
      return;
   }
   fflush (map.fdtwo);
}

int mnem_writestats (void);
void dumpsyms (void)
{				/* Called on exit in ANSI systems */
   (void) mnem_writestats ();
}

/* initialize everything - symbol tables, files, and maps */
static void initmap (void)
{
   register int xp;

   if (mnem_noglobals) {
      fprintf (ERRFILE,
	       "In order to use mnemosyne with your program, you need to\n");
      fprintf (ERRFILE, "call mnem_init(argv[0]) from main.\n");
      fflush (ERRFILE);
      exit (1);
   }

   if (rec_state & REC_INITTED)
      return;

   if ((map.fp = fopen (LINESFILE, "w")) == (FILE *) 0) {
#ifdef _REPORT_ACTIONS
      fprintf (ERRFILE, "%s failed to open\n", LINESFILE);	/* ## omit */
#endif
      return;
   }
   if ((map.fdtwo = fopen (PTRFILE, "wb+")) == (FILE *) 0) {
      (void) fclose (map.fp);
#ifdef _REPORT_ACTIONS
      fprintf (ERRFILE, "%s failed to open\n", PTRFILE);	/* ## omit */
#endif
      return;
   }

   map.log = ERRFILE;
   map.lmap = map.pmap = 0;
   map.nalloc = map.nrlloc = map.nfree = map.nbfree = 0L;
   map.ninuse = 0L;
   map.avgsiz = (float) 0.0;

   for (xp = 0; xp < HASHSIZ; xp++) {
      map.phash[xp] = (struct s_ptr *) 0;
      map.shash[xp] = (struct sym *) 0;
   }

   rec_state = REC_INITTED | REC_ON;
   atexit (dumpsyms);
}

/* set logging to a FILE * */
void mnem_setlog (FILE * fp)
{
   map.log = fp;
}

/* return state of the recorder */
int mnem_recording (void)
{
   return ((rec_state & REC_ON) && !(rec_state & REC_ERR));
}

/* turn on or off recording */
int mnem_setrecording (int val)
{
   if (val)
      rec_state |= REC_ON;
   else
      rec_state &= ~REC_ON;

   if (map.fp != (FILE *) 0)
      (void) fflush (map.fp);

   rec_state |= REC_ONOFF;
   return (0);
}

/* lookup a pointer record - search pointer hash table */
static struct s_ptr *lookupptr (mptr)
     mall_t mptr;
{
register struct s_ptr *p;

   /* this probably give simply terrible hash performance */
   p = map.phash[(unsigned long) mptr % HASHSIZ];
   while (p != (struct s_ptr *) 0) {
      if (mptr == p->ptr)
	 return (p);
      p = p->next;
   }
   return ((struct s_ptr *) 0);
}

/*
 * polynomial conversion ignoring overflows
 * [this seems to work remarkably well, in fact better
 * then the ndbm hash function. Replace at your own risk]
 * use: 65599   nice.
 *      65587   even better.
 * author: oz@nexus.yorku.ca
 */
static unsigned int dbm_hash (str)
     register char *str;
{
register unsigned long n = 0L;

   while (*str != '\0')
      n = *str++ + 65599L * n;
   return ((int) n);
}

/* lookup a line/source entry by name (search hash table) */
static struct sym *lookupsymbyname (nam, lin)
     char *nam;
     int lin;
{
register struct sym *s;
char *p = nam;

   if (p == (char *) 0)
      p = "unknown";

   s = map.shash[(dbm_hash (p) + lin) % HASHSIZ];
   while (s != (struct sym *) 0) {
      if (!strcmp (s->labl, nam) && s->lineno == lin)
	 return (s);
      s = s->next;
   }

   return ((struct sym *) 0);
}

/* lookup a line/source entry by number (exhaustively search hash table) */
static struct sym *lookupsymbynum (int num)
{
   struct sym *s;
   int x;

   for (x = 0; x < HASHSIZ; x++) {
      s = map.shash[x];
      while (s != (struct sym *) 0) {
	 if (s->mapno == num)
	    return (s);
	 s = s->next;
      }
   }
   return (NULL);
}

/* stuff a pointer's value in the pointer map table */
static void storeptr (mptr, siz, lab, lin)
     mall_t mptr;
     unsigned siz;
     char *lab;
     int lin;
{
register struct s_ptr *p;
register struct sym *s;
int hv;

   /* 
      is there is no existing symbol entry for this line of code... we must
      needs make one - and painful it is... */
   if ((s = lookupsymbyname (lab, lin)) == (struct sym *) 0) {
      s = (struct sym *) malloc (sizeof (struct sym));
      if (s == (struct sym *) 0) {
	 pmsg ("mnemosyne: cannot allocate sym entry\n");
	 rec_state |= REC_ERR;
	 return;
      }

      /* 
         this is funky - since we know the label is (?) compiled-in, we can
         just keep a pointer to it, rather than copying our own version of
         it.

         *** Unfortunately *** this isn't so on the Archimedes, where I
         append the caller-proc to the file-name :-(

         So... keep a private buffer of unique strings (don't use the heap)
         Most will be reused, so shouldn't get too big.

       */
      if (lab != (char *) 0) {

static char pool[4096] = { '\0' };
static char *next_free_pool = pool;
char *p = next_free_pool;

	 s->labl = lab;
	 for (;;) {
	    if (strcmp (lab, p) == 0) {
	       s->labl = p;
	       break;
	    }
	    if (p == pool) {
	       if ((next_free_pool - pool) + strlen (lab) + 1 >= 4096) {
		  pmsg ("memdebug warning: not enough room for symbols\n");
		  break;
	       }
	       strcpy (next_free_pool, lab);
	       s->labl = next_free_pool;
	       next_free_pool += strlen (lab) + 1;
	       break;
	    }
	    p -= 1;
	 }
      } else {
	 s->labl = "unknown";
      }

      s->mapno = map.lmap++;
#ifdef _REPORT_ACTIONS
      fprintf (ERRFILE, "info: next mapno is %d\n", s->mapno);
#endif

      /* add sym to hash table */
      s->next = map.shash[hv = ((dbm_hash (s->labl) + lin) % HASHSIZ)];
      map.shash[hv] = s;

      s->lineno = lin;
      s->mallcnt = 1;
      s->avsiz = (float) siz;
      savesym (s);
   } else {
      /* found an already defined symbol. store some averages */
      s->avsiz = ((s->avsiz * (float) s->mallcnt) + (float) siz) /
       ((float) s->mallcnt + (float) 1.0);
      (s->mallcnt)++;
   }

   p = lookupptr (mptr);
   if (p != (struct s_ptr *) 0 && p->dsk.siz != 0) {
struct sym *x;

      pmsg ("pointer re-allocated without being freed");
      ploc (lab, lin, siz);
      pmsg ("\n");
      if ((x = lookupsymbynum (p->dsk.smap)) != (struct sym *) 0) {
	 pmsg ("last allocated at");
	 psym (x);
	 pmsg ("\n");
      }
   }

   /* heavy sigh. no entry for this pointer. make one. */
   if (p == (struct s_ptr *) 0) {
      p = (struct s_ptr *) malloc (sizeof (struct s_ptr));
      if (p == (struct s_ptr *) 0) {
	 pmsg ("mnemosyne: cannot expand pointer table\n");
	 rec_state |= REC_ERR;
	 return;
      }

      /* link it in */
      p->next = map.phash[(unsigned long) mptr % HASHSIZ];
      map.phash[(unsigned long) mptr % HASHSIZ] = p;
   }

   /* if we get to here (hazoo! hazaa!) both 's' and 'p' are OK */
   p->ptr = mptr;
   p->dsk.siz = siz;
   p->dsk.smap = s->mapno;
   p->map = map.pmap++;
#ifdef TOMBSTONES
   p->warned = (0 != 0);
#endif

   /* store the size */
   map.ninuse += siz;

   saveptr (p);
}

/*
mark a pointer as now being free. note that a 1 is returned IF
the actual value should NOT be really passed to free()
*/
static int freeptr (mptr, lab, lin, varname)
     mall_t mptr;
     char *lab;
     int lin;
     char *varname;
{
register struct s_ptr *p;

   p = lookupptr (mptr);
   if (p == (struct s_ptr *) 0) {
#ifndef TOMBSTONES		/* already warned about elsewhere */
      pmsg (varname);
      pmsg (" freed but was never allocated");
      ploc (lab, lin, (unsigned int) -1);
      pmsg ("\n");
#endif
      return (1);
   }

   if (p != (struct s_ptr *) 0 && p->dsk.siz == 0) {
struct sym *x;

      pmsg (varname);
      pmsg (" re-freed when already free");
      ploc (lab, lin, (unsigned int) -1);
      pmsg ("\n");
      if ((x = lookupsymbynum (p->dsk.smap)) != (struct sym *) 0) {
	 pmsg ("last allocated at");
	 psym (x);
	 pmsg ("\n");
      }
      return (1);
   }

   /* get some free */
   map.ninuse -= p->dsk.siz;

   /* write in the map that it is free */
   p->dsk.siz = 0;
   saveptr (p);

   return (0);
}

/*
  Check a pointer as in use and valid.
    Return 0
      'valid pointer'
    Return 1
      'pointer freed that was never allocated'
    Returns 2
      'array trampled on; already warned about'
    Returns 3
      'pointer freed'
*/
static int checkptr (mall_t mptr, char **declab, int *declin	/* , char
								   *caller,
								   char
								   *varname */ )
{
#ifdef TOMBSTONES
   register struct s_ptr *p;
   struct sym *inf;

   p = lookupptr (mptr);
   if (p == (struct s_ptr *) 0) {
      *declab = "another module?";
      *declin = -1;
      return (1);
   }

   inf = lookupsymbynum (p->map);
   if (inf != NULL) {
      *declab = inf->labl;
      *declin = inf->lineno;
   } else {
   static char tmp[128];

      sprintf (tmp, "(see map entry %d in %s)", p->map, PTRFILE);
      *declab = tmp;
      *declin = -1;
   }

   if (p != (struct s_ptr *) 0 && p->dsk.siz == 0) {
      return (3);
   }

   if (p->warned) {
      return (2);		/* earlier check spotted data corruption */
   }
#endif
   return (0);
}

/* pretend we are malloc() */
mall_t mnem_malloc (unsigned siz, char *lab, int lin, char *caller)
{
static char tmp[256];
mall_t ret;

   if (!(rec_state & REC_INITTED))
      initmap ();

   if (checking_all && (rec_state & REC_INITTED))
      checkheap (lab, lin, caller);

   sprintf (tmp, "%s/%s", lab, caller);
#define lab tmp

   if ((sizeof (int) > 2) && ((int) siz < 0)) {
      pmsg ("negative parameter to malloc");
      ploc (lab, lin, siz);
      pmsg ("\n");
      return (NULL);
   }

   if ((ret = malloc (siz
#ifdef TOMBSTONES
		      + (((siz + 3) & (~3)) - siz /* 0..3 */ ) +
		      sizeof (*pre) + sizeof (*post)
#endif
	)) == (mall_t) 0) {
      pmsg ("malloc returned null pointer at");
      ploc (lab, lin, siz);
      pmsg ("\n");
      return (ret);
   }
#ifdef TOMBSTONES
   pre = (struct prestruct *) ret;
   pre->bytesize = (siz + 3) & (~3);
   pre->heappremagic = HEAP_PRE_MAGIC;
   ret = (mall_t) (((char *) ret) + sizeof (*pre));	/* because mall_t
							   could be void * */
   post = (struct poststruct *) (((char *) ret) + pre->bytesize);
   post->heappostmagic = HEAP_POST_MAGIC;
   if (pre->bytesize != siz) {
int i;

      for (i = siz; i < pre->bytesize; i++) {
	 *(((char *) ret) + i) = 42;
#ifdef _REPORT_ACTIONS
	 fprintf (ERRFILE, "diag: adding extra byte check at %p\n", (((char *) ret) + i));	/* ## 
												   omit 
												 */
#endif
      }
   }
   pre->bytesize = siz;
#ifdef _REPORT_ACTIONS
   fprintf (ERRFILE, "Tombstones added to %u-byte (%u) array at %p\n", siz, pre->bytesize, ret);	/* ## 
													   omit 
													 */
   fprintf (ERRFILE, "Tombstones start addr: %p\n", pre);	/* ## omit */
   fprintf (ERRFILE, "Tombstones end addr:   %p\n", post);	/* ## omit */
#endif
#endif
   if ((rec_state & REC_ON) && !(rec_state & REC_ERR))
      storeptr (ret, siz, lab, lin);

   map.avgsiz = ((map.avgsiz * (float) map.nalloc) + (float) siz) /
    ((float) map.nalloc + (float) 1.0);
   map.nalloc++;
   return (ret);
#undef lab
}

/* pretend we are strdup() */
mall_t mnem_strdup (char *s, char *lab, int lin, char *caller)
{
char *new;

   new = mnem_malloc (strlen (s) + 1, lab, lin, caller);
   strcpy (new, s);
   return new;
}

/* pretend we are calloc() */
mall_t mnem_calloc (cnt, siz, lab, lin, caller)
     unsigned cnt;
     unsigned siz;
     char *lab;
     int lin;
     char *caller;
{
static char tmp[256];
mall_t ret;

   if (!(rec_state & REC_INITTED))
      initmap ();

   if (checking_all && (rec_state & REC_INITTED))
      checkheap (lab, lin, caller);

   sprintf (tmp, "%s/%s", lab, caller);
#define lab tmp

   if ((sizeof (int) > 2) && (((int) siz < 0) || ((int) cnt < 0))) {
      pmsg ("negative parameter to calloc");
      ploc (lab, lin, siz);
      pmsg ("\n");
      return (NULL);
   }

   /* makes it easier for us if we have a single siz instead of cnt*siz;
      however *if* (and I don't know if it is true) calloc has the size
      parameter in order to guarantee alignment to that storage unit size,
      then this may possibly return badly aligned data.  However the hassles
      of working out the size needed etc in the general case, in order to add 
      tombstones, is so nasty that I'm leaving it like this and keeping my
      fingers crossed.  It's probably just my paranoia showing again. */

   siz = cnt * siz;
   if ((ret = calloc (1, siz
#ifdef TOMBSTONES
		      + (((siz + 3) & (~3)) - siz /* 0..3 */ ) +
		      sizeof (*pre) + sizeof (*post)
#endif
	)) == (mall_t) 0) {
      pmsg ("calloc returned null pointer at");
      ploc (lab, lin, siz);
      pmsg ("\n");
      return (ret);
   }
#ifdef TOMBSTONES
   pre = (struct prestruct *) ret;
   pre->bytesize = (siz + 3) & (~3);
   pre->heappremagic = HEAP_PRE_MAGIC;
   ret = (mall_t) (((char *) ret) + sizeof (*pre));	/* because mall_t
							   could be void * */
   post = (struct poststruct *) (((char *) ret) + pre->bytesize);
   post->heappostmagic = HEAP_POST_MAGIC;
   if (pre->bytesize != siz) {
int i;

      for (i = siz; i < pre->bytesize; i++) {
	 *(((char *) ret) + i) = 42;
#ifdef _REPORT_ACTIONS
	 fprintf (ERRFILE, "diag: adding extra byte check at %p\n", (((char *) ret) + i));	/* ## 
												   omit 
												 */
#endif
      }
   }
   pre->bytesize = siz;
#ifdef _REPORT_ACTIONS
   fprintf (ERRFILE, "Tombstones added to %u-byte (%u) array at %p\n", siz, pre->bytesize, ret);	/* ## 
													   omit 
													 */
   fprintf (ERRFILE, "Tombstones start addr: %p\n", pre);	/* ## omit */
   fprintf (ERRFILE, "Tombstones end addr:   %p\n", post);	/* ## omit */
#endif
#endif
   if ((rec_state & REC_ON) && !(rec_state & REC_ERR))
      storeptr (ret, siz, lab, lin);

   map.avgsiz = ((map.avgsiz * (float) map.nalloc) + (float) siz) /
    ((float) map.nalloc + (float) 1.0);
   map.nalloc++;
   return (ret);
#undef lab
}

/* pretend we are realloc() */
mall_t mnem_realloc (mall_t mptr,
		     unsigned siz,
		     char *lab, int lin, char *caller, char *varname)
{
static char tmp[256];
mall_t ret;

   if (!(rec_state & REC_INITTED))
      initmap ();

   if (checking_all && (rec_state & REC_INITTED))
      checkheap (lab, lin, caller);

   sprintf (tmp, "%s/%s", lab, caller);
#define lab tmp

   if ((sizeof (int) > 2) && ((int) siz < 0)) {
      pmsg ("negative parameter to realloc");
      ploc (lab, lin, siz);
      pmsg ("\n");
      return (NULL);
   }
#ifdef TOMBSTONES
   pre = NULL;
   if (mnem_checkptr (mptr, lab, lin, caller, varname)) {
      pre = (struct prestruct *) (((char *) mptr) - sizeof (*pre));
      post =
       (struct poststruct *) (((char *) mptr) + ((pre->bytesize + 3) & (~3)));
      pre->heappremagic = HEAP_PRE_DEAD;
      post->heappostmagic = HEAP_POST_DEAD;
   }
   if (pre != NULL) {
      mptr = pre;
      if ((ret = realloc (mptr, siz + (((siz + 3) & (~3)) - siz /* 0..3 */ ) +
			  sizeof (*pre) + sizeof (*post)
	   )) == (mall_t) 0) {
	 pmsg ("realloc returned null pointer at");
	 ploc (lab, lin, siz);
	 pmsg ("\n");
	 return (ret);
      }
      /* Now tweak ret, adjust values in our private header, and and
         tombstone at end.  Initialise new part of data? */
      pre = (struct prestruct *) ret;
      pre->bytesize = (siz + 3) & (~3);
      pre->heappremagic = HEAP_PRE_MAGIC;
      ret = (mall_t) (((char *) ret) + sizeof (*pre));	/* because mall_t
							   could be void * */
      post = (struct poststruct *) (((char *) ret) + pre->bytesize);
      post->heappostmagic = HEAP_POST_MAGIC;
      if (pre->bytesize != siz) {
int i;

	 for (i = siz; i < pre->bytesize; i++) {
	    *(((char *) ret) + i) = 42;
	    fprintf (ERRFILE, "diag: adding extra byte check at %p\n",
		     (((char *) ret) + i));
	 }
      }
      pre->bytesize = siz;
#ifdef _REPORT_ACTIONS
      fprintf (ERRFILE, "Tombstones added to %u-byte (%u) array at %p\n", siz, pre->bytesize, ret);	/* ## 
													   omit 
													 */
      fprintf (ERRFILE, "Tombstones start addr: %p\n", pre);	/* ## omit */
      fprintf (ERRFILE, "Tombstones end addr:   %p\n", post);	/* ## omit */
#endif
      /* re-adjust old mptr so it can be freed properly */
      mptr = (mall_t) (((char *) mptr) + sizeof (*pre));
   } else {
      /* Not one of our pointers? Try a kosher realloc anyway */
      if ((ret = realloc (mptr, siz)) == (mall_t) 0) {
	 pmsg ("realloc returned null pointer at");
	 ploc (lab, lin, siz);
	 pmsg ("\n");
	 return (ret);
      }
   }
#else
   /* Should still check that it is one of ours and warn if not? Maybe the
      freeptr/storeptr below should be split into a freeptr before this
      realloc and a storeptr after? Or maybe just add a checkptr before? */
   if ((ret = realloc (mptr, siz)) == (mall_t) 0) {
      pmsg ("realloc returned null pointer at");
      ploc (lab, lin, siz);
      pmsg ("\n");
      return (ret);
   }
#endif
   if ((rec_state & REC_ON) && !(rec_state & REC_ERR)) {
      /* freeptr doesn't free the data; it just undoes the entry for it in
         our tables, so that it can be replaced with what is possibly a new
         location. (realloc can move data around) */
      if (!freeptr (mptr, lab, lin, varname))
	 storeptr (ret, siz, lab, lin);
   }

   map.nrlloc++;
   return (ret);
#undef lab
}

int
mnem_checkptr (mall_t mptr, char *lab, int lin, char *caller, char *varname)
{
#ifdef TOMBSTONES
   char *declab = "unknown";
   int declin = -1;
   int checkcode;

   if (!(rec_state & REC_INITTED))
      initmap ();

   if ((rec_state & REC_ON) && !(rec_state & REC_ERR)) {
      if ((checkcode =
	   checkptr (mptr, &declab, &declin /* , caller, varname */ )) == 0) {
	 pre = (struct prestruct *) (((char *) mptr) - sizeof (*pre));
	 if (pre->heappremagic != HEAP_PRE_MAGIC) {
	    pmsg ("You may have written before the start of the array\n");
	    pmsg (varname);
	    pmsg (" allocated at");
	    ploc (declab, declin, (unsigned int) -1);
	    pmsg ("\ndetected at");
	    ploc (lab, lin, (unsigned int) -1);
	    pmsg ("\n");
#ifdef _REPORT_ACTIONS
	    fprintf (ERRFILE, "Warning: heap area at %p may have become corrupt (low end)\n", mptr);	/* ## 
													   omit 
													 */
#endif
	    return ((0 != 0));
	 } else {
#ifdef _REPORT_ACTIONS
	    fprintf (ERRFILE, "diag: pre-magic OK at %p\n", mptr);	/* ## 
									   omit 
									 */
#endif
	    post =
	     (struct poststruct *) (((char *) mptr) +
				    ((pre->bytesize + 3) & (~3)));
	    if (post->heappostmagic != HEAP_POST_MAGIC) {
	       pmsg ("You may have written past the end of the array\n");
	       pmsg (varname);
	       pmsg (" allocated at");
	       ploc (declab, declin, pre->bytesize);
	       pmsg ("\ndetected at");
	       ploc (lab, lin, (unsigned int) -1);
	       pmsg ("\n");
#ifdef _REPORT_ACTIONS
	       fprintf (ERRFILE, "Warning: heap area at %p may have become corrupt (high end)\n", mptr);	/* ## 
														   omit 
														 */
#endif
	       return ((0 != 0));
	    } else {
	       if (((pre->bytesize) & 3) != 0) {
   int i, j;

		  j = ((pre->bytesize) + 3) & (~3);
		  i = pre->bytesize;
		  while (i < j) {
#ifdef _REPORT_ACTIONS
		     fprintf (ERRFILE, "diag: checking extra byte at %p\n", (((char *) mptr) + i));	/* ## 
													   omit 
													 */
#endif
		     if (*(((char *) mptr) + i) != 42) {
			pmsg
			 ("You may have written past the end of the array\n");
			pmsg (varname);
			pmsg (" allocated at");
			ploc (declab, declin, pre->bytesize);
			pmsg ("\ndetected at");
			ploc (lab, lin, (unsigned int) -1);
			pmsg ("\n");
#ifdef _REPORT_ACTIONS
			fprintf (ERRFILE, "Warning: byte after %p may have become corrupt (now = %d)\n", mptr, *(((char *) mptr) + i));	/* ## 
																	   omit 
																	 */
#endif
			return ((0 != 0));
		     }
		     i += 1;
		  }
		  return ((0 == 0));
	       } else {
#ifdef _REPORT_ACTIONS
		  fprintf (ERRFILE, "diag: post-magic OK at %p\n", mptr);	/* ## 
										   omit 
										 */
#endif
		  return ((0 == 0));
	       }
	    }
	 }
	 /* mptr = (mall_t)pre; ... no point - not passed back... */
      } else if (checkcode == 2) {
	 return ((0 == 0));	/* say its OK, so that free'd correctly */
      } else if (checkcode == 3) {
	 return ((0 == 0));	/* say its OK, and let 'free' complain */
      } else if (mptr == NULL) {
	 return ((0 != 0));	/* null pointer - let someone else complain */
      } else {
   mall_t start = NULL;

	 pre = (struct prestruct *) (((char *) mptr) - sizeof (*pre));
	 if (pre->heappremagic != HEAP_PRE_MAGIC) {
   char msg[1024];

	    if (static_area (mptr)) {
	       sprintf (msg, "%s at %p is in the static area", varname, mptr);
	    } else if (prog_area (mptr)) {
	       sprintf (msg,
			"%s at %p is in the code area (literal constant or procedure)",
			varname, mptr);
	    } else if (stack_area (mptr)) {
	       sprintf (msg, "%s at %p is in the stack area", varname, mptr);
	    } else if (withinheap (mptr, &start)) {
	       checkptr (start, &declab, &declin /* , caller, varname */ );
	       pre = (struct prestruct *) (((char *) start) - sizeof (*pre));
	       sprintf (msg,
			"%s at %p is %d bytes inside the heap block\n"
			"allocated at",
			varname, mptr, (char *) mptr - (char *) start);
	       /* WARNING: the offset calculation above is a little dubious
	          on brain-dead segmented architectures */
	       pmsg (msg);
	       if (pre->heappremagic != HEAP_PRE_MAGIC) {
		  ploc (declab, declin, (unsigned int) -1);
	       } else {
		  ploc (declab, declin, pre->bytesize);
	       }
	       sprintf (msg, "");
	       /* mptr = start; ... no point - not passed up ... */
	    } else {
	       sprintf (msg,
			"heap area at %p may have become corrupt,\nor %s%s%s%s%s%s",
			mptr, varname, " not allocated in this module,\nor ",
			varname, " stepped past initial value,\nor ",
			varname, " points to static/auto data not in heap");
	    }
	    pmsg (msg);
	    pmsg ("\ndetected at");
	    ploc (lab, lin, (unsigned int) -1);
	    pmsg ("\n");
	    return ((0 != 0));
	 } else if (pre->heappremagic == HEAP_PRE_DEAD) {
   char msg[1024];

	    sprintf (msg,
		     "heap area %s at %p appears to have been freed already,\n%s",
		     varname, mptr,
		     "perhaps you have two pointers to the same structure?");
	    pmsg (msg);
	    pmsg ("\ndetected at");
	    ploc (lab, lin, (unsigned int) -1);
	    pmsg ("\n");
	    return ((0 != 0));
	 } else {
#ifdef _REPORT_ACTIONS
	    fprintf (ERRFILE, "Xdiags: pre-magic OK at %p\n", mptr);	/* ## 
									   omit 
									 */
#endif
	    mptr = (mall_t) (((char *) mptr) + sizeof (*pre));
	    return ((0 == 0));	/* maybe */
	 }
      }
   }
#endif /* Tombstones */
   /* NOT REACHED */
   return ((0 != 0));
}

static void checkheap (char *lab, int lin, char *caller)
{				/* Run down *all* known arrays if possible
				   and check for corruption */
#ifdef TOMBSTONES
   int x;
   struct s_ptr *p;
   int maxlmap = map.lmap;

   for (x = 0; x < HASHSIZ; x++) {
      p = map.phash[x];
      while (p != (struct s_ptr *) 0) {
	 if (p->dsk.siz != 0) {	/* skip freed entries */
#ifdef _REPORT_ACTIONS
	    fprintf (ERRFILE, "Checking pointer to %p\n", p->ptr);	/* ## 
									   omit 
									 */
#endif
	     /*GT*/		/* this stuff is wrong.  I think I've got
				   confused over the roles of map/mapno/smap */
#ifdef NEVER
	     if (p->map >= maxlmap) {

	       if (strcmp (lab, __FILE__) == 0)
		  return;	/* avoid recursion */
	       fprintf (ERRFILE,
			"Possible internal corruption: smap = %d\n", p->map);
	       fprintf (ERRFILE,
			"Detected at %s/%s, Line %d\n", lab, caller, lin);
	       exit (0);

	    }
#endif
	    if (p->warned
		|| mnem_checkptr (p->ptr, lab, lin, caller, "pointer")) {
	    } else {
	       p->warned = (0 == 0);
	    }
	 }
	 p = p->next;
      }
   }
#endif
}

void mnem_checkheap (char *lab, int lin, char *caller)
{				/* Run down *all* known arrays if possible
				   and check for corruption */
   checking_all = (0 != 0);
   checkheap (lab, lin, caller);
}

/* pretend we are free() */
void mnem_free (mall_t mptr, char *lab, int lin, char *caller, char *varname)
{
   static char tmp[256];

   if (!(rec_state & REC_INITTED))
      initmap ();

#ifdef TOMBSTONES
   if (rec_state & REC_INITTED)
      checkheap (lab, lin, caller);
#endif

   sprintf (tmp, "%s/%s", lab, caller);
#define lab tmp

   if ((rec_state & REC_ON) && !(rec_state & REC_ERR)) {
      if (mptr == NULL) {
	 pmsg ("free: ");
	 pmsg (varname);	/* or 'pointer' */
	 pmsg (" is null at");
	 ploc (lab, lin, (unsigned int) -1);
	 pmsg ("\n");
	 return;
      }
#ifdef TOMBSTONES
      pre = NULL;
      if (mnem_checkptr (mptr, lab, lin, caller, varname)) {
	 /* fprintf (stdout, "mptr is at %p\n", mptr); */
	 pre = (struct prestruct *) (((char *) mptr) - sizeof (*pre));
	 /* fprintf (stdout, "pre is at %p, pre->bytesize is %d\n", pre,
	    pre->bytesize); */
	 /* fprintf (stdout, "pre->heappremagic = %04x\n",
	    pre->heappremagic); */

	 if (pre->heappremagic != HEAP_PRE_MAGIC) {
	    /* corrupt.  can't trust length */
	    pre->bytesize = 0;
	 }

	 post =
	  (struct poststruct *) (((char *) mptr) +
				 ((pre->bytesize + 3) & (~3)));
	 pre->heappremagic = HEAP_PRE_DEAD;
	 fflush (stdout);
	 /* fprintf (stdout, "post is at %p, post->heappostmagic is at %p\n",
	    post, &post->heappostmagic); */
	 /* fflush (stdout); */
	 post->heappostmagic = HEAP_POST_DEAD;
      }
#endif
      if (freeptr (mptr, lab, lin, varname) == 0) {
#ifdef TOMBSTONES
	 if (pre != NULL)
	    mptr = (mall_t) pre;
#endif
	 (void) free (mptr);
	 map.nfree++;
      } else {
	 map.nbfree++;
      }
   }
#undef lab
}

/* dump everything we know about nothing in particular */
int mnem_writestats (void)
{
   register struct sym *s;
   register int x;

   if (map.fp == (FILE *) 0) {
      fprintf (ERRFILE, "mnemosyn: warning -- no info to save\n");
      return (-1);
   }

   (void) fseek (map.fp, 0L, SEEK_SET);

   /* dump our life's story */
   (void) fprintf (map.fp, "#total allocations:%ld\n", map.nalloc);
   (void) fprintf (map.fp, "#total re-allocations:%ld\n", map.nrlloc);
   (void) fprintf (map.fp, "#total frees:%ld\n", map.nfree);

   if (map.nbfree != 0L)
      (void) fprintf (map.fp, "#bad/dup frees:%ld\n", map.nbfree);

   (void) fprintf (map.fp, "#total allocated never freed:%ld\n", map.ninuse);

   (void) fprintf (map.fp, "#average size of allocations:%.1f\n", map.avgsiz);

   /* note if we detected an internal error */
   if (rec_state & REC_ERR)
      (void) fprintf (map.fp, "#(figures likely inaccurate due to error)\n");

   /* note if the system was on all the time ? */
   if (!(rec_state & REC_ON) || (rec_state & REC_ONOFF))
      (void) fprintf (map.fp,
		      "#(figures likely inaccurate as recording was off)\n");

   /* write the legend */
   (void) fprintf (map.fp, "#map#  calls        ave        line#   file\n");

   for (x = 0; x < HASHSIZ; x++) {
      s = map.shash[x];
      while (s != (struct sym *) 0) {
	 savesym (s);
	 s = s->next;
      }
   }

   (void) fflush (map.fp);
#ifdef TOMBSTONES
   /* beware of recursion if exiting due to an internal error! */
   if (rec_state & REC_INITTED)
      checkheap (__FILE__, __LINE__, __FUNCTION__);
#endif
   fprintf (ERRFILE, "Heap usage information written to %s and %s\n",
	    LINESFILE, PTRFILE);

   return (0);
}