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