578 lines
14 KiB
C++
578 lines
14 KiB
C++
/*
|
|
* 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: <http://www.andreasen.org/LeakTracer/>
|
|
*
|
|
* Authors:
|
|
* Erwin S. Andreasen <erwin@andreasen.org>
|
|
* Henner Zeller <foobar@to.com>
|
|
*
|
|
* This program is Public Domain
|
|
*/
|
|
|
|
#ifdef THREAD_SAVE
|
|
#define _THREAD_SAVE
|
|
//#include <pthread.h>
|
|
#include "minorGems/system/MutexLock.h"
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
|
|
|
|
/*
|
|
* 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
|
|
* <ftp://ftp.perens.com/pub/ElectricFence/>
|
|
*/
|
|
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<void*,index-in-leaks-array>
|
|
*/
|
|
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:
|
|
*/
|