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