/* * Modification History * * 2002-March-31 Jason Rohrer * Added support for strdup. * Fixed bug in strdup. * * 2003-October-3 Jason Rohrer * Added printout of leak contents in report. * * 2004-January-16 Jason Rohrer * Switched to use minorGems platform-independed mutexes. * Changed to use simpler fopen call to open report. */ /* * Homepage: * * Authors: * Erwin S. Andreasen * Henner Zeller * * This program is Public Domain */ #ifdef THREAD_SAVE #define _THREAD_SAVE //#include #include "minorGems/system/MutexLock.h" #endif #include #include #include #include #include #include #include #include #include #include /* * underlying allocation, de-allocation used within * this tool */ #define LT_MALLOC malloc #define LT_FREE free #define LT_REALLOC realloc /* * prime number for the address lookup hash table. * if you have _really_ many memory allocations, use a * higher value, like 343051 for instance. */ #define SOME_PRIME 35323 #define ADDR_HASH(addr) ((unsigned long) addr % SOME_PRIME) /** * Filedescriptor to write to. This should not be a low number, * because these often have special meanings (stdin, out, err) * and may be closed by the program (daemons) * So choose an arbitrary higher FileDescriptor .. e.g. 42 */ #define FILEDESC 42 /** * allocate a bit more memory in order to check if there is a memory * overwrite. Either 0 or more than sizeof(unsigned int). Note, you can * only detect memory over_write_, not _reading_ beyond the boundaries. Better * use electric fence for these kind of bugs * */ typedef unsigned long magic_t; #define MAGIC ((magic_t) 0xAABBCCDDLu) /** * this may be more than sizeof(magic_t); if you want more, then * sepecify it like #define SAVESIZE (sizeof(magic_t) + 12) */ #define SAVESIZE (sizeof(magic_t) + 0) /** * on 'new', initialize the memory with this value. * if not defined - uninitialized. This is very helpful because * it detects if you initialize your classes correctly .. if not, * this helps you faster to get the segmentation fault you're * implicitly asking for :-). * * Set this to some value which is likely to produce a * segmentation fault on your platform. */ #define SAVEVALUE 0xAA /** * on 'delete', clean memory with this value. * if not defined - no memory clean. * * Set this to some value which is likely to produce a * segmentation fault on your platform. */ #define MEMCLEAN 0xEE /** * Initial Number of memory allocations in our list. * Doubles for each re-allocation. */ #define INITIALSIZE 32768 static class LeakTracer { struct Leak { const void *addr; size_t size; const void *allocAddr; bool type; int nextBucket; }; int newCount; // how many memory blocks do we have int leaksCount; // amount of entries in the leaks array int firstFreeSpot; // Where is the first free spot in the leaks array? int currentAllocated; // currentAllocatedMemory int maxAllocated; // maximum Allocated unsigned long totalAllocations; // total number of allocations. stats. unsigned int abortOn; // resons to abort program (see abortReason_t) /** * Have we been initialized yet? We depend on this being * false before constructor has been called! */ bool initialized; bool destroyed; // Has our destructor been called? FILE *report; // filedescriptor to write to /** * pre-allocated array of leak info structs. */ Leak *leaks; /** * fast hash to lookup the spot where an allocation is * stored in case of an delete. map */ int *leakHash; // fast lookup #ifdef THREAD_SAVE MutexLock mutex; #endif enum abortReason_t { OVERWRITE_MEMORY = 0x01, DELETE_NONEXISTENT = 0x02, NEW_DELETE_MISMATCH = 0x04 }; public: LeakTracer() { initialize(); } void initialize() { // Unfortunately we might be called before our constructor has actualy fired if (initialized) return; // fprintf(stderr, "LeakTracer::initialize()\n"); initialized = true; newCount = 0; leaksCount = 0; firstFreeSpot = 1; // index '0' is special currentAllocated = 0; maxAllocated = 0; totalAllocations = 0; abortOn = OVERWRITE_MEMORY; // only _severe_ reason report = 0; leaks = 0; leakHash = 0; char uniqFilename[256]; const char *filename = getenv("LEAKTRACE_FILE") ? : "leak.out"; struct stat dummy; if (stat(filename, &dummy) == 0) { sprintf(uniqFilename, "%s.%d", filename, getpid()); fprintf(stderr, "LeakTracer: file exists; using %s instead\n", uniqFilename); } else { sprintf(uniqFilename, "%s", filename); } // not sure why this "open" code is here // (it doesn't open the file properly in MinGW on win32) /* int reportfd = open(uniqFilename, O_WRONLY|O_CREAT|O_TRUNC,S_IREAD|S_IWRITE); if (reportfd < 0) { fprintf(stderr, "LeakTracer: cannot open %s: %m\n", filename); report = stderr; } else { int dupfd = dup2(reportfd, FILEDESC); close(reportfd); report = fdopen(dupfd, "w"); if (report == NULL) { report = stderr; } } */ // simpler version using only fopen report = fopen( uniqFilename, "w" ); if( report == NULL ) { fprintf( stderr, "LeakTracer: cannot open %s\n", uniqFilename ); report = stderr; } time_t t = time(NULL); fprintf (report, "# starting %s", ctime(&t)); leakHash = (int*) LT_MALLOC(SOME_PRIME * sizeof(int)); memset ((void*) leakHash, 0x00, SOME_PRIME * sizeof(int)); #ifdef MAGIC fprintf (report, "# memory overrun protection of %d Bytes " "with magic 0x%4lX\n", SAVESIZE, MAGIC); #endif #ifdef SAVEVALUE fprintf (report, "# initializing new memory with 0x%2X\n", SAVEVALUE); #endif #ifdef MEMCLEAN fprintf (report, "# sweeping deleted memory with 0x%2X\n", MEMCLEAN); #endif if (getenv("LT_ABORTREASON")) { abortOn = atoi(getenv("LT_ABORTREASON")); } #define PRINTREASON(x) if (abortOn & x) fprintf(report, "%s ", #x); fprintf (report, "# aborts on "); PRINTREASON( OVERWRITE_MEMORY ); PRINTREASON( DELETE_NONEXISTENT ); PRINTREASON( NEW_DELETE_MISMATCH ); fprintf (report, "\n"); #undef PRINTREASON #ifdef THREAD_SAVE fprintf (report, "# thread save\n"); /* * create default, non-recursive ('fast') mutex * to lock our datastructure where we keep track of * the user's new/deletes */ /*if (pthread_mutex_init(&mutex, NULL) < 0) { fprintf(report, "# couldn't init mutex ..\n"); fclose(report); _exit(1); }*/ #else fprintf(report, "# not thread save; if you use threads, recompile with -DTHREAD_SAVE\n"); #endif fflush(report); } /* * the workhorses: */ void *registerAlloc(size_t size, bool type); void registerFree (void *p, bool type); /** * write a hexdump of the given area. */ void hexdump(const unsigned char* area, int size); /** * Terminate current running progam. */ void progAbort(abortReason_t reason) { if (abortOn & reason) { fprintf(report, "# abort; DUMP of current state\n"); writeLeakReport(); fclose(report); abort(); } else fflush(report); } /** * write a Report over leaks, e.g. still pending deletes */ void writeLeakReport(); ~LeakTracer() { // fprintf(stderr, "LeakTracer::destroy()\n"); time_t t = time(NULL); fprintf (report, "# finished %s", ctime(&t)); writeLeakReport(); fclose(report); free(leaks); #ifdef THREAD_SAVE //pthread_mutex_destroy(&mutex); #endif destroyed = true; } } leakTracer; void* LeakTracer::registerAlloc (size_t size, bool type) { initialize(); // fprintf(stderr, "LeakTracer::registerAlloc()\n"); if (destroyed) { fprintf(stderr, "Oops, registerAlloc called after destruction of LeakTracer (size=%d)\n", size); return LT_MALLOC(size); } void *p = LT_MALLOC(size + SAVESIZE); // Need to call the new-handler if (!p) { fprintf(report, "LeakTracer malloc %m\n"); _exit (1); } #ifdef SAVEVALUE /* initialize with some defined pattern */ memset(p, SAVEVALUE, size + SAVESIZE); #endif #ifdef MAGIC /* * the magic value is a special pattern which does not need * to be uniform. */ if (SAVESIZE >= sizeof(magic_t)) { magic_t *mag; mag = (magic_t*)((char*)p + size); *mag = MAGIC; } #endif #ifdef THREAD_SAVE //pthread_mutex_lock(&mutex); mutex.lock(); #endif ++newCount; ++totalAllocations; currentAllocated += size; if (currentAllocated > maxAllocated) maxAllocated = currentAllocated; for (;;) { for (int i = firstFreeSpot; i < leaksCount; i++) if (leaks[i].addr == NULL) { leaks[i].addr = p; leaks[i].size = size; leaks[i].type = type; leaks[i].allocAddr=__builtin_return_address(1); firstFreeSpot = i+1; // allow to lookup our index fast. int *hashPos = &leakHash[ ADDR_HASH(p) ]; leaks[i].nextBucket = *hashPos; *hashPos = i; #ifdef THREAD_SAVE //pthread_mutex_unlock(&mutex); mutex.unlock(); #endif return p; } // Allocate a bigger array // Note that leaksCount starts out at 0. int new_leaksCount = (leaksCount == 0) ? INITIALSIZE : leaksCount * 2; leaks = (Leak*)LT_REALLOC(leaks, sizeof(Leak) * new_leaksCount); if (!leaks) { fprintf(report, "# LeakTracer realloc failed: %m\n"); _exit(1); } else { fprintf(report, "# internal buffer now %d\n", new_leaksCount); fflush(report); } memset(leaks+leaksCount, 0x00, sizeof(Leak) * (new_leaksCount-leaksCount)); leaksCount = new_leaksCount; } } void LeakTracer::hexdump(const unsigned char* area, int size) { fprintf(report, "# "); for (int j=0; j < size ; ++j) { fprintf (report, "%02x ", *(area+j)); if (j % 16 == 15) { fprintf(report, " "); for (int k=-15; k < 0 ; k++) { char c = (char) *(area + j + k); fprintf (report, "%c", isprint(c) ? c : '.'); } fprintf(report, "\n# "); } } fprintf(report, "\n"); } void LeakTracer::registerFree (void *p, bool type) { initialize(); if (p == NULL) return; if (destroyed) { fprintf(stderr, "Oops, allocation destruction of LeakTracer (p=%p)\n", p); return; } #ifdef THREAD_SAVE //pthread_mutex_lock(&mutex); mutex.lock(); #endif int *lastPointer = &leakHash[ ADDR_HASH(p) ]; int i = *lastPointer; while (i != 0 && leaks[i].addr != p) { lastPointer = &leaks[i].nextBucket; i = *lastPointer; } if (leaks[i].addr == p) { *lastPointer = leaks[i].nextBucket; // detach. newCount--; leaks[i].addr = NULL; currentAllocated -= leaks[i].size; if (i < firstFreeSpot) firstFreeSpot = i; if (leaks[i].type != type) { fprintf(report, "S %10p %10p # new%s but delete%s " "; size %d\n", leaks[i].allocAddr, __builtin_return_address(1), ((!type) ? "[]" : " normal"), ((type) ? "[]" : " normal"), leaks[i].size); progAbort( NEW_DELETE_MISMATCH ); } #ifdef MAGIC if ((SAVESIZE >= sizeof(magic_t)) && *((magic_t*)((char*)p + leaks[i].size)) != MAGIC) { fprintf(report, "O %10p %10p " "# memory overwritten beyond allocated" " %d bytes\n", leaks[i].allocAddr, __builtin_return_address(1), leaks[i].size); fprintf(report, "# %d byte beyond area:\n", SAVESIZE); hexdump((unsigned char*)p+leaks[i].size, SAVESIZE); progAbort( OVERWRITE_MEMORY ); } #endif #ifdef THREAD_SAVE # ifdef MEMCLEAN int allocationSize = leaks[i].size; # endif //pthread_mutex_unlock(&mutex); mutex.unlock(); #else #define allocationSize leaks[i].size #endif #ifdef MEMCLEAN // set it to some garbage value. memset((unsigned char*)p, MEMCLEAN, allocationSize + SAVESIZE); #endif LT_FREE(p); return; } #ifdef THREAD_SAVE //pthread_mutex_unlock(&mutex); mutex.unlock(); #endif fprintf(report, "D %10p # delete non alloc or twice pointer %10p\n", __builtin_return_address(1), p); progAbort( DELETE_NONEXISTENT ); } void LeakTracer::writeLeakReport() { initialize(); if (newCount > 0) { fprintf(report, "# LeakReport\n"); fprintf(report, "# %10s | %9s # Pointer Addr\n", "from new @", "size"); } for (int i = 0; i < leaksCount; i++) if (leaks[i].addr != NULL) { // This ought to be 64-bit safe? char *memContents = (char *)LT_MALLOC( leaks[i].size + 1 ); memcpy( (void *)memContents, (void *)( leaks[i].addr ), leaks[i].size ); memContents[ leaks[i].size ] = '\0'; fprintf(report, "L %10p %9ld # %p \"%s\"\n", leaks[i].allocAddr, (long) leaks[i].size, leaks[i].addr, memContents ); LT_FREE( memContents ); } fprintf(report, "# total allocation requests: %6ld ; max. mem used" " %d kBytes\n", totalAllocations, maxAllocated / 1024); fprintf(report, "# leak %6d Bytes\t:-%c\n", currentAllocated, (currentAllocated == 0) ? ')' : '('); if (currentAllocated > 50 * 1024) { fprintf(report, "# .. that is %d kByte!! A lot ..\n", currentAllocated / 1024); } } /** -- The actual new/delete operators -- **/ void* operator new(size_t size) { return leakTracer.registerAlloc(size,false); } void* operator new[] (size_t size) { return leakTracer.registerAlloc(size,true); } void operator delete (void *p) { leakTracer.registerFree(p,false); } void operator delete[] (void *p) { leakTracer.registerFree(p,true); } // added by Jason Rohrer char *strdup( const char *inString ) { char *outString = (char*)leakTracer.registerAlloc( strlen( inString ) + 1, true ); strcpy( outString, inString ); return outString; } /* Emacs: * Local variables: * c-basic-offset: 8 * End: * vi:set tabstop=8 shiftwidth=8 nowrap: */