#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); }