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