1/*
2 * Copyright (c) 2000-2001, 2004 Sendmail, Inc. and its suppliers.
3 *	All rights reserved.
4 *
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
8 */
9
10#pragma ident	"%Z%%M%	%I%	%E% SMI"
11
12#include <sm/gen.h>
13SM_RCSID("@(#)$Id: heap.c,v 1.51 2004/08/03 20:32:00 ca Exp $")
14
15/*
16**  debugging memory allocation package
17**  See heap.html for documentation.
18*/
19
20#include <string.h>
21
22#include <sm/assert.h>
23#include <sm/debug.h>
24#include <sm/exc.h>
25#include <sm/heap.h>
26#include <sm/io.h>
27#include <sm/signal.h>
28#include <sm/xtrap.h>
29
30/* undef all macro versions of the "functions" so they can be specified here */
31#undef sm_malloc
32#undef sm_malloc_x
33#undef sm_malloc_tagged
34#undef sm_malloc_tagged_x
35#undef sm_free
36#undef sm_free_tagged
37#undef sm_realloc
38#if SM_HEAP_CHECK
39# undef sm_heap_register
40# undef sm_heap_checkptr
41# undef sm_heap_report
42#endif /* SM_HEAP_CHECK */
43
44#if SM_HEAP_CHECK
45SM_DEBUG_T SmHeapCheck = SM_DEBUG_INITIALIZER("sm_check_heap",
46    "@(#)$Debug: sm_check_heap - check sm_malloc, sm_realloc, sm_free calls $");
47# define HEAP_CHECK sm_debug_active(&SmHeapCheck, 1)
48static int	ptrhash __P((void *p));
49#endif /* SM_HEAP_CHECK */
50
51const SM_EXC_TYPE_T SmHeapOutOfMemoryType =
52{
53	SmExcTypeMagic,
54	"F:sm.heap",
55	"",
56	sm_etype_printf,
57	"out of memory",
58};
59
60SM_EXC_T SmHeapOutOfMemory = SM_EXC_INITIALIZER(&SmHeapOutOfMemoryType, NULL);
61
62
63/*
64**  The behaviour of malloc with size==0 is platform dependent (it
65**  says so in the C standard): it can return NULL or non-NULL.  We
66**  don't want sm_malloc_x(0) to raise an exception on some platforms
67**  but not others, so this case requires special handling.  We've got
68**  two choices: "size = 1" or "return NULL". We use the former in the
69**  following.
70**	If we had something like autoconf we could figure out the
71**	behaviour of the platform and either use this hack or just
72**	use size.
73*/
74
75#define MALLOC_SIZE(size)	((size) == 0 ? 1 : (size))
76
77/*
78**  SM_MALLOC_X -- wrapper around malloc(), raises an exception on error.
79**
80**	Parameters:
81**		size -- size of requested memory.
82**
83**	Returns:
84**		Pointer to memory region.
85**
86**	Note:
87**		sm_malloc_x only gets called from source files in which heap
88**		debugging is disabled at compile time.  Otherwise, a call to
89**		sm_malloc_x is macro expanded to a call to sm_malloc_tagged_x.
90**
91**	Exceptions:
92**		F:sm_heap -- out of memory
93*/
94
95void *
96sm_malloc_x(size)
97	size_t size;
98{
99	void *ptr;
100
101	ENTER_CRITICAL();
102	ptr = malloc(MALLOC_SIZE(size));
103	LEAVE_CRITICAL();
104	if (ptr == NULL)
105		sm_exc_raise_x(&SmHeapOutOfMemory);
106	return ptr;
107}
108
109#if !SM_HEAP_CHECK
110
111/*
112**  SM_MALLOC -- wrapper around malloc()
113**
114**	Parameters:
115**		size -- size of requested memory.
116**
117**	Returns:
118**		Pointer to memory region.
119*/
120
121void *
122sm_malloc(size)
123	size_t size;
124{
125	void *ptr;
126
127	ENTER_CRITICAL();
128	ptr = malloc(MALLOC_SIZE(size));
129	LEAVE_CRITICAL();
130	return ptr;
131}
132
133/*
134**  SM_REALLOC -- wrapper for realloc()
135**
136**	Parameters:
137**		ptr -- pointer to old memory area.
138**		size -- size of requested memory.
139**
140**	Returns:
141**		Pointer to new memory area, NULL on failure.
142*/
143
144void *
145sm_realloc(ptr, size)
146	void *ptr;
147	size_t size;
148{
149	void *newptr;
150
151	ENTER_CRITICAL();
152	newptr = realloc(ptr, MALLOC_SIZE(size));
153	LEAVE_CRITICAL();
154	return newptr;
155}
156
157/*
158**  SM_REALLOC_X -- wrapper for realloc()
159**
160**	Parameters:
161**		ptr -- pointer to old memory area.
162**		size -- size of requested memory.
163**
164**	Returns:
165**		Pointer to new memory area.
166**
167**	Exceptions:
168**		F:sm_heap -- out of memory
169*/
170
171void *
172sm_realloc_x(ptr, size)
173	void *ptr;
174	size_t size;
175{
176	void *newptr;
177
178	ENTER_CRITICAL();
179	newptr = realloc(ptr, MALLOC_SIZE(size));
180	LEAVE_CRITICAL();
181	if (newptr == NULL)
182		sm_exc_raise_x(&SmHeapOutOfMemory);
183	return newptr;
184}
185/*
186**  SM_FREE -- wrapper around free()
187**
188**	Parameters:
189**		ptr -- pointer to memory region.
190**
191**	Returns:
192**		none.
193*/
194
195void
196sm_free(ptr)
197	void *ptr;
198{
199	if (ptr == NULL)
200		return;
201	ENTER_CRITICAL();
202	free(ptr);
203	LEAVE_CRITICAL();
204	return;
205}
206
207#else /* !SM_HEAP_CHECK */
208
209/*
210**  Each allocated block is assigned a "group number".
211**  By default, all blocks are assigned to group #1.
212**  By convention, group #0 is for memory that is never freed.
213**  You can use group numbers any way you want, in order to help make
214**  sense of sm_heap_report output.
215*/
216
217int SmHeapGroup = 1;
218int SmHeapMaxGroup = 1;
219
220/*
221**  Total number of bytes allocated.
222**  This is only maintained if the sm_check_heap debug category is active.
223*/
224
225size_t SmHeapTotal = 0;
226
227/*
228**  High water mark: the most that SmHeapTotal has ever been.
229*/
230
231size_t SmHeapMaxTotal = 0;
232
233/*
234**  Maximum number of bytes that may be allocated at any one time.
235**  0 means no limit.
236**  This is only honoured if sm_check_heap is active.
237*/
238
239SM_DEBUG_T SmHeapLimit = SM_DEBUG_INITIALIZER("sm_heap_limit",
240    "@(#)$Debug: sm_heap_limit - max # of bytes permitted in heap $");
241
242/*
243**  This is the data structure that keeps track of all currently
244**  allocated blocks of memory known to the heap package.
245*/
246
247typedef struct sm_heap_item SM_HEAP_ITEM_T;
248struct sm_heap_item
249{
250	void		*hi_ptr;
251	size_t		hi_size;
252	char		*hi_tag;
253	int		hi_num;
254	int		hi_group;
255	SM_HEAP_ITEM_T	*hi_next;
256};
257
258#define SM_HEAP_TABLE_SIZE	256
259static SM_HEAP_ITEM_T *SmHeapTable[SM_HEAP_TABLE_SIZE];
260
261/*
262**  This is a randomly generated table
263**  which contains exactly one occurrence
264**  of each of the numbers between 0 and 255.
265**  It is used by ptrhash.
266*/
267
268static unsigned char hashtab[SM_HEAP_TABLE_SIZE] =
269{
270	161, 71, 77,187, 15,229,  9,176,221,119,239, 21, 85,138,203, 86,
271	102, 65, 80,199,235, 32,140, 96,224, 78,126,127,144,  0, 11,179,
272	 64, 30,120, 23,225,226, 33, 50,205,167,130,240,174, 99,206, 73,
273	231,210,189,162, 48, 93,246, 54,213,141,135, 39, 41,192,236,193,
274	157, 88, 95,104,188, 63,133,177,234,110,158,214,238,131,233, 91,
275	125, 82, 94, 79, 66, 92,151, 45,252, 98, 26,183,  7,191,171,106,
276	145,154,251,100,113,  5, 74, 62, 76,124, 14,217,200, 75,115,190,
277	103, 28,198,196,169,219, 37,118,150, 18,152,175, 49,136,  6,142,
278	 89, 19,243,254, 47,137, 24,166,180, 10, 40,186,202, 46,184, 67,
279	148,108,181, 81, 25,241, 13,139, 58, 38, 84,253,201, 12,116, 17,
280	195, 22,112, 69,255, 43,147,222,111, 56,194,216,149,244, 42,173,
281	232,220,249,105,207, 51,197,242, 72,211,208, 59,122,230,237,170,
282	165, 44, 68,123,129,245,143,101,  8,209,215,247,185, 57,218, 53,
283	114,121,  3,128,  4,204,212,146,  2,155, 83,250, 87, 29, 31,159,
284	 60, 27,107,156,227,182,  1, 61, 36,160,109, 97, 90, 20,168,132,
285	223,248, 70,164, 55,172, 34, 52,163,117, 35,153,134, 16,178,228
286};
287
288/*
289**  PTRHASH -- hash a pointer value
290**
291**	Parameters:
292**		p -- pointer.
293**
294**	Returns:
295**		hash value.
296**
297**  ptrhash hashes a pointer value to a uniformly distributed random
298**  number between 0 and 255.
299**
300**  This hash algorithm is based on Peter K. Pearson,
301**  "Fast Hashing of Variable-Length Text Strings",
302**  in Communications of the ACM, June 1990, vol 33 no 6.
303*/
304
305static int
306ptrhash(p)
307	void *p;
308{
309	int h;
310
311	if (sizeof(void*) == 4 && sizeof(unsigned long) == 4)
312	{
313		unsigned long n = (unsigned long)p;
314
315		h = hashtab[n & 0xFF];
316		h = hashtab[h ^ ((n >> 8) & 0xFF)];
317		h = hashtab[h ^ ((n >> 16) & 0xFF)];
318		h = hashtab[h ^ ((n >> 24) & 0xFF)];
319	}
320# if 0
321	else if (sizeof(void*) == 8 && sizeof(unsigned long) == 8)
322	{
323		unsigned long n = (unsigned long)p;
324
325		h = hashtab[n & 0xFF];
326		h = hashtab[h ^ ((n >> 8) & 0xFF)];
327		h = hashtab[h ^ ((n >> 16) & 0xFF)];
328		h = hashtab[h ^ ((n >> 24) & 0xFF)];
329		h = hashtab[h ^ ((n >> 32) & 0xFF)];
330		h = hashtab[h ^ ((n >> 40) & 0xFF)];
331		h = hashtab[h ^ ((n >> 48) & 0xFF)];
332		h = hashtab[h ^ ((n >> 56) & 0xFF)];
333	}
334# endif /* 0 */
335	else
336	{
337		unsigned char *cp = (unsigned char *)&p;
338		int i;
339
340		h = 0;
341		for (i = 0; i < sizeof(void*); ++i)
342			h = hashtab[h ^ cp[i]];
343	}
344	return h;
345}
346
347/*
348**  SM_MALLOC_TAGGED -- wrapper around malloc(), debugging version.
349**
350**	Parameters:
351**		size -- size of requested memory.
352**		tag -- tag for debugging.
353**		num -- additional value for debugging.
354**		group -- heap group for debugging.
355**
356**	Returns:
357**		Pointer to memory region.
358*/
359
360void *
361sm_malloc_tagged(size, tag, num, group)
362	size_t size;
363	char *tag;
364	int num;
365	int group;
366{
367	void *ptr;
368
369	if (!HEAP_CHECK)
370	{
371		ENTER_CRITICAL();
372		ptr = malloc(MALLOC_SIZE(size));
373		LEAVE_CRITICAL();
374		return ptr;
375	}
376
377	if (sm_xtrap_check())
378		return NULL;
379	if (sm_debug_active(&SmHeapLimit, 1)
380	    && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
381		return NULL;
382	ENTER_CRITICAL();
383	ptr = malloc(MALLOC_SIZE(size));
384	LEAVE_CRITICAL();
385	if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
386	{
387		ENTER_CRITICAL();
388		free(ptr);
389		LEAVE_CRITICAL();
390		ptr = NULL;
391	}
392	SmHeapTotal += size;
393	if (SmHeapTotal > SmHeapMaxTotal)
394		SmHeapMaxTotal = SmHeapTotal;
395	return ptr;
396}
397
398/*
399**  SM_MALLOC_TAGGED_X -- wrapper around malloc(), debugging version.
400**
401**	Parameters:
402**		size -- size of requested memory.
403**		tag -- tag for debugging.
404**		num -- additional value for debugging.
405**		group -- heap group for debugging.
406**
407**	Returns:
408**		Pointer to memory region.
409**
410**	Exceptions:
411**		F:sm_heap -- out of memory
412*/
413
414void *
415sm_malloc_tagged_x(size, tag, num, group)
416	size_t size;
417	char *tag;
418	int num;
419	int group;
420{
421	void *ptr;
422
423	if (!HEAP_CHECK)
424	{
425		ENTER_CRITICAL();
426		ptr = malloc(MALLOC_SIZE(size));
427		LEAVE_CRITICAL();
428		if (ptr == NULL)
429			sm_exc_raise_x(&SmHeapOutOfMemory);
430		return ptr;
431	}
432
433	sm_xtrap_raise_x(&SmHeapOutOfMemory);
434	if (sm_debug_active(&SmHeapLimit, 1)
435	    && sm_debug_level(&SmHeapLimit) < SmHeapTotal + size)
436	{
437		sm_exc_raise_x(&SmHeapOutOfMemory);
438	}
439	ENTER_CRITICAL();
440	ptr = malloc(MALLOC_SIZE(size));
441	LEAVE_CRITICAL();
442	if (ptr != NULL && !sm_heap_register(ptr, size, tag, num, group))
443	{
444		ENTER_CRITICAL();
445		free(ptr);
446		LEAVE_CRITICAL();
447		ptr = NULL;
448	}
449	if (ptr == NULL)
450		sm_exc_raise_x(&SmHeapOutOfMemory);
451	SmHeapTotal += size;
452	if (SmHeapTotal > SmHeapMaxTotal)
453		SmHeapMaxTotal = SmHeapTotal;
454	return ptr;
455}
456
457/*
458**  SM_HEAP_REGISTER -- register a pointer into the heap for debugging.
459**
460**	Parameters:
461**		ptr -- pointer to register.
462**		size -- size of requested memory.
463**		tag -- tag for debugging.
464**		num -- additional value for debugging.
465**		group -- heap group for debugging.
466**
467**	Returns:
468**		true iff successfully registered (not yet in table).
469*/
470
471bool
472sm_heap_register(ptr, size, tag, num, group)
473	void *ptr;
474	size_t size;
475	char *tag;
476	int num;
477	int group;
478{
479	int i;
480	SM_HEAP_ITEM_T *hi;
481
482	if (!HEAP_CHECK)
483		return true;
484	SM_REQUIRE(ptr != NULL);
485	i = ptrhash(ptr);
486# if SM_CHECK_REQUIRE
487
488	/*
489	** We require that ptr is not already in SmHeapTable.
490	*/
491
492	for (hi = SmHeapTable[i]; hi != NULL; hi = hi->hi_next)
493	{
494		if (hi->hi_ptr == ptr)
495			sm_abort("sm_heap_register: ptr %p is already registered (%s:%d)",
496				 ptr, hi->hi_tag, hi->hi_num);
497	}
498# endif /* SM_CHECK_REQUIRE */
499	ENTER_CRITICAL();
500	hi = (SM_HEAP_ITEM_T *) malloc(sizeof(SM_HEAP_ITEM_T));
501	LEAVE_CRITICAL();
502	if (hi == NULL)
503		return false;
504	hi->hi_ptr = ptr;
505	hi->hi_size = size;
506	hi->hi_tag = tag;
507	hi->hi_num = num;
508	hi->hi_group = group;
509	hi->hi_next = SmHeapTable[i];
510	SmHeapTable[i] = hi;
511	return true;
512}
513/*
514**  SM_REALLOC -- wrapper for realloc(), debugging version.
515**
516**	Parameters:
517**		ptr -- pointer to old memory area.
518**		size -- size of requested memory.
519**
520**	Returns:
521**		Pointer to new memory area, NULL on failure.
522*/
523
524void *
525sm_realloc(ptr, size)
526	void *ptr;
527	size_t size;
528{
529	void *newptr;
530	SM_HEAP_ITEM_T *hi, **hp;
531
532	if (!HEAP_CHECK)
533	{
534		ENTER_CRITICAL();
535		newptr = realloc(ptr, MALLOC_SIZE(size));
536		LEAVE_CRITICAL();
537		return newptr;
538	}
539
540	if (ptr == NULL)
541		return sm_malloc_tagged(size, "realloc", 0, SmHeapGroup);
542
543	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
544	{
545		if ((**hp).hi_ptr == ptr)
546		{
547			if (sm_xtrap_check())
548				return NULL;
549			hi = *hp;
550			if (sm_debug_active(&SmHeapLimit, 1)
551			    && sm_debug_level(&SmHeapLimit)
552			       < SmHeapTotal - hi->hi_size + size)
553			{
554				return NULL;
555			}
556			ENTER_CRITICAL();
557			newptr = realloc(ptr, MALLOC_SIZE(size));
558			LEAVE_CRITICAL();
559			if (newptr == NULL)
560				return NULL;
561			SmHeapTotal = SmHeapTotal - hi->hi_size + size;
562			if (SmHeapTotal > SmHeapMaxTotal)
563				SmHeapMaxTotal = SmHeapTotal;
564			*hp = hi->hi_next;
565			hi->hi_ptr = newptr;
566			hi->hi_size = size;
567			hp = &SmHeapTable[ptrhash(newptr)];
568			hi->hi_next = *hp;
569			*hp = hi;
570			return newptr;
571		}
572	}
573	sm_abort("sm_realloc: bad argument (%p)", ptr);
574	/* NOTREACHED */
575	return NULL;	/* keep Irix compiler happy */
576}
577
578/*
579**  SM_REALLOC_X -- wrapper for realloc(), debugging version.
580**
581**	Parameters:
582**		ptr -- pointer to old memory area.
583**		size -- size of requested memory.
584**
585**	Returns:
586**		Pointer to new memory area.
587**
588**	Exceptions:
589**		F:sm_heap -- out of memory
590*/
591
592void *
593sm_realloc_x(ptr, size)
594	void *ptr;
595	size_t size;
596{
597	void *newptr;
598	SM_HEAP_ITEM_T *hi, **hp;
599
600	if (!HEAP_CHECK)
601	{
602		ENTER_CRITICAL();
603		newptr = realloc(ptr, MALLOC_SIZE(size));
604		LEAVE_CRITICAL();
605		if (newptr == NULL)
606			sm_exc_raise_x(&SmHeapOutOfMemory);
607		return newptr;
608	}
609
610	if (ptr == NULL)
611		return sm_malloc_tagged_x(size, "realloc", 0, SmHeapGroup);
612
613	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
614	{
615		if ((**hp).hi_ptr == ptr)
616		{
617			sm_xtrap_raise_x(&SmHeapOutOfMemory);
618			hi = *hp;
619			if (sm_debug_active(&SmHeapLimit, 1)
620			    && sm_debug_level(&SmHeapLimit)
621			       < SmHeapTotal - hi->hi_size + size)
622			{
623				sm_exc_raise_x(&SmHeapOutOfMemory);
624			}
625			ENTER_CRITICAL();
626			newptr = realloc(ptr, MALLOC_SIZE(size));
627			LEAVE_CRITICAL();
628			if (newptr == NULL)
629				sm_exc_raise_x(&SmHeapOutOfMemory);
630			SmHeapTotal = SmHeapTotal - hi->hi_size + size;
631			if (SmHeapTotal > SmHeapMaxTotal)
632				SmHeapMaxTotal = SmHeapTotal;
633			*hp = hi->hi_next;
634			hi->hi_ptr = newptr;
635			hi->hi_size = size;
636			hp = &SmHeapTable[ptrhash(newptr)];
637			hi->hi_next = *hp;
638			*hp = hi;
639			return newptr;
640		}
641	}
642	sm_abort("sm_realloc_x: bad argument (%p)", ptr);
643	/* NOTREACHED */
644	return NULL;	/* keep Irix compiler happy */
645}
646
647/*
648**  SM_FREE_TAGGED -- wrapper around free(), debugging version.
649**
650**	Parameters:
651**		ptr -- pointer to memory region.
652**		tag -- tag for debugging.
653**		num -- additional value for debugging.
654**
655**	Returns:
656**		none.
657*/
658
659void
660sm_free_tagged(ptr, tag, num)
661	void *ptr;
662	char *tag;
663	int num;
664{
665	SM_HEAP_ITEM_T **hp;
666
667	if (ptr == NULL)
668		return;
669	if (!HEAP_CHECK)
670	{
671		ENTER_CRITICAL();
672		free(ptr);
673		LEAVE_CRITICAL();
674		return;
675	}
676	for (hp = &SmHeapTable[ptrhash(ptr)]; *hp != NULL; hp = &(**hp).hi_next)
677	{
678		if ((**hp).hi_ptr == ptr)
679		{
680			SM_HEAP_ITEM_T *hi = *hp;
681
682			*hp = hi->hi_next;
683
684			/*
685			**  Fill the block with zeros before freeing.
686			**  This is intended to catch problems with
687			**  dangling pointers.  The block is filled with
688			**  zeros, not with some non-zero value, because
689			**  it is common practice in some C code to store
690			**  a zero in a structure member before freeing the
691			**  structure, as a defense against dangling pointers.
692			*/
693
694			(void) memset(ptr, 0, hi->hi_size);
695			SmHeapTotal -= hi->hi_size;
696			ENTER_CRITICAL();
697			free(ptr);
698			free(hi);
699			LEAVE_CRITICAL();
700			return;
701		}
702	}
703	sm_abort("sm_free: bad argument (%p) (%s:%d)", ptr, tag, num);
704}
705
706/*
707**  SM_HEAP_CHECKPTR_TAGGED -- check whether ptr is a valid argument to sm_free
708**
709**	Parameters:
710**		ptr -- pointer to memory region.
711**		tag -- tag for debugging.
712**		num -- additional value for debugging.
713**
714**	Returns:
715**		none.
716**
717**	Side Effects:
718**		aborts if check fails.
719*/
720
721void
722sm_heap_checkptr_tagged(ptr, tag, num)
723	void *ptr;
724	char *tag;
725	int num;
726{
727	SM_HEAP_ITEM_T *hp;
728
729	if (!HEAP_CHECK)
730		return;
731	if (ptr == NULL)
732		return;
733	for (hp = SmHeapTable[ptrhash(ptr)]; hp != NULL; hp = hp->hi_next)
734	{
735		if (hp->hi_ptr == ptr)
736			return;
737	}
738	sm_abort("sm_heap_checkptr(%p): bad ptr (%s:%d)", ptr, tag, num);
739}
740
741/*
742**  SM_HEAP_REPORT -- output "map" of used heap.
743**
744**	Parameters:
745**		stream -- the file pointer to write to.
746**		verbosity -- how much info?
747**
748**	Returns:
749**		none.
750*/
751
752void
753sm_heap_report(stream, verbosity)
754	SM_FILE_T *stream;
755	int verbosity;
756{
757	int i;
758	unsigned long group0total, group1total, otherstotal, grandtotal;
759
760	if (!HEAP_CHECK || verbosity <= 0)
761		return;
762	group0total = group1total = otherstotal = grandtotal = 0;
763	for (i = 0; i < sizeof(SmHeapTable) / sizeof(SmHeapTable[0]); ++i)
764	{
765		SM_HEAP_ITEM_T *hi = SmHeapTable[i];
766
767		while (hi != NULL)
768		{
769			if (verbosity > 2
770			    || (verbosity > 1 && hi->hi_group != 0))
771			{
772				sm_io_fprintf(stream, SM_TIME_DEFAULT,
773					"%4d %*lx %7lu bytes",
774					hi->hi_group,
775					(int) sizeof(void *) * 2,
776					(long)hi->hi_ptr,
777					(unsigned long)hi->hi_size);
778				if (hi->hi_tag != NULL)
779				{
780					sm_io_fprintf(stream, SM_TIME_DEFAULT,
781						"  %s",
782						hi->hi_tag);
783					if (hi->hi_num)
784					{
785						sm_io_fprintf(stream,
786							SM_TIME_DEFAULT,
787							":%d",
788							hi->hi_num);
789					}
790				}
791				sm_io_fprintf(stream, SM_TIME_DEFAULT, "\n");
792			}
793			switch (hi->hi_group)
794			{
795			  case 0:
796				group0total += hi->hi_size;
797				break;
798			  case 1:
799				group1total += hi->hi_size;
800				break;
801			  default:
802				otherstotal += hi->hi_size;
803				break;
804			}
805			grandtotal += hi->hi_size;
806			hi = hi->hi_next;
807		}
808	}
809	sm_io_fprintf(stream, SM_TIME_DEFAULT,
810		"heap max=%lu, total=%lu, ",
811		(unsigned long) SmHeapMaxTotal, grandtotal);
812	sm_io_fprintf(stream, SM_TIME_DEFAULT,
813		"group 0=%lu, group 1=%lu, others=%lu\n",
814		group0total, group1total, otherstotal);
815	if (grandtotal != SmHeapTotal)
816	{
817		sm_io_fprintf(stream, SM_TIME_DEFAULT,
818			"BUG => SmHeapTotal: got %lu, expected %lu\n",
819			(unsigned long) SmHeapTotal, grandtotal);
820	}
821}
822#endif /* !SM_HEAP_CHECK */
823