/* ** MEMWATCH.C ** Nonintrusive ANSI C memory leak / overwrite detection ** Copyright (C) 1992-99 Johan Lindh ** All rights reserved. ** Version 2.59 ** ** 920810 JLI [1.00] ** 920830 JLI [1.10 double-free detection] ** 920912 JLI [1.15 mwPuts, mwGrab/Drop, mwLimit] ** 921022 JLI [1.20 ASSERT and VERIFY] ** 921105 JLI [1.30 C++ support and TRACE] ** 921116 JLI [1.40 mwSetOutFunc] ** 930215 JLI [1.50 modified ASSERT/VERIFY] ** 930327 JLI [1.51 better auto-init & PC-lint support] ** 930506 JLI [1.55 MemWatch class, improved C++ support] ** 930507 JLI [1.60 mwTest & CHECK()] ** 930809 JLI [1.65 Abort/Retry/Ignore] ** 930820 JLI [1.70 data dump when unfreed] ** 931016 JLI [1.72 modified C++ new/delete handling] ** 931108 JLI [1.77 mwSetAssertAction() & some small changes] ** 940110 JLI [1.80 no-mans-land alloc/checking] ** 940328 JLI [2.00 version 2.0 rewrite] ** Improved NML (no-mans-land) support. ** Improved performance (especially for free()ing!). ** Support for 'read-only' buffers (checksums) ** ^^ NOTE: I never did this... maybe I should? ** FBI (free'd block info) tagged before freed blocks ** Exporting of the mwCounter variable ** mwBreakOut() localizes debugger support ** Allocation statistics (global, per-module, per-line) ** Self-repair ability with relinking ** 950913 JLI [2.10 improved garbage handling] ** 951201 JLI [2.11 improved auto-free in emergencies] ** 960125 JLI [X.01 implemented auto-checking using mwAutoCheck()] ** 960514 JLI [2.12 undefining of existing macros] ** 960515 JLI [2.13 possibility to use default new() & delete()] ** 960516 JLI [2.20 suppression of file flushing on unfreed msgs] ** 960516 JLI [2.21 better support for using MEMWATCH with DLL's] ** 960710 JLI [X.02 multiple logs and mwFlushNow()] ** 960801 JLI [2.22 merged X.01 version with current] ** 960805 JLI [2.30 mwIsXXXXAddr() to avoid unneeded GP's] ** 960805 JLI [2.31 merged X.02 version with current] ** 961002 JLI [2.32 support for realloc() + fixed STDERR bug] ** 961222 JLI [2.40 added mwMark() & mwUnmark()] ** 970101 JLI [2.41 added over/underflow checking after failed ASSERT/VERIFY] ** 970113 JLI [2.42 added support for PC-Lint 7.00g] ** 970207 JLI [2.43 added support for strdup()] ** 970209 JLI [2.44 changed default filename to lowercase] ** 970405 JLI [2.45 fixed bug related with atexit() and some C++ compilers] ** 970723 JLI [2.46 added MW_ARI_NULLREAD flag] ** 970813 JLI [2.47 stabilized marker handling] ** 980317 JLI [2.48 ripped out C++ support; wasn't working good anyway] ** 980318 JLI [2.50 improved self-repair facilities & SIGSEGV support] ** 980417 JLI [2.51 more checks for invalid addresses] ** 980512 JLI [2.52 moved MW_ARI_NULLREAD to occur before aborting] ** 990112 JLI [2.53 added check for empty heap to mwIsOwned] ** 990217 JLI [2.55 improved the emergency repairs diagnostics and NML] ** 990224 JLI [2.56 changed ordering of members in structures] ** 990303 JLI [2.57 first maybe-fixit-for-hpux test] ** 990516 JLI [2.58 added 'static' to the definition of mwAutoInit] ** 990517 JLI [2.59 fixed some high-sensitivity warnings] */ #define __MEMWATCH_CPP #ifdef MW_NOCPP #define MEMWATCH_NOCPP #endif #ifdef MW_STDIO #define MEMWATCH_STDIO #endif /*********************************************************************** ** Include files ***********************************************************************/ #include #include #include #include #include #include #include #include "memwatch.h" #ifndef toupper #include #endif /*********************************************************************** ** Defines & other weird stuff ***********************************************************************/ /*lint -save -e767 */ #define VERSION "2.59" /* the current version number */ #define CHKVAL(mw) (0xFE0180L^(long)mw->count^(long)mw->size^(long)mw->line) #define FLUSH() mwFlush() #define TESTS(f,l) if(mwTestAlways) (void)mwTestNow(f,l,1) #define PRECHK 0x01234567L #define POSTCHK 0x76543210L /*lint -restore */ #define MW_NML 0x0001 #ifdef _MSC_VER #define COMMIT "c" /* Microsoft C requires the 'c' to perform as desired */ #else #define COMMIT "" /* Normal ANSI */ #endif /* _MSC_VER */ #ifdef __cplusplus #define CPPTEXT "++" #else #define CPPTEXT "" #endif /* __cplusplus */ #ifdef MEMWATCH_STDIO #define mwSTDERR stderr #else #define mwSTDERR mwLog #endif /*********************************************************************** ** Defines to read/write 32 bit words in a portable way ** Note: Assumes that a 'long int' is 32 bits, and a 'char' is 8 bits. ***********************************************************************/ typedef unsigned char mwBYTE; typedef unsigned long mwDWORD; #define GETDWORD(l, cp) { \ register mwBYTE *t_cp = (mwBYTE *)(cp); \ (l) = ((mwDWORD)t_cp[0] << 24) \ | ((mwDWORD)t_cp[1] << 16) \ | ((mwDWORD)t_cp[2] << 8) \ | ((mwDWORD)t_cp[3]) \ ; \ } #define PUTDWORD(l, cp) { \ register mwDWORD t_l = (mwDWORD)(l); \ register mwBYTE *t_cp = (mwBYTE *)(cp); \ *t_cp++ = t_l >> 24; \ *t_cp++ = t_l >> 16; \ *t_cp++ = t_l >> 8; \ *t_cp = t_l; \ } /*********************************************************************** ** Typedefs & structures ***********************************************************************/ /* main data holding area, precedes actual allocation */ typedef struct mwData_ mwData; struct mwData_ { mwData *prev; /* previous allocation in chain */ mwData *next; /* next allocation in chain */ const char *file; /* file name where allocated */ long count; /* action count */ long check; /* integrity check value */ #if 0 long crc; /* data crc value */ #endif size_t size; /* size of allocation */ int line; /* line number where allocated */ unsigned flag; /* flag word */ }; /* statistics structure */ typedef struct mwStat_ mwStat; struct mwStat_ { mwStat *next; /* next statistic buffer */ const char *file; long total; /* total bytes allocated */ long num; /* total number of allocations */ long max; /* max allocated at one time */ long curr; /* current allocations */ int line; }; /* grabbing structure, 1K in size */ typedef struct mwGrabData_ mwGrabData; struct mwGrabData_ { mwGrabData *next; int type; char blob[1024 - sizeof(mwGrabData *) - sizeof(int)]; }; typedef struct mwMarker_ mwMarker; struct mwMarker_ { void *host; char *text; mwMarker *next; int level; }; /*********************************************************************** ** Static variables ***********************************************************************/ static int mwInited = 0; static int mwInfoWritten = 0; static int mwUseAtexit = 0; static FILE *mwLog = NULL; static int mwFlushing = 0; static int mwStatLevel = MW_STAT_DEFAULT; static int mwNML = MW_NML_DEFAULT; static int mwFBI = 0; static long mwAllocLimit = 0L; static int mwUseLimit = 0; static long mwNumCurAlloc = 0L; static mwData *mwHead = NULL; static mwData *mwTail = NULL; static void (*mwOutFunction) (int) = NULL; static int (*mwAriFunction) (const char *) = NULL; static int mwAriAction = MW_ARI_ABORT; static char mwPrintBuf[MW_TRACE_BUFFER + 8]; static long mwCounter = 0L; static long mwErrors = 0L; static int mwTestFlags = 0; static int mwTestAlways = 0; static FILE *mwLogB1 = NULL; static int mwFlushingB1 = 0; static mwStat *mwStatList = NULL; static long mwStatTotAlloc = 0L; static long mwStatMaxAlloc = 0L; static long mwStatNumAlloc = 0L; static long mwStatCurAlloc = 0L; static long mwNmlNumAlloc = 0L; static long mwNmlCurAlloc = 0L; static mwGrabData *mwGrabList = NULL; static long mwGrabSize = 0L; static void *mwLastFree[MW_FREE_LIST]; static const char *mwLFfile[MW_FREE_LIST]; static int mwLFline[MW_FREE_LIST]; static int mwLFcur = 0; static mwMarker *mwFirstMark = NULL; static FILE *mwLogB2 = NULL; static int mwFlushingB2 = 0; /*********************************************************************** ** Static function declarations ***********************************************************************/ static void mwAutoInit(void); static FILE *mwLogR(void); static void mwLogW(FILE *); static int mwFlushR(void); static void mwFlushW(int); static void mwFlush(void); static void mwIncErr(void); static void mwUnlink(mwData *, const char *file, int line); static int mwRelink(mwData *, const char *file, int line); static int mwIsHeapOK(mwData * mw); static int mwIsOwned(mwData * mw, const char *file, int line); static int mwTestBuf(mwData * mw, const char *file, int line); static void mwDefaultOutFunc(int); static void mwWrite(const char *format,...); static void mwLogFile(const char *name); static size_t mwFreeUp(size_t, int); static const void *mwTestMem(const void *, unsigned, int); static int mwStrCmpI(const char *s1, const char *s2); static int mwTestNow(const char *file, int line, int always_invoked); static void mwDropAll(void); static const char *mwGrabType(int type); static unsigned mwGrab_(unsigned kb, int type, int silent); static unsigned mwDrop_(unsigned kb, int type, int silent); static int mwARI(const char *text); static void mwStatReport(void); static mwStat *mwStatGet(const char *, int, int); static void mwStatAlloc(size_t, const char *, int); static void mwStatFree(size_t, const char *, int); /*********************************************************************** ** System functions ***********************************************************************/ void mwInit(void) { time_t tid; if (mwInited++ > 0) return; /* start a log if none is running */ if (mwLogR() == NULL) mwLogFile("memwatch.log"); if (mwLogR() == NULL) { int i; char buf[32]; /* oops, could not open it! */ /* probably because it's already open */ /* so we try some other names */ for (i = 1; i < 100; i++) { sprintf(buf, "memwat%02d.log", i); mwLogFile(buf); if (mwLogR() != NULL) break; } } /* initialize the statistics */ mwStatList = NULL; mwStatTotAlloc = 0L; mwStatCurAlloc = 0L; mwStatMaxAlloc = 0L; mwStatNumAlloc = 0L; mwNmlCurAlloc = 0L; mwNmlNumAlloc = 0L; /* write informational header if needed */ if (!mwInfoWritten) { mwInfoWritten = 1; (void) time(&tid); mwWrite( "\n=============" " MEMWATCH " VERSION " Copyright (C) 1992-1999 Johan Lindh " "=============\n"); mwWrite("\nStarted at %s\n", ctime(&tid)); /**************************************************************** Generic */ #ifdef mwNew mwWrite("C++ new/delete tracking enabled\n"); #endif /* mwNew */ #ifdef __STDC__ mwWrite("Compiled as standard ANSI C\n"); #endif /* __STDC__ */ /**************************************************************** Generic */ /************************************************************ Microsoft C */ #ifdef _MSC_VER mwWrite("Compiled using Microsoft C" CPPTEXT " %d.%02d\n", _MSC_VER / 100, _MSC_VER % 100); #endif /* _MSC_VER */ /************************************************************ Microsoft C */ /************************************************************** Borland C */ #ifdef __BORLANDC__ mwWrite("Compiled using Borland C" #ifdef __cplusplus "++ %d.%01d\n", __BCPLUSPLUS__ / 0x100, (__BCPLUSPLUS__ % 0x100) / 0x10); #else " %d.%01d\n", __BORLANDC__ / 0x100, (__BORLANDC__ % 0x100) / 0x10); #endif /* __cplusplus */ #endif /* __BORLANDC__ */ /************************************************************** Borland C */ /************************************************************** Watcom C */ #ifdef __WATCOMC__ mwWrite("Compiled using Watcom C %d.%02d ", __WATCOMC__ / 100, __WATCOMC__ % 100); #ifdef __FLAT__ mwWrite("(32-bit flat model)"); #endif /* __FLAT__ */ mwWrite("\n"); #endif /* __WATCOMC__ */ /************************************************************** Watcom C */ mwWrite("\n"); FLUSH(); } if (mwUseAtexit) (void) atexit(mwAbort); return; } void mwAbort(void) { mwData *mw; mwMarker *mrk; char *data; time_t tid; int c, i, j; int errors; long chk; tid = time(NULL); mwWrite("\nStopped at %s\n", ctime(&tid)); if (!mwInited) mwWrite("internal: mwAbort(): MEMWATCH not initialized!\n"); /* release the grab list */ mwDropAll(); /* report mwMarked items */ while (mwFirstMark) { mrk = mwFirstMark->next; mwWrite("mark: %p: %s\n", mwFirstMark->host, mwFirstMark->text); free(mwFirstMark->text); free(mwFirstMark); mwFirstMark = mrk; mwErrors++; } /* release all still allocated memory */ errors = 0; while (mwHead != NULL && errors < 3) { if (!mwIsOwned(mwHead, __FILE__, __LINE__)) { if (errors < 3) { errors++; mwWrite("internal: NML/unfreed scan restarting\n"); FLUSH(); mwHead = mwHead; continue; } mwWrite("internal: NML/unfreed scan aborted, heap too damaged\n"); FLUSH(); break; } mwFlushW(0); if (!(mwHead->flag & MW_NML)) { mwErrors++; data = ((char *) (mwHead + 1)); mwWrite("unfreed: <%ld> %s(%d), %ld bytes at %p ", mwHead->count, mwHead->file, mwHead->line, (long) mwHead->size, data + sizeof(long)); GETDWORD(chk, data); if (chk != PRECHK) { mwWrite("[underflowed] "); FLUSH(); } GETDWORD(chk, (data + sizeof(long) + mwHead->size)); if (chk != POSTCHK) { mwWrite("[overflowed] "); FLUSH(); } mwWrite(" \t{"); j = 16; if (mwHead->size < 16) j = (int) mwHead->size; for (i = 0; i < 16; i++) { if (i < j) mwWrite("%02X ", (unsigned char) *(data + sizeof(long) + i)); else mwWrite(".. "); } for (i = 0; i < j; i++) { c = *(data + sizeof(long) + i); if (c < 32 || c > 126) c = '.'; mwWrite("%c", c); } mwWrite("}\n"); mw = mwHead; mwUnlink(mw, __FILE__, __LINE__); free(mw); } else { data = ((char *) (mwHead + 1)) + sizeof(long); if (mwTestMem(data, mwHead->size, MW_VAL_NML)) { mwErrors++; mwWrite("wild pointer: <%ld> NoMansLand %p alloc'd at %s(%d)\n", mwHead->count, data + sizeof(long), mwHead->file, mwHead->line); FLUSH(); } mwNmlNumAlloc--; mwNmlCurAlloc -= mwHead->size; mw = mwHead; mwUnlink(mw, __FILE__, __LINE__); free(mw); } } if (mwNmlNumAlloc) mwWrite("internal: NoMansLand block counter %ld, not zero\n", mwNmlNumAlloc); if (mwNmlCurAlloc) mwWrite("internal: NoMansLand byte counter %ld, not zero\n", mwNmlCurAlloc); /* report statistics */ mwStatReport(); FLUSH(); mwInited = 0; mwHead = mwTail = NULL; if (mwErrors) fprintf(mwSTDERR, "MEMWATCH detected %ld anomalies\n", mwErrors); mwLogFile(NULL); mwErrors = 0; } void mwTerm(void) { if (mwInited == 1) { mwAbort(); return; } if (!mwInited) mwWrite("internal: mwTerm(): MEMWATCH has not been started!\n"); else mwInited--; } void mwStatistics(int level) { mwAutoInit(); if (level < 0) level = 0; mwStatLevel = level; } void mwAutoCheck(int onoff) { mwAutoInit(); mwTestAlways = onoff; if (onoff) mwTestFlags = MW_TEST_ALL; } void mwSetOutFunc(void (*func) (int)) { mwAutoInit(); mwOutFunction = func; } int mwTest(const char *file, int line, int items) { mwAutoInit(); mwTestFlags = items; return mwTestNow(file, line, 0); } void mwBreakOut(const char *cause) { fprintf(mwSTDERR, "breakout: %s\n", cause); mwWrite("breakout: %s\n", cause); return; } /* ** 981217 JLI: is it possible that ->next is not always set? */ void *mwMark(void *p, const char *desc, const char *file, unsigned line) { mwMarker *mrk; unsigned n, isnew; char *buf; int tot, oflow = 0; char wherebuf[128]; mwAutoInit(); TESTS(NULL, 0); if (desc == NULL) desc = "unknown"; if (file == NULL) file = "unknown"; tot = sprintf(wherebuf, "%.48s called from %s(%d)", desc, file, line); if (tot >= (int) sizeof(wherebuf)) { wherebuf[sizeof(wherebuf) - 1] = 0; oflow = 1; } if (p == NULL) { mwWrite("mark: %s(%d), no mark for NULL:'%s' may be set\n", file, line, desc); return p; } if (mwFirstMark != NULL && !mwIsReadAddr(mwFirstMark, sizeof(mwMarker))) { mwWrite("mark: %s(%d), mwFirstMark (%p) is trashed, can't mark for %s\n", file, line, mwFirstMark, desc); return p; } for (mrk = mwFirstMark; mrk; mrk = mrk->next) { if (mrk->next != NULL && !mwIsReadAddr(mrk->next, sizeof(mwMarker))) { mwWrite("mark: %s(%d), mark(%p)->next(%p) is trashed, can't mark for %s\n", file, line, mrk, mrk->next, desc); return p; } if (mrk->host == p) break; } if (mrk == NULL) { isnew = 1; mrk = (mwMarker *) malloc(sizeof(mwMarker)); if (mrk == NULL) { mwWrite("mark: %s(%d), no mark for %p:'%s', out of memory\n", file, line, p, desc); return p; } mrk->next = NULL; n = 0; } else { isnew = 0; n = strlen(mrk->text); } n += strlen(wherebuf); buf = (char *) malloc(n + 3); if (buf == NULL) { if (isnew) free(mrk); mwWrite("mark: %s(%d), no mark for %p:'%s', out of memory\n", file, line, p, desc); return p; } if (isnew) { memcpy(buf, wherebuf, n + 1); mrk->next = mwFirstMark; mrk->host = p; mrk->text = buf; mrk->level = 1; mwFirstMark = mrk; } else { strcpy(buf, mrk->text); strcat(buf, ", "); strcat(buf, wherebuf); free(mrk->text); mrk->text = buf; mrk->level++; } if (oflow) { mwIncErr(); mwTrace(" [WARNING: OUTPUT BUFFER OVERFLOW - SYSTEM UNSTABLE]\n"); } return p; } void *mwUnmark(void *p, const char *file, unsigned line) { mwMarker *mrk, *prv; mrk = mwFirstMark; prv = NULL; while (mrk) { if (mrk->host == p) { if (mrk->level < 2) { if (prv) prv->next = mrk->next; else mwFirstMark = mrk->next; free(mrk->text); free(mrk); return p; } mrk->level--; return p; } prv = mrk; mrk = mrk->next; } mwWrite("mark: %s(%d), no mark found for %p\n", file, line, p); return p; } /*********************************************************************** ** Safe memory checkers ** ** Using ifdefs, implement the operating-system specific mechanism ** of identifying a piece of memory as legal to access with read ** and write priviliges. Default: return nonzero for non-NULL pointers. ***********************************************************************/ static char mwDummy(char c) { return c; } #ifndef MW_SAFEADDR #ifdef WIN32 #define MW_SAFEADDR #define WIN32_LEAN_AND_MEAN #include int mwIsReadAddr(const void *p, unsigned len) { if (p == NULL) return 0; if (IsBadReadPtr(p, len)) return 0; return 1; } int mwIsSafeAddr(void *p, unsigned len) { /* NOTE: For some reason, under Win95 the IsBad... */ /* can return false for invalid pointers. */ if (p == NULL) return 0; if (IsBadReadPtr(p, len) || IsBadWritePtr(p, len)) return 0; return 1; } #endif /* WIN32 */ #endif /* MW_SAFEADDR */ #ifndef MW_SAFEADDR #ifdef SIGSEGV #define MW_SAFEADDR typedef void (*mwSignalHandlerPtr) (int); mwSignalHandlerPtr mwOldSIGSEGV = (mwSignalHandlerPtr) 0; jmp_buf mwSIGSEGVjump; static void mwSIGSEGV(int n); static void mwSIGSEGV(int n) { longjmp(mwSIGSEGVjump, 1); } int mwIsReadAddr(const void *p, unsigned len) { const char *ptr; if (p == NULL) return 0; if (!len) return 1; /* set up to catch the SIGSEGV signal */ mwOldSIGSEGV = signal(SIGSEGV, mwSIGSEGV); if (setjmp(mwSIGSEGVjump)) { signal(SIGSEGV, mwOldSIGSEGV); return 0; } /* read all the bytes in the range */ ptr = (const char *) p; ptr += len; /* the reason for this rather strange construct is that */ /* we want to keep the number of used parameters and locals */ /* to a minimum. if we use len for a counter gcc will complain */ /* it may get clobbered by longjmp() at high warning levels. */ /* it's a harmless warning, but this way we don't have to see it. */ do { ptr--; if (*ptr == 0x7C) (void) mwDummy((char) 0); } while (ptr != p); /* remove the handler */ signal(SIGSEGV, mwOldSIGSEGV); return 1; } int mwIsSafeAddr(void *p, unsigned len) { char *ptr; if (p == NULL) return 0; if (!len) return 1; /* set up to catch the SIGSEGV signal */ mwOldSIGSEGV = signal(SIGSEGV, mwSIGSEGV); if (setjmp(mwSIGSEGVjump)) { signal(SIGSEGV, mwOldSIGSEGV); return 0; } /* read and write-back all the bytes in the range */ ptr = (char *) p; ptr += len; /* the reason for this rather strange construct is that */ /* we want to keep the number of used parameters and locals */ /* to a minimum. if we use len for a counter gcc will complain */ /* it may get clobbered by longjmp() at high warning levels. */ /* it's a harmless warning, but this way we don't have to see it. */ do { ptr--; *ptr = mwDummy(*ptr); } while (ptr != p); /* remove the handler */ signal(SIGSEGV, mwOldSIGSEGV); return 1; } #endif /* SIGSEGV */ #endif /* MW_SAFEADDR */ #ifndef MW_SAFEADDR int mwIsReadAddr(const void *p, unsigned len) { if (p == NULL) return 0; if (len == 0) return 1; return 1; } int mwIsSafeAddr(void *p, unsigned len) { if (p == NULL) return 0; if (len == 0) return 1; return 1; } #endif /*********************************************************************** ** Abort/Retry/Ignore handlers ***********************************************************************/ static int mwARI(const char *estr) { char inbuf[81]; int c; fprintf(mwSTDERR, "\n%s\nMEMWATCH: Abort, Retry or Ignore? ", estr); (void) fgets(inbuf, sizeof(inbuf), stdin); for (c = 0; inbuf[c] && inbuf[c] <= ' '; c++); c = inbuf[c]; if (c == 'R' || c == 'r') { mwBreakOut(estr); return MW_ARI_RETRY; } if (c == 'I' || c == 'i') return MW_ARI_IGNORE; return MW_ARI_ABORT; } /* standard ARI handler (exported) */ int mwAriHandler(const char *estr) { mwAutoInit(); return mwARI(estr); } /* used to set the ARI function */ void mwSetAriFunc(int (*func) (const char *)) { mwAutoInit(); mwAriFunction = func; } /*********************************************************************** ** Allocation handlers ***********************************************************************/ void *mwMalloc(size_t size, const char *file, int line) { size_t needed; mwData *mw; char *ptr; void *p; mwAutoInit(); TESTS(file, line); mwCounter++; needed = sizeof(mwData) + sizeof(long) + sizeof(long) + size; /* if this allocation would violate the limit, fail it */ if (mwUseLimit && ((long) size + mwStatCurAlloc > mwAllocLimit)) { mwWrite("limit fail: <%ld> %s(%d), %ld wanted %ld available\n", mwCounter, file, line, (long) size, mwAllocLimit - mwStatCurAlloc); mwIncErr(); FLUSH(); return NULL; } mw = (mwData *) malloc(needed); if (mw == NULL) { if (mwFreeUp(needed, 0) >= needed) { mw = (mwData *) malloc(needed); if (mw == NULL) { mwWrite("internal: mwFreeUp(%u) reported success, but malloc() fails\n", needed); mwIncErr(); FLUSH(); } } if (mw == NULL) { mwWrite("fail: <%ld> %s(%d), %ld wanted %ld allocated\n", mwCounter, file, line, (long) size, mwStatCurAlloc); mwIncErr(); FLUSH(); return NULL; } } mw->count = mwCounter; mw->prev = NULL; mw->next = mwHead; mw->file = file; mw->size = size; mw->line = line; mw->flag = 0; mw->check = CHKVAL(mw); if (mwHead) mwHead->prev = mw; mwHead = mw; if (mwTail == NULL) mwTail = mw; ptr = (char *) (void *) (mw + 1); PUTDWORD(PRECHK, ptr); /* '*(long*)ptr = PRECHK;' */ ptr += sizeof(long); p = ptr; memset(ptr, MW_VAL_NEW, size); ptr += size; PUTDWORD(POSTCHK, ptr); /* '*(long*)ptr = POSTCHK;' */ mwNumCurAlloc++; mwStatCurAlloc += (long) size; mwStatTotAlloc += (long) size; if (mwStatCurAlloc > mwStatMaxAlloc) mwStatMaxAlloc = mwStatCurAlloc; mwStatNumAlloc++; if (mwStatLevel) mwStatAlloc(size, file, line); return p; } void *mwRealloc(void *p, size_t size, const char *file, int line) { int oldUseLimit, i; mwData *mw; char *ptr; mwAutoInit(); if (p == NULL) return mwMalloc(size, file, line); if (size == 0) { mwFree(p, file, line); return NULL; } /* do the quick ownership test */ mw = (mwData *) (((char *) p) - sizeof(long) - sizeof(mwData)); if (mwIsOwned(mw, file, line)) { /* if the buffer is an NML, treat this as a double-free */ if (mw->flag & MW_NML) { mwIncErr(); if (*((unsigned char *) (mw + 1) + sizeof(long)) != MW_VAL_NML) { mwWrite("internal: <%ld> %s(%d), no-mans-land MW-%p is corrupted\n", mwCounter, file, line, mw); } goto check_dbl_free; } /* if this allocation would violate the limit, fail it */ if (mwUseLimit && ((long) size + mwStatCurAlloc - (long) mw->size > mwAllocLimit)) { TESTS(file, line); mwCounter++; mwWrite("limit fail: <%ld> %s(%d), %ld wanted %ld available\n", mwCounter, file, line, (unsigned long) size - mw->size, mwAllocLimit - mwStatCurAlloc); mwIncErr(); FLUSH(); return NULL; } /* fake realloc operation */ oldUseLimit = mwUseLimit; mwUseLimit = 0; ptr = (char *) mwMalloc(size, file, line); if (ptr != NULL) { if (size < mw->size) memcpy(ptr, p, size); else memcpy(ptr, p, mw->size); mwFree(p, file, line); } mwUseLimit = oldUseLimit; return (void *) ptr; } /* Unknown pointer! */ /* using free'd pointer? */ check_dbl_free: for (i = 0; i < MW_FREE_LIST; i++) { if (mwLastFree[i] == p) { mwIncErr(); mwWrite("realloc: <%ld> %s(%d), %p was" " freed from %s(%d)\n", mwCounter, file, line, p, mwLFfile[i], mwLFline[i]); FLUSH(); return NULL; } } /* some weird pointer */ mwIncErr(); mwWrite("realloc: <%ld> %s(%d), unknown pointer %p\n", mwCounter, file, line, p); FLUSH(); return NULL; } char *mwStrdup(char *str, const char *file, int line) { size_t len; char *newstring; if (str == NULL) { mwIncErr(); mwWrite("strdup: <%ld> %s(%d), strdup(NULL) called\n", mwCounter, file, line); FLUSH(); return NULL; } len = strlen(str) + 1; newstring = (char *) mwMalloc(len, file, line); if (newstring != NULL) memcpy(newstring, str, len); return newstring; } void mwFree(void *p, const char *file, int line) { int i; mwData *mw; char buffer[sizeof(mwData) + sizeof(long) + 64]; TESTS(file, line); /* this code is in support of C++ delete */ if (file == NULL) { mwFree_(p); return; } mwAutoInit(); mwCounter++; /* on NULL free, write a warning and return */ if (p == NULL) { mwWrite("NULL free: <%ld> %s(%d), NULL pointer free'd\n", mwCounter, file, line); FLUSH(); return; } /* do the quick ownership test */ mw = (mwData *) (((char *) p) - sizeof(long) - sizeof(mwData)); if (mwIsOwned(mw, file, line)) { (void) mwTestBuf(mw, file, line); /* if the buffer is an NML, treat this as a double-free */ if (mw->flag & MW_NML) { if (*((unsigned char *) (mw + 1) + sizeof(long)) != MW_VAL_NML) { mwWrite("internal: <%ld> %s(%d), no-mans-land MW-%p is corrupted\n", mwCounter, file, line, mw); } goto check_dbl_free; } /* update the statistics */ mwNumCurAlloc--; mwStatCurAlloc -= (long) mw->size; if (mwStatLevel) mwStatFree(mw->size, mw->file, mw->line); /* we should either free the allocation or keep it as NML */ if (mwNML) { mw->flag |= MW_NML; mwNmlNumAlloc++; mwNmlCurAlloc += (long) mw->size; memset((char *) (mw + 1) + sizeof(long), MW_VAL_NML, mw->size); } else { /* unlink the allocation, and enter the post-free data */ mwUnlink(mw, file, line); memset(mw, MW_VAL_DEL, mw->size + sizeof(mwData) + sizeof(long) + sizeof(long)); if (mwFBI) { memset(mw, '.', sizeof(mwData) + sizeof(long)); sprintf(buffer, "FBI<%ld>%s(%d)", mwCounter, file, line); strncpy((char *) (void *) mw, buffer, sizeof(mwData) + sizeof(long)); } free(mw); } /* add the pointer to the last-free track */ mwLFfile[mwLFcur] = file; mwLFline[mwLFcur] = line; mwLastFree[mwLFcur++] = p; if (mwLFcur == MW_FREE_LIST) mwLFcur = 0; return; } /* check for double-freeing */ check_dbl_free: for (i = 0; i < MW_FREE_LIST; i++) { if (mwLastFree[i] == p) { mwIncErr(); mwWrite("double-free: <%ld> %s(%d), %p was" " freed from %s(%d)\n", mwCounter, file, line, p, mwLFfile[i], mwLFline[i]); FLUSH(); return; } } /* some weird pointer... block the free */ mwIncErr(); mwWrite("WILD free: <%ld> %s(%d), unknown pointer %p\n", mwCounter, file, line, p); FLUSH(); return; } void *mwCalloc(size_t a, size_t b, const char *file, int line) { void *p; size_t size = a * b; p = mwMalloc(size, file, line); if (p == NULL) return NULL; memset(p, 0, size); return p; } void mwFree_(void *p) { TESTS(NULL, 0); free(p); } void *mwMalloc_(size_t size) { TESTS(NULL, 0); return malloc(size); } void *mwRealloc_(void *p, size_t size) { TESTS(NULL, 0); return realloc(p, size); } void *mwCalloc_(size_t a, size_t b) { TESTS(NULL, 0); return calloc(a, b); } void mwFlushNow(void) { if (mwLogR()) fflush(mwLogR()); return; } void mwDoFlush(int onoff) { mwFlushW(onoff < 1 ? 0 : onoff); if (onoff) if (mwLogR()) fflush(mwLogR()); return; } void mwLimit(long lim) { TESTS(NULL, 0); mwWrite("limit: old limit = "); if (!mwAllocLimit) mwWrite("none"); else mwWrite("%ld bytes", mwAllocLimit); mwWrite(", new limit = "); if (!lim) { mwWrite("none\n"); mwUseLimit = 0; } else { mwWrite("%ld bytes\n", lim); mwUseLimit = 1; } mwAllocLimit = lim; FLUSH(); } void mwSetAriAction(int action) { TESTS(NULL, 0); mwAriAction = action; return; } int mwAssert(int exp, const char *exps, const char *fn, int ln) { int i; char buffer[MW_TRACE_BUFFER + 8]; TESTS(fn, ln); if (exp) return 0; mwAutoInit(); mwIncErr(); mwCounter++; mwWrite("assert trap: <%ld> %s(%d), %s\n", mwCounter, fn, ln, exps); if (mwAriFunction != NULL) { sprintf(buffer, "MEMWATCH: assert trap: %s(%d), %s", fn, ln, exps); i = (*mwAriFunction) (buffer); switch (i) { case MW_ARI_IGNORE: mwWrite("assert trap: <%ld> IGNORED - execution continues\n", mwCounter); return 0; case MW_ARI_RETRY: mwWrite("assert trap: <%ld> RETRY - executing again\n", mwCounter); return 1; } } else { if (mwAriAction & MW_ARI_IGNORE) { mwWrite("assert trap: <%ld> AUTO IGNORED - execution continues\n", mwCounter); return 0; } fprintf(mwSTDERR, "\nMEMWATCH: assert trap: %s(%d), %s\n", fn, ln, exps); } FLUSH(); (void) mwTestNow(fn, ln, 1); FLUSH(); if (mwAriAction & MW_ARI_NULLREAD) { /* This is made in an attempt to kick in */ /* any debuggers or OS stack traces */ FLUSH(); /*lint -save -e413 */ i = *((int *) NULL); mwDummy((char) i); /*lint -restore */ } exit(255); /* NOT REACHED - the return statement is in to keep */ /* stupid compilers from squeaking about differing return modes. */ /* Smart compilers instead say 'code unreachable...' */ /*lint -save -e527 */ return 0; /*lint -restore */ } int mwVerify(int exp, const char *exps, const char *fn, int ln) { int i; char buffer[MW_TRACE_BUFFER + 8]; TESTS(fn, ln); if (exp) return 0; mwAutoInit(); mwIncErr(); mwCounter++; mwWrite("verify trap: <%ld> %s(%d), %s\n", mwCounter, fn, ln, exps); if (mwAriFunction != NULL) { sprintf(buffer, "MEMWATCH: verify trap: %s(%d), %s", fn, ln, exps); i = (*mwAriFunction) (buffer); if (i == 0) { mwWrite("verify trap: <%ld> IGNORED - execution continues\n", mwCounter); return 0; } if (i == 1) { mwWrite("verify trap: <%ld> RETRY - executing again\n", mwCounter); return 1; } } else { if (mwAriAction & MW_ARI_NULLREAD) { /* This is made in an attempt to kick in */ /* any debuggers or OS stack traces */ FLUSH(); /*lint -save -e413 */ i = *((int *) NULL); mwDummy((char) i); /*lint -restore */ } if (mwAriAction & MW_ARI_IGNORE) { mwWrite("verify trap: <%ld> AUTO IGNORED - execution continues\n", mwCounter); return 0; } fprintf(mwSTDERR, "\nMEMWATCH: verify trap: %s(%d), %s\n", fn, ln, exps); } FLUSH(); (void) mwTestNow(fn, ln, 1); FLUSH(); exit(255); /* NOT REACHED - the return statement is in to keep */ /* stupid compilers from squeaking about differing return modes. */ /* Smart compilers instead say 'code unreachable...' */ /*lint -save -e527 */ return 0; /*lint -restore */ } void mwTrace(const char *format,...) { int tot, oflow = 0; va_list mark; mwAutoInit(); TESTS(NULL, 0); if (mwOutFunction == NULL) mwOutFunction = mwDefaultOutFunc; va_start(mark, format); tot = vsprintf(mwPrintBuf, format, mark); va_end(mark); if (tot >= MW_TRACE_BUFFER) { mwPrintBuf[MW_TRACE_BUFFER] = 0; oflow = 1; } for (tot = 0; mwPrintBuf[tot]; tot++) (*mwOutFunction) (mwPrintBuf[tot]); if (oflow) { mwIncErr(); mwTrace(" [WARNING: OUTPUT BUFFER OVERFLOW - SYSTEM UNSTABLE]\n"); } FLUSH(); } /*********************************************************************** ** Grab & Drop ***********************************************************************/ unsigned mwGrab(unsigned kb) { TESTS(NULL, 0); return mwGrab_(kb, MW_VAL_GRB, 0); } unsigned mwDrop(unsigned kb) { TESTS(NULL, 0); return mwDrop_(kb, MW_VAL_GRB, 0); } static void mwDropAll() { TESTS(NULL, 0); (void) mwDrop_(0, MW_VAL_GRB, 0); (void) mwDrop_(0, MW_VAL_NML, 0); if (mwGrabList != NULL) mwWrite("internal: the grab list is not empty after mwDropAll()\n"); } static const char *mwGrabType(int type) { switch (type) { case MW_VAL_GRB: return "grabbed"; case MW_VAL_NML: return "no-mans-land"; default: /* do nothing */ ; } return ""; } static unsigned mwGrab_(unsigned kb, int type, int silent) { unsigned i = kb; mwGrabData *gd; if (!kb) i = kb = 65000U; for (; kb; kb--) { if (mwUseLimit && (mwStatCurAlloc + mwGrabSize + (long) sizeof(mwGrabData) > mwAllocLimit)) { if (!silent) { mwWrite("grabbed: all allowed memory to %s (%u kb)\n", mwGrabType(type), i - kb); FLUSH(); } return i - kb; } gd = (mwGrabData *) malloc(sizeof(mwGrabData)); if (gd == NULL) { if (!silent) { mwWrite("grabbed: all available memory to %s (%u kb)\n", mwGrabType(type), i - kb); FLUSH(); } return i - kb; } mwGrabSize += (long) sizeof(mwGrabData); gd->next = mwGrabList; memset(gd->blob, type, sizeof(gd->blob)); gd->type = type; mwGrabList = gd; } if (!silent) { mwWrite("grabbed: %u kilobytes of %s memory\n", i, mwGrabType(type)); FLUSH(); } return i; } static unsigned mwDrop_(unsigned kb, int type, int silent) { unsigned i = kb; mwGrabData *gd, *tmp, *pr; const void *p; if (mwGrabList == NULL && kb == 0) return 0; if (!kb) i = kb = 60000U; pr = NULL; gd = mwGrabList; for (; kb;) { if (gd == NULL) { if (i - kb > 0 && !silent) { mwWrite("dropped: all %s memory (%u kb)\n", mwGrabType(type), i - kb); FLUSH(); } return i - kb; } if (gd->type == type) { if (pr) pr->next = gd->next; kb--; tmp = gd; if (mwGrabList == gd) mwGrabList = gd->next; gd = gd->next; p = mwTestMem(tmp->blob, sizeof(tmp->blob), type); if (p != NULL) { mwWrite("wild pointer: <%ld> %s memory hit at %p\n", mwCounter, mwGrabType(type), p); FLUSH(); } mwGrabSize -= (long) sizeof(mwGrabData); free(tmp); } else { pr = gd; gd = gd->next; } } if (!silent) { mwWrite("dropped: %u kilobytes of %s memory\n", i, mwGrabType(type)); FLUSH(); } return i; } /*********************************************************************** ** No-Mans-Land ***********************************************************************/ void mwNoMansLand(int level) { mwAutoInit(); TESTS(NULL, 0); switch (level) { case MW_NML_NONE: (void) mwDrop_(0, MW_VAL_NML, 0); break; case MW_NML_FREE: break; case MW_NML_ALL: (void) mwGrab_(0, MW_VAL_NML, 0); break; default: return; } mwNML = level; } /*********************************************************************** ** Static functions ***********************************************************************/ static void mwAutoInit(void) { if (mwInited) return; mwUseAtexit = 1; mwInit(); return; } static FILE *mwLogR() { if ((mwLog == mwLogB1) && (mwLog == mwLogB2)) return mwLog; if (mwLog == mwLogB1) mwLogB2 = mwLog; if (mwLog == mwLogB2) mwLogB1 = mwLog; if (mwLogB1 == mwLogB2) mwLog = mwLogB1; if ((mwLog == mwLogB1) && (mwLog == mwLogB2)) { mwWrite("internal: log file handle damaged and recovered\n"); FLUSH(); return mwLog; } fprintf(mwSTDERR, "\nMEMWATCH: log file handle destroyed, using mwSTDERR\n"); mwLog = mwLogB1 = mwLogB2 = mwSTDERR; return mwSTDERR; } static void mwLogW(FILE * p) { mwLog = mwLogB1 = mwLogB2 = p; } static int mwFlushR() { if ((mwFlushing == mwFlushingB1) && (mwFlushing == mwFlushingB2)) return mwFlushing; if (mwFlushing == mwFlushingB1) mwFlushingB2 = mwFlushing; if (mwFlushing == mwFlushingB2) mwFlushingB1 = mwFlushing; if (mwFlushingB1 == mwFlushingB2) mwFlushing = mwFlushingB1; if ((mwFlushing == mwFlushingB1) && (mwFlushing == mwFlushingB2)) { mwWrite("internal: flushing flag damaged and recovered\n"); FLUSH(); return mwFlushing; } mwWrite("internal: flushing flag destroyed, so set to true\n"); mwFlushing = mwFlushingB1 = mwFlushingB2 = 1; return 1; } static void mwFlushW(int n) { mwFlushing = mwFlushingB1 = mwFlushingB2 = n; } static void mwIncErr() { mwErrors++; mwFlushW(mwFlushR() + 1); FLUSH(); } static void mwFlush() { if (mwLogR() == NULL) return; #ifdef MW_FLUSH fflush(mwLogR()); #else if (mwFlushR()) fflush(mwLogR()); #endif return; } static void mwUnlink(mwData * mw, const char *file, int line) { if (mw->prev == NULL) { if (mwHead != mw) mwWrite("internal: <%ld> %s(%d), MW-%p: link1 NULL, but not head\n", mwCounter, file, line, mw); mwHead = mw->next; } else { if (mw->prev->next != mw) mwWrite("internal: <%ld> %s(%d), MW-%p: link1 failure\n", mwCounter, file, line, mw); else mw->prev->next = mw->next; } if (mw->next == NULL) { if (mwTail != mw) mwWrite("internal: <%ld> %s(%d), MW-%p: link2 NULL, but not tail\n", mwCounter, file, line, mw); mwTail = mw->prev; } else { if (mw->next->prev != mw) mwWrite("internal: <%ld> %s(%d), MW-%p: link2 failure\n", mwCounter, file, line, mw); else mw->next->prev = mw->prev; } } /* ** Relinking tries to repair a damaged mw block. ** Returns nonzero if it thinks it successfully ** repaired the heap chain. */ static int mwRelink(mwData * mw, const char *file, int line) { int fails; mwData *mw1, *mw2; long count, size; mwStat *ms; if (file == NULL) file = "unknown"; if (mw == NULL) { mwWrite("relink: cannot repair MW at NULL\n"); FLUSH(); goto emergency; } if (!mwIsSafeAddr(mw, sizeof(mwData))) { mwWrite("relink: MW-%p is a garbage pointer\n"); FLUSH(); goto emergency; } mwWrite("relink: <%ld> %s(%d) attempting to repair MW-%p...\n", mwCounter, file, line, mw); FLUSH(); fails = 0; /* Repair from head */ if (mwHead != mw) { if (!mwIsSafeAddr(mwHead, sizeof(mwData))) { mwWrite("relink: failed for MW-%p; head pointer destroyed\n", mw); FLUSH(); goto emergency; } for (mw1 = mwHead; mw1; mw1 = mw1->next) { if (mw1->next == mw) { mw->prev = mw1; break; } if (mw1->next && (!mwIsSafeAddr(mw1->next, sizeof(mwData)) || mw1->next->prev != mw1)) { mwWrite("relink: failed for MW-%p; forward chain fragmented at MW-%p: 'next' is %p\n", mw, mw1, mw1->next); FLUSH(); goto emergency; } } if (mw1 == NULL) { mwWrite("relink: MW-%p not found in forward chain search\n", mw); FLUSH(); fails++; } } else { mwWrite("relink: MW-%p is the head (first) allocation\n", mw); if (mw->prev != NULL) { mwWrite("relink: MW-%p prev pointer is non-NULL, you have a wild pointer\n", mw); mw->prev = NULL; } } /* Repair from tail */ if (mwTail != mw) { if (!mwIsSafeAddr(mwTail, sizeof(mwData))) { mwWrite("relink: failed for MW-%p; tail pointer destroyed\n", mw); FLUSH(); goto emergency; } for (mw1 = mwTail; mw1; mw1 = mw1->prev) { if (mw1->prev == mw) { mw->next = mw1; break; } if (mw1->prev && (!mwIsSafeAddr(mw1->prev, sizeof(mwData)) || mw1->prev->next != mw1)) { mwWrite("relink: failed for MW-%p; reverse chain fragmented at MW-%p, 'prev' is %p\n", mw, mw1, mw1->prev); FLUSH(); goto emergency; } } if (mw1 == NULL) { mwWrite("relink: MW-%p not found in reverse chain search\n", mw); FLUSH(); fails++; } } else { mwWrite("relink: MW-%p is the tail (last) allocation\n", mw); if (mw->next != NULL) { mwWrite("relink: MW-%p next pointer is non-NULL, you have a wild pointer\n", mw); mw->next = NULL; } } if (fails > 1) { mwWrite("relink: heap appears intact, MW-%p probably garbage pointer\n", mw); FLUSH(); goto verifyok; } /* restore MW info where possible */ if (mwIsReadAddr(mw->file, 1)) { ms = mwStatGet(mw->file, -1, 0); if (ms == NULL) mw->file = ""; } mw->check = CHKVAL(mw); goto verifyok; /* Emergency repair */ emergency: if (mwHead == NULL && mwTail == NULL) { if (mwStatCurAlloc == 0) mwWrite("relink: <%ld> %s(%d) heap is empty, nothing to repair\n", mwCounter, file, line); else mwWrite("relink: <%ld> %s(%d) heap damaged beyond repair\n", mwCounter, file, line); FLUSH(); return 0; } mwWrite("relink: <%ld> %s(%d) attempting emergency repairs...\n", mwCounter, file, line); FLUSH(); if (mwHead == NULL || mwTail == NULL) { if (mwHead == NULL) mwWrite("relink: mwHead is NULL, but mwTail is %p\n", mwTail); else mwWrite("relink: mwTail is NULL, but mwHead is %p\n", mwHead); } mw1 = NULL; if (mwHead != NULL) { if (!mwIsReadAddr(mwHead, sizeof(mwData)) || mwHead->check != CHKVAL(mwHead)) { mwWrite("relink: mwHead (MW-%p) is damaged, skipping forward scan\n", mwHead); mwHead = NULL; goto scan_reverse; } if (mwHead->prev != NULL) { mwWrite("relink: the mwHead pointer's 'prev' member is %p, not NULL\n", mwHead->prev); } for (mw1 = mwHead; mw1; mw1 = mw1->next) { if (mw1->next) { if (!mwIsReadAddr(mw1->next, sizeof(mwData)) || !mw1->next->check != CHKVAL(mw1) || mw1->next->prev != mw1) { mwWrite("relink: forward chain's last intact MW is MW-%p, %ld %sbytes at %s(%d)\n", mw1, mw1->size, (mw->flag & MW_NML) ? "NoMansLand " : "", mw1->file, mw1->line); if (mwIsReadAddr(mw1->next, sizeof(mwData))) { mwWrite("relink: forward chain's first damaged MW is MW-%p, %ld %sbytes at %s(%d)\n", mw1->next, mw1->size, (mw->flag & MW_NML) ? "NoMansLand " : "", mwIsReadAddr(mw1->file, 16) ? mw1->file : "", mw1->line); } else { mwWrite("relink: the 'next' pointer of this MW points to %p, which is out-of-legal-access\n", mw1->next); } break; } } } } scan_reverse: mw2 = NULL; if (mwTail != NULL) { if (!mwIsReadAddr(mwTail, sizeof(mwData)) || mwTail->check != CHKVAL(mwTail)) { mwWrite("relink: mwTail (%p) is damaged, skipping reverse scan\n", mwTail); mwTail = NULL; goto analyze; } if (mwTail->next != NULL) { mwWrite("relink: the mwTail pointer's 'next' member is %p, not NULL\n", mwTail->next); } for (mw2 = mwTail; mw2; mw2 = mw2->prev) { if (mw2->prev) { if (!mwIsReadAddr(mw2->prev, sizeof(mwData)) || !mw2->prev->check != CHKVAL(mw2) || mw2->prev->next != mw2) { mwWrite("relink: reverse chain's last intact MW is MW-%p, %ld %sbytes at %s(%d)\n", mw2, mw2->size, (mw->flag & MW_NML) ? "NoMansLand " : "", mw2->file, mw2->line); if (mwIsReadAddr(mw2->prev, sizeof(mwData))) { mwWrite("relink: reverse chain's first damaged MW is MW-%p, %ld %sbytes at %s(%d)\n", mw2->prev, mw2->size, (mw->flag & MW_NML) ? "NoMansLand " : "", mwIsReadAddr(mw2->file, 16) ? mw2->file : "", mw2->line); } else { mwWrite("relink: the 'prev' pointer of this MW points to %p, which is out-of-legal-access\n", mw2->prev); } break; } } } } analyze: if (mwHead == NULL && mwTail == NULL) { mwWrite("relink: both head and tail pointers damaged, aborting program\n"); mwFlushW(1); FLUSH(); abort(); } if (mwHead == NULL) { mwHead = mw2; mwWrite("relink: heap truncated, MW-%p designated as new mwHead\n", mw2); mw2->prev = NULL; mw1 = mw2 = NULL; } if (mwTail == NULL) { mwTail = mw1; mwWrite("relink: heap truncated, MW-%p designated as new mwTail\n", mw1); mw1->next = NULL; mw1 = mw2 = NULL; } if (mw1 == NULL && mw2 == NULL && mwHead->prev == NULL && mwTail->next == NULL) { mwWrite("relink: verifying heap integrity...\n"); FLUSH(); goto verifyok; } if (mw1 && mw2 && mw1 != mw2) { mw1->next = mw2; mw2->prev = mw1; mwWrite("relink: emergency repairs successful, assessing damage...\n"); FLUSH(); } else { mwWrite("relink: heap totally destroyed, aborting program\n"); mwFlushW(1); FLUSH(); abort(); } /* Verify by checking that the number of active allocations */ /* match the number of entries in the chain */ verifyok: if (!mwIsHeapOK(NULL)) { mwWrite("relink: heap verification FAILS - aborting program\n"); mwFlushW(1); FLUSH(); abort(); } for (size = count = 0, mw1 = mwHead; mw1; mw1 = mw1->next) { count++; size += (long) mw1->size; } if (count == mwNumCurAlloc) { mwWrite("relink: successful, "); if (size == mwStatCurAlloc) { mwWrite("no allocations lost\n"); } else { if (mw != NULL) { mwWrite("size information lost for MW-%p\n", mw); mw->size = 0; } } } else { mwWrite("relink: partial, %ld MW-blocks of %ld bytes lost\n", mwNmlNumAlloc + mwNumCurAlloc - count, mwNmlCurAlloc + mwStatCurAlloc - size); return 0; } return 1; } /* ** If mwData* is NULL: ** Returns 0 if heap chain is broken. ** Returns 1 if heap chain is intact. ** If mwData* is not NULL: ** Returns 0 if mwData* is missing or if chain is broken. ** Returns 1 if chain is intact and mwData* is found. */ static int mwIsHeapOK(mwData * includes_mw) { int found = 0; mwData *mw; for (mw = mwHead; mw; mw = mw->next) { if (includes_mw == mw) found++; if (!mwIsSafeAddr(mw, sizeof(mwData))) return 0; if (mw->prev) { if (!mwIsSafeAddr(mw->prev, sizeof(mwData))) return 0; if (mw == mwHead || mw->prev->next != mw) return 0; } if (mw->next) { if (!mwIsSafeAddr(mw->next, sizeof(mwData))) return 0; if (mw == mwTail || mw->next->prev != mw) return 0; } else if (mw != mwTail) return 0; } if (includes_mw != NULL && !found) return 0; return 1; } static int mwIsOwned(mwData * mw, const char *file, int line) { int retv; mwStat *ms; /* see if the address is legal according to OS */ if (!mwIsSafeAddr(mw, sizeof(mwData))) return 0; /* make sure we have _anything_ allocated */ if (mwHead == NULL && mwTail == NULL && mwStatCurAlloc == 0) return 0; /* calculate checksum */ if (mw->check != CHKVAL(mw)) { /* may be damaged checksum, see if block is in heap */ if (mwIsHeapOK(mw)) { /* damaged checksum, repair it */ mwWrite("internal: <%ld> %s(%d), checksum for MW-%p is incorrect\n", mwCounter, file, line, mw); mwIncErr(); if (mwIsReadAddr(mw->file, 1)) { ms = mwStatGet(mw->file, -1, 0); if (ms == NULL) mw->file = ""; } else mw->file = ""; mw->check = CHKVAL(mw); return 1; } /* no, it's just some garbage data */ return 0; } /* check that the non-NULL pointers are safe */ if (mw->prev && !mwIsSafeAddr(mw->prev, sizeof(mwData))) mwRelink(mw, file, line); if (mw->next && !mwIsSafeAddr(mw->next, sizeof(mwData))) mwRelink(mw, file, line); /* safe address, checksum OK, proceed with heap checks */ /* see if the block is in the heap */ retv = 0; if (mw->prev) { if (mw->prev->next == mw) retv++; } else { if (mwHead == mw) retv++; } if (mw->next) { if (mw->next->prev == mw) retv++; } else { if (mwTail == mw) retv++; } if (mw->check == CHKVAL(mw)) retv++; if (retv > 2) return 1; /* block not in heap, check heap for corruption */ if (!mwIsHeapOK(mw)) { if (mwRelink(mw, file, line)) return 1; } /* unable to repair */ mwWrite("internal: <%ld> %s(%d), mwIsOwned fails for MW-%p\n", mwCounter, file, line, mw); mwIncErr(); return 0; } /* ** mwTestBuf: ** Checks a buffers links and pre/postfixes. ** Writes errors found to the log. ** Returns zero if no errors found. */ static int mwTestBuf(mwData * mw, const char *file, int line) { int retv = 0; char *p; long chk; if (file == NULL) file = "unknown"; if (!mwIsSafeAddr(mw, sizeof(mwData))) { mwWrite("internal: <%ld> %s(%d): pointer MW-%p is invalid\n", mwCounter, file, line, mw); mwIncErr(); return 2; } if (mw->check != CHKVAL(mw)) { mwWrite("internal: <%ld> %s(%d), info trashed; relinking\n", mwCounter, file, line); mwIncErr(); if (!mwRelink(mw, file, line)) return 2; } if (mw->prev && mw->prev->next != mw) { mwWrite("internal: <%ld> %s(%d), buffer <%ld> %s(%d) link1 broken\n", mwCounter, file, line, (long) mw->size, mw->count, mw->file, mw->line); mwIncErr(); if (!mwRelink(mw, file, line)) retv = 2; } if (mw->next && mw->next->prev != mw) { mwWrite("internal: <%ld> %s(%d), buffer <%ld> %s(%d) link2 broken\n", mwCounter, file, line, (long) mw->size, mw->count, mw->file, mw->line); mwIncErr(); if (!mwRelink(mw, file, line)) retv = 2; } p = (char *) (mw + 1); GETDWORD(chk, p); if (chk != PRECHK) { mwWrite("underflow: <%ld> %s(%d), %ld bytes alloc'd at <%ld> %s(%d)\n", mwCounter, file, line, (long) mw->size, mw->count, mw->file, mw->line); mwIncErr(); retv = 1; } p += mw->size + sizeof(long); GETDWORD(chk, p); if (chk != POSTCHK) { mwWrite("overflow: <%ld> %s(%d), %ld bytes alloc'd at <%ld> %s(%d)\n", mwCounter, file, line, (long) mw->size, mw->count, mw->file, mw->line); mwIncErr(); retv = 1; } return retv; } static void mwDefaultOutFunc(int c) { if (mwLogR()) fputc(c, mwLogR()); } static void mwWrite(const char *format,...) { int tot, oflow = 0; va_list mark; mwAutoInit(); if (mwOutFunction == NULL) mwOutFunction = mwDefaultOutFunc; va_start(mark, format); tot = vsprintf(mwPrintBuf, format, mark); va_end(mark); if (tot >= MW_TRACE_BUFFER) { mwPrintBuf[MW_TRACE_BUFFER] = 0; oflow = 1; } for (tot = 0; mwPrintBuf[tot]; tot++) (*mwOutFunction) (mwPrintBuf[tot]); if (oflow) { mwWrite("\ninternal: mwWrite(): WARNING! OUTPUT EXCEEDED %u CHARS: SYSTEM UNSTABLE\n", MW_TRACE_BUFFER - 1); FLUSH(); } return; } static void mwLogFile(const char *name) { time_t tid; (void) time(&tid); if (mwLogR() != NULL) { fclose(mwLogR()); mwLogW(NULL); } if (name == NULL) return; mwLogW(fopen(name, "a" COMMIT)); if (mwLogR() == NULL) mwWrite("logfile: failed to open/create file '%s'\n", name); } /* ** Try to free NML memory until a contiguous allocation of ** 'needed' bytes can be satisfied. If this is not enough ** and the 'urgent' parameter is nonzero, grabbed memory is ** also freed. */ static size_t mwFreeUp(size_t needed, int urgent) { void *p; mwData *mw, *mw2; char *data; /* free grabbed NML memory */ for (;;) { if (mwDrop_(1, MW_VAL_NML, 1) == 0) break; p = malloc(needed); if (p == NULL) continue; free(p); return needed; } /* free normal NML memory */ mw = mwHead; while (mw != NULL) { if (!(mw->flag & MW_NML)) mw = mw->next; else { data = ((char *) (mw + 1)) + sizeof(long); if (mwTestMem(data, mw->size, MW_VAL_NML)) { mwIncErr(); mwWrite("wild pointer: <%ld> NoMansLand %p alloc'd at %s(%d)\n", mw->count, data + sizeof(long), mw->file, mw->line); } mw2 = mw->next; mwUnlink(mw, "mwFreeUp", 0); free(mw); mw = mw2; p = malloc(needed); if (p == NULL) continue; free(p); return needed; } } /* if not urgent (for internal purposes), fail */ if (!urgent) return 0; /* free grabbed memory */ for (;;) { if (mwDrop_(1, MW_VAL_GRB, 1) == 0) break; p = malloc(needed); if (p == NULL) continue; free(p); return needed; } return 0; } static const void *mwTestMem(const void *p, unsigned len, int c) { const unsigned char *ptr; ptr = (const unsigned char *) p; while (len--) { if (*ptr != (unsigned char) c) return (const void *) ptr; ptr++; } return NULL; } static int mwStrCmpI(const char *s1, const char *s2) { if (s1 == NULL || s2 == NULL) return 0; while (*s1) { if (toupper(*s2) == toupper(*s1)) { s1++; s2++; continue; } return 1; } return 0; } static int mwTestNow(const char *file, int line, int always_invoked) { int retv = 0; mwData *mw; char *data; if (file && !always_invoked) mwWrite("check: <%ld> %s(%d), checking %s%s%s\n", mwCounter, file, line, (mwTestFlags & MW_TEST_CHAIN) ? "chain " : "", (mwTestFlags & MW_TEST_ALLOC) ? "alloc " : "", (mwTestFlags & MW_TEST_NML) ? "nomansland " : "" ); if (mwTestFlags & MW_TEST_CHAIN) { for (mw = mwHead; mw; mw = mw->next) { if (!mwIsSafeAddr(mw, sizeof(mwData))) { mwWrite("check: heap corruption detected\n"); mwIncErr(); return retv + 1; } if (mw->prev) { if (!mwIsSafeAddr(mw->prev, sizeof(mwData))) { mwWrite("check: heap corruption detected\n"); mwIncErr(); return retv + 1; } if (mw == mwHead || mw->prev->next != mw) { mwWrite("check: heap chain broken, prev link incorrect\n"); mwIncErr(); retv++; } } if (mw->next) { if (!mwIsSafeAddr(mw->next, sizeof(mwData))) { mwWrite("check: heap corruption detected\n"); mwIncErr(); return retv + 1; } if (mw == mwTail || mw->next->prev != mw) { mwWrite("check: heap chain broken, next link incorrect\n"); mwIncErr(); retv++; } } else if (mw != mwTail) { mwWrite("check: heap chain broken, tail incorrect\n"); mwIncErr(); retv++; } } } if (mwTestFlags & MW_TEST_ALLOC) { for (mw = mwHead; mw; mw = mw->next) { if (mwTestBuf(mw, file, line)) retv++; } } if (mwTestFlags & MW_TEST_NML) { for (mw = mwHead; mw; mw = mw->next) { if ((mw->flag & MW_NML)) { data = ((char *) (mw + 1)) + sizeof(long); if (mwTestMem(data, mw->size, MW_VAL_NML)) { mwIncErr(); mwWrite("wild pointer: <%ld> NoMansLand %p alloc'd at %s(%d)\n", mw->count, data + sizeof(long), mw->file, mw->line); } } } } if (file && !always_invoked && !retv) mwWrite("check: <%ld> %s(%d), complete; no errors\n", mwCounter, file, line); return retv; } /********************************************************************** ** Statistics **********************************************************************/ static void mwStatReport() { mwStat *ms, *ms2; /* global statistics report */ mwWrite("\nMemory usage statistics (global):\n"); mwWrite(" N)umber of allocations made: %ld\n", mwStatNumAlloc); mwWrite(" L)argest memory usage : %ld\n", mwStatMaxAlloc); mwWrite(" T)otal of all alloc() calls: %ld\n", mwStatTotAlloc); mwWrite(" U)nfreed bytes totals : %ld\n", mwStatCurAlloc); if (mwStatLevel < 1) return; /* on a per-module basis */ mwWrite("\nMemory usage statistics (detailed):\n"); for (ms = mwStatList; ms; ms = ms->next) { if (ms->line == -1) { mwWrite(" module '%s' \t(N=%ld \tL=%ld \tT=%ld \tU=%ld)\n", ms->file ? ms->file : "", ms->num, ms->max, ms->total, ms->curr); if (ms->file && mwStatLevel > 1) { for (ms2 = mwStatList; ms2; ms2 = ms2->next) { if (ms2->line != -1 && ms2->file != NULL && !mwStrCmpI(ms2->file, ms->file)) { mwWrite(" line %d \t \t \t(N=%ld \tL=%ld \tT=%ld \tU=%ld)\n", ms2->line, ms2->num, ms2->max, ms2->total, ms2->curr); } } } } } } static mwStat *mwStatGet(const char *file, int line, int makenew) { mwStat *ms; if (mwStatLevel < 2) line = -1; for (ms = mwStatList; ms != NULL; ms = ms->next) { if (line != ms->line) continue; if (file == NULL) { if (ms->file == NULL) break; continue; } if (ms->file == NULL) continue; if (!strcmp(ms->file, file)) break; } if (ms != NULL) return ms; if (!makenew) return NULL; ms = (mwStat *) malloc(sizeof(mwStat)); if (ms == NULL) { if (mwFreeUp(sizeof(mwStat), 0) < sizeof(mwStat) || (ms = (mwStat *) malloc(sizeof(mwStat))) == NULL) { mwWrite("internal: memory low, statistics incomplete for '%s'\n", file); return NULL; } } ms->file = file; ms->line = line; ms->total = 0L; ms->max = 0L; ms->num = 0L; ms->curr = 0L; ms->next = mwStatList; mwStatList = ms; return ms; } static void mwStatAlloc(size_t size, const char *file, int line) { mwStat *ms; /* update the module statistics */ ms = mwStatGet(file, -1, 1); if (ms != NULL) { ms->total += (long) size; ms->curr += (long) size; ms->num++; if (ms->curr > ms->max) ms->max = ms->curr; } /* update the line statistics */ if (mwStatLevel > 1 && line != -1 && file) { ms = mwStatGet(file, line, 1); if (ms != NULL) { ms->total += (long) size; ms->curr += (long) size; ms->num++; if (ms->curr > ms->max) ms->max = ms->curr; } } } static void mwStatFree(size_t size, const char *file, int line) { mwStat *ms; /* update the module statistics */ ms = mwStatGet(file, -1, 1); if (ms != NULL) ms->curr -= (long) size; /* update the line statistics */ if (mwStatLevel > 1 && line != -1 && file) { ms = mwStatGet(file, line, 1); if (ms != NULL) ms->curr -= (long) size; } } #if 0 /* 980317: disabled C++ */ /********************************************************************** ** C++ new & delete **********************************************************************/ #ifdef __cplusplus #ifndef MEMWATCH_NOCPP int mwNCur = 0; const char *mwNFile = NULL; int mwNLine = 0; class MemWatch { public: MemWatch(); ~MemWatch(); }; MemWatch: :MemWatch() { if (mwInited) return; mwUseAtexit = 0; mwInit(); } MemWatch: :~MemWatch() { if (mwUseAtexit) return; mwTerm(); } /* ** This global new will catch all 'new' calls where MEMWATCH is ** not active. */ void *operator new(unsigned size) { mwNCur = 0; return mwMalloc(size, "", 0); } /* ** This is the new operator that's called when a module uses mwNew. */ void *operator new(unsigned size, const char *file, int line) { mwNCur = 0; return mwMalloc(size, file, line); } /* ** Since this delete operator will recieve ALL delete's ** even those from within libraries, we must accept ** delete's before we've been initialized. Nor can we ** reliably check for wild free's if the mwNCur variable ** is not set. */ void operator delete(void *p) { if (p == NULL) return; if (!mwInited) { free(p); return; } if (mwNCur) { mwFree(p, mwNFile, mwNLine); mwNCur = 0; return; } mwFree_(p); } #endif /* MEMWATCH_NOCPP */ #endif /* __cplusplus */ #endif /* 980317: disabled C++ */ /* MEMWATCH.C */