xref: /illumos-gate/usr/src/cmd/mdb/common/modules/libumem/libumem.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * Copyright (c) 2012, Joyent, Inc. All rights reserved.
28  */
29 
30 #include "umem.h"
31 #include <libproc.h>
32 #include <mdb/mdb_modapi.h>
33 
34 #include "kgrep.h"
35 #include "leaky.h"
36 #include "misc.h"
37 #include "proc_kludges.h"
38 
39 #include <umem_impl.h>
40 #include <sys/vmem_impl_user.h>
41 #include <thr_uberdata.h>
42 
43 #include "umem_pagesize.h"
44 
45 typedef struct datafmt {
46 	char	*hdr1;
47 	char	*hdr2;
48 	char	*dashes;
49 	char	*fmt;
50 } datafmt_t;
51 
52 static datafmt_t ptcfmt[] = {
53 	{ "   ",	"tid",		"---",		"%3u "		},
54 	{ " memory",	" cached",	"-------",	"%7lH "		},
55 	{ "  %",	"cap",		"---",		"%3u "		},
56 	{ "  %",	NULL,		"---",		"%3u "		},
57 	{ NULL,		NULL,		NULL,		NULL		}
58 };
59 
60 static datafmt_t umemfmt[] = {
61 	{ "cache                    ", "name                     ",
62 	"-------------------------", "%-25s "				},
63 	{ "   buf",	"  size",	"------",	"%6u "		},
64 	{ "    buf",	" in use",	"-------",	"%7u "		},
65 	{ "    buf",	" in ptc",	"-------",	"%7s "		},
66 	{ "    buf",	"  total",	"-------",	"%7u "		},
67 	{ " memory",	" in use",	"-------",	"%7H "		},
68 	{ "    alloc",	"  succeed",	"---------",	"%9u "		},
69 	{ "alloc",	" fail",	"-----",	"%5llu"		},
70 	{ NULL,		NULL,		NULL,		NULL		}
71 };
72 
73 static datafmt_t vmemfmt[] = {
74 	{ "vmem                     ", "name                     ",
75 	"-------------------------", "%-*s "				},
76 	{ "   memory",	"   in use",	"---------",	"%9H "		},
77 	{ "    memory",	"     total",	"----------",	"%10H "		},
78 	{ "   memory",	"   import",	"---------",	"%9H "		},
79 	{ "    alloc",	"  succeed",	"---------",	"%9llu "	},
80 	{ "alloc",	" fail",	"-----",	"%5llu "	},
81 	{ NULL,		NULL,		NULL,		NULL		}
82 };
83 
84 /*ARGSUSED*/
85 static int
86 umastat_cpu_avail(uintptr_t addr, const umem_cpu_cache_t *ccp, int *avail)
87 {
88 	if (ccp->cc_rounds > 0)
89 		*avail += ccp->cc_rounds;
90 	if (ccp->cc_prounds > 0)
91 		*avail += ccp->cc_prounds;
92 
93 	return (WALK_NEXT);
94 }
95 
96 /*ARGSUSED*/
97 static int
98 umastat_cpu_alloc(uintptr_t addr, const umem_cpu_cache_t *ccp, int *alloc)
99 {
100 	*alloc += ccp->cc_alloc;
101 
102 	return (WALK_NEXT);
103 }
104 
105 /*ARGSUSED*/
106 static int
107 umastat_slab_avail(uintptr_t addr, const umem_slab_t *sp, int *avail)
108 {
109 	*avail += sp->slab_chunks - sp->slab_refcnt;
110 
111 	return (WALK_NEXT);
112 }
113 
114 typedef struct umastat_vmem {
115 	uintptr_t kv_addr;
116 	struct umastat_vmem *kv_next;
117 	int kv_meminuse;
118 	int kv_alloc;
119 	int kv_fail;
120 } umastat_vmem_t;
121 
122 /*ARGSUSED*/
123 static int
124 umastat_cache_nptc(uintptr_t addr, const umem_cache_t *cp, int *nptc)
125 {
126 	if (!(cp->cache_flags & UMF_PTC))
127 		return (WALK_NEXT);
128 
129 	(*nptc)++;
130 	return (WALK_NEXT);
131 }
132 
133 /*ARGSUSED*/
134 static int
135 umastat_cache_hdr(uintptr_t addr, const umem_cache_t *cp, void *ignored)
136 {
137 	if (!(cp->cache_flags & UMF_PTC))
138 		return (WALK_NEXT);
139 
140 	mdb_printf("%3d ", cp->cache_bufsize);
141 	return (WALK_NEXT);
142 }
143 
144 /*ARGSUSED*/
145 static int
146 umastat_lwp_ptc(uintptr_t addr, void *buf, int *nbufs)
147 {
148 	(*nbufs)++;
149 	return (WALK_NEXT);
150 }
151 
152 /*ARGSUSED*/
153 static int
154 umastat_lwp_cache(uintptr_t addr, const umem_cache_t *cp, ulwp_t *ulwp)
155 {
156 	char walk[60];
157 	int nbufs = 0;
158 
159 	if (!(cp->cache_flags & UMF_PTC))
160 		return (WALK_NEXT);
161 
162 	(void) mdb_snprintf(walk, sizeof (walk), "umem_ptc_%d",
163 	    cp->cache_bufsize);
164 
165 	if (mdb_pwalk(walk, (mdb_walk_cb_t)umastat_lwp_ptc,
166 	    &nbufs, (uintptr_t)ulwp->ul_self) == -1) {
167 		mdb_warn("unable to walk '%s'", walk);
168 		return (WALK_ERR);
169 	}
170 
171 	mdb_printf("%3d ", ulwp->ul_tmem.tm_size ?
172 	    (nbufs * cp->cache_bufsize * 100) / ulwp->ul_tmem.tm_size : 0);
173 
174 	return (WALK_NEXT);
175 }
176 
177 /*ARGSUSED*/
178 static int
179 umastat_lwp(uintptr_t addr, const ulwp_t *ulwp, void *ignored)
180 {
181 	size_t size;
182 	datafmt_t *dfp = ptcfmt;
183 
184 	mdb_printf((dfp++)->fmt, ulwp->ul_lwpid);
185 	mdb_printf((dfp++)->fmt, ulwp->ul_tmem.tm_size);
186 
187 	if (umem_readvar(&size, "umem_ptc_size") == -1) {
188 		mdb_warn("unable to read 'umem_ptc_size'");
189 		return (WALK_ERR);
190 	}
191 
192 	mdb_printf((dfp++)->fmt, (ulwp->ul_tmem.tm_size * 100) / size);
193 
194 	if (mdb_walk("umem_cache",
195 	    (mdb_walk_cb_t)umastat_lwp_cache, (void *)ulwp) == -1) {
196 		mdb_warn("can't walk 'umem_cache'");
197 		return (WALK_ERR);
198 	}
199 
200 	mdb_printf("\n");
201 
202 	return (WALK_NEXT);
203 }
204 
205 /*ARGSUSED*/
206 static int
207 umastat_cache_ptc(uintptr_t addr, const void *ignored, int *nptc)
208 {
209 	(*nptc)++;
210 	return (WALK_NEXT);
211 }
212 
213 static int
214 umastat_cache(uintptr_t addr, const umem_cache_t *cp, umastat_vmem_t **kvp)
215 {
216 	umastat_vmem_t *kv;
217 	datafmt_t *dfp = umemfmt;
218 	char buf[10];
219 	int magsize;
220 
221 	int avail, alloc, total, nptc = 0;
222 	size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) *
223 	    cp->cache_slabsize;
224 
225 	mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)umastat_cpu_avail;
226 	mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)umastat_cpu_alloc;
227 	mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)umastat_slab_avail;
228 
229 	magsize = umem_get_magsize(cp);
230 
231 	alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc;
232 	avail = cp->cache_full.ml_total * magsize;
233 	total = cp->cache_buftotal;
234 
235 	(void) mdb_pwalk("umem_cpu_cache", cpu_alloc, &alloc, addr);
236 	(void) mdb_pwalk("umem_cpu_cache", cpu_avail, &avail, addr);
237 	(void) mdb_pwalk("umem_slab_partial", slab_avail, &avail, addr);
238 
239 	if (cp->cache_flags & UMF_PTC) {
240 		char walk[60];
241 
242 		(void) mdb_snprintf(walk, sizeof (walk),
243 		    "umem_ptc_%d", cp->cache_bufsize);
244 
245 		if (mdb_walk(walk,
246 		    (mdb_walk_cb_t)umastat_cache_ptc, &nptc) == -1) {
247 			mdb_warn("unable to walk '%s'", walk);
248 			return (WALK_ERR);
249 		}
250 
251 		(void) mdb_snprintf(buf, sizeof (buf), "%d", nptc);
252 	}
253 
254 	for (kv = *kvp; kv != NULL; kv = kv->kv_next) {
255 		if (kv->kv_addr == (uintptr_t)cp->cache_arena)
256 			goto out;
257 	}
258 
259 	kv = mdb_zalloc(sizeof (umastat_vmem_t), UM_SLEEP | UM_GC);
260 	kv->kv_next = *kvp;
261 	kv->kv_addr = (uintptr_t)cp->cache_arena;
262 	*kvp = kv;
263 out:
264 	kv->kv_meminuse += meminuse;
265 	kv->kv_alloc += alloc;
266 	kv->kv_fail += cp->cache_alloc_fail;
267 
268 	mdb_printf((dfp++)->fmt, cp->cache_name);
269 	mdb_printf((dfp++)->fmt, cp->cache_bufsize);
270 	mdb_printf((dfp++)->fmt, total - avail);
271 	mdb_printf((dfp++)->fmt, cp->cache_flags & UMF_PTC ? buf : "-");
272 	mdb_printf((dfp++)->fmt, total);
273 	mdb_printf((dfp++)->fmt, meminuse);
274 	mdb_printf((dfp++)->fmt, alloc);
275 	mdb_printf((dfp++)->fmt, cp->cache_alloc_fail);
276 	mdb_printf("\n");
277 
278 	return (WALK_NEXT);
279 }
280 
281 static int
282 umastat_vmem_totals(uintptr_t addr, const vmem_t *v, umastat_vmem_t *kv)
283 {
284 	while (kv != NULL && kv->kv_addr != addr)
285 		kv = kv->kv_next;
286 
287 	if (kv == NULL || kv->kv_alloc == 0)
288 		return (WALK_NEXT);
289 
290 	mdb_printf("Total [%s]%*s %6s %7s %7s %7s %7H %9u %5u\n", v->vm_name,
291 	    17 - strlen(v->vm_name), "", "", "", "", "",
292 	    kv->kv_meminuse, kv->kv_alloc, kv->kv_fail);
293 
294 	return (WALK_NEXT);
295 }
296 
297 /*ARGSUSED*/
298 static int
299 umastat_vmem(uintptr_t addr, const vmem_t *v, void *ignored)
300 {
301 	datafmt_t *dfp = vmemfmt;
302 	uintptr_t paddr;
303 	vmem_t parent;
304 	int ident = 0;
305 
306 	for (paddr = (uintptr_t)v->vm_source; paddr != 0; ident += 4) {
307 		if (mdb_vread(&parent, sizeof (parent), paddr) == -1) {
308 			mdb_warn("couldn't trace %p's ancestry", addr);
309 			ident = 0;
310 			break;
311 		}
312 		paddr = (uintptr_t)parent.vm_source;
313 	}
314 
315 	mdb_printf("%*s", ident, "");
316 	mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name);
317 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_inuse);
318 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_total);
319 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_import);
320 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_alloc);
321 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_fail);
322 
323 	mdb_printf("\n");
324 
325 	return (WALK_NEXT);
326 }
327 
328 /*ARGSUSED*/
329 int
330 umastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
331 {
332 	umastat_vmem_t *kv = NULL;
333 	datafmt_t *dfp;
334 	int nptc = 0, i;
335 
336 	if (argc != 0)
337 		return (DCMD_USAGE);
338 
339 	/*
340 	 * We need to determine if we have any caches that have per-thread
341 	 * caching enabled.
342 	 */
343 	if (mdb_walk("umem_cache",
344 	    (mdb_walk_cb_t)umastat_cache_nptc, &nptc) == -1) {
345 		mdb_warn("can't walk 'umem_cache'");
346 		return (DCMD_ERR);
347 	}
348 
349 	if (nptc) {
350 		for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
351 			mdb_printf("%s ", dfp->hdr1);
352 
353 		for (i = 0; i < nptc; i++)
354 			mdb_printf("%s ", dfp->hdr1);
355 
356 		mdb_printf("\n");
357 
358 		for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
359 			mdb_printf("%s ", dfp->hdr2);
360 
361 		if (mdb_walk("umem_cache",
362 		    (mdb_walk_cb_t)umastat_cache_hdr, NULL) == -1) {
363 			mdb_warn("can't walk 'umem_cache'");
364 			return (DCMD_ERR);
365 		}
366 
367 		mdb_printf("\n");
368 
369 		for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
370 			mdb_printf("%s ", dfp->dashes);
371 
372 		for (i = 0; i < nptc; i++)
373 			mdb_printf("%s ", dfp->dashes);
374 
375 		mdb_printf("\n");
376 
377 		if (mdb_walk("ulwp", (mdb_walk_cb_t)umastat_lwp, NULL) == -1) {
378 			mdb_warn("can't walk 'ulwp'");
379 			return (DCMD_ERR);
380 		}
381 
382 		mdb_printf("\n");
383 	}
384 
385 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
386 		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr1);
387 	mdb_printf("\n");
388 
389 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
390 		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr2);
391 	mdb_printf("\n");
392 
393 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
394 		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes);
395 	mdb_printf("\n");
396 
397 	if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache, &kv) == -1) {
398 		mdb_warn("can't walk 'umem_cache'");
399 		return (DCMD_ERR);
400 	}
401 
402 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
403 		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes);
404 	mdb_printf("\n");
405 
406 	if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem_totals, kv) == -1) {
407 		mdb_warn("can't walk 'vmem'");
408 		return (DCMD_ERR);
409 	}
410 
411 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
412 		mdb_printf("%s ", dfp->dashes);
413 	mdb_printf("\n");
414 
415 	mdb_printf("\n");
416 
417 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
418 		mdb_printf("%s ", dfp->hdr1);
419 	mdb_printf("\n");
420 
421 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
422 		mdb_printf("%s ", dfp->hdr2);
423 	mdb_printf("\n");
424 
425 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
426 		mdb_printf("%s ", dfp->dashes);
427 	mdb_printf("\n");
428 
429 	if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem, NULL) == -1) {
430 		mdb_warn("can't walk 'vmem'");
431 		return (DCMD_ERR);
432 	}
433 
434 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
435 		mdb_printf("%s ", dfp->dashes);
436 	mdb_printf("\n");
437 	return (DCMD_OK);
438 }
439 
440 /*
441  * kmdb doesn't use libproc, and thus doesn't have any prmap_t's to walk.
442  * We have other ways to grep kmdb's address range.
443  */
444 #ifndef _KMDB
445 
446 typedef struct ugrep_walk_data {
447 	kgrep_cb_func *ug_cb;
448 	void *ug_cbdata;
449 } ugrep_walk_data_t;
450 
451 /*ARGSUSED*/
452 int
453 ugrep_mapping_cb(uintptr_t addr, const void *prm_arg, void *data)
454 {
455 	ugrep_walk_data_t *ug = data;
456 	const prmap_t *prm = prm_arg;
457 
458 	return (ug->ug_cb(prm->pr_vaddr, prm->pr_vaddr + prm->pr_size,
459 	    ug->ug_cbdata));
460 }
461 
462 int
463 kgrep_subr(kgrep_cb_func *cb, void *cbdata)
464 {
465 	ugrep_walk_data_t ug;
466 
467 	prockludge_add_walkers();
468 
469 	ug.ug_cb = cb;
470 	ug.ug_cbdata = cbdata;
471 
472 	if (mdb_walk(KLUDGE_MAPWALK_NAME, ugrep_mapping_cb, &ug) == -1) {
473 		mdb_warn("Unable to walk "KLUDGE_MAPWALK_NAME);
474 		return (DCMD_ERR);
475 	}
476 
477 	prockludge_remove_walkers();
478 	return (DCMD_OK);
479 }
480 
481 size_t
482 kgrep_subr_pagesize(void)
483 {
484 	return (PAGESIZE);
485 }
486 
487 #endif /* !_KMDB */
488 
489 static const mdb_dcmd_t dcmds[] = {
490 
491 	/* from libumem.c */
492 	{ "umastat", NULL, "umem allocator stats", umastat },
493 
494 	/* from misc.c */
495 	{ "umem_debug", NULL, "toggle umem dcmd/walk debugging", umem_debug},
496 
497 	/* from umem.c */
498 	{ "umem_status", NULL, "Print umem status and message buffer",
499 		umem_status },
500 	{ "allocdby", ":", "given a thread, print its allocated buffers",
501 		allocdby },
502 	{ "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] "
503 		"[-t thd]", "print or filter a bufctl", bufctl, bufctl_help },
504 	{ "bufctl_audit", ":", "print a bufctl_audit", bufctl_audit },
505 	{ "freedby", ":", "given a thread, print its freed buffers", freedby },
506 	{ "umalog", "[ fail | slab ]",
507 	    "display umem transaction log and stack traces", umalog },
508 	{ "umausers", "[-ef] [cache ...]", "display current medium and large "
509 		"users of the umem allocator", umausers },
510 	{ "umem_cache", "?", "print a umem cache", umem_cache },
511 	{ "umem_log", "?", "dump umem transaction log", umem_log },
512 	{ "umem_malloc_dist", "[-dg] [-b maxbins] [-B minbinsize]",
513 		"report distribution of outstanding malloc()s",
514 		umem_malloc_dist, umem_malloc_dist_help },
515 	{ "umem_malloc_info", "?[-dg] [-b maxbins] [-B minbinsize]",
516 		"report information about malloc()s by cache",
517 		umem_malloc_info, umem_malloc_info_help },
518 	{ "umem_verify", "?", "check integrity of umem-managed memory",
519 		umem_verify },
520 	{ "vmem", "?", "print a vmem_t", vmem },
521 	{ "vmem_seg", ":[-sv] [-c caller] [-e earliest] [-l latest] "
522 		"[-m minsize] [-M maxsize] [-t thread] [-T type]",
523 		"print or filter a vmem_seg", vmem_seg, vmem_seg_help },
524 
525 #ifndef _KMDB
526 	/* from ../genunix/kgrep.c + libumem.c */
527 	{ "ugrep", KGREP_USAGE, "search user address space for a pointer",
528 	    kgrep, kgrep_help },
529 
530 	/* from ../genunix/leaky.c + leaky_subr.c */
531 	{ "findleaks", FINDLEAKS_USAGE, "search for potential memory leaks",
532 	    findleaks, findleaks_help },
533 #endif
534 
535 	{ NULL }
536 };
537 
538 static const mdb_walker_t walkers[] = {
539 
540 	/* from umem.c */
541 	{ "allocdby", "given a thread, walk its allocated bufctls",
542 		allocdby_walk_init, allocdby_walk_step, allocdby_walk_fini },
543 	{ "bufctl", "walk a umem cache's bufctls",
544 		bufctl_walk_init, umem_walk_step, umem_walk_fini },
545 	{ "bufctl_history", "walk the available history of a bufctl",
546 		bufctl_history_walk_init, bufctl_history_walk_step,
547 		bufctl_history_walk_fini },
548 	{ "freectl", "walk a umem cache's free bufctls",
549 		freectl_walk_init, umem_walk_step, umem_walk_fini },
550 	{ "freedby", "given a thread, walk its freed bufctls",
551 		freedby_walk_init, allocdby_walk_step, allocdby_walk_fini },
552 	{ "freemem", "walk a umem cache's free memory",
553 		freemem_walk_init, umem_walk_step, umem_walk_fini },
554 	{ "umem", "walk a umem cache",
555 		umem_walk_init, umem_walk_step, umem_walk_fini },
556 	{ "umem_cpu", "walk the umem CPU structures",
557 		umem_cpu_walk_init, umem_cpu_walk_step, umem_cpu_walk_fini },
558 	{ "umem_cpu_cache", "given a umem cache, walk its per-CPU caches",
559 		umem_cpu_cache_walk_init, umem_cpu_cache_walk_step, NULL },
560 	{ "umem_hash", "given a umem cache, walk its allocated hash table",
561 		umem_hash_walk_init, umem_hash_walk_step, umem_hash_walk_fini },
562 	{ "umem_log", "walk the umem transaction log",
563 		umem_log_walk_init, umem_log_walk_step, umem_log_walk_fini },
564 	{ "umem_slab", "given a umem cache, walk its slabs",
565 		umem_slab_walk_init, umem_slab_walk_step, NULL },
566 	{ "umem_slab_partial",
567 	    "given a umem cache, walk its partially allocated slabs (min 1)",
568 		umem_slab_walk_partial_init, umem_slab_walk_step, NULL },
569 	{ "vmem", "walk vmem structures in pre-fix, depth-first order",
570 		vmem_walk_init, vmem_walk_step, vmem_walk_fini },
571 	{ "vmem_alloc", "given a vmem_t, walk its allocated vmem_segs",
572 		vmem_alloc_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
573 	{ "vmem_free", "given a vmem_t, walk its free vmem_segs",
574 		vmem_free_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
575 	{ "vmem_postfix", "walk vmem structures in post-fix, depth-first order",
576 		vmem_walk_init, vmem_postfix_walk_step, vmem_walk_fini },
577 	{ "vmem_seg", "given a vmem_t, walk all of its vmem_segs",
578 		vmem_seg_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
579 	{ "vmem_span", "given a vmem_t, walk its spanning vmem_segs",
580 		vmem_span_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
581 
582 #ifndef _KMDB
583 	/* from ../genunix/leaky.c + leaky_subr.c */
584 	{ "leak", "given a leak ctl, walk other leaks w/ that stacktrace",
585 		leaky_walk_init, leaky_walk_step, leaky_walk_fini },
586 	{ "leakbuf", "given a leak ctl, walk addr of leaks w/ that stacktrace",
587 		leaky_walk_init, leaky_buf_walk_step, leaky_walk_fini },
588 #endif
589 
590 	{ NULL }
591 };
592 
593 static const mdb_modinfo_t modinfo = {MDB_API_VERSION, dcmds, walkers};
594 
595 const mdb_modinfo_t *
596 _mdb_init(void)
597 {
598 	if (umem_init() != 0)
599 		return (NULL);
600 
601 	return (&modinfo);
602 }
603 
604 void
605 _mdb_fini(void)
606 {
607 #ifndef _KMDB
608 	leaky_cleanup(1);
609 #endif
610 }
611