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