This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
deb-mbse/lib/memwatch.c.org
2001-08-17 05:46:24 +00:00

67 KiB
Raw Blame History

/*

MEMWATCH.C

Nonintrusive ANSI C memory leak / overwrite detection

Copyright (C) 1992-99 Johan Lindh

All rights reserved.

Version 2.62

**

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]

990610 JLI [2.60 fixed some more high-sensitivity warnings]

990715 JLI [2.61 changed TRACE/ASSERT/VERIFY macro names]

991001 JLI [2.62 added CHECK_BUFFER() and mwTestBuffer()]

*/

#define __MEMWATCH_C 1

#ifdef MW_NOCPP #define MEMWATCH_NOCPP #endif #ifdef MW_STDIO #define MEMWATCH_STDIO #endif

/***********************************************************************

Include files

***********************************************************************/

#include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <time.h> #include "memwatch.h"

#ifndef toupper #include <ctype.h> #endif

/***********************************************************************

Defines & other weird stuff

***********************************************************************/

*lint -save -e767 * #define VERSION "2.62" * 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++ = (mwBYTE)(t_l >> 24); \ *t_cp++ = (mwBYTE)(t_l >> 16); \ *t_cp++ = (mwBYTE)(t_l >> 8); \ *t_cp = (mwBYTE)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 unsigned 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; if( mwStatLevel != level ) { mwWrite( "statistics: now collecting on a %s basis\n", level<1?"global":(level<2?"module":"line") ); 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 ); }

/*

Returns zero if there are no errors.

Returns nonzero if there are errors.

*/ int mwTestBuffer( const char file, int line, void *p ) { mwData mw;

mwAutoInit();

* do the quick ownership test * mw = (mwData*) ( ((char*)p)-sizeof(long)-sizeof(mwData) );

if( mwIsOwned( mw, file, line ) ) { return mwTestBuf( mw, file, line ); } return 1; }

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 <windows.h> 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 "<unknown type>"; }

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 = "<relinked>"; } 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:"<garbage-pointer>", 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:"<garbage-pointer>", 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 = "<relinked>"; } else mw->file = "<unknown>"; 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( "∋nternal: 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; }

#define AIPH() if( always_invoked ) { mwWrite("autocheck: <%ld> %s(%d) ", mwCounter, file, line ); always_invoked = 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)) ) { AIPH(); mwWrite("check: heap corruption detected\n"); mwIncErr(); return retv + 1; } if( mw->prev ) { if( !mwIsSafeAddr(mw->prev, sizeof(mwData)) ) { AIPH(); mwWrite("check: heap corruption detected\n"); mwIncErr(); return retv + 1; } if( mw==mwHead || mw->prev->next != mw ) { AIPH(); mwWrite("check: heap chain broken, prev link incorrect\n"); mwIncErr(); retv ++; } } if( mw->next ) { if( !mwIsSafeAddr(mw->next, sizeof(mwData)) ) { AIPH(); mwWrite("check: heap corruption detected\n"); mwIncErr(); return retv + 1; } if( mw==mwTail || mw->next->prev != mw ) { AIPH(); mwWrite("check: heap chain broken, next link incorrect\n"); mwIncErr(); retv ++; } } else if( mw!=mwTail ) { AIPH(); 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; const char *modname; int modnamelen;

* 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 ); FLUSH();

if( mwStatLevel < 1 ) return;

* on a per-module basis * mwWrite( "\nMemory usage statistics (detailed):\n"); mwWrite( " Module/Line Number Largest Total Unfreed \n"); for( ms=mwStatList; ms; ms=ms->next ) { if( ms->line == -1 ) { if( ms->file == NULL || !mwIsReadAddr(ms->file,22) ) modname = "<unknown>"; else modname = ms->file; modnamelen = strlen(modname); if( modnamelen > 42 ) { modname = modname + modnamelen - 42; }

mwWrite(" %-42s %-8ld %-8ld %-8ld %-8ld\n", modname, 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( " %-8d %-8ld %-8ld %-8ld %-8ld\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, "<unknown>", 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 *