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