[comp.sys.amiga.tech] tracker.shar - Aztec C programmer's resource tracking aids

karl@sugar.uu.net (Karl Lehenbauer) (01/27/89)

Below are my resource tracking aids for Aztec C programmers.  What is
most of use are the tracking versions of AllocMem and FreeMem which,
at run time, print out file name and line numbers at exit time for 
memory that was allocated but wasn't freed, memory that was freed twice, 
memory for which the free amount was not the same as the allocation amount
and so forth.  It is much better than watching the free memory bytes
display in Intuition then hunting through your code for missing FreeMems, 
because it isn't vulnerable to other programs' allocations screwing
up the number (for example, memory allocation for history functions in
various shells) and its ability to help pinpoint where the problem is
in the source.

I am posting it here because it's relevant to current discussion, has
been very useful to me in debugging several recent programs, is reasonably 
short, and I have the hubris to think it's important enough to get
it out -- moderated code distribution seems to have been intermittent of late.

It should port over to Lattice pretty easily.  If anyone does so, please
email diffs or an #ifdef'ed version or equivalent to me and post (?).
-karl

---------------------- cut here ---------------------
:
#! /bin/sh
# This is a shell archive,
# created by Karl Lehenbauer on snoc (his amiga) Wed Jan 25 19:42:38 1989
# Remove anything before the "#! /bin/sh" line, then unpack it by saving
# it into a file and typing "sh file".  If you do not have sh, you need 
# unshar, a dearchiving program which is widely available.  In the absolute
# wost case, you can crack the files out by hand.
# If the archive is complete, you will see the message "End of archive."
# at the end.
# This archive contains the following files...
#    'README'
#    'tracker.h'
#    'tracker.c'
#
# To extract them, run the following through /bin/sh
echo x - README
sed 's/^X//' > README << '//END_OF_FILE'
X[even though in an alpha state, I think this will be of great use to
X Aztec C programmers who want to be sure they're freeing all of their
X memory -karl]
X
X
XC Programmer's Amiga Resource Tracking Routines    Version 0.0a   1/5/89
X-------------------------------------------------------------------------
X
XThis code and documentation is released to the Public Domain without any
Xrestrictions on use, resale or redistribution.
X
XNo license or warranty of appropriateness, usefulness or bug-freeness is
Xexpressed or implied.  This is free code.  We don't have a contract.
X
XWritten by:
X
XKarl Lehenbauer, Hackercorp, 3918 Panorama, Missouri City, TX, USA  77459
X(713) 438-4964 voice,  (713) 438-5018 data
XUsenet: uunet!sugar!karl, Internet: karl@sugar.uu.net, BIX: kelehen
X
X
XThese routines were written to aid C programmers in insuring that their
Xprograms are  properly returning all the memory, signals and locks they
Xhave allocated.  
X
XTo use them, include tracker.h in your C source programs and recompile.
X(The use of an "includes.c" along with the Manx +i and +h flags to
Xprecompile the symbol table obviates the necessity of editing 
X'#include "tracker.h"' into every one of your source files.)
XNext, edit your exit routine to call TrackerExitReport() after it has
Xfreed everything.  Then, compile tracker.c using the memory model you're 
Xusing for the rest of your code and link your program to include tracker.o.
X(This can all be done in your makefile if you've got it set up right.)
XFinally, run your program.
X
XThe program must either be initiated from the CLI or you must edit your 
Xprogram's startup code to fopen stderr and direct it somewhere  (like 
Xto a  window or a file) or you won't get any of the resource tracker's 
Xmessages, or worse.
X
XAs your program runs, every time it allocs memory via AllocMem(), allocs
Xa signal via AllocSignal() or gets a Lock via Lock(), special tracking
Xroutines execute instead (thanks to some macros defined by tracker.h)
Xwhich, in addition to performing the action you requested, record 
Xinformation about what you requested and what you got.  For AllocMem(),
Xthe source file and line of the AllocMem call as well as the amount of memory
Xrequested and the pointer to the memory returned are recorded.  For
XAllocSignal(), only the signal numbers allocated are recorded at this time.
XFor Lock(), the file name to be locked, source file and line number and 
Xthe lock returned are recorded.
X
XWhen your program frees memory via FreeMem(), a special tracking version
Xof FreeMem is executed that searches the list of entries recorded by the
Xtracking version of AllocMem().  The resource tracker reports if you free
Xsomething more than once, if you free something that you didn't allocate
Xor if the length that you are freeing differes from what you allocated.
XThis includes the source file name and line number of the matching AllocMem 
X(when it is known) and always includes the source file and line for FreeMem.
X
XWhen your program frees a signal via FreeSignal(), a tracking version
Xof FreeSignal checks to see if you have allocated the signal you are
Xnow freeing.  If you haven't, it reports it, but it doesn't include the
Xfile name and line number at this time.  I don't think this is a serious
Xproblem, as signals aren't as critical as the other stuff, but I may add
Xit in a future version.
X
XWhen your program unlocks a lock via UnLock(), a tracking version of UnLock
Xsearches the list of recorded locks to see if you locked the lock you are
Xunlocking and report accordingly.
X
XThe tracker exit report provided by TrackerExitReport() is where most of
Xthe bugs are identified.  TrackerExitReport identifies all AllocMems that
Xdidn't have a corresponding FreeMem, including the source file and line
Xof the call to AllocMem as well as the address and size of the memory
Xin question.  The resource tracker does not free the memory for you because
Xyou may have not freed the memory on purpose (for example, you may have
Xspawned a task that uses it will free it later) and it cannot know that.
X
XThe exit report details all signals that weren't freed.  This isn't very
Ximportant, in my opinion.
X
XAlso, the exit report prints information on all file locks that were made
Xthat didn't have a corresponding UnLock.  This information includes the
Xname of the file, value of the lock and the source file and line of the
Xcode that locked it.
X
XThe exit report also prints the number of calls to allocate and free memory,
Xallocate and free signals and to lock and unlock files as a gross indicator
Xof whether you're cleaning everything up properly.
X
XNote that, in the default configuration, memory that is freed and 
Xreallocated will screw up the tracker because the tracker continues 
Xto track memory objects after they have been  freed. This is a tradeoff 
Xbetween being to be able to detect multiple frees of the same memory or 
Xnot.  If that's a problem, tracker.c can be recompiled with a
X-DFORGET_MEMORY_WHEN_FREED option so that it will not try to detect
Xmultiple frees.
X
XThe same is true for the lock tracking routines, although in that case 
Xthe argument is more clear that unlocks should cause the lock tracking 
Xentry to be discarded, because multiple unlocks are common and multiple 
Xlocks and unlocks of the same file  during execution are also conceivably 
Xpretty common.  Right now by default, the tracker will track locks after
Xthey have been freed.  To change this behavior, recompile tracker.c with
Xthe -DFORGET_LOCKS_WHEN_UNLOCKED option.
X
XUnfortunately, the tracker macros that redefine AllocMem and such will
Xcause your compiler to barf on any files you have that declare them
Xas external.  If that happens, either remove the external declarations
X(and include <functions.h>) or move them to be before the include of
Xtracker.h.
X
X
XALPHA RELEASE, SOFTWARE STATUS
X------------------------------
X
XThe Lock, Unlock and DupLock tracking routines have not been tested
Xadequately.  The signal stuff works OK, but that's no biggie.  The
Xmain thing of interest is the tracking AllocMem and FreeMem, which
XI have used successfully on several programs that I have been working
Xon.
X
X-karl @ The Hacker's Haven, Houston, TX -- 5-Jan-89
X
XP.S.  Note that TrackerExitReport() must be called to get the tracking
X routines to free the memory they have allocated, so it's a good idea
X to call it from your abnormal exit (_abort, etc) routines as well as
X normal exit.  Also, that's good because you can make sure you're freeing
X properly from your strange abort conditions, a thing that's hard to get
X right.
//END_OF_FILE
echo x - tracker.h
sed 's/^X//' > tracker.h << '//END_OF_FILE'
X/* tracking macros to use tracker routines */
X
X#define AllocMem(x,y) TrackingAllocMem(x,y,__FILE__,__LINE__)
X#define FreeMem(x,y) TrackingFreeMem(x,y,__FILE__,__LINE__)
X
X#define AllocSignal(x) TrackingAllocSignal(x,__FILE__,__LINE__)
X#define FreeSignal(x) TrackingFreeSignal(x,__FILE__,__LINE__);
X
X#define Lock(x,y) TrackingLock(x,__FILE__,__LINE__);
X#define UnLock(x,y) TrackingUnLock(x,__FILE__,__LINE__);
X#define DupLock(x) TrackingDupLock(x,__FILE__,__LINE__);
X
Xvoid *TrackingAllocMem();
Xvoid TrackingFreeMem();
X
Xlong TrackingAllocSignal();
Xvoid TrackingFreeSignal();
X
Xvoid *TrackingAllocRaster();
Xvoid TrackingFreeRaster();
X
Xstruct FileLock *TrackingLock();
Xvoid TrackingUnLock();
//END_OF_FILE
echo x - tracker.c
sed 's/^X//' > tracker.c << '//END_OF_FILE'
X/* tracking memory allocator */
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <functions.h>
X#include <stdio.h>
X
X/* comment out the following line if you want locks freed twice reported
X * at the cost of getting spurious resource error messages when
X * reusing the lock */
X/* #define FORGET_LOCKS_WHEN_UNLOCKED */
X
X/* comment out the following line if you want memory freed twice reported
X * at the cost of getting spurious resource error messages when
X * freeing and reallocating memory */
X/* #define FORGET_MEMORY_WHEN_FREED */
X
X/* make sure our invocations of the real routines on behalf of the user
X   don't cause us to recurse */
X
X#ifdef AllocMem
X#undef AllocMem
X#undef FreeMem
X#undef AllocSignal
X#undef FreeSignal
X#undef Lock
X#undef UnLock
X#undef DupLock
X#endif
X
X/* my flags */
X#define FREED_IT 1
X
Xstruct TrackingAllocMemData
X{
X	UBYTE *where;			/* address returned by allocator */
X	long amount;			/* number of bytes allocated */
X	long alloc_flags;		/* flags passed to allocator */
X	char *file;				/* filename of caller from the macro */
X	int line;				/* line number of caller from macro */
X	long my_flags;			/* flags internal to tracker */
X	struct TrackingAllocMemData *next;	/* pointer to next entry */
X};
X
Xstruct TrackingAllocMemData *TrackingAllocMemList = NULL;
Xlong MemAllocCount = 0, MemFreeCount = 0;
X
Xvoid *TrackingAllocMem(amount,flags,file,line)
Xlong amount;
Xlong flags;
Xchar *file;
Xint line;
X{
X	register struct TrackingAllocMemData *rp;
X	UBYTE *users_memory;
X
X	/* perform the actual alloc */
X	users_memory = AllocMem(amount,flags);
X
X	/* if it succeeded, record tracking info */
X	if (users_memory)
X	{
X		MemAllocCount++;
X
X		if ((rp = AllocMem((long)sizeof(struct TrackingAllocMemData),0L)) == NULL)
X			panic("tracker: can't alloc memory to record AllocMem data");
X
X		/* add new alloc data entry to linked list */
X		rp->next = TrackingAllocMemList;
X		TrackingAllocMemList = rp;
X
X		/* shove in save values */
X		rp->amount = amount;
X		rp->alloc_flags = flags;
X		rp->where = users_memory;
X		rp->file = file;
X		rp->line = line;
X		rp->my_flags = 0;
X	}
X	/* return pointer to the space allocated */
X	return(users_memory);
X}
X
Xvoid TrackingFreeMem(where,amount,file,line)
XUBYTE *where;
Xlong amount;
Xchar *file;
Xint line;
X{
X	register struct TrackingAllocMemData *rp, *op, *freep;
X
X	MemFreeCount++;
X	/* scan the memory tracking list for a match */
X	for (rp = TrackingAllocMemList, op = NULL; rp != NULL; op = rp, rp = rp->next)
X	{
X		/* if we matched the address */
X		if (rp->where == where)
X		{
X			/* if they got the amount wrong, tell them */
X			if (rp->amount != amount)
X			{
X				fprintf(stderr,"freed addr %lx OK but length differs, talloc'ed %ld, freed %ld,\n\tallocated at file %s line %d, freed at file %s line %d\n",
X				where,rp->amount,amount,rp->file,rp->line,file,line);
X			}
X#ifndef FORGET_MEMORY_WHEN_FREED
X			/* if it's already free, tell them they freed twice */
X			if (rp->my_flags & FREED_IT)
X			{
X				fprintf(stderr,"freed memory twice at %lx, amount %ld,\n\tallocated in file %s at line %d, freed in file %s at line %d\n",where,amount,rp->file,rp->line,file,line);
X				return;
X			}
X			else
X			{
X				/* mark this entry as free */
X				rp->my_flags |= FREED_IT;
X			}
X#else
X			/* remove entry from linked list and free it */
X			if (op != NULL) op->next = rp->next;
X			else
X				TrackingAllocMemList = rp->next;
X			freep = rp;
X			rp = rp->next;
X			FreeMem(freep,(long)sizeof(struct TrackingAllocMemData));
X#endif
X			FreeMem(where,(long)amount);
X
X			return;
X		}
X	}
X	fprintf(stderr,"Freed memory at %lx of amount %ld that wasn't allocated,\n\tfreed at file %s line %d\n",where,amount,file,line);
X	FreeMem(where,amount);
X}
X
Xvoid ReportUnfreedMemory()
X{
X	struct TrackingAllocMemData *rp = TrackingAllocMemList, *freep;
X
X	while (rp != NULL)
X	{
X		if (!(rp->my_flags & FREED_IT))
X		{
X			fprintf(stderr,"FreeMem was never called for memory at %lx, amount %ld,\n\tthe alloc was performed at file %s line %d\n",rp->where,rp->amount,rp->file,rp->line);
X		}
X		freep = rp;
X		rp = rp->next;
X		FreeMem(freep,(long)sizeof(struct TrackingAllocMemData));
X	}
X	printf("Total tracked AllocMem calls %ld, FreeMem calls %ld\n",MemAllocCount,MemFreeCount);
X}
X
X
X/* track signals */
X/* tracking AllocSignal doesn't currently track where it was called from */
X
Xlong TrackingSignalMask = 0;
Xlong SignalAllocCount = 0, SignalFreeCount = 0;
X
Xlong TrackingAllocSignal(signal_num,file,line)
Xlong signal_num;
Xchar *file;
Xint line;
X{
X	SignalAllocCount++;
X
X	signal_num = AllocSignal(signal_num);
X
X	if (signal_num != -1)
X		TrackingSignalMask |= (1 << signal_num);
X
X	return(signal_num);
X}
X
Xvoid TrackingFreeSignal(signal_num,file,line)
Xlong signal_num;
Xchar *file;
Xint line;
X{
X	SignalFreeCount++;
X
X	if (!(TrackingSignalMask & (1 << signal_num)))
X	{
X		fprintf("freed a signal (%ld) that was never allocated, at file %s line %d\n",
X			signal_num,file,line);
X		TrackingSignalMask &= ~(1 << signal_num);
X	}
X}
X
Xvoid ReportUnfreedSignals()
X{
X	if (TrackingSignalMask)
X		fprintf("failed to free signals indicated by this mask: %8lx\n",
X			TrackingSignalMask);
X	printf("Total tracked AllocSignal calls %ld, FreeSignal calls %ld\n",SignalAllocCount,SignalFreeCount);
X}
X
X/* tracking lock and unlock */
X
Xstruct TrackingLockData
X{
X	struct FileLock *lock;	/* lock returned by Lock */
X	char *name;				/* name of file that was locked */
X	long accessMode;		/* access mode of the file that was locked */
X	char *file;				/* ptr to file name of line of caller */
X	int line;				/* ptr to line number text of locker */
X	long my_flags;			/* flags internal to tracker */
X	struct TrackingLockData *next;	/* pointer to next entry */
X};
X
X/* flags */
X#define CREATED_BY_DUPLOCK 1
X
Xstruct TrackingLockData *TrackingLockList = NULL;
Xlong TrackerLockCount = 0, TrackerUnLockCount = 0;
X
Xstruct FileLock *TrackingLock(name, accessMode, file, line)
Xchar *name;
Xlong accessMode;
Xchar *file;
Xint line;
X{
X	register struct TrackingLockData *lp;
X	struct FileLock *users_lock;
X
X	users_lock = Lock(name, (long)accessMode);
X
X	if (users_lock)
X	{
X		TrackerLockCount++;
X
X		if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
X			panic("tracker: can't alloc memory to record lock data");
X
X		/* add new alloc data entry to linked list */
X		lp->next = TrackingLockList;
X		TrackingLockList = lp;
X
X		/* shove in save values */
X		lp->accessMode = accessMode;
X		lp->file = file;
X		lp->line = line;
X		lp->my_flags = 0;
X		lp->lock = users_lock;
X
X		/* alloc space for filename and save */
X		if ((lp->name = AllocMem((long)(strlen(name)+1),0L)) == NULL)
X			panic("tracker: can't alloc memory to record lock filename");
X		strcpy(lp->name,name);
X	}
X	return(users_lock);
X}
X
Xstruct FileLock *TrackingDupLock(lock, file, line)
Xstruct FileLock *lock;
Xchar *file;
Xint line;
X{
X	register struct TrackingLockData *lp;
X	struct FileLock *users_lock;
X
X	users_lock = DupLock(lock);
X
X	if (users_lock)
X	{
X		TrackerLockCount++;
X
X		if ((lp = AllocMem((long)sizeof(struct TrackingLockData),0L)) == NULL)
X			panic("tracker: can't alloc memory to record lock data");
X
X		/* add new alloc data entry to linked list */
X		lp->next = TrackingLockList;
X		TrackingLockList = lp;
X
X		lp->file = file;
X		lp->line = line;
X		lp->name = NULL;
X		lp->lock = users_lock;
X		lp->my_flags = CREATED_BY_DUPLOCK;			
X	}
X	return(users_lock);
X}
X
Xvoid TrackingUnLock(lock,file,line)
Xstruct FileLock *lock;
Xchar *file;
Xint line;
X{
X	register struct TrackingLockData *lp, *op, *freep;
X
X	TrackerUnLockCount++;
X
X	/* scan the lock tracking list for a match */
X	for (lp = TrackingLockList, op = NULL; lp != NULL; op = lp, lp = lp->next)
X	{
X		/* if we matched the lock */
X		if (lp->lock == lock)
X		{
X#ifndef FORGET_LOCKS_WHEN_UNLOCKED
X			/* if it's already free, tell them they freed twice */
X			if (lp->my_flags & FREED_IT)
X			{
X				fprintf(stderr,"freed lock twice, lock %lx, filename %s\n\tlocked at file %s line %d, freed at file %s line %d\n",lock,lp->name,lp->file,lp->line,file,line);
X				return;
X			}
X			else
X			{
X				/* mark this entry as free */
X				lp->my_flags |= FREED_IT;
X			}
X#else
X			if (op != NULL) op->next = lp->next;
X			else TrackingLockList = lp->next;
X			freep = lp;
X			lp = lp->next;
X			if (lp->name != NULL)
X				FreeMem(lp->name,(long)(strlen(lp->name)+1));
X			FreeMem(freep,(long)(sizeof(struct TrackingLockData)));
X#endif
X			UnLock(lock);
X			return;
X		}
X	}
X	fprintf(stderr,"Freed lock %lx that hadn't been allocated at file %s line %d\n",lock,file,line);
X}
X
XReportUnfreedLocks()
X{
X	struct TrackingLockData *lp = TrackingLockList, *freep;
X
X	while (lp != NULL)
X	{
X		if (!(lp->my_flags & FREED_IT))
X		{
X			if (lp->my_flags & CREATED_BY_DUPLOCK)
X			{
X				fprintf(stderr,"UnLock was never called for lock %lx,\n\It was created by DupLock at file %s line %d\n",lp->lock,lp->file,lp->line);
X			}
X			else
X			{
X				fprintf(stderr,"UnLock was never called for lock %lx,\n\It was created by a Lock of %s\nat file %s line %d\n",lp->lock,lp->name,lp->file,lp->line);
X			}
X		}
X		if (lp->name != NULL)
X			FreeMem(lp->name,(long)(strlen(lp->name)+1));
X		freep = lp;
X		lp = lp->next;
X		FreeMem(freep,(long)sizeof(struct TrackingLockData));
X	}
X	printf("Total tracked Lock and DupLock calls %ld, UnLock calls %ld\n",TrackerLockCount,TrackerUnLockCount);
X}
X
XTrackerExitReport()
X{
X	ReportUnfreedMemory();
X	ReportUnfreedLocks();
X	ReportUnfreedSignals();
X}
//END_OF_FILE
echo "End of archive."
# end of archive.
exit 0
-- 
-- uunet!sugar!karl  | "We've been following your progress with considerable 
-- karl@sugar.uu.net |  interest, not to say contempt."  -- Zaphod Beeblebrox IV
-- Usenet BBS (713) 438-5018