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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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