[comp.sources.misc] maltrace -- trace un-free

schwartz@uw-taos.UUCP (Michael F. Schwartz) (07/10/87)

[I have a malloc package of my own which lets you call _malldmp() to dump
the malloc/free lists, _mallchk() to insure the list pointers are valid,
and set a flag to print all calls to malloc() or free().  Send mail for
info.  ++bsa]

- Michael Schwartz
  University of Washington Computer Science Department
  Seattle, Washington, USA
  schwartz@cs.washington.edu  (ARPANET)
  schwartz%cs.washington.edu@relay.cs.net  (CSNET)
  ...{allegra,caip,ihnp4,nike}!uw-beaver!schwartz  (UUCP)

#!/bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #!/bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	btree.c
#	btree.h
#	makefile
#	maltrace.c
#	test.c
# This archive created: Sun Apr  5 23:28:49 1987
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README'" '(6452 characters)'
if test -f 'README'
then
	echo shar: over-writing existing file "'README'"
fi
sed 's/^X//' << \SHAR_EOF > 'README'
X			  Malloc Leak Trace Package
X
X			     by Michael Schwartz
X	     University of Washington Computer Science Department
X			   Seattle, Washington, USA
X		    schwartz@cs.washington.edu  (ARPANET)
X	       schwartz%cs.washington.edu@relay.cs.net  (CSNET)
X	    ...{allegra,caip,ihnp4,nike}!uw-beaver!schwartz  (UUCP)
X
X				  April 1987
X
X
X
X
X
X
X
X1. Description
X
XThis package is designed to help trace dynamic memory allocation leaks.
XIt works by providing the standard malloc/free/realloc interface, but at
Xthe same time keeping track of all malloc'd buffers, including their size,
Xorder of call, and address.  That way, at any point during the execution of
Xyour program, you can see what malloc'd buffers haven't yet been freed.
XIt is particularly useful when your program performs many allocations
Xbefore reaching some steady state, and hence you want to ignore
Xthe initial allocations and concentrate on where steady-state
Xleaks occur.
X
XThe idea is that you have some code (usually a server) that looks as follows:
X	initialization code;
X	do {
X		...
X	} while (1); /* main loop */
X
XThere might be some dynamic allocation during the initialization,
Xbut this isn't where the memory leak is, since it's a one-shot allocation
X(i.e., at worst the initialization wastes some memory, but doesn't
Xcontinually leak it).  There might also be some dynamic allocation in
Xthe first few iterations of the main loop, until a "steady state" is
Xreached (e.g., until some cache gets filled).  In both cases (initialization
Xand pre-steady state iterations), there may be many allocation calls, but
Xyou don't really want to look at them; rather, you want to look at what
Xmemory isn't being free'd once steady state has been reached.  This
Xpackage helps you to see the state of memory allocation after steady
Xstate has been reached.
X
XBug reports and suggestions for improvements are welcome.
X
X
X
X
X
X
X
X2. Instructions
X
XTo use this package, take your favorite malloc/free/realloc code,
Xand change the routine names as follows:
X
X	malloc -> mmalloc
X	free -> ffree
X	realloc -> rrealloc
X
XYou'll probably also need to add the following line to the beginning of
Xyour malloc.c:
X
X	char *malloc();
X
X(because realloc still calls malloc, but malloc is no longer defined in
Xthat file).  Then link the program to be leak-traced with maltrace.o,
Xbtree.o, and (your modified) malloc.o.  I would have included my malloc.c,
Xbut it's the copyrighted BSD 4.3 code, and besides, there are plenty
Xof public domain malloc's available (e.g., in volume 6 of mod.sources).
X
XTo trace a leak, take the example program skeleton, and augment it as
Xfollows:
X	extern int MalTraceEnbld;
X	extern int MalBrkNum;
X	initialization code;
X	do {
X		...
X		if ( steady state reached)
X			MalTraceEnbld = 1;
X		...
X		at end of first steady-state cycle:
X			PMal(-10);	/* print last 10 (say) malloc's
X					   that haven't yet been free'd */
X	} while (1); /* main loop */
X
XThen compile the program with -g, and run it.  At the end of the first
Xcycle, PMal will print a list of the last 10 malloc's that haven't yet been
Xfreed.  (PMal(n) will print the first n entries if n > 0, the last -n
Xentries if n < 0, and all entries if n == 0).  Note the sequence number of
Xone of these mallocs, and then go into dbx on the program, and put a
Xbreakpoint somewhere in the initialization code, and run the program.  When
Xyou hit the breakpoint, use dbx to set MalBrkNum to the number of the
Xmalloc you just noticed, and set a break in MalBreak.  Then, continue the
Xprogram.  When the malloc call in question is reached, MalBreak will get
Xcalled, breaking, and giving you a chance to examine the state of the
Xprogram at the time of this (potentially leaking) malloc call.  In case
Xthis call is still within the steady-state setup (it is sometimes difficult
Xto find where the setup ends), you can use dbx to call NextMal, to set
XMakBrkNum to be the next traced malloc call.
X
X
X
X
X
X
X
X2.1 Usage Details
X
XThis technique is not applicable to situations where the steady state
Xallocation behavior (i.e., order and size of malloc requests) exhibits
Xvariation, e.g., due to pseudo-randomization or interaction with other
Xprocesses via non-deterministically ordered message exchanges.  In such
Xsituations you can sometimes inhibit the variation during debugging (e.g.,
Xby forcing interactions to occur in the same order each time).
XAlternatively, you can use dbx to set MalBreakSize to be the size of the
Xmalloc request at which to have MalBreak called, to reach a breakpoint
X(similar to the MalBrkNum scheme described above).  This can be useful when
Xthe order of mallocs isn't fixed, but a particular size keeps showing up in
Xthe list of malloc's that haven't yet been free'd.
X
XThere is also a routine called UntracedFree that gets called when a free
Xcall is made on an address that was not malloc'd with tracing enabled
X(again, this routine is present to allow one to set dbx breakpoints for
Xthis event).  This could either indicate a free call on an address that
Xisn't malloc'd (a bug) or a free call on an address that was malloc'd with
Xtracing disabled.  You can determine if it was of the former nature by
Xgoing through the standard malloc code.  For example, in the BSD code, you
Xcan set the switches -DDEBUG and -DRCHECK to check for this and other
Xtypes of bugs.  Alternatively, you can enable tracing from the very
Xbeginning of your program, and then any time UntracedFree gets called, it
Xindicates a free call on an addresss that isn't malloc'd.
X
X
X
X
X
X
X
X3. Interactive Demo
X
XYou can try out this package interactively by making the program 'test'.
XNote that if you tell it to free some memory that was not malloc'd (with
XMalTraceEnbld = 1), it will give you a warning and then try to free the
Xaddress anyway (for the reasons explained earlier).  This may or may not
Xcause malloc/free to get into a bad state; in BSD malloc this can cause a
Xcore dump, for instance.
X
X
X
X
X
X
X
X4. Acknoledgements, History
X
XThanks to Richard Hellier from the University of Kent at Canterbury
X(rlh@ukc.UUCP) for the btree package (which I modified slightly for the
Xcurrent package).  I probably could have implemented my trace package more
Xefficiently than it works currently (e.g., by incorporating the linked-list
Xand btree nodes into the malloc header nodes), but I was more into hacking
Xsomething together quickly that would solve my problems than making
Xefficient code.  Besides, this code doesn't need to be efficient, since
Xit's only plugged in during debugging.
SHAR_EOF
if test 6452 -ne "`wc -c 'README'`"
then
	echo shar: error transmitting "'README'" '(should have been 6452 characters)'
fi
echo shar: extracting "'btree.c'" '(14571 characters)'
if test -f 'btree.c'
then
	echo shar: over-writing existing file "'btree.c'"
fi
sed 's/^X//' << \SHAR_EOF > 'btree.c'
X/***
X
X* module name:
X	btree.c
X* function:
X	Provide a library of routines for creating and manipulating
X	B-Trees.  The "order" of the B-Tree is controlled by a manifest
X	constant.
X
X	This module runs stand-alone with a dummy main program
X	if the symbol STAND_ALONE is defined.
X* interface routines:
X	BTREE
X	Insert(dtm, tree)	Insert DATUM dtm into B-tree "tree",
X				returning a reference to the updated
X				tree.
X	BTREE
X	Delete(key, tree)	Remove the entry associated with "key"
X				from the B-Tree.  Returns a reference
X				to the updated tree.
X	
X	DATUM *
X	Search(key, tree)	Look for key "key" in B-tree "tree".
X				Return a reference to the matching
X				DATUM if found, else NULL.
X
X	Apply(t, func)		Applies function "func" to every cell
X				in the B-Tree -- uses an inorder
X				traversal.
X* libraries used:
X	standard
X* compile time parameters:
X	cc -O -c btree.c
X* history:
X	Richard Hellier, University of Kent at Canterbury,
X	working from "Algorithms+Data Structures = Programs"
X	by N.Wirth -- also, "Data Structures and Program Design" by B.Kruse
X	Pub. Prentice-Hall.
X
X	Modified for use in dynamic memory allocation leak trace tool
X	by Mike Schwartz, 3-20-87.  We call mmalloc and ffree instead of
X	malloc and free here, since malloc and free call this btree package
X	in the dynamic memory allocation leak detection package.
X
X***/
X
X#include "btree.h"
X
XDATUM	NullDatum = {
X	(char *) NULL,
X};
X
Xstatic BTREE		NewNode;
X
X
X/*
X**  ERROR -- Print an error message
X**
X**	Write the error text to the
X**	standard error stream.
X**
X**	Parameters:
X**		fmt       --  Printf-style format string
X**		arg[1-3]  --  Arguments as needed.
X**
X**	Returns:
X**		None.
X**
X*/
X
X/* ARGSUSED */
X
XError(fmt, arg1, arg2, arg3)
Xchar	*fmt;{
X	fprintf(stderr, fmt, arg1, arg2, arg3);
X}
X
X/*
X**  KEYCMP -- Compare two key values
X**
X**	Like "strcmp" but use key
X**	values rather than strings.
X**
X**	Parameters:
X**		key1  --  First key,
X**		key2  --  Second key.
X**
X**	Returns:
X**		-1 if key1 < key2;
X**		0  if key1 = key2;
X**		1  if key1 > key2;
X**
X*/
X
XKeyCmp(key1, key2)
XKEY	key1,
X	key2;{
X
X	return key1 < key2 ? -1 : key1 == key2 ? 0 : 1;
X}
X
X/*
X**  SHOWDATUM -- Display a datum
X**
X**	Atomic routine used to display
X**	the contents of a datum.
X**
X**	Parameters:
X**		datum  --  Datum to print.
X**
X**	Returns:
X**		None.
X**
X*/
X
XShowDatum(dtm)
XDATUM	dtm;{
X	printf("\tkey x%x: callnum %d, size %d\n", dtm.key, dtm.inf.MalCallNum,
X		dtm.inf.MalSize);
X}
X
X/*
X**  MKNODE -- Make a new B-tree node
X**
X**	Allocate storage for a new B-tree node
X**	and copy in some pointers.
X**
X**	Parameters:
X**		k1  --  First key value,
X**		p0  --  First ptr,
X**		p1  --  Second ptr.
X**
X**	Returns:
X**		Reference to the new node.
X**
X*/
X
XBTREE
XMkNode(dtm, p0, p1)
XDATUM	dtm;
XBTREE	p0,
X	p1;{
X	char	*mmalloc();
X	BTREE	t;
X
X	t = (BTREE) mmalloc(sizeof(NODE));
X
X	t -> t_ptr [0] = p0;
X	t -> t_ptr [1] = p1;
X	t -> t_dat [0] = dtm;
X	t -> t_active  = 1;
X
X	return t;
X}
X/*
X**  DISPOSE -- Dispose of a tree node
X**
X**	Return the storage occupied by the
X**	tree node to the pool.
X**
X**	Parameters:
X**		t  --  Ptr to the tree node.
X**
X**	Returns:
X**		None.
X**
X*/
X
XDispose(t)
XBTREE	t;{
X	ffree((char *) t);
X}
X
X/*
X**  INSINNODE -- Add a key to a node
X**
X**	Add a key value into a
X**	B-tree node.  Splitting of
X**	nodes is handled elsewhere.
X**
X**	Parameters:
X**		t   --  Ptr to the node,
X**		key --  Key value to enter,
X**		ptr --  Link to subordinate node.
X**
X**	Returns:
X**		None.
X**
X*/
X
XInsInNode(t, d, ptr)
XBTREE	t,
X	ptr;
XDATUM	d;{
X	int	i;
X
X	for ( i = t -> t_active; i > 0 && KeyCmp(d . key, t -> t_dat [i-1] . key) < 0; i--) {
X		t -> t_dat [i]     = t -> t_dat [i - 1];
X		t -> t_ptr [i + 1] = t -> t_ptr [i];
X	}
X
X	t -> t_active++;
X	t -> t_dat [i]   = d;
X	t -> t_ptr [i+1] = ptr;
X}
X
X/*
X**  INTERNALINSERT -- Key insert routine proper
X**
X**	The business end of the key insertion
X**	routine.
X**
X**	Parameters:
X**		key  --  Key to insert,
X**		t    --  Tree to use.
X**
X**	Returns:
X**		Value of the key inserted.
X**
X*/
X
XDATUM
XInternalInsert(dtm, t)
XDATUM	dtm;
XBTREE	t;{
X	int	i,
X		j;
X	DATUM	ins;
X	BTREE	tmp;
X
X	if (t == NULL) {
X		NewNode = NULL;
X		return dtm;
X	} else {
X		for (i = 0; i < t -> t_active && KeyCmp(t -> t_dat [i] . key, dtm . key) < 0; ++i)
X			;		/* NULL BODY */
X		if (i < t -> t_active && KeyCmp(dtm . key, t -> t_dat [i] . key) == 0)
X			fprintf(stderr, "Already had it!\n");
X		else {
X			ins = InternalInsert(dtm, t -> t_ptr [i]);
X
X			if (KeyCmp(ins . key, NullDatum . key) != 0)
X				if (t -> t_active < 2 * M)
X					InsInNode(t, ins, NewNode);
X				else {
X					if (i <= M) {
X						tmp = MkNode(t -> t_dat [2 * M - 1], (BTREE) NULL, t -> t_ptr [2 * M]);
X						t -> t_active--;
X						InsInNode(t, ins, NewNode);
X					} else
X						tmp = MkNode(ins, (BTREE) NULL, NewNode);
X					/*
X					 *	Move keys and pointers ...
X					 */
X
X					for (j = M + 2; j <= 2 * M; ++j)
X						InsInNode(tmp, t -> t_dat [j-1], t -> t_ptr [j]);
X
X					t -> t_active = M;
X					tmp -> t_ptr [0] = t -> t_ptr [M+1];
X					NewNode = tmp;
X
X					return t -> t_dat [M];
X				}
X		}
X		return NullDatum;
X	}
X}
X/*
X**  INSERT -- Put a key into the B-tree
X**
X**	Enter the given key into the
X**	B-tree.
X**
X**	Parameters:
X**		key  --  Key value to enter.
X**
X**	Returns:
X**		Reference to the new B-tree.
X**
X*/
X
XBTREE
XInsert(dtm, t)
XDATUM	dtm;
XBTREE	t;{
X	DATUM	ins;
X
X	ins = InternalInsert(dtm, t);
X
X	/*
X	 *	Did the root grow ?
X	 */
X
X	return KeyCmp(ins . key, NullDatum . key) != 0 ? MkNode(ins, t, NewNode) : t;
X}
X/*
X**  DELETE -- Remove a key from a B-tree
X**
X**	Remove the data associated with a
X**	given key from a B-tree.
X**
X**	Parameters:
X**		key  --  Key to use,
X**		t    --  B-tree to update.
X**
X**	Returns:
X**		Reference to the updated B-tree.
X**
X**	Notes:
X**		Real work is done by RealDelete() q.v.
X**
X*/
X
Xstatic int	hadit = FALSE;
X
XBTREE
XDelete(key, t)
XKEY	key;
XBTREE	t;{
X	BTREE	savet;
X
X	RealDelete(key, t);
X
X	if (!hadit) {
X		Error("No such key\n");
X		return NULL;
X	} else if (t -> t_active == 0) {	/* Root is now empty */
X		savet = t -> t_ptr [0];
X		Dispose(t);
X		return savet;
X	} else
X		return t;
X}
X
X/*
X**  SEARCHNODE -- Find a key in a node
X**
X**	Look for a key in a single B-tree
X**	node.
X**
X**	Parameters:
X**		key  --  Key to look for,
X**		t    --  Ptr to B-tree node.
X**
X**	Returns:
X**		Index of matching key.
X**
X*/
X
Xint
XSearchNode(key, t)
XKEY	key;
XBTREE	t;{
X	int	i;
X
X	if (KeyCmp(key, t -> t_dat [0] . key) < 0) {
X		hadit = FALSE;
X		return 0;
X	} else {
X		for (i = t -> t_active; KeyCmp(key, t -> t_dat [i-1] . key) < 0 && i > 1; --i)
X			;		/* NULL BODY */
X		hadit = (KeyCmp(key, t -> t_dat [i-1] . key) == 0);
X
X		return i;
X	}
X}
X/*
X**  REALDELETE -- Remove a key from a tree
X**
X**	The business end of the Delete() function.
X**
X**	Parameters:
X**		key  --  Key to use,
X**		t    --  Tree to update.
X**
X**	Returns:
X**		None.
X**
X*/
X
XRealDelete(key, t)
XKEY	key;
XBTREE	t;{
X	int	k;
X
X	if (t == NULL)
X		hadit = FALSE;
X	else {
X		k = SearchNode(key, t);
X
X		if (hadit) {
X			if (t -> t_ptr [k-1] == NULL)		/* A leaf */
X				Remove(t, k);
X			else {
X				Succ(t, k);
X				RealDelete(t -> t_dat [k-1] . key, t -> t_ptr [k]);
X				if (!hadit)
X					Error("Hmm ???");
X			}
X		} else {
X			RealDelete(key, t -> t_ptr [k]);
X
X			if (t -> t_ptr [k] != NULL && t -> t_ptr [k] -> t_active < M)
X				Restore(t, k);
X		}
X	}
X}
X
X/*
X**  REMOVE -- Remove a single datum
X**
X**	Remove a datum from a B-tree node
X**	by shuffling down the pointers and
X**	data above it.
X**
X**	Parameters:
X**		t   --  Ptr to the B-tree node,
X**		pos --  Index of the key to be removed.
X**
X**	Returns:
X**		None.
X**
X*/
X
XRemove(t, pos)
XBTREE	t;
Xint	pos;{
X	int	i;
X
X	for (i = pos + 1; i <= t -> t_active; ++i) {
X		t -> t_dat [i-2] = t -> t_dat [i-1];
X		t -> t_ptr [i-1] = t -> t_ptr [i];
X	}
X	t -> t_active--;
X}
X
X/*
X**  SUCC -- Replace a key by its successor
X**
X**	Using the natural ordering, replace
X**	a key with its successor.
X**
X**	Parameters:
X**		t   --  Ptr to a B-tree node,
X**		pos --  Index of the key to be succ'ed.
X**
X**	Returns:
X**		None.
X**
X**	Notes:
X**		This routine relies on the fact
X**		that if the key to be deleted is
X**		not in a leaf node, then its
X**		immediate successor will be.
X*/
X
XSucc(t, pos)
XBTREE	t;
Xint	pos;{
X	BTREE	tsucc;
X
X	/*
X	 *	Using the branch *above* the key
X	 *	chain down to leftmost node below
X	 *	it.
X	 */
X
X	for (tsucc = t -> t_ptr [pos]; tsucc -> t_ptr [0] != NULL; tsucc = tsucc -> t_ptr [0])
X		;		/* NULL BODY */
X
X	t -> t_dat [pos-1] = tsucc -> t_dat [0];
X}
X/*
X**  RESTORE -- Restore balance to a B-tree
X**
X**	After removing an item from a B-tree
X**	we must restore the balance.
X**
X**	Parameters:
X**		t   --  Ptr to the B-tree node,
X**		pos --  Index of the out-of-balance point.
X**
X**	Returns:
X**		None.
X**
X*/
X
XRestore(t, pos)
XBTREE	t;
Xint	pos;{
X	if (pos > 0) {
X		if (t -> t_ptr [pos - 1] -> t_active > M)
X			MoveRight(t, pos);
X		else
X			Combine(t, pos);
X	} else {
X		if (t -> t_ptr [1] -> t_active > M)
X			MoveLeft(t, 1);
X		else
X			Combine(t, 1);
X	}
X}
X
X/*
X**  MOVERIGHT -- Shuffle keys up
X**
X**	Make room for a key in a B-tree
X**	node.
X**
X**	Parameters:
X**		t   --  Ptr to the B-tree node,
X**		pos --  Index of the first key
X**			to be moved.
X**
X**	Returns:
X**		None.
X**
X*/
X
XMoveRight(t, pos)
XBTREE	t;
Xint	pos;{
X	int	i;
X	BTREE	tpos;
X
X	tpos = t -> t_ptr [pos];
X
X	for (i = tpos -> t_active; i >= 1; i--) {
X		tpos -> t_dat [i]   = tpos -> t_dat [i-1];
X		tpos -> t_ptr [i+1] = tpos -> t_ptr [i];
X	}
X
X	tpos -> t_ptr [1] = tpos -> t_ptr [0];
X	tpos -> t_active++;
X	tpos -> t_dat [0] = t -> t_dat [pos-1];
X
X	tpos = t -> t_ptr [pos-1];
X
X	t -> t_dat [pos-1]            = tpos -> t_dat [tpos -> t_active-1];
X	t -> t_ptr [pos] -> t_ptr [0] = tpos -> t_ptr [tpos -> t_active];
X	tpos -> t_active--;
X}
X/*
X**  MOVELEFT -- Shuffle keys down
X**
X**	Shuffle keys down after a removal
X**	from a B-tree node.
X**
X**	Parameters:
X**		t   --  Ptr to the B-tree node,
X**		pos --  Index of the first key
X**			to be moved.
X**
X**	Returns:
X**		None.
X**
X*/
X
XMoveLeft(t, pos)
XBTREE	t;
Xint	pos;{
X	int	i;
X	BTREE	tpos;
X
X	tpos = t -> t_ptr [pos-1];
X
X	tpos -> t_active++;
X	tpos -> t_dat [tpos -> t_active-1] = t -> t_dat [pos-1];
X	tpos -> t_ptr [tpos -> t_active]   = t -> t_ptr [pos] -> t_ptr [0];
X
X
X	tpos = t -> t_ptr [pos];
X
X	t -> t_dat [pos-1]  = tpos -> t_dat [0];
X	tpos -> t_ptr [0]   = tpos -> t_ptr [1];
X	tpos -> t_active--;
X
X	for (i = 1; i <= tpos -> t_active; ++i) {
X		tpos -> t_dat [i-1] = tpos -> t_dat [i];
X		tpos -> t_ptr [i]   = tpos -> t_ptr [i+1];
X	}
X}
X/*
X**  COMBINE -- Combine two nodes.
X**
X**	Coalesce two nodes of a
X**	B-tree when they have too few nodes.
X**
X**	Parameters:
X**		t   --  Ptr to B-tree node,
X**		pos --  Index of the split point.
X**
X**	Returns:
X**		None.
X**
X*/
X
XCombine(t, k)
XBTREE	t;
Xint	k;{
X	int	i;
X	BTREE	p,
X		q;
X
X	p = t -> t_ptr [k-1];
X	q = t -> t_ptr [k];
X
X	p -> t_active++;
X	p -> t_dat [p -> t_active-1] = t -> t_dat [k-1];
X	p -> t_ptr [p -> t_active]   = q -> t_ptr [0];
X
X	for (i = 1; i <= q -> t_active; ++i) {
X		p -> t_active++;
X		p -> t_dat [p -> t_active-1] = q -> t_dat [i-1];
X		p -> t_ptr [p -> t_active]   = q -> t_ptr [i];
X	}
X
X	for (i = k; i <= t -> t_active - 1; ++i) {
X		t -> t_dat [i-1] = t -> t_dat [i];
X		t -> t_ptr [i]   = t -> t_ptr [i+1];
X	}
X	t -> t_active--;
X
X	Dispose(q);
X}
X
X/*
X**  SEARCH -- Fetch a key from a tree
X**
X**	Look for a key in a tree.
X**
X**	Parameters:
X**		key  --  Key value to look for,
X**		t    --  Tree to look in.
X**
X**	Returns:
X**		None.
X**
X*/
X
XDATUM *
XSearch(key, t)
XKEY	key;
XBTREE	t;{
X	int	i;
X
X	while (t != NULL) {
X		for (i = 0; i < t -> t_active && KeyCmp(t -> t_dat [i] . key, key) < 0; ++i)
X			;		/* NULL BODY */
X
X		if (i < t -> t_active && KeyCmp(key, t -> t_dat [i] . key) == 0) {
X			/* FOUND IT */
X			return &t -> t_dat [i];
X		}
X		t = t -> t_ptr [i];
X	}
X	return NULL;
X}
X/*
X**  SHOWTREE -- Display a tree
X**
X**	Print the contents of
X**	a tree, showing the level
X**	of each node.
X**
X**	Parameters:
X**		t     --  Tree to print,
X**		level --  Depth in tree.
X**
X**	Returns:
X**		None.
X**
X*/
X
XShowTree(t, level)
XBTREE	t;
Xint	level;{
X	int	i;
X
X	if (t != NULL) {
X		for (i = 0; i < level; ++i)
X			putchar(' ');
X		for (i = 0; i < t -> t_active; ++i)
X			ShowDatum(t -> t_dat [i]);
X		putchar('\n');
X		for (i = 0; i <= t -> t_active; ++i)
X			ShowTree(t -> t_ptr [i], 1 + level);
X	}
X}
X
X/*
X**  APPLY -- Apply a function to a b-tree
X**
X**	Traverse a B-tree, applying the function
X**	to each key we find.
X**
X**	Parameters:
X**		t    --  The tree to display,
X**		func --  The function to apply.
X**
X**	Returns:
X**		None.
X**
X*/
X
XApply(t, func)
XBTREE	t;
Xint	(*func)();{
X	int	i;
X
X	if (t != NULL) {
X		for (i = 0; i < t -> t_active; ++i) {
X			Apply(t -> t_ptr [i], func);
X			(*func) (t -> t_dat [i]);
X		}
X		Apply(t -> t_ptr [t -> t_active], func);
X	}
X}
X#ifdef STAND_ALONE
Xmain(){
X	BTREE	t,
X		oldt;
X	DATUM	d,
X		*dp;
X	KEY	k;
X	char	buf [BUFSIZ];
X
X	t = NULL;
X
X	printf("Command: "); fflush(stdout);
X	while (fgets(buf, sizeof buf, stdin) != NULL) {
X		buf [strlen(buf) - 1] = '\0';
X
X		/*
X		 *	Get the command
X		 */
X
X		switch (buf [0]) {
X			default:		/* Error case */
X				fprintf(stderr, "I, D, L, P or S please!\n");
X				break;
X
X			case '0':
X			case '1':
X			case '2':
X			case '3':
X			case '4':
X			case '5':
X			case '6':
X			case '7':
X			case '8':
X			case '9':
X				sscanf(buf, "%d", &d . key);
X				t = Insert(d, t);
X				break;
X
X			case 'S':		/* Set up default tree */
X				t = NULL;
X				d . key = 20;
X				t = Insert(d, t);
X				d . key = 10;
X				t = Insert(d, t);
X				d . key = 15;
X				t = Insert(d, t);
X				d . key = 30;
X				t = Insert(d, t);
X				d . key = 40;
X				t = Insert(d, t);
X				d . key = 7;
X				t = Insert(d, t);
X				d . key = 18;
X				t = Insert(d, t);
X				d . key = 22;
X				t = Insert(d, t);
X				d . key = 26;
X				t = Insert(d, t);
X				d . key = 5;
X				t = Insert(d, t);
X				d . key = 35;
X				t = Insert(d, t);
X				d . key = 13;
X				t = Insert(d, t);
X				d . key = 27;
X				t = Insert(d, t);
X				d . key = 32;
X				t = Insert(d, t);
X				d . key = 42;
X				t = Insert(d, t);
X				d . key = 46;
X				t = Insert(d, t);
X				d . key = 24;
X				t = Insert(d, t);
X				d . key = 45;
X				t = Insert(d, t);
X				d . key = 25;
X				t = Insert(d, t);
X				ShowTree(t, 0);
X				break;
X
X			case 'I':		/* Insert a key */
X				sscanf(buf+1, "%d", &d . key);
X				t = Insert(d, t);
X				break;
X
X			case 'D':		/* Delete a key */
X				sscanf(buf+1, "%d", &d . key);
X				oldt = t;
X				t = Delete(d . key, t);
X				if (t == NULL)
X					t = oldt;
X				break;
X
X			case 'L':		/* Lookup a key */
X				sscanf(buf+1, "%d", &d . key);
X				dp = Search(d . key, t);
X				printf("%s\n",
X					dp != NULL ? "Found it" : "No such key");
X				break;
X
X			case 'P':		/* Show the tree */
X				ShowTree(t, 0);
X				break;
X		}
X		printf("Command: "); fflush(stdout);
X	}
X	Apply(t, ShowDatum);
X	exit(0);
X}
X#endif STAND_ALONE
SHAR_EOF
if test 14571 -ne "`wc -c 'btree.c'`"
then
	echo shar: error transmitting "'btree.c'" '(should have been 14571 characters)'
fi
echo shar: extracting "'btree.h'" '(801 characters)'
if test -f 'btree.h'
then
	echo shar: over-writing existing file "'btree.h'"
fi
sed 's/^X//' << \SHAR_EOF > 'btree.h'
X#include <stdio.h>
X
X
X		/*
X		 *	Global structures and definitions
X		 */
X
X
X#define TRUE	1
X#define FALSE	0
X
X		/*
X		 *	Declare the type of the KEY
X		 */
X
Xtypedef char * KEY;	/* Key = addr returned from malloc */
X
X
X		/*
X		 *	... ditto for the INFO field
X		 */
X
Xtypedef struct {
X	int MalCallNum;	/* malloc call number */
X	int MalSize;	/* malloc'd size */
X	char * MalAddr;	/* malloc'd address */
X	struct list *lp;
X} INFO;
X
Xtypedef struct Datum {
X	KEY	key;
X	INFO	inf;
X} DATUM;
X
X		/*
X		 *	This is the definition of
X		 *	the nodes of the B-Tree
X		 */
X
X#define	M	2
Xtypedef struct btnode {
X	int			t_active;		/* # of active keys */
X	DATUM			t_dat  [2 * M];		/* Keys  + Data */
X	struct btnode		*t_ptr [2 * M + 1];	/* Subtree ptrs */
X} NODE, *BTREE;
X
XBTREE Insert();
X
XBTREE Delete();
X
XDATUM *Search();
X
Xint Apply();
SHAR_EOF
if test 801 -ne "`wc -c 'btree.h'`"
then
	echo shar: error transmitting "'btree.h'" '(should have been 801 characters)'
fi
echo shar: extracting "'makefile'" '(177 characters)'
if test -f 'makefile'
then
	echo shar: over-writing existing file "'makefile'"
fi
sed 's/^X//' << \SHAR_EOF > 'makefile'
XCFLAGS = -g
X
Xmaltrace.a:	maltrace.o malloc.o btree.o
X	ar rvu maltrace.a maltrace.o malloc.o btree.o
X	ranlib maltrace.a
X
Xtest:	test.o maltrace.a
X	cc -g -o test test.o maltrace.a
SHAR_EOF
if test 177 -ne "`wc -c 'makefile'`"
then
	echo shar: error transmitting "'makefile'" '(should have been 177 characters)'
fi
echo shar: extracting "'maltrace.c'" '(5349 characters)'
if test -f 'maltrace.c'
then
	echo shar: over-writing existing file "'maltrace.c'"
fi
sed 's/^X//' << \SHAR_EOF > 'maltrace.c'
X/* Code for dynamic memory allocation leak trace tool.  By Mike
X   Schwartz, 3-20-87. */
X
X#include "btree.h"
X
XBTREE MalBtree = NULL;
Xint MalTraceEnbld = 0; /* Flag to enable/disable tracing of malloc/free
X                           calls.  Need to turn it off sometime (e.g.,
X                           during printf calls, since printf (indirectly)
X                           calls malloc/free, and during
X                           uninteresting/debugged initialization code) */
X
Xint MallocCallCounter = 0;
Xint NumPendingMallocs = 0;
Xint MalBrkNum = -1; /* Malloc call number at which to call MalBreak routine */
Xint MalBrkSize = -1; /* Malloc size at which to call MalBreak routine */
Xstruct list {
X	KEY MalAddr;
X	struct list *prev;
X	struct list *next;
X};
Xstruct list *ListHead = NULL, *ListTail = NULL;
X
Xchar *
Xmalloc(nbytes)
X	unsigned nbytes;
X{
X	char *mmalloc(), *MalAddr;
X	struct list *lp;
X	DATUM datum, *dp;
X
X	MalAddr = mmalloc(nbytes);
X	if (MalTraceEnbld == 1) {	/* printf calls malloc/free */
X		datum.key = MalAddr;
X		datum.inf.MalCallNum = ++MallocCallCounter;
X		datum.inf.MalSize = nbytes;
X		datum.inf.MalAddr = MalAddr;
X		MalBtree = Insert(datum, MalBtree);
X		NumPendingMallocs++;
X		dp = Search(MalAddr, MalBtree);
X		/* Insert in ordered doubly linked list of mallocs */
X		lp = (struct list *) mmalloc(sizeof(struct list));
X		lp->MalAddr = MalAddr;
X		(dp->inf).lp = lp;
X		if (ListHead == NULL) {
X			ListHead = lp;
X			lp->prev = NULL;
X		} else {
X			ListTail->next = lp;
X			lp->prev = ListTail;
X		}
X		lp->next = NULL;
X		ListTail = lp;
X#ifdef DEBUG_MAL_TRACE
X		MalTraceEnbld = 0;	/* printf calls malloc/free */
X		printf("\tmalloc(%d)=x%x\n", nbytes, MalAddr);
X		fflush(stdout);
X		MalTraceEnbld = 1;
X#endif DEBUG_MAL_TRACE
X		if (MallocCallCounter == MalBrkNum || MalBrkSize == nbytes)
X			MalBreak();	/* So we can hit dbx(1) breakpoint */
X	}
X	return(MalAddr);
X}
X
Xfree(cp)
X	char *cp;
X{   
X	struct list *lp;
X	DATUM datum, *dp;
X
X	if (MalTraceEnbld == 1) {
X		dp = Search(cp, MalBtree);
X		if (dp == NULL) {
X			MalTraceEnbld = 0;	/* printf calls malloc/free */
X			printf("\tfree(x%x): not malloc'd with MalTraceEnbld\n", cp);
X			MalTraceEnbld = 1;
X			UntracedFree();
X			ffree(lp);
X			return;
X		}
X		NumPendingMallocs--;
X
X		lp = (dp->inf).lp;
X
X		/* Delete from ordered doubly linked list of mallocs */
X		if (ListHead == lp) {
X			ListHead = ListHead->next;
X		} else {
X			(lp->prev)->next = lp->next;
X		}
X		if (ListTail == lp) {
X			ListTail = lp->prev;
X		} else {
X			(lp->next)->prev = lp->prev;
X		}
X		ffree(lp);
X		MalBtree = Delete(cp, MalBtree);
X
X#ifdef DEBUG_MAL_TRACE
X		MalTraceEnbld = 0;	/* printf calls malloc/free */
X		printf("\tfree(x%x)\n", cp);
X		fflush(stdout);
X		MalTraceEnbld = 1;
X#endif DEBUG_MAL_TRACE
X	}
X	ffree(cp);
X}
X
X/* Don't need to modify btree/linked list structures here, since realloc
X   will call malloc/free if it needs to, which will do the right thing. */
Xchar *
Xrealloc(cp, nbytes)
X	char *cp; 
X	unsigned nbytes;
X{   
X	char *rrealloc(), *tmp;
X
X	tmp=rrealloc(cp, nbytes);
X#ifdef DEBUG_MAL_TRACE
X	if (MalTraceEnbld) {
X		MalTraceEnbld = 0;	/* printf calls malloc/free */
X		printf("\trealloc(x%x, %d)=x%x\n", cp, nbytes, tmp);
X		MalTraceEnbld = 1;
X	}
X#endif DEBUG_MAL_TRACE
X	return(tmp);
X}
X
X/* If n > 0, print (at most) the first n entries of list of pending info
X   (i.e., mallocs which haven't yet been free'd).  If n == 0, print all
X   pending entries.  If n < 0, print (at most) the last -n pending entries */
XPMal(n)
Xint n;
X{
X	struct list *lp, *Start;
X	DATUM datum, *dp;
X	int i;
X	int MalTraceInitialVal = MalTraceEnbld;
X	char *OrdSuffix();
X
X	MalTraceEnbld = 0;	/* puts and printf call malloc/free */
X	if (ListHead == NULL)
X		puts("\tNo pending mallocs");
X	else {
X		Start = ListHead;
X		if (n == 0)
X			printf("\tPending mallocs (%d total):\n",
X				NumPendingMallocs);
X		else if (n == 1)
X			printf("\tFirst pending malloc (of %d):\n",
X				NumPendingMallocs);
X		else if (n > 1)
X			printf("\tFirst %d pending mallocs (of %d):\n",
X				n, NumPendingMallocs);
X		else {
X			if (n == -1)
X				printf("\tLast pending malloc (of %d):\n",
X					NumPendingMallocs);
X			else
X				printf("\tLast %d pending mallocs (of %d):\n",
X					-n, NumPendingMallocs);
X			for (Start = ListTail, i = n + 1;
X			     Start != NULL && i < 0; Start = Start->prev, i++) {
X			}
X			if (Start == NULL)
X				Start = ListHead;
X			n = -n;
X		}
X		for (lp = Start, i = 0; lp != NULL; lp = lp->next, i++) {
X			if (n != 0 && i >= n)
X				break;
X			dp = Search(lp->MalAddr, MalBtree);
X			printf("\t%d%s malloc(%d): x%x\n",
X				(dp->inf).MalCallNum,
X				OrdSuffix((dp->inf).MalCallNum),
X				(dp->inf).MalSize,
X				(dp->inf).MalAddr);
X		}
X	}
X	MalTraceEnbld = MalTraceInitialVal;
X	return;
X}
X
X/* Return a character string suffix for the ordinal number n */
Xchar *
XOrdSuffix(n)
Xint n;
X{
X	if ( (n % 100) > 10 && (n % 100) < 20)
X		return("th");
X	switch (n % 10) {
X		case 0: case 4: case 5: case 6: case 7: case 8: case 9:
X			return("th");
X		case 1:
X			return("st");
X		case 2:
X			return("nd");
X		case 3:
X			return("rd");
X	}
X}
X
X
X
X/* Routines called to allow user to set dbx(1) breakpoints */
X
X/* Breakpoint in suspected leaking malloc call */
XMalBreak()
X{
X}
X
X/* Breakpoint in free call made on data not malloc'd with tracing enabled */
XUntracedFree()
X{
X}
X
X/* Set MalBrkNum to the next malloc call, for the next iteration of tracing */
XNextMal()
X{
X	MalBrkNum = MallocCallCounter + 1;
X}
SHAR_EOF
if test 5349 -ne "`wc -c 'maltrace.c'`"
then
	echo shar: error transmitting "'maltrace.c'" '(should have been 5349 characters)'
fi
echo shar: extracting "'test.c'" '(1898 characters)'
if test -f 'test.c'
then
	echo shar: over-writing existing file "'test.c'"
fi
sed 's/^X//' << \SHAR_EOF > 'test.c'
X/* Test program for dynamic memory allocation leak trace tool.  By Mike
X   Schwartz, 3-20-87. */
X
X#include "btree.h"
X#include <stdio.h>
X
Xmain()
X{
X	int size, n;
X	extern int MalTraceEnbld;
X	char buf[10];
X	char *p, *malloc();
X	extern BTREE MalBtree;
X
X	MalTraceEnbld = 0;	/* Only turn on malloc info printing/checking
X				   for direct calls (not for prints, etc.) */
X	do {
X		fputs("m/f/p/b: ", stdout);
X		gets(buf);
X		switch (buf[0]) {
X			case 'm':	/* interactive malloc */
X				fputs("\t# bytes: ", stdout);
X				scanf("%d", &size);
X				gets(buf);	/* throw away CR */
X				MalTraceEnbld = 1;
X				p = malloc(size);
X				MalTraceEnbld = 0;
X				printf("\tmalloc returned x%x\n", p);
X				break;
X
X			case 'f':	/* interactive free */
X				fputs("\taddr: ", stdout);
X				scanf("%x", &p);
X				gets(buf);	/* throw away CR */
X				MalTraceEnbld = 1;
X				free(p);
X				MalTraceEnbld = 0;
X				break;
X
X                        case 'p':	/* print pending (not yet freed)
X                                           mallocs.  If n > 0, print (at
X                                           most) the first n entries.  If n
X                                           == 0, print all entries.  If n <
X                                           0, print (at most) the last -n
X                                           entries */
X				fputs("\tnumber of mallocs to print: ", stdout);
X				scanf("%d", &n);
X				gets(buf);	/* throw away CR */
X				MalTraceEnbld = 1;
X				PMal(n);
X				fflush(stdout);
X				MalTraceEnbld = 0;
X				break;
X			
X			case 'b':	/* print btree of pending mallocs */
X				puts("btree of pending mallocs:");
X				ShowTree(MalBtree, 0);
X				break;
X
X			default:
X				puts("Invalid command; choose one of:");
X				puts("\tm to malloc");
X				puts("\tf to free");
X				puts("\tp to print pending (not yet freed) mallocs");
X				puts("\t(b to print the btree associated with pending mallocs)");
X				break;
X		}
X	} while(1);
X}
SHAR_EOF
if test 1898 -ne "`wc -c 'test.c'`"
then
	echo shar: error transmitting "'test.c'" '(should have been 1898 characters)'
fi
#	End of shell archive
exit 0