xref: /illumos-gate/usr/src/cmd/mdb/common/modules/libumem/libumem.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include "umem.h"
30 #include <libproc.h>
31 #include <mdb/mdb_modapi.h>
32 
33 #include "kgrep.h"
34 #include "leaky.h"
35 #include "misc.h"
36 #include "proc_kludges.h"
37 
38 #include <umem_impl.h>
39 #include <sys/vmem_impl_user.h>
40 
41 #include "umem_pagesize.h"
42 
43 typedef struct datafmt {
44 	char	*hdr1;
45 	char	*hdr2;
46 	char	*dashes;
47 	char	*fmt;
48 } datafmt_t;
49 
50 static datafmt_t umemfmt[] = {
51 	{ "cache                    ", "name                     ",
52 	"-------------------------", "%-25s "				},
53 	{ "   buf",	"  size",	"------",	"%6u "		},
54 	{ "   buf",	"in use",	"------",	"%6u "		},
55 	{ "   buf",	" total",	"------",	"%6u "		},
56 	{ "   memory",	"   in use",	"---------",	"%9u "		},
57 	{ "    alloc",	"  succeed",	"---------",	"%9u "		},
58 	{ "alloc",	" fail",	"-----",	"%5llu "	},
59 	{ NULL,		NULL,		NULL,		NULL		}
60 };
61 
62 static datafmt_t vmemfmt[] = {
63 	{ "vmem                     ", "name                     ",
64 	"-------------------------", "%-*s "				},
65 	{ "   memory",	"   in use",	"---------",	"%9llu "	},
66 	{ "    memory",	"     total",	"----------",	"%10llu "	},
67 	{ "   memory",	"   import",	"---------",	"%9llu "	},
68 	{ "    alloc",	"  succeed",	"---------",	"%9llu "	},
69 	{ "alloc",	" fail",	"-----",	"%5llu "	},
70 	{ NULL,		NULL,		NULL,		NULL		}
71 };
72 
73 /*ARGSUSED*/
74 static int
75 umastat_cpu_avail(uintptr_t addr, const umem_cpu_cache_t *ccp, int *avail)
76 {
77 	if (ccp->cc_rounds > 0)
78 		*avail += ccp->cc_rounds;
79 	if (ccp->cc_prounds > 0)
80 		*avail += ccp->cc_prounds;
81 
82 	return (WALK_NEXT);
83 }
84 
85 /*ARGSUSED*/
86 static int
87 umastat_cpu_alloc(uintptr_t addr, const umem_cpu_cache_t *ccp, int *alloc)
88 {
89 	*alloc += ccp->cc_alloc;
90 
91 	return (WALK_NEXT);
92 }
93 
94 /*ARGSUSED*/
95 static int
96 umastat_slab_avail(uintptr_t addr, const umem_slab_t *sp, int *avail)
97 {
98 	*avail += sp->slab_chunks - sp->slab_refcnt;
99 
100 	return (WALK_NEXT);
101 }
102 
103 typedef struct umastat_vmem {
104 	uintptr_t kv_addr;
105 	struct umastat_vmem *kv_next;
106 	int kv_meminuse;
107 	int kv_alloc;
108 	int kv_fail;
109 } umastat_vmem_t;
110 
111 static int
112 umastat_cache(uintptr_t addr, const umem_cache_t *cp, umastat_vmem_t **kvp)
113 {
114 	umastat_vmem_t *kv;
115 	datafmt_t *dfp = umemfmt;
116 	int magsize;
117 
118 	int avail, alloc, total;
119 	size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) *
120 	    cp->cache_slabsize;
121 
122 	mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)umastat_cpu_avail;
123 	mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)umastat_cpu_alloc;
124 	mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)umastat_slab_avail;
125 
126 	magsize = umem_get_magsize(cp);
127 
128 	alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc;
129 	avail = cp->cache_full.ml_total * magsize;
130 	total = cp->cache_buftotal;
131 
132 	(void) mdb_pwalk("umem_cpu_cache", cpu_alloc, &alloc, addr);
133 	(void) mdb_pwalk("umem_cpu_cache", cpu_avail, &avail, addr);
134 	(void) mdb_pwalk("umem_slab_partial", slab_avail, &avail, addr);
135 
136 	for (kv = *kvp; kv != NULL; kv = kv->kv_next) {
137 		if (kv->kv_addr == (uintptr_t)cp->cache_arena)
138 			goto out;
139 	}
140 
141 	kv = mdb_zalloc(sizeof (umastat_vmem_t), UM_SLEEP | UM_GC);
142 	kv->kv_next = *kvp;
143 	kv->kv_addr = (uintptr_t)cp->cache_arena;
144 	*kvp = kv;
145 out:
146 	kv->kv_meminuse += meminuse;
147 	kv->kv_alloc += alloc;
148 	kv->kv_fail += cp->cache_alloc_fail;
149 
150 	mdb_printf((dfp++)->fmt, cp->cache_name);
151 	mdb_printf((dfp++)->fmt, cp->cache_bufsize);
152 	mdb_printf((dfp++)->fmt, total - avail);
153 	mdb_printf((dfp++)->fmt, total);
154 	mdb_printf((dfp++)->fmt, meminuse);
155 	mdb_printf((dfp++)->fmt, alloc);
156 	mdb_printf((dfp++)->fmt, cp->cache_alloc_fail);
157 	mdb_printf("\n");
158 
159 	return (WALK_NEXT);
160 }
161 
162 static int
163 umastat_vmem_totals(uintptr_t addr, const vmem_t *v, umastat_vmem_t *kv)
164 {
165 	while (kv != NULL && kv->kv_addr != addr)
166 		kv = kv->kv_next;
167 
168 	if (kv == NULL || kv->kv_alloc == 0)
169 		return (WALK_NEXT);
170 
171 	mdb_printf("Total [%s]%*s %6s %6s %6s %9u %9u %5u\n", v->vm_name,
172 	    17 - strlen(v->vm_name), "", "", "", "",
173 	    kv->kv_meminuse, kv->kv_alloc, kv->kv_fail);
174 
175 	return (WALK_NEXT);
176 }
177 
178 /*ARGSUSED*/
179 static int
180 umastat_vmem(uintptr_t addr, const vmem_t *v, void *ignored)
181 {
182 	datafmt_t *dfp = vmemfmt;
183 	uintptr_t paddr;
184 	vmem_t parent;
185 	int ident = 0;
186 
187 	for (paddr = (uintptr_t)v->vm_source; paddr != NULL; ident += 4) {
188 		if (mdb_vread(&parent, sizeof (parent), paddr) == -1) {
189 			mdb_warn("couldn't trace %p's ancestry", addr);
190 			ident = 0;
191 			break;
192 		}
193 		paddr = (uintptr_t)parent.vm_source;
194 	}
195 
196 	mdb_printf("%*s", ident, "");
197 	mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name);
198 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_inuse);
199 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_total);
200 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_import);
201 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_alloc);
202 	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_fail);
203 
204 	mdb_printf("\n");
205 
206 	return (WALK_NEXT);
207 }
208 
209 /*ARGSUSED*/
210 int
211 umastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
212 {
213 	umastat_vmem_t *kv = NULL;
214 	datafmt_t *dfp;
215 
216 	if (argc != 0)
217 		return (DCMD_USAGE);
218 
219 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
220 		mdb_printf("%s ", dfp->hdr1);
221 	mdb_printf("\n");
222 
223 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
224 		mdb_printf("%s ", dfp->hdr2);
225 	mdb_printf("\n");
226 
227 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
228 		mdb_printf("%s ", dfp->dashes);
229 	mdb_printf("\n");
230 
231 	if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache, &kv) == -1) {
232 		mdb_warn("can't walk 'umem_cache'");
233 		return (DCMD_ERR);
234 	}
235 
236 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
237 		mdb_printf("%s ", dfp->dashes);
238 	mdb_printf("\n");
239 
240 	if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem_totals, kv) == -1) {
241 		mdb_warn("can't walk 'vmem'");
242 		return (DCMD_ERR);
243 	}
244 
245 	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
246 		mdb_printf("%s ", dfp->dashes);
247 	mdb_printf("\n");
248 
249 	mdb_printf("\n");
250 
251 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
252 		mdb_printf("%s ", dfp->hdr1);
253 	mdb_printf("\n");
254 
255 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
256 		mdb_printf("%s ", dfp->hdr2);
257 	mdb_printf("\n");
258 
259 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
260 		mdb_printf("%s ", dfp->dashes);
261 	mdb_printf("\n");
262 
263 	if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem, NULL) == -1) {
264 		mdb_warn("can't walk 'vmem'");
265 		return (DCMD_ERR);
266 	}
267 
268 	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
269 		mdb_printf("%s ", dfp->dashes);
270 	mdb_printf("\n");
271 	return (DCMD_OK);
272 }
273 
274 /*
275  * kmdb doesn't use libproc, and thus doesn't have any prmap_t's to walk.
276  * We have other ways to grep kmdb's address range.
277  */
278 #ifndef _KMDB
279 
280 typedef struct ugrep_walk_data {
281 	kgrep_cb_func *ug_cb;
282 	void *ug_cbdata;
283 } ugrep_walk_data_t;
284 
285 /*ARGSUSED*/
286 int
287 ugrep_mapping_cb(uintptr_t addr, const void *prm_arg, void *data)
288 {
289 	ugrep_walk_data_t *ug = data;
290 	const prmap_t *prm = prm_arg;
291 
292 	return (ug->ug_cb(prm->pr_vaddr, prm->pr_vaddr + prm->pr_size,
293 	    ug->ug_cbdata));
294 }
295 
296 int
297 kgrep_subr(kgrep_cb_func *cb, void *cbdata)
298 {
299 	ugrep_walk_data_t ug;
300 
301 	prockludge_add_walkers();
302 
303 	ug.ug_cb = cb;
304 	ug.ug_cbdata = cbdata;
305 
306 	if (mdb_walk(KLUDGE_MAPWALK_NAME, ugrep_mapping_cb, &ug) == -1) {
307 		mdb_warn("Unable to walk "KLUDGE_MAPWALK_NAME);
308 		return (DCMD_ERR);
309 	}
310 
311 	prockludge_remove_walkers();
312 	return (DCMD_OK);
313 }
314 
315 size_t
316 kgrep_subr_pagesize(void)
317 {
318 	return (PAGESIZE);
319 }
320 
321 #endif /* !_KMDB */
322 
323 static const mdb_dcmd_t dcmds[] = {
324 
325 	/* from libumem.c */
326 	{ "umastat", NULL, "umem allocator stats", umastat },
327 
328 	/* from misc.c */
329 	{ "umem_debug", NULL, "toggle umem dcmd/walk debugging", umem_debug},
330 
331 	/* from umem.c */
332 	{ "umem_status", NULL, "Print umem status and message buffer",
333 		umem_status },
334 	{ "allocdby", ":", "given a thread, print its allocated buffers",
335 		allocdby },
336 	{ "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] "
337 		"[-t thd]", "print or filter a bufctl", bufctl, bufctl_help },
338 	{ "bufctl_audit", ":", "print a bufctl_audit", bufctl_audit },
339 	{ "freedby", ":", "given a thread, print its freed buffers", freedby },
340 	{ "umalog", "[ fail | slab ]",
341 	    "display umem transaction log and stack traces", umalog },
342 	{ "umausers", "[-ef] [cache ...]", "display current medium and large "
343 		"users of the umem allocator", umausers },
344 	{ "umem_cache", "?", "print a umem cache", umem_cache },
345 	{ "umem_log", "?", "dump umem transaction log", umem_log },
346 	{ "umem_verify", "?", "check integrity of umem-managed memory",
347 		umem_verify },
348 	{ "vmem", "?", "print a vmem_t", vmem },
349 	{ "vmem_seg", ":[-sv] [-c caller] [-e earliest] [-l latest] "
350 		"[-m minsize] [-M maxsize] [-t thread] [-T type]",
351 		"print or filter a vmem_seg", vmem_seg, vmem_seg_help },
352 	{ "whatis", ":[-abv]", "given an address, return information", whatis },
353 
354 #ifndef _KMDB
355 	/* from ../genunix/kgrep.c + libumem.c */
356 	{ "ugrep", KGREP_USAGE, "search user address space for a pointer",
357 	    kgrep },
358 
359 	/* from ../genunix/leaky.c + leaky_subr.c */
360 	{ "findleaks", FINDLEAKS_USAGE, "search for potential memory leaks",
361 	    findleaks, findleaks_help },
362 #endif
363 
364 	{ NULL }
365 };
366 
367 static const mdb_walker_t walkers[] = {
368 
369 	/* from umem.c */
370 	{ "allocdby", "given a thread, walk its allocated bufctls",
371 		allocdby_walk_init, allocdby_walk_step, allocdby_walk_fini },
372 	{ "bufctl", "walk a umem cache's bufctls",
373 		bufctl_walk_init, umem_walk_step, umem_walk_fini },
374 	{ "bufctl_history", "walk the available history of a bufctl",
375 		bufctl_history_walk_init, bufctl_history_walk_step,
376 		bufctl_history_walk_fini },
377 	{ "freectl", "walk a umem cache's free bufctls",
378 		freectl_walk_init, umem_walk_step, umem_walk_fini },
379 	{ "freedby", "given a thread, walk its freed bufctls",
380 		freedby_walk_init, allocdby_walk_step, allocdby_walk_fini },
381 	{ "freemem", "walk a umem cache's free memory",
382 		freemem_walk_init, umem_walk_step, umem_walk_fini },
383 	{ "umem", "walk a umem cache",
384 		umem_walk_init, umem_walk_step, umem_walk_fini },
385 	{ "umem_cpu", "walk the umem CPU structures",
386 		umem_cpu_walk_init, umem_cpu_walk_step, umem_cpu_walk_fini },
387 	{ "umem_cpu_cache", "given a umem cache, walk its per-CPU caches",
388 		umem_cpu_cache_walk_init, umem_cpu_cache_walk_step, NULL },
389 	{ "umem_hash", "given a umem cache, walk its allocated hash table",
390 		umem_hash_walk_init, umem_hash_walk_step, umem_hash_walk_fini },
391 	{ "umem_log", "walk the umem transaction log",
392 		umem_log_walk_init, umem_log_walk_step, umem_log_walk_fini },
393 	{ "umem_slab", "given a umem cache, walk its slabs",
394 		umem_slab_walk_init, umem_slab_walk_step, NULL },
395 	{ "umem_slab_partial",
396 	    "given a umem cache, walk its partially allocated slabs (min 1)",
397 		umem_slab_walk_partial_init, umem_slab_walk_step, NULL },
398 	{ "vmem", "walk vmem structures in pre-fix, depth-first order",
399 		vmem_walk_init, vmem_walk_step, vmem_walk_fini },
400 	{ "vmem_alloc", "given a vmem_t, walk its allocated vmem_segs",
401 		vmem_alloc_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
402 	{ "vmem_free", "given a vmem_t, walk its free vmem_segs",
403 		vmem_free_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
404 	{ "vmem_postfix", "walk vmem structures in post-fix, depth-first order",
405 		vmem_walk_init, vmem_postfix_walk_step, vmem_walk_fini },
406 	{ "vmem_seg", "given a vmem_t, walk all of its vmem_segs",
407 		vmem_seg_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
408 	{ "vmem_span", "given a vmem_t, walk its spanning vmem_segs",
409 		vmem_span_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
410 
411 #ifndef _KMDB
412 	/* from ../genunix/leaky.c + leaky_subr.c */
413 	{ "leak", "given a leak ctl, walk other leaks w/ that stacktrace",
414 		leaky_walk_init, leaky_walk_step, leaky_walk_fini },
415 	{ "leakbuf", "given a leak ctl, walk addr of leaks w/ that stacktrace",
416 		leaky_walk_init, leaky_buf_walk_step, leaky_walk_fini },
417 #endif
418 
419 	{ NULL }
420 };
421 
422 static const mdb_modinfo_t modinfo = {MDB_API_VERSION, dcmds, walkers};
423 
424 const mdb_modinfo_t *
425 _mdb_init(void)
426 {
427 	mdb_walker_t w = {
428 		"umem_cache", "walk list of umem caches", umem_cache_walk_init,
429 		umem_cache_walk_step, umem_cache_walk_fini
430 	};
431 
432 	if (umem_init() != 0)
433 		return (NULL);
434 
435 	/*
436 	 * Load the umem_cache walker manually, and then invoke it to add
437 	 * named walks for each cache.  This walker needs to be added by
438 	 * hand since walkers in the linkage structure cannot be loaded
439 	 * until _mdb_init returns the pointer to the linkage structure.
440 	 */
441 	if (mdb_add_walker(&w) == 0) {
442 		(void) mdb_walk("umem_cache", (mdb_walk_cb_t)
443 		    umem_init_walkers, NULL);
444 	} else
445 		mdb_warn("failed to add umem_cache walker");
446 
447 	return (&modinfo);
448 }
449 
450 void
451 _mdb_fini(void)
452 {
453 #ifndef _KMDB
454 	leaky_cleanup(1);
455 #endif
456 }
457