xref: /illumos-gate/usr/src/cmd/mdb/common/modules/genunix/rctl.c (revision 892ad1623e11186cba8b2eb40d70318d2cb89605)
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 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <mdb/mdb_modapi.h>
28 #include <sys/rctl.h>
29 #include <sys/proc.h>
30 #include <sys/task.h>
31 #include <sys/project.h>
32 #include <sys/zone.h>
33 
34 static int
print_val(uintptr_t addr,rctl_val_t * val,uintptr_t * enforced)35 print_val(uintptr_t addr, rctl_val_t *val, uintptr_t *enforced)
36 {
37 	char *priv;
38 	static const mdb_bitmask_t val_localflag_bits[] = {
39 		{ "SIGNAL", RCTL_LOCAL_SIGNAL, RCTL_LOCAL_SIGNAL },
40 		{ "DENY", RCTL_LOCAL_DENY, RCTL_LOCAL_DENY },
41 		{ "MAX", RCTL_LOCAL_MAXIMAL, RCTL_LOCAL_MAXIMAL },
42 		{ NULL, 0, 0 }
43 	};
44 
45 	switch (val->rcv_privilege) {
46 	case (RCPRIV_BASIC):
47 		priv = "basic";
48 		break;
49 	case (RCPRIV_PRIVILEGED):
50 		priv = "privileged";
51 		break;
52 	case (RCPRIV_SYSTEM):
53 		priv = "system";
54 		break;
55 	default:
56 		priv = "???";
57 		break;
58 	};
59 
60 	mdb_printf("\t%s ", addr == *enforced ? "(cur)": "     ");
61 
62 	mdb_printf("%-#18llx %11s\tflags=<%b>\n",
63 	    val->rcv_value, priv, val->rcv_flagaction, val_localflag_bits);
64 
65 	return (WALK_NEXT);
66 }
67 
68 int
rctl(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)69 rctl(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
70 {
71 	rctl_t rctl;
72 	rctl_dict_entry_t dict;
73 	char name[256];
74 	rctl_hndl_t hndl;
75 
76 	if (!(flags & DCMD_ADDRSPEC))
77 		return (DCMD_USAGE);
78 
79 	if (mdb_vread(&rctl, sizeof (rctl_t), addr) == -1) {
80 		mdb_warn("failed to read rctl_t structure at %p", addr);
81 		return (DCMD_ERR);
82 	}
83 
84 	if (argc != 0) {
85 		const mdb_arg_t *argp = &argv[0];
86 
87 		if (argp->a_type == MDB_TYPE_IMMEDIATE)
88 			hndl = (rctl_hndl_t)argp->a_un.a_val;
89 		else
90 			hndl = (rctl_hndl_t)mdb_strtoull(argp->a_un.a_str);
91 
92 		if (rctl.rc_id != hndl)
93 			return (DCMD_OK);
94 	}
95 
96 	if (mdb_vread(&dict, sizeof (rctl_dict_entry_t),
97 	    (uintptr_t)rctl.rc_dict_entry) == -1) {
98 		mdb_warn("failed to read dict entry for rctl_t %p at %p",
99 		    addr, rctl.rc_dict_entry);
100 		return (DCMD_ERR);
101 	}
102 
103 	if (mdb_readstr(name, 256, (uintptr_t)(dict.rcd_name)) == -1) {
104 		mdb_warn("failed to read name for rctl_t %p", addr);
105 		return (DCMD_ERR);
106 	}
107 
108 	mdb_printf("%0?p\t%3d : %s\n", addr, rctl.rc_id, name);
109 
110 	if (mdb_pwalk("rctl_val", (mdb_walk_cb_t)print_val, &(rctl.rc_cursor),
111 	    addr) == -1) {
112 		mdb_warn("failed to walk all values for rctl_t %p", addr);
113 		return (DCMD_ERR);
114 	}
115 
116 	return (DCMD_OK);
117 }
118 
119 int
rctl_dict(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)120 rctl_dict(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
121 {
122 	rctl_dict_entry_t dict;
123 	char name[256], *type = NULL;
124 
125 	if (!(flags & DCMD_ADDRSPEC)) {
126 		if (mdb_walk_dcmd("rctl_dict_list", "rctl_dict", argc,
127 		    argv) == -1) {
128 			mdb_warn("failed to walk 'rctl_dict_list'");
129 			return (DCMD_ERR);
130 		}
131 		return (DCMD_OK);
132 	}
133 
134 	if (DCMD_HDRSPEC(flags))
135 		mdb_printf("%<u>%2s %-27s %?s %7s %s%</u>\n",
136 		    "ID", "NAME", "ADDR", "TYPE", "GLOBAL_FLAGS");
137 
138 	if (mdb_vread(&dict, sizeof (dict), addr) == -1) {
139 		mdb_warn("failed to read rctl_dict at %p", addr);
140 		return (DCMD_ERR);
141 	}
142 	if (mdb_readstr(name, 256, (uintptr_t)(dict.rcd_name)) == -1) {
143 		mdb_warn("failed to read rctl_dict name for %p", addr);
144 		return (DCMD_ERR);
145 	}
146 
147 	switch (dict.rcd_entity) {
148 	case RCENTITY_PROCESS:
149 		type = "process";
150 		break;
151 	case RCENTITY_TASK:
152 		type = "task";
153 		break;
154 	case RCENTITY_PROJECT:
155 		type = "project";
156 		break;
157 	case RCENTITY_ZONE:
158 		type = "zone";
159 		break;
160 	default:
161 		type = "unknown";
162 		break;
163 	}
164 
165 	mdb_printf("%2d %-27s %0?p %7s 0x%08x", dict.rcd_id, name, addr,
166 	    type, dict.rcd_flagaction);
167 
168 	return (DCMD_OK);
169 }
170 
171 typedef struct dict_data {
172 	rctl_hndl_t hndl;
173 	uintptr_t dict_addr;
174 	rctl_entity_t type;
175 } dict_data_t;
176 
177 static int
hndl2dict(uintptr_t addr,rctl_dict_entry_t * entry,dict_data_t * data)178 hndl2dict(uintptr_t addr, rctl_dict_entry_t *entry, dict_data_t *data)
179 {
180 	if (data->hndl == entry->rcd_id) {
181 		data->dict_addr = addr;
182 		data->type = entry->rcd_entity;
183 		return (WALK_DONE);
184 	}
185 
186 	return (WALK_NEXT);
187 }
188 
189 /*
190  * Print out all project, task, and process rctls for a given process.
191  * If a handle is specified, print only the rctl matching that handle
192  * for the process.
193  */
194 int
rctl_list(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)195 rctl_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
196 {
197 	proc_t proc;
198 	uintptr_t set;
199 	task_t task;
200 	kproject_t proj;
201 	zone_t zone;
202 	dict_data_t rdict;
203 	int i;
204 
205 	rdict.dict_addr = 0;
206 
207 	if (!(flags & DCMD_ADDRSPEC))
208 		return (DCMD_USAGE);
209 
210 	if (argc == 0)
211 		rdict.hndl = 0;
212 	else if (argc == 1) {
213 		/*
214 		 * User specified a handle. Go find the rctl_dict_entity_t
215 		 * structure so we know what type of rctl to look for.
216 		 */
217 		const mdb_arg_t *argp = &argv[0];
218 
219 		if (argp->a_type == MDB_TYPE_IMMEDIATE)
220 			rdict.hndl = (rctl_hndl_t)argp->a_un.a_val;
221 		else
222 			rdict.hndl =
223 			    (rctl_hndl_t)mdb_strtoull(argp->a_un.a_str);
224 
225 		if (mdb_walk("rctl_dict_list", (mdb_walk_cb_t)hndl2dict,
226 		    &rdict) == -1) {
227 			mdb_warn("failed to walk rctl_dict_list");
228 			return (DCMD_ERR);
229 		}
230 		/* Couldn't find a rctl_dict_entry_t for this handle */
231 		if (rdict.dict_addr == 0)
232 			return (DCMD_ERR);
233 	} else
234 		return (DCMD_USAGE);
235 
236 
237 	if (mdb_vread(&proc, sizeof (proc_t), addr) == -1) {
238 		mdb_warn("failed to read proc at %p", addr);
239 		return (DCMD_ERR);
240 	}
241 	if (mdb_vread(&zone, sizeof (zone_t), (uintptr_t)proc.p_zone) == -1) {
242 		mdb_warn("failed to read zone at %p", proc.p_zone);
243 		return (DCMD_ERR);
244 	}
245 	if (mdb_vread(&task, sizeof (task_t), (uintptr_t)proc.p_task) == -1) {
246 		mdb_warn("failed to read task at %p", proc.p_task);
247 		return (DCMD_ERR);
248 	}
249 	if (mdb_vread(&proj, sizeof (kproject_t),
250 	    (uintptr_t)task.tk_proj) == -1) {
251 		mdb_warn("failed to read proj at %p", task.tk_proj);
252 		return (DCMD_ERR);
253 	}
254 
255 	for (i = 0; i <= RC_MAX_ENTITY; i++) {
256 		/*
257 		 * If user didn't specify a handle, print rctls for all
258 		 * types. Otherwise, we can walk the rctl_set for only the
259 		 * entity specified by the handle.
260 		 */
261 		if (rdict.hndl != 0 && rdict.type != i)
262 			continue;
263 
264 		switch (i) {
265 		case (RCENTITY_PROCESS):
266 			set = (uintptr_t)proc.p_rctls;
267 			break;
268 		case (RCENTITY_TASK):
269 			set = (uintptr_t)task.tk_rctls;
270 			break;
271 		case (RCENTITY_PROJECT):
272 			set = (uintptr_t)proj.kpj_rctls;
273 			break;
274 		case (RCENTITY_ZONE):
275 			set = (uintptr_t)zone.zone_rctls;
276 			break;
277 		default:
278 			mdb_warn("Unknown rctl type %d", i);
279 			return (DCMD_ERR);
280 		}
281 
282 		if (mdb_pwalk_dcmd("rctl_set", "rctl", argc, argv, set) == -1) {
283 			mdb_warn("failed to walk rctls in set %p", set);
284 			return (DCMD_ERR);
285 		}
286 	}
287 
288 	return (DCMD_OK);
289 }
290 
291 typedef struct dict_walk_data {
292 	int num_dicts;
293 	int num_cur;
294 	rctl_dict_entry_t **curdict;
295 } dict_walk_data_t;
296 
297 int
rctl_dict_walk_init(mdb_walk_state_t * wsp)298 rctl_dict_walk_init(mdb_walk_state_t *wsp)
299 {
300 	uintptr_t ptr;
301 	int nlists;
302 	GElf_Sym sym;
303 	rctl_dict_entry_t **dicts;
304 	dict_walk_data_t *dwd;
305 
306 	if (mdb_lookup_by_name("rctl_lists", &sym) == -1) {
307 		mdb_warn("failed to find 'rctl_lists'\n");
308 		return (WALK_ERR);
309 	}
310 
311 	nlists = sym.st_size / sizeof (rctl_dict_entry_t *);
312 	ptr = (uintptr_t)sym.st_value;
313 
314 	dicts = mdb_alloc(nlists * sizeof (rctl_dict_entry_t *), UM_SLEEP);
315 	mdb_vread(dicts, sym.st_size, ptr);
316 
317 	dwd = mdb_alloc(sizeof (dict_walk_data_t), UM_SLEEP);
318 	dwd->num_dicts = nlists;
319 	dwd->num_cur = 0;
320 	dwd->curdict = dicts;
321 
322 	wsp->walk_addr = 0;
323 	wsp->walk_data = dwd;
324 
325 	return (WALK_NEXT);
326 }
327 
328 int
rctl_dict_walk_step(mdb_walk_state_t * wsp)329 rctl_dict_walk_step(mdb_walk_state_t *wsp)
330 {
331 	dict_walk_data_t *dwd = wsp->walk_data;
332 	uintptr_t dp;
333 	rctl_dict_entry_t entry;
334 	int status;
335 
336 	dp = (uintptr_t)((dwd->curdict)[dwd->num_cur]);
337 
338 	while (dp != 0) {
339 		if (mdb_vread(&entry, sizeof (rctl_dict_entry_t), dp) == -1) {
340 			mdb_warn("failed to read rctl_dict_entry_t structure "
341 			    "at %p", dp);
342 			return (WALK_ERR);
343 		}
344 
345 		status = wsp->walk_callback(dp, &entry, wsp->walk_cbdata);
346 		if (status != WALK_NEXT)
347 			return (status);
348 
349 		dp = (uintptr_t)entry.rcd_next;
350 	}
351 
352 	dwd->num_cur++;
353 
354 	if (dwd->num_cur == dwd->num_dicts)
355 		return (WALK_DONE);
356 
357 	return (WALK_NEXT);
358 }
359 
360 void
rctl_dict_walk_fini(mdb_walk_state_t * wsp)361 rctl_dict_walk_fini(mdb_walk_state_t *wsp)
362 {
363 	dict_walk_data_t *wd = wsp->walk_data;
364 	mdb_free(wd->curdict, wd->num_dicts * sizeof (rctl_dict_entry_t *));
365 	mdb_free(wd, sizeof (dict_walk_data_t));
366 }
367 
368 typedef struct set_walk_data {
369 	uint_t hashsize;
370 	int hashcur;
371 	void **hashloc;
372 } set_walk_data_t;
373 
374 int
rctl_set_walk_init(mdb_walk_state_t * wsp)375 rctl_set_walk_init(mdb_walk_state_t *wsp)
376 {
377 	rctl_set_t rset;
378 	uint_t hashsz;
379 	set_walk_data_t *swd;
380 	rctl_t **rctls;
381 
382 	if (mdb_vread(&rset, sizeof (rctl_set_t), wsp->walk_addr) == -1) {
383 		mdb_warn("failed to read rset at %p", wsp->walk_addr);
384 		return (WALK_ERR);
385 	}
386 
387 	if (mdb_readvar(&hashsz, "rctl_set_size") == -1 || hashsz == 0) {
388 		mdb_warn("rctl_set_size not found or invalid");
389 		return (WALK_ERR);
390 	}
391 
392 	rctls = mdb_alloc(hashsz * sizeof (rctl_t *), UM_SLEEP);
393 	if (mdb_vread(rctls, hashsz * sizeof (rctl_t *),
394 	    (uintptr_t)rset.rcs_ctls) == -1) {
395 		mdb_warn("cannot read rctl hash at %p", rset.rcs_ctls);
396 		mdb_free(rctls, hashsz * sizeof (rctl_t *));
397 		return (WALK_ERR);
398 	}
399 
400 	swd = mdb_alloc(sizeof (set_walk_data_t), UM_SLEEP);
401 	swd->hashsize = hashsz;
402 	swd->hashcur = 0;
403 	swd->hashloc = (void **)rctls;
404 
405 	wsp->walk_addr = 0;
406 	wsp->walk_data = swd;
407 
408 	return (WALK_NEXT);
409 }
410 
411 
412 int
rctl_set_walk_step(mdb_walk_state_t * wsp)413 rctl_set_walk_step(mdb_walk_state_t *wsp)
414 {
415 	set_walk_data_t	*swd = wsp->walk_data;
416 	rctl_t rctl;
417 	void **rhash = swd->hashloc;
418 	int status;
419 
420 	if (swd->hashcur >= swd->hashsize)
421 		return (WALK_DONE);
422 
423 	if (wsp->walk_addr == 0) {
424 		while (swd->hashcur < swd->hashsize) {
425 			if (rhash[swd->hashcur] != NULL) {
426 				break;
427 			}
428 			swd->hashcur++;
429 		}
430 
431 		if (rhash[swd->hashcur] == NULL ||
432 		    swd->hashcur >= swd->hashsize)
433 			return (WALK_DONE);
434 
435 		wsp->walk_addr = (uintptr_t)rhash[swd->hashcur];
436 		swd->hashcur++;
437 	}
438 
439 	if (mdb_vread(&rctl, sizeof (rctl_t), wsp->walk_addr) == -1) {
440 		wsp->walk_addr = 0;
441 		mdb_warn("unable to read from %#p", wsp->walk_addr);
442 		return (WALK_ERR);
443 	}
444 
445 	status = wsp->walk_callback(wsp->walk_addr, &rctl, wsp->walk_cbdata);
446 
447 	wsp->walk_addr = (uintptr_t)rctl.rc_next;
448 
449 	return (status);
450 }
451 
452 void
rctl_set_walk_fini(mdb_walk_state_t * wsp)453 rctl_set_walk_fini(mdb_walk_state_t *wsp)
454 {
455 	set_walk_data_t *sd = wsp->walk_data;
456 
457 	mdb_free(sd->hashloc, sd->hashsize * sizeof (rctl_t *));
458 	mdb_free(sd, sizeof (set_walk_data_t));
459 }
460 
461 int
rctl_val_walk_init(mdb_walk_state_t * wsp)462 rctl_val_walk_init(mdb_walk_state_t *wsp)
463 {
464 	rctl_t rctl;
465 
466 	if (mdb_vread(&rctl, sizeof (rctl_t), wsp->walk_addr) == -1) {
467 		mdb_warn("failed to read rctl at %p", wsp->walk_addr);
468 		return (WALK_ERR);
469 	}
470 	wsp->walk_addr = (uintptr_t)rctl.rc_values;
471 	wsp->walk_data = rctl.rc_values;
472 	return (WALK_NEXT);
473 }
474 
475 int
rctl_val_walk_step(mdb_walk_state_t * wsp)476 rctl_val_walk_step(mdb_walk_state_t *wsp)
477 {
478 	rctl_val_t val;
479 	int status;
480 
481 	if (mdb_vread(&val, sizeof (rctl_val_t), wsp->walk_addr) == -1) {
482 		mdb_warn("failed to read rctl_val at %p", wsp->walk_addr);
483 		return (WALK_DONE);
484 	}
485 
486 	status = wsp->walk_callback(wsp->walk_addr, &val, wsp->walk_cbdata);
487 
488 	if ((wsp->walk_addr = (uintptr_t)val.rcv_next) == 0)
489 		return (WALK_DONE);
490 
491 	return (status);
492 }
493 
494 typedef struct rctl_val_seen {
495 	uintptr_t		s_ptr;
496 	rctl_qty_t		s_val;
497 } rctl_val_seen_t;
498 
499 typedef struct rctl_validate_data {
500 	uintptr_t		v_rctl_addr;
501 	rctl_val_t		*v_cursor;
502 	uint_t			v_flags;
503 	int			v_bad_rctl;
504 	int			v_cursor_valid;
505 	int			v_circularity_detected;
506 	uint_t			v_seen_size;
507 	uint_t			v_seen_cnt;
508 	rctl_val_seen_t		*v_seen;
509 } rctl_validate_data_t;
510 
511 #define	RCV_VERBOSE 0x1
512 
513 /*
514  * rctl_val_validate()
515  * Do validation on an individual rctl_val_t. This function is called
516  * as part of the rctl_val walker, and helps perform the checks described
517  * in the ::rctl_validate dcmd.
518  */
519 static int
rctl_val_validate(uintptr_t addr,rctl_val_t * val,rctl_validate_data_t * data)520 rctl_val_validate(uintptr_t addr, rctl_val_t *val, rctl_validate_data_t *data)
521 {
522 	int i;
523 
524 	data->v_seen[data->v_seen_cnt].s_ptr = addr;
525 
526 	if (addr == (uintptr_t)data->v_cursor)
527 		data->v_cursor_valid++;
528 
529 	data->v_seen[data->v_seen_cnt].s_val = val->rcv_value;
530 
531 	if (val->rcv_prev == (void *)0xbaddcafe ||
532 	    val->rcv_next == (void *)0xbaddcafe ||
533 	    val->rcv_prev == (void *)0xdeadbeef ||
534 	    val->rcv_next == (void *)0xdeadbeef) {
535 		if (data->v_bad_rctl++ == 0)
536 			mdb_printf("%p ", data->v_rctl_addr);
537 		if (data->v_flags & RCV_VERBOSE)
538 			mdb_printf("/ uninitialized or previously "
539 			    "freed link at %p ", addr);
540 	}
541 
542 	if (data->v_seen_cnt == 0) {
543 		if (val->rcv_prev != NULL) {
544 			if (data->v_bad_rctl++ == 0)
545 				mdb_printf("%p ", data->v_rctl_addr);
546 			if (data->v_flags & RCV_VERBOSE)
547 				mdb_printf("/ bad prev pointer at "
548 				    "head ");
549 		}
550 	} else {
551 		if ((uintptr_t)val->rcv_prev !=
552 		    data->v_seen[data->v_seen_cnt - 1].s_ptr) {
553 			if (data->v_bad_rctl++ == 0)
554 				mdb_printf("%p ", data->v_rctl_addr);
555 			if (data->v_flags & RCV_VERBOSE)
556 				mdb_printf("/ bad prev pointer at %p ",
557 				    addr);
558 		}
559 
560 		if (data->v_seen[data->v_seen_cnt].s_val <
561 		    data->v_seen[data->v_seen_cnt - 1].s_val) {
562 			if (data->v_bad_rctl++ == 0)
563 				mdb_printf("%p ", data->v_rctl_addr);
564 			if (data->v_flags & RCV_VERBOSE)
565 				mdb_printf("/ ordering error at %p ",
566 				    addr);
567 		}
568 	}
569 
570 	for (i = data->v_seen_cnt; i >= 0; i--) {
571 		if (data->v_seen[i].s_ptr == (uintptr_t)val->rcv_next) {
572 			if (data->v_bad_rctl++ == 0)
573 				mdb_printf("%p ", data->v_rctl_addr);
574 			if (data->v_flags & RCV_VERBOSE)
575 				mdb_printf("/ circular next pointer "
576 				    "at %p ", addr);
577 			data->v_circularity_detected++;
578 			break;
579 		}
580 	}
581 
582 	if (data->v_circularity_detected)
583 		return (WALK_DONE);
584 
585 	data->v_seen_cnt++;
586 	if (data->v_seen_cnt >= data->v_seen_size) {
587 		uint_t new_seen_size = data->v_seen_size * 2;
588 		rctl_val_seen_t *tseen = mdb_zalloc(new_seen_size *
589 		    sizeof (rctl_val_seen_t), UM_SLEEP | UM_GC);
590 
591 		bcopy(data->v_seen, tseen, data->v_seen_size *
592 		    sizeof (rctl_val_seen_t));
593 
594 		data->v_seen = tseen;
595 		data->v_seen_size = new_seen_size;
596 	}
597 
598 	return (WALK_NEXT);
599 }
600 
601 /*
602  * Validate a rctl pointer by checking:
603  *   - rctl_val_t's for that rctl form an ordered, non-circular list
604  *   - the cursor points to a rctl_val_t within that list
605  *   - there are no more than UINT64_MAX (or # specified by -n)
606  *     rctl_val_t's in the list
607  */
608 int
rctl_validate(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)609 rctl_validate(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
610 {
611 	rctl_validate_data_t data;
612 
613 	rctl_t r;
614 
615 	uint64_t long_threshold = UINT64_MAX;
616 
617 	/* Initialize validate data structure */
618 	data.v_rctl_addr = addr;
619 	data.v_flags = 0;
620 	data.v_bad_rctl = 0;
621 	data.v_seen_cnt = 0;
622 	data.v_cursor_valid = 0;
623 	data.v_circularity_detected = 0;
624 	data.v_seen_size = 1;
625 	data.v_seen = mdb_zalloc(data.v_seen_size * sizeof (rctl_val_seen_t),
626 	    UM_SLEEP | UM_GC);
627 
628 	if (!(flags & DCMD_ADDRSPEC))
629 		return (DCMD_USAGE);
630 
631 	if (mdb_getopts(argc, argv,
632 	    'v', MDB_OPT_SETBITS, RCV_VERBOSE, &data.v_flags,
633 	    'n', MDB_OPT_UINT64, &long_threshold,
634 	    NULL) != argc)
635 		return (DCMD_USAGE);
636 
637 	if (mdb_vread(&r, sizeof (rctl_t), addr) != sizeof (rctl_t)) {
638 		mdb_warn("failed to read rctl structure at %p", addr);
639 		return (DCMD_ERR);
640 	}
641 
642 	data.v_cursor = r.rc_cursor;
643 
644 	if (data.v_cursor == NULL) {
645 		if (data.v_bad_rctl++ == 0)
646 			mdb_printf("%p ", addr);
647 		if (data.v_flags & RCV_VERBOSE)
648 			mdb_printf("/ NULL cursor seen ");
649 	} else if (data.v_cursor == (rctl_val_t *)0xbaddcafe) {
650 		if (data.v_bad_rctl++ == 0)
651 			mdb_printf("%p ", addr);
652 		if (data.v_flags & RCV_VERBOSE)
653 			mdb_printf("/ uninitialized cursor seen ");
654 	}
655 
656 	/* Walk through each val in this rctl for individual validation. */
657 	if (mdb_pwalk("rctl_val", (mdb_walk_cb_t)rctl_val_validate, &data,
658 	    addr) == -1) {
659 		mdb_warn("failed to walk all values for rctl_t %p", addr);
660 		return (DCMD_ERR);
661 	}
662 
663 	if (data.v_seen_cnt >= long_threshold) {
664 		if (data.v_bad_rctl++ == 0)
665 			mdb_printf("%p ", addr);
666 		if (data.v_flags & RCV_VERBOSE)
667 			mdb_printf("/ sequence length = %d ",
668 			    data.v_seen_cnt);
669 	}
670 
671 	if (!data.v_cursor_valid) {
672 		if (data.v_bad_rctl++ == 0)
673 			mdb_printf("%p ", addr);
674 		if (data.v_flags & RCV_VERBOSE)
675 			mdb_printf("/ cursor outside sequence");
676 	}
677 
678 	if (data.v_bad_rctl)
679 		mdb_printf("\n");
680 
681 	if (data.v_circularity_detected)
682 		mdb_warn("circular list implies possible memory leak; "
683 		    "recommend invoking ::findleaks");
684 
685 	return (DCMD_OK);
686 }
687