1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                   Phong Vo <kpv@research.att.com>                    *
20*                                                                      *
21***********************************************************************/
22#if defined(_UWIN) && defined(_BLD_ast)
23
24void _STUB_vmdebug(){}
25
26#else
27
28#include	"vmhdr.h"
29
30/*	Method to help with debugging. This does rigorous checks on
31**	addresses and arena integrity.
32**
33**	Written by Kiem-Phong Vo, kpv@research.att.com, 01/16/94.
34*/
35
36/* structure to keep track of file names */
37typedef struct _dbfile_s	Dbfile_t;
38struct _dbfile_s
39{	Dbfile_t*	next;
40	char		file[1];
41};
42static Dbfile_t*	Dbfile;
43
44/* global watch list */
45#define S_WATCH	32
46static int	Dbnwatch;
47static Void_t*	Dbwatch[S_WATCH];
48
49/* types of warnings reported by dbwarn() */
50#define	DB_CHECK	0
51#define DB_ALLOC	1
52#define DB_FREE		2
53#define DB_RESIZE	3
54#define DB_WATCH	4
55#define DB_RESIZED	5
56
57static int Dbinit = 0;
58#define DBINIT()	(Dbinit ? 0 : (dbinit(), Dbinit=1) )
59static void dbinit()
60{	int	fd;
61	if((fd = vmtrace(-1)) >= 0)
62		vmtrace(fd);
63}
64
65static int	Dbfd = 2;	/* default warning file descriptor */
66#if __STD_C
67int vmdebug(int fd)
68#else
69int vmdebug(fd)
70int	fd;
71#endif
72{
73	int	old = Dbfd;
74	Dbfd = fd;
75	return old;
76}
77
78
79/* just an entry point to make it easy to set break point */
80#if __STD_C
81static void vmdbwarn(Vmalloc_t* vm, char* mesg, int n)
82#else
83static void vmdbwarn(vm, mesg, n)
84Vmalloc_t*	vm;
85char*		mesg;
86int		n;
87#endif
88{
89	reg Vmdata_t*	vd = vm->data;
90
91	write(Dbfd,mesg,n);
92	if(vd->mode&VM_DBABORT)
93		abort();
94}
95
96/* issue a warning of some type */
97#if __STD_C
98static void dbwarn(Vmalloc_t* vm, Void_t* data, int where,
99		   char* file, int line, Void_t* func, int type)
100#else
101static void dbwarn(vm, data, where, file, line, func, type)
102Vmalloc_t*	vm;	/* region holding the block	*/
103Void_t*		data;	/* data block			*/
104int		where;	/* byte that was corrupted	*/
105char*		file;	/* file where call originates	*/
106int		line;	/* line number of call		*/
107Void_t*		func;	/* function called from		*/
108int		type;	/* operation being done		*/
109#endif
110{
111	char	buf[1024], *bufp, *endbuf, *s;
112#define SLOP	64	/* enough for a message and an int */
113
114	DBINIT();
115
116	bufp = buf;
117	endbuf = buf + sizeof(buf);
118
119	if(type == DB_ALLOC)
120		bufp = (*_Vmstrcpy)(bufp, "alloc error", ':');
121	else if(type == DB_FREE)
122		bufp = (*_Vmstrcpy)(bufp, "free error", ':');
123	else if(type == DB_RESIZE)
124		bufp = (*_Vmstrcpy)(bufp, "resize error", ':');
125	else if(type == DB_CHECK)
126		bufp = (*_Vmstrcpy)(bufp, "corrupted data", ':');
127	else if(type == DB_WATCH)
128		bufp = (*_Vmstrcpy)(bufp, "alert", ':');
129
130	/* region info */
131	bufp = (*_Vmstrcpy)(bufp, "region", '=');
132	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(vm), 0), ':');
133
134	if(data)
135	{	bufp = (*_Vmstrcpy)(bufp,"block",'=');
136		bufp = (*_Vmstrcpy)(bufp,(*_Vmitoa)(VLONG(data),0),':');
137	}
138
139	if(!data)
140	{	if(where == DB_ALLOC)
141			bufp = (*_Vmstrcpy)(bufp, "can't get memory", ':');
142		else	bufp = (*_Vmstrcpy)(bufp, "region is locked", ':');
143	}
144	else if(type == DB_FREE || type == DB_RESIZE)
145	{	if(where == 0)
146			bufp = (*_Vmstrcpy)(bufp, "unallocated block", ':');
147		else	bufp = (*_Vmstrcpy)(bufp, "already freed", ':');
148	}
149	else if(type == DB_WATCH)
150	{	bufp = (*_Vmstrcpy)(bufp, "size", '=');
151		bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)((Vmulong_t)DBSIZE(data),-1), ':');
152		if(where == DB_ALLOC)
153			bufp = (*_Vmstrcpy)(bufp,"just allocated", ':');
154		else if(where == DB_FREE)
155			bufp = (*_Vmstrcpy)(bufp,"being freed", ':');
156		else if(where == DB_RESIZE)
157			bufp = (*_Vmstrcpy)(bufp,"being resized", ':');
158		else if(where == DB_RESIZED)
159			bufp = (*_Vmstrcpy)(bufp,"just resized", ':');
160	}
161	else if(type == DB_CHECK)
162	{	bufp = (*_Vmstrcpy)(bufp, "bad byte at", '=');
163		bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(where),-1), ':');
164		if((s = DBFILE(data)) && (bufp + strlen(s) + SLOP) < endbuf)
165		{	bufp = (*_Vmstrcpy)(bufp,"allocated at", '=');
166			bufp = (*_Vmstrcpy)(bufp, s, ',');
167			bufp = (*_Vmstrcpy)(bufp,(*_Vmitoa)(VLONG(DBLINE(data)),-1),':');
168		}
169	}
170
171	/* location where offending call originates from */
172	if(file && file[0] && line > 0 && (bufp + strlen(file) + SLOP) < endbuf)
173	{	bufp = (*_Vmstrcpy)(bufp, "detected at", '=');
174		bufp = (*_Vmstrcpy)(bufp, file, ',');
175		bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(line),-1), ',');
176		bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(func),-1), ':');
177	}
178
179	*bufp++ = '\n';
180	*bufp = '\0';
181
182	vmdbwarn(vm,buf,(int)(bufp-buf));
183}
184
185/* check for watched address and issue warnings */
186#if __STD_C
187static void dbwatch(Vmalloc_t* vm, Void_t* data,
188		    char* file, int line, Void_t* func, int type)
189#else
190static void dbwatch(vm, data, file, line, func, type)
191Vmalloc_t*	vm;
192Void_t*		data;
193char*		file;
194int		line;
195Void_t*		func;
196int		type;
197#endif
198{
199	reg int		n;
200
201	for(n = Dbnwatch; n >= 0; --n)
202	{	if(Dbwatch[n] == data)
203		{	dbwarn(vm,data,type,file,line,func,DB_WATCH);
204			return;
205		}
206	}
207}
208
209/* record information about the block */
210#if __STD_C
211static void dbsetinfo(Vmuchar_t* data, size_t size, char* file, int line)
212#else
213static void dbsetinfo(data, size, file, line)
214Vmuchar_t*	data;	/* real address not the one from Vmbest	*/
215size_t		size;	/* the actual requested size		*/
216char*		file;	/* file where the request came from	*/
217int		line;	/* and line number			*/
218#endif
219{
220	reg Vmuchar_t	*begp, *endp;
221	reg Dbfile_t	*last, *db;
222
223	DBINIT();
224
225	/* find the file structure */
226	if(!file || !file[0])
227		db = NIL(Dbfile_t*);
228	else
229	{	for(last = NIL(Dbfile_t*), db = Dbfile; db; last = db, db = db->next)
230			if(strcmp(db->file,file) == 0)
231				break;
232		if(!db)
233		{	db = (Dbfile_t*)vmalloc(Vmheap,sizeof(Dbfile_t)+strlen(file));
234			if(db)
235			{	(*_Vmstrcpy)(db->file,file,0);
236				db->next = Dbfile;
237				Dbfile = db;
238			}
239		}
240		else if(last) /* move-to-front heuristic */
241		{	last->next = db->next;
242			db->next = Dbfile;
243			Dbfile = db;
244		}
245	}
246
247	DBSETFL(data,(db ? db->file : NIL(char*)),line);
248	DBSIZE(data) = size;
249	DBSEG(data)  = SEG(DBBLOCK(data));
250
251	DBHEAD(data,begp,endp);
252	while(begp < endp)
253		*begp++ = DB_MAGIC;
254	DBTAIL(data,begp,endp);
255	while(begp < endp)
256		*begp++ = DB_MAGIC;
257}
258
259/* Check to see if an address is in some data block of a region.
260** This returns -(offset+1) if block is already freed, +(offset+1)
261** if block is live, 0 if no match.
262*/
263#if __STD_C
264static long dbaddr(Vmalloc_t* vm, Void_t* addr, int local)
265#else
266static long dbaddr(vm, addr, local)
267Vmalloc_t*	vm;
268Void_t*		addr;
269int		local;
270#endif
271{
272	reg Block_t	*b, *endb;
273	reg Seg_t	*seg;
274	reg Vmuchar_t	*data;
275	reg long	offset = -1L;
276	reg Vmdata_t	*vd = vm->data;
277
278	SETLOCK(vm, local);
279
280	b = endb = NIL(Block_t*);
281	for(seg = vd->seg; seg; seg = seg->next)
282	{	b = SEGBLOCK(seg);
283		endb = (Block_t*)(seg->baddr - sizeof(Head_t));
284		if((Vmuchar_t*)addr > (Vmuchar_t*)b &&
285		   (Vmuchar_t*)addr < (Vmuchar_t*)endb)
286			break;
287	}
288	if(!seg)
289		goto done;
290
291	if(local) /* must be vmfree or vmresize checking address */
292	{	if(DBSEG(addr) == seg)
293		{	b = DBBLOCK(addr);
294			if(ISBUSY(SIZE(b)) && !ISJUNK(SIZE(b)) )
295				offset = 0;
296			else	offset = -2L;
297		}
298		goto done;
299	}
300
301	while(b < endb)
302	{	data = (Vmuchar_t*)DATA(b);
303		if((Vmuchar_t*)addr >= data && (Vmuchar_t*)addr < data+SIZE(b))
304		{	if(ISBUSY(SIZE(b)) && !ISJUNK(SIZE(b)) )
305			{	data = DB2DEBUG(data);
306				if((Vmuchar_t*)addr >= data &&
307				   (Vmuchar_t*)addr < data+DBSIZE(data))
308					offset = (long)((Vmuchar_t*)addr - data);
309			}
310			goto done;
311		}
312
313		b = (Block_t*)((Vmuchar_t*)DATA(b) + (SIZE(b)&~BITS) );
314	}
315
316done:
317	CLRLOCK(vm, local);
318	return offset;
319}
320
321
322#if __STD_C
323static long dbsize(Vmalloc_t* vm, Void_t* addr, int local)
324#else
325static long dbsize(vm, addr, local)
326Vmalloc_t*	vm;
327Void_t*		addr;
328int		local;
329#endif
330{
331	Block_t		*b, *endb;
332	Seg_t		*seg;
333	long		size;
334	Vmdata_t	*vd = vm->data;
335
336	SETLOCK(vm, local);
337
338	size = -1L;
339	for(seg = vd->seg; seg; seg = seg->next)
340	{	b = SEGBLOCK(seg);
341		endb = (Block_t*)(seg->baddr - sizeof(Head_t));
342		if((Vmuchar_t*)addr <= (Vmuchar_t*)b ||
343		   (Vmuchar_t*)addr >= (Vmuchar_t*)endb)
344			continue;
345		while(b < endb)
346		{	if(addr == (Void_t*)DB2DEBUG(DATA(b)))
347			{	if(ISBUSY(SIZE(b)) && !ISJUNK(SIZE(b)) )
348					size = (long)DBSIZE(addr);
349				goto done;
350			}
351
352			b = (Block_t*)((Vmuchar_t*)DATA(b) + (SIZE(b)&~BITS) );
353		}
354	}
355
356done:
357	CLRLOCK(vm, local);
358	return size;
359}
360
361#if __STD_C
362static Void_t* dballoc(Vmalloc_t* vm, size_t size, int local)
363#else
364static Void_t* dballoc(vm, size, local)
365Vmalloc_t*	vm;
366size_t		size;
367int		local;
368#endif
369{
370	size_t		s;
371	Vmuchar_t	*data;
372	char		*file;
373	int		line;
374	Void_t		*func;
375	Vmdata_t	*vd = vm->data;
376	VMFLF(vm,file,line,func);
377
378	SETLOCK(vm, local);
379
380	if(vd->mode&VM_DBCHECK)
381		vmdbcheck(vm);
382
383	s = ROUND(size,ALIGN) + DB_EXTRA;
384	if(s < sizeof(Body_t))	/* no tiny blocks during Vmdebug */
385		s = sizeof(Body_t);
386
387	if(!(data = (Vmuchar_t*)KPVALLOC(vm,s,(*(Vmbest->allocf))) ) )
388	{	dbwarn(vm,NIL(Vmuchar_t*),DB_ALLOC,file,line,func,DB_ALLOC);
389		goto done;
390	}
391
392	data = DB2DEBUG(data);
393	dbsetinfo(data,size,file,line);
394
395	if((vd->mode&VM_TRACE) && _Vmtrace)
396	{	vm->file = file; vm->line = line; vm->func = func;
397		(*_Vmtrace)(vm,NIL(Vmuchar_t*),data,size,0);
398	}
399
400	if(Dbnwatch > 0 )
401		dbwatch(vm,data,file,line,func,DB_ALLOC);
402
403done:
404	CLRLOCK(vm, local);
405
406	return (Void_t*)data;
407}
408
409
410#if __STD_C
411static int dbfree(Vmalloc_t* vm, Void_t* data, int local )
412#else
413static int dbfree(vm, data, local )
414Vmalloc_t*	vm;
415Void_t*		data;
416int		local;
417#endif
418{
419	char		*file;
420	int		line;
421	Void_t		*func;
422	long		offset;
423	int		rv, *ip, *endip;
424	Vmdata_t	*vd = vm->data;
425	VMFLF(vm,file,line,func);
426
427	if(!data)
428		return 0;
429
430	SETLOCK(vm, local);
431
432	if(vd->mode&VM_DBCHECK)
433		vmdbcheck(vm);
434
435	if((offset = KPVADDR(vm,data,dbaddr)) != 0)
436	{	dbwarn(vm,(Vmuchar_t*)data,offset == -1L ? 0 : 1,file,line,func,DB_FREE);
437		rv = -1;
438	}
439	else
440	{	if(Dbnwatch > 0)
441			dbwatch(vm,data,file,line,func,DB_FREE);
442
443		if((vd->mode&VM_TRACE) && _Vmtrace)
444		{	vm->file = file; vm->line = line; vm->func = func;
445			(*_Vmtrace)(vm,(Vmuchar_t*)data,NIL(Vmuchar_t*),DBSIZE(data),0);
446		}
447
448		/* clear free space */
449		ip = (int*)data;
450		endip = ip + (DBSIZE(data)+sizeof(int)-1)/sizeof(int);
451		while(ip < endip)
452			*ip++ = 0;
453
454		rv = KPVFREE((vm), (Void_t*)DB2BEST(data), (*Vmbest->freef));
455	}
456
457	CLRLOCK(vm, local);
458	return rv;
459}
460
461/*	Resizing an existing block */
462#if __STD_C
463static Void_t* dbresize(Vmalloc_t* vm, Void_t* addr, reg size_t size, int type, int local)
464#else
465static Void_t* dbresize(vm, addr, size, type, local)
466Vmalloc_t*	vm;	/* region allocating from	*/
467Void_t*		addr;	/* old block of data		*/
468reg size_t	size;	/* new size			*/
469int		type;	/* !=0 for movable, >0 for copy	*/
470int		local;
471#endif
472{
473	Vmuchar_t	*data;
474	long		offset;
475	size_t		s, oldsize;
476	char		*file, *oldfile;
477	int		line, oldline;
478	Void_t		*func;
479	Vmdata_t	*vd = vm->data;
480	VMFLF(vm,file,line,func);
481
482	if(!addr)
483	{	vm->file = file; vm->line = line;
484		data = (Vmuchar_t*)dballoc(vm, size, local);
485		if(data && (type&VM_RSZERO) )
486			memset((Void_t*)data, 0, size);
487		return data;
488	}
489	if(size == 0)
490	{	vm->file = file; vm->line = line;
491		(void)dbfree(vm, addr, local);
492		return NIL(Void_t*);
493	}
494
495	SETLOCK(vm, local);
496
497	if(vd->mode&VM_DBCHECK)
498		vmdbcheck(vm);
499
500	if((offset = KPVADDR(vm,addr,dbaddr)) != 0)
501	{	dbwarn(vm,(Vmuchar_t*)addr,offset == -1L ? 0 : 1,file,line,func,DB_RESIZE);
502		data = NIL(Vmuchar_t*);
503	}
504	else
505	{	if(Dbnwatch > 0)
506			dbwatch(vm,addr,file,line,func,DB_RESIZE);
507
508		/* Vmbest data block */
509		data = DB2BEST(addr);
510		oldsize = DBSIZE(addr);
511		oldfile = DBFILE(addr);
512		oldline = DBLINE(addr);
513
514		/* do the resize */
515		s = ROUND(size,ALIGN) + DB_EXTRA;
516		if(s < sizeof(Body_t))
517			s = sizeof(Body_t);
518		data = (Vmuchar_t*)KPVRESIZE(vm,(Void_t*)data,s,
519					 (type&~VM_RSZERO),(*(Vmbest->resizef)) );
520		if(!data) /* failed, reset data for old block */
521		{	dbwarn(vm,NIL(Vmuchar_t*),DB_ALLOC,file,line,func,DB_RESIZE);
522			dbsetinfo((Vmuchar_t*)addr,oldsize,oldfile,oldline);
523		}
524		else
525		{	data = DB2DEBUG(data);
526			dbsetinfo(data,size,file,line);
527
528			if((vd->mode&VM_TRACE) && _Vmtrace)
529			{	vm->file = file; vm->line = line;
530				(*_Vmtrace)(vm,(Vmuchar_t*)addr,data,size,0);
531			}
532			if(Dbnwatch > 0)
533				dbwatch(vm,data,file,line,func,DB_RESIZED);
534		}
535
536		if(data && (type&VM_RSZERO) && size > oldsize)
537		{	Vmuchar_t *d = data+oldsize, *ed = data+size;
538			do { *d++ = 0; } while(d < ed);
539		}
540	}
541
542	CLRLOCK(vm, local);
543
544	return (Void_t*)data;
545}
546
547/* compact any residual free space */
548#if __STD_C
549static int dbcompact(Vmalloc_t* vm, int local)
550#else
551static int dbcompact(vm, local)
552Vmalloc_t*	vm;
553int		local;
554#endif
555{
556	return (*(Vmbest->compactf))(vm, local);
557}
558
559/* check for memory overwrites over all live blocks */
560#if __STD_C
561int vmdbcheck(Vmalloc_t* vm)
562#else
563int vmdbcheck(vm)
564Vmalloc_t*	vm;
565#endif
566{
567	reg Block_t	*b, *endb;
568	reg Seg_t*	seg;
569	int		rv;
570	reg Vmdata_t*	vd = vm->data;
571
572	/* check the meta-data of this region */
573	if(vd->mode & (VM_MTDEBUG|VM_MTBEST|VM_MTPROFILE))
574	{	if(_vmbestcheck(vd, NIL(Block_t*)) < 0)
575			return -1;
576		if(!(vd->mode&VM_MTDEBUG) )
577			return 0;
578	}
579	else	return -1;
580
581	rv = 0;
582	for(seg = vd->seg; seg; seg = seg->next)
583	{	b = SEGBLOCK(seg);
584		endb = (Block_t*)(seg->baddr - sizeof(Head_t));
585		while(b < endb)
586		{	reg Vmuchar_t	*data, *begp, *endp;
587
588			if(ISJUNK(SIZE(b)) || !ISBUSY(SIZE(b)))
589				goto next;
590
591			data = DB2DEBUG(DATA(b));
592			if(DBISBAD(data))	/* seen this before */
593			{	rv += 1;
594				goto next;
595			}
596
597			DBHEAD(data,begp,endp);
598			for(; begp < endp; ++begp)
599				if(*begp != DB_MAGIC)
600					goto set_bad;
601
602			DBTAIL(data,begp,endp);
603			for(; begp < endp; ++begp)
604			{	if(*begp == DB_MAGIC)
605					continue;
606			set_bad:
607				dbwarn(vm,data,(long)(begp-data),vm->file,vm->line,0,DB_CHECK);
608				DBSETBAD(data);
609				rv += 1;
610				goto next;
611			}
612
613		next:	b = (Block_t*)((Vmuchar_t*)DATA(b) + (SIZE(b)&~BITS));
614		}
615	}
616
617	return rv;
618}
619
620/* set/delete an address to watch */
621#if __STD_C
622Void_t* vmdbwatch(Void_t* addr)
623#else
624Void_t* vmdbwatch(addr)
625Void_t*		addr;	/* address to insert	*/
626#endif
627{
628	reg int		n;
629	reg Void_t*	out;
630
631	out = NIL(Void_t*);
632	if(!addr)
633		Dbnwatch = 0;
634	else
635	{	for(n = Dbnwatch - 1; n >= 0; --n)
636			if(Dbwatch[n] == addr)
637				break;
638		if(n < 0)	/* insert */
639		{	if(Dbnwatch == S_WATCH)
640			{	/* delete left-most */
641				out = Dbwatch[0];
642				Dbnwatch -= 1;
643				for(n = 0; n < Dbnwatch; ++n)
644					Dbwatch[n] = Dbwatch[n+1];
645			}
646			Dbwatch[Dbnwatch] = addr;
647			Dbnwatch += 1;
648		}
649	}
650	return out;
651}
652
653#if __STD_C
654static Void_t* dbalign(Vmalloc_t* vm, size_t size, size_t align, int local)
655#else
656static Void_t* dbalign(vm, size, align, local)
657Vmalloc_t*	vm;
658size_t		size;
659size_t		align;
660int		local;
661#endif
662{
663	Vmuchar_t	*data;
664	size_t		s;
665	char		*file;
666	int		line;
667	Void_t		*func;
668	Vmdata_t	*vd = vm->data;
669	VMFLF(vm,file,line,func);
670
671	if(size <= 0 || align <= 0)
672		return NIL(Void_t*);
673
674	SETLOCK(vm, local);
675
676	if((s = ROUND(size,ALIGN) + DB_EXTRA) < sizeof(Body_t))
677		s = sizeof(Body_t);
678
679	if((data = (Vmuchar_t*)KPVALIGN(vm,s,align,(*(Vmbest->alignf)))) )
680	{	data += DB_HEAD;
681		dbsetinfo(data,size,file,line);
682
683		if((vd->mode&VM_TRACE) && _Vmtrace)
684		{	vm->file = file; vm->line = line; vm->func = func;
685			(*_Vmtrace)(vm,NIL(Vmuchar_t*),data,size,align);
686		}
687	}
688
689	CLRLOCK(vm, local);
690
691	return (Void_t*)data;
692}
693
694/* print statistics of region vm. If vm is NULL, use Vmregion */
695#if __STD_C
696ssize_t vmdbstat(Vmalloc_t* vm)
697#else
698ssize_t vmdbstat(vm)
699Vmalloc_t*	vm;
700#endif
701{	Vmstat_t	st;
702	char		buf[1024], *bufp;
703
704	vmstat(vm ? vm : Vmregion, &st);
705	bufp = buf;
706	bufp = (*_Vmstrcpy)(bufp, "n_busy", '=');
707	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.n_busy),-1), ',');
708	bufp = (*_Vmstrcpy)(bufp, " s_busy", '=');
709	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.s_busy),-1), '\n');
710	bufp = (*_Vmstrcpy)(bufp, "n_free", '=');
711	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.n_free),-1), ',');
712	bufp = (*_Vmstrcpy)(bufp, " s_free", '=');
713	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.s_free),-1), '\n');
714	bufp = (*_Vmstrcpy)(bufp, "m_busy", '=');
715	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.m_busy),-1), ',');
716	bufp = (*_Vmstrcpy)(bufp, " m_free", '=');
717	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.m_free),-1), '\n');
718	bufp = (*_Vmstrcpy)(bufp, "n_segment", '=');
719	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.n_seg),-1), ',');
720	bufp = (*_Vmstrcpy)(bufp, " extent", '=');
721	bufp = (*_Vmstrcpy)(bufp, (*_Vmitoa)(VLONG(st.extent),-1), '\n');
722	*bufp = 0;
723	write(Dbfd, buf, strlen(buf));
724	return strlen(buf);
725}
726
727static Vmethod_t _Vmdebug =
728{
729	dballoc,
730	dbresize,
731	dbfree,
732	dbaddr,
733	dbsize,
734	dbcompact,
735	dbalign,
736	VM_MTDEBUG
737};
738
739__DEFINE__(Vmethod_t*,Vmdebug,&_Vmdebug);
740
741#ifdef NoF
742NoF(vmdebug)
743#endif
744
745#endif
746