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/types.h> 31 #include <sys/mutex.h> 32 #include <sys/thread.h> 33 #include <sys/condvar.h> 34 #include <sys/sleepq.h> 35 #include <sys/sobject.h> 36 #include <sys/rwlock_impl.h> 37 #include <sys/turnstile.h> 38 #include <sys/proc.h> 39 #include <sys/mutex_impl.h> 40 41 #include <stdio.h> 42 43 44 typedef struct wchan_walk_data { 45 caddr_t *ww_seen; 46 int ww_seen_size; 47 int ww_seen_ndx; 48 uintptr_t ww_thr; 49 sleepq_head_t ww_sleepq[NSLEEPQ]; 50 int ww_sleepq_ndx; 51 uintptr_t ww_compare; 52 } wchan_walk_data_t; 53 54 int 55 wchan_walk_init(mdb_walk_state_t *wsp) 56 { 57 wchan_walk_data_t *ww = 58 mdb_zalloc(sizeof (wchan_walk_data_t), UM_SLEEP); 59 60 if (mdb_readvar(&ww->ww_sleepq[0], "sleepq_head") == -1) { 61 mdb_warn("failed to read sleepq"); 62 mdb_free(ww, sizeof (wchan_walk_data_t)); 63 return (WALK_ERR); 64 } 65 66 if ((ww->ww_compare = wsp->walk_addr) == NULL) { 67 if (mdb_readvar(&ww->ww_seen_size, "nthread") == -1) { 68 mdb_warn("failed to read nthread"); 69 mdb_free(ww, sizeof (wchan_walk_data_t)); 70 return (WALK_ERR); 71 } 72 73 ww->ww_seen = mdb_alloc(ww->ww_seen_size * 74 sizeof (caddr_t), UM_SLEEP); 75 } else { 76 ww->ww_sleepq_ndx = SQHASHINDEX(wsp->walk_addr); 77 } 78 79 wsp->walk_data = ww; 80 return (WALK_NEXT); 81 } 82 83 int 84 wchan_walk_step(mdb_walk_state_t *wsp) 85 { 86 wchan_walk_data_t *ww = wsp->walk_data; 87 sleepq_head_t *sq; 88 kthread_t thr; 89 uintptr_t t; 90 int i; 91 92 again: 93 /* 94 * Get the address of the first thread on the next sleepq in the 95 * sleepq hash. If ww_compare is set, ww_sleepq_ndx is already 96 * set to the appropriate sleepq index for the desired cv. 97 */ 98 for (t = ww->ww_thr; t == NULL; ) { 99 if (ww->ww_sleepq_ndx == NSLEEPQ) 100 return (WALK_DONE); 101 102 sq = &ww->ww_sleepq[ww->ww_sleepq_ndx++]; 103 t = (uintptr_t)sq->sq_queue.sq_first; 104 105 /* 106 * If we were looking for a specific cv and we're at the end 107 * of its sleepq, we're done walking. 108 */ 109 if (t == NULL && ww->ww_compare != NULL) 110 return (WALK_DONE); 111 } 112 113 /* 114 * Read in the thread. If it's t_wchan pointer is NULL, the thread has 115 * woken up since we took a snapshot of the sleepq (i.e. we are probably 116 * being applied to a live system); we can't believe the t_link pointer 117 * anymore either, so just skip to the next sleepq index. 118 */ 119 if (mdb_vread(&thr, sizeof (thr), t) != sizeof (thr)) { 120 mdb_warn("failed to read thread at %p", t); 121 return (WALK_ERR); 122 } 123 124 if (thr.t_wchan == NULL) { 125 ww->ww_thr = NULL; 126 goto again; 127 } 128 129 /* 130 * Set ww_thr to the address of the next thread in the sleepq list. 131 */ 132 ww->ww_thr = (uintptr_t)thr.t_link; 133 134 /* 135 * If we're walking a specific cv, invoke the callback if we've 136 * found a match, or loop back to the top and read the next thread. 137 */ 138 if (ww->ww_compare != NULL) { 139 if (ww->ww_compare == (uintptr_t)thr.t_wchan) 140 return (wsp->walk_callback(t, &thr, wsp->walk_cbdata)); 141 142 if (ww->ww_thr == NULL) 143 return (WALK_DONE); 144 145 goto again; 146 } 147 148 /* 149 * If we're walking all cvs, seen if we've already encountered this one 150 * on the current sleepq. If we have, skip to the next thread. 151 */ 152 for (i = 0; i < ww->ww_seen_ndx; i++) { 153 if (ww->ww_seen[i] == thr.t_wchan) 154 goto again; 155 } 156 157 /* 158 * If we're not at the end of a sleepq, save t_wchan; otherwise reset 159 * the seen index so our array is empty at the start of the next sleepq. 160 * If we hit seen_size this is a live kernel and nthread is now larger, 161 * cope by replacing the final element in our memory. 162 */ 163 if (ww->ww_thr != NULL) { 164 if (ww->ww_seen_ndx < ww->ww_seen_size) 165 ww->ww_seen[ww->ww_seen_ndx++] = thr.t_wchan; 166 else 167 ww->ww_seen[ww->ww_seen_size - 1] = thr.t_wchan; 168 } else 169 ww->ww_seen_ndx = 0; 170 171 return (wsp->walk_callback((uintptr_t)thr.t_wchan, 172 NULL, wsp->walk_cbdata)); 173 } 174 175 void 176 wchan_walk_fini(mdb_walk_state_t *wsp) 177 { 178 wchan_walk_data_t *ww = wsp->walk_data; 179 180 mdb_free(ww->ww_seen, ww->ww_seen_size * sizeof (uintptr_t)); 181 mdb_free(ww, sizeof (wchan_walk_data_t)); 182 } 183 184 struct wcdata { 185 sobj_ops_t sobj; 186 int nwaiters; 187 }; 188 189 /*ARGSUSED*/ 190 static int 191 wchaninfo_twalk(uintptr_t addr, const kthread_t *t, struct wcdata *wc) 192 { 193 if (wc->sobj.sobj_type == SOBJ_NONE) { 194 (void) mdb_vread(&wc->sobj, sizeof (sobj_ops_t), 195 (uintptr_t)t->t_sobj_ops); 196 } 197 198 wc->nwaiters++; 199 return (WALK_NEXT); 200 } 201 202 static int 203 wchaninfo_vtwalk(uintptr_t addr, const kthread_t *t, int *first) 204 { 205 proc_t p; 206 207 (void) mdb_vread(&p, sizeof (p), (uintptr_t)t->t_procp); 208 209 if (*first) { 210 *first = 0; 211 mdb_printf(": %0?p %s\n", addr, p.p_user.u_comm); 212 } else { 213 mdb_printf("%*s%0?p %s\n", (int)(sizeof (uintptr_t) * 2 + 17), 214 "", addr, p.p_user.u_comm); 215 } 216 217 return (WALK_NEXT); 218 } 219 220 /*ARGSUSED*/ 221 static int 222 wchaninfo_walk(uintptr_t addr, void *ignored, uint_t *verbose) 223 { 224 struct wcdata wc; 225 int first = 1; 226 227 bzero(&wc, sizeof (wc)); 228 wc.sobj.sobj_type = SOBJ_NONE; 229 230 if (mdb_pwalk("wchan", (mdb_walk_cb_t)wchaninfo_twalk, &wc, addr) < 0) { 231 mdb_warn("failed to walk wchan %p", addr); 232 return (WALK_NEXT); 233 } 234 235 mdb_printf("%0?p %4s %8d%s", addr, 236 wc.sobj.sobj_type == SOBJ_CV ? "cond" : 237 wc.sobj.sobj_type == SOBJ_SEMA ? "sema" : "??", 238 wc.nwaiters, (*verbose) ? "" : "\n"); 239 240 if (*verbose != 0 && wc.nwaiters != 0 && mdb_pwalk("wchan", 241 (mdb_walk_cb_t)wchaninfo_vtwalk, &first, addr) == -1) { 242 mdb_warn("failed to walk waiters for wchan %p", addr); 243 mdb_printf("\n"); 244 } 245 246 return (WALK_NEXT); 247 } 248 249 int 250 wchaninfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 251 { 252 uint_t v = FALSE; 253 254 if (mdb_getopts(argc, argv, 255 'v', MDB_OPT_SETBITS, TRUE, &v, NULL) != argc) 256 return (DCMD_USAGE); 257 258 if (v == TRUE) { 259 mdb_printf("%-?s %-4s %8s %-?s %s\n", 260 "ADDR", "TYPE", "NWAITERS", "THREAD", "PROC"); 261 } else 262 mdb_printf("%-?s %-4s %8s\n", "ADDR", "TYPE", "NWAITERS"); 263 264 if (flags & DCMD_ADDRSPEC) { 265 if (wchaninfo_walk(addr, NULL, &v) == WALK_ERR) 266 return (DCMD_ERR); 267 } else if (mdb_walk("wchan", (mdb_walk_cb_t)wchaninfo_walk, &v) == -1) { 268 mdb_warn("failed to walk wchans"); 269 return (DCMD_ERR); 270 } 271 272 return (DCMD_OK); 273 } 274 275 int 276 blocked_walk_init(mdb_walk_state_t *wsp) 277 { 278 if ((wsp->walk_data = (void *)wsp->walk_addr) == NULL) { 279 mdb_warn("must specify a sobj * for blocked walk"); 280 return (WALK_ERR); 281 } 282 283 wsp->walk_addr = NULL; 284 285 if (mdb_layered_walk("thread", wsp) == -1) { 286 mdb_warn("couldn't walk 'thread'"); 287 return (WALK_ERR); 288 } 289 290 return (WALK_NEXT); 291 } 292 293 int 294 blocked_walk_step(mdb_walk_state_t *wsp) 295 { 296 uintptr_t addr = (uintptr_t)((const kthread_t *)wsp->walk_layer)->t_ts; 297 uintptr_t taddr = wsp->walk_addr; 298 turnstile_t ts; 299 300 if (mdb_vread(&ts, sizeof (ts), addr) == -1) { 301 mdb_warn("couldn't read %p's turnstile at %p", taddr, addr); 302 return (WALK_ERR); 303 } 304 305 if (ts.ts_waiters == 0 || ts.ts_sobj != wsp->walk_data) 306 return (WALK_NEXT); 307 308 return (wsp->walk_callback(taddr, wsp->walk_layer, wsp->walk_cbdata)); 309 } 310 311 typedef struct rwlock_block { 312 struct rwlock_block *rw_next; 313 int rw_qnum; 314 uintptr_t rw_thread; 315 } rwlock_block_t; 316 317 static int 318 rwlock_walk(uintptr_t taddr, const kthread_t *t, rwlock_block_t **rwp) 319 { 320 turnstile_t ts; 321 uintptr_t addr = (uintptr_t)t->t_ts; 322 rwlock_block_t *rw; 323 int state, i; 324 325 if (mdb_vread(&ts, sizeof (ts), addr) == -1) { 326 mdb_warn("couldn't read %p's turnstile at %p", taddr, addr); 327 return (WALK_ERR); 328 } 329 330 for (i = 0; i < TS_NUM_Q; i++) { 331 if ((uintptr_t)t->t_sleepq == 332 (uintptr_t)&ts.ts_sleepq[i] - (uintptr_t)&ts + addr) 333 break; 334 } 335 336 if (i == TS_NUM_Q) { 337 if ((state = mdb_get_state()) == MDB_STATE_DEAD || 338 state == MDB_STATE_STOPPED) { 339 /* 340 * This shouldn't happen post-mortem or under kmdb; 341 * the blocked walk returned a thread which wasn't 342 * actually blocked on its turnstile. This may happen 343 * in-situ if the thread wakes up during the ::rwlock. 344 */ 345 mdb_warn("thread %p isn't blocked on ts %p\n", 346 taddr, addr); 347 return (WALK_ERR); 348 } 349 350 return (WALK_NEXT); 351 } 352 353 rw = mdb_alloc(sizeof (rwlock_block_t), UM_SLEEP | UM_GC); 354 355 rw->rw_next = *rwp; 356 rw->rw_qnum = i; 357 rw->rw_thread = taddr; 358 *rwp = rw; 359 360 return (WALK_NEXT); 361 } 362 363 /* 364 * > rwd_rwlock::rwlock 365 * ADDR OWNER/COUNT FLAGS WAITERS 366 * 7835dee8 READERS=1 B011 30004393d20 (W) 367 * || 368 * WRITE_WANTED -------+| 369 * HAS_WAITERS --------+ 370 * 371 * |--ADDR_WIDTH--| |--OWNR_WIDTH--| 372 * |--LBL_OFFSET--||-LBL_WIDTH| 373 * |--------------LONG-------------| 374 * |------------WAITER_OFFSET------------| 375 */ 376 377 #ifdef _LP64 378 #define RW_ADDR_WIDTH 16 379 #define RW_OWNR_WIDTH 16 380 #else 381 #define RW_ADDR_WIDTH 8 382 #define RW_OWNR_WIDTH 11 383 #endif 384 385 #define RW_LONG (RW_ADDR_WIDTH + 1 + RW_OWNR_WIDTH) 386 #define RW_LBL_WIDTH 12 387 #define RW_LBL_OFFSET (RW_ADDR_WIDTH + RW_OWNR_WIDTH - 3 - RW_LBL_WIDTH) 388 #define RW_WAITER_OFFSET (RW_LONG + 6) 389 390 /* Access rwlock bits */ 391 #define RW_BIT(n, offon) (wwwh & (1 << (n)) ? offon[1] : offon[0]) 392 #define RW_BIT_SET(n) (wwwh & (1 << (n))) 393 394 /* Print a waiter (if any) and a newline */ 395 #define RW_NEWLINE \ 396 if (rw != NULL) { \ 397 int q = rw->rw_qnum; \ 398 mdb_printf(" %?p (%s)", rw->rw_thread, \ 399 q == TS_READER_Q ? "R" : q == TS_WRITER_Q ? "W" : "?"); \ 400 rw = rw->rw_next; \ 401 } \ 402 mdb_printf("\n"); 403 404 /*ARGSUSED*/ 405 int 406 rwlock(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 407 { 408 rwlock_impl_t lock; 409 rwlock_block_t *rw = NULL; 410 uintptr_t wwwh; 411 412 if (!(flags & DCMD_ADDRSPEC) || addr == NULL || argc != 0) 413 return (DCMD_USAGE); 414 415 if (mdb_vread(&lock, sizeof (lock), addr) == -1) { 416 mdb_warn("failed to read rwlock at 0x%p", addr); 417 return (DCMD_ERR); 418 } 419 420 if (mdb_pwalk("blocked", (mdb_walk_cb_t)rwlock_walk, &rw, addr) == -1) { 421 mdb_warn("couldn't walk 'blocked' for sobj %p", addr); 422 return (WALK_ERR); 423 } 424 425 mdb_printf("%?s %*s %5s %?s\n", "ADDR", 426 RW_OWNR_WIDTH, "OWNER/COUNT", "FLAGS", "WAITERS"); 427 428 mdb_printf("%?p ", addr); 429 430 if (((wwwh = lock.rw_wwwh) & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK) 431 mdb_printf("%*s", RW_OWNR_WIDTH, "1"); 432 else if ((wwwh = lock.rw_wwwh) & RW_WRITE_LOCKED) 433 mdb_printf("%*p", RW_OWNR_WIDTH, wwwh & RW_OWNER); 434 else { 435 uintptr_t count = (wwwh & RW_HOLD_COUNT) >> RW_HOLD_COUNT_SHIFT; 436 char c[20]; 437 438 mdb_snprintf(c, 20, "READERS=%ld", count); 439 mdb_printf("%*s", RW_OWNR_WIDTH, count ? c : "-"); 440 } 441 442 mdb_printf(" B%c%c%c", 443 RW_BIT(2, "01"), RW_BIT(1, "01"), RW_BIT(0, "01")); 444 RW_NEWLINE; 445 446 mdb_printf("%*s%c %c%c%c", RW_LONG - 1, "", 447 " |"[(wwwh & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK], 448 RW_BIT(2, " |"), RW_BIT(1, " |"), RW_BIT(0, " |")); 449 RW_NEWLINE; 450 451 if ((wwwh & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK) { 452 mdb_printf("%*s%*s --+---+", RW_LBL_OFFSET, "", RW_LBL_WIDTH, 453 "DESTROYED"); 454 goto no_zero; 455 } 456 457 if (!RW_BIT_SET(2)) 458 goto no_two; 459 460 mdb_printf("%*s%*s ------+%c%c", RW_LBL_OFFSET, "", RW_LBL_WIDTH, 461 "WRITE_LOCKED", RW_BIT(1, " |"), RW_BIT(0, " |")); 462 RW_NEWLINE; 463 464 no_two: 465 if (!RW_BIT_SET(1)) 466 goto no_one; 467 468 mdb_printf("%*s%*s -------+%c", RW_LBL_OFFSET, "", RW_LBL_WIDTH, 469 "WRITE_WANTED", RW_BIT(0, " |")); 470 RW_NEWLINE; 471 472 no_one: 473 if (!RW_BIT_SET(0)) 474 goto no_zero; 475 476 mdb_printf("%*s%*s --------+", RW_LBL_OFFSET, "", RW_LBL_WIDTH, 477 "HAS_WAITERS"); 478 RW_NEWLINE; 479 480 no_zero: 481 while (rw != NULL) { 482 mdb_printf("%*s", RW_WAITER_OFFSET, ""); 483 RW_NEWLINE; 484 } 485 486 return (DCMD_OK); 487 } 488 489 int 490 mutex(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 491 { 492 mutex_impl_t lock; 493 uint_t force = FALSE; 494 495 if (!(flags & DCMD_ADDRSPEC)) { 496 return (DCMD_USAGE); 497 } 498 499 if (mdb_getopts(argc, argv, 500 'f', MDB_OPT_SETBITS, TRUE, &force, NULL) != argc) { 501 return (DCMD_USAGE); 502 } 503 504 if (mdb_vread(&lock, sizeof (lock), addr) == -1) { 505 mdb_warn("failed to read mutex at 0x%0?p", addr); 506 return (DCMD_ERR); 507 } 508 509 if (DCMD_HDRSPEC(flags)) { 510 mdb_printf("%<u>%?s %5s %?s %6s %6s %7s%</u>\n", 511 "ADDR", "TYPE", "HELD", "MINSPL", "OLDSPL", "WAITERS"); 512 } 513 514 if (MUTEX_TYPE_SPIN(&lock)) { 515 struct spin_mutex *sp = &lock.m_spin; 516 517 if (!force && (sp->m_filler != 0 || 518 sp->m_minspl > PIL_MAX || sp->m_oldspl > PIL_MAX || 519 (sp->m_spinlock != 0 && sp->m_spinlock != 0xff))) { 520 mdb_warn("%a: invalid spin lock " 521 "(-f to dump anyway)\n", addr); 522 return (DCMD_ERR); 523 } 524 525 if (sp->m_spinlock == 0xff) { 526 mdb_printf("%0?p %5s %?s %6d %6d %7s\n", 527 addr, "spin", "yes", sp->m_minspl, sp->m_oldspl, 528 "-"); 529 } else { 530 mdb_printf("%0?p %5s %?s %6d %6s %7s\n", 531 addr, "spin", "no", sp->m_minspl, "-", "-"); 532 } 533 534 } else { 535 kthread_t *owner = MUTEX_OWNER(&lock); 536 char *waiters = MUTEX_HAS_WAITERS(&lock) ? "yes" : "no"; 537 538 if (!force && (!MUTEX_TYPE_ADAPTIVE(&lock) || 539 (owner == NULL && MUTEX_HAS_WAITERS(&lock)))) { 540 mdb_warn("%a: invalid adaptive mutex " 541 "(-f to dump anyway)\n", addr); 542 return (DCMD_ERR); 543 } 544 545 if (owner != NULL) { 546 mdb_printf("%0?p %5s %?p %6s %6s %7s\n", 547 addr, "adapt", owner, "-", "-", waiters); 548 } else { 549 mdb_printf("%0?p %5s %?s %6s %6s %7s\n", 550 addr, "adapt", "no", "-", "-", waiters); 551 } 552 } 553 return (DCMD_OK); 554 } 555 556 void 557 mutex_help(void) 558 { 559 mdb_printf("Options:\n" 560 " -f force printing even if the data seems to be" 561 " inconsistent\n"); 562 } 563 564 int 565 turnstile(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 566 { 567 turnstile_t t; 568 569 if (argc != 0) 570 return (DCMD_USAGE); 571 572 if (!(flags & DCMD_ADDRSPEC)) { 573 if (mdb_walk_dcmd("turnstile_cache", "turnstile", argc, argv) 574 == -1) { 575 mdb_warn("can't walk turnstiles"); 576 return (DCMD_ERR); 577 } 578 return (DCMD_OK); 579 } 580 581 if (DCMD_HDRSPEC(flags)) 582 mdb_printf("%<u>%?s %?s %5s %4s %?s %?s%</u>\n", 583 "ADDR", "SOBJ", "WTRS", "EPRI", "ITOR", "PRIOINV"); 584 585 if (mdb_vread(&t, sizeof (turnstile_t), addr) == -1) { 586 mdb_warn("can't read turnstile_t at %p", addr); 587 return (DCMD_ERR); 588 } 589 590 mdb_printf("%0?p %?p %5d %4d %?p %?p\n", 591 addr, t.ts_sobj, t.ts_waiters, t.ts_epri, 592 t.ts_inheritor, t.ts_prioinv); 593 594 return (DCMD_OK); 595 } 596 597 /* 598 * Macros and structure definition copied from turnstile.c. 599 * This is unfortunate, but half the macros we need aren't usable from 600 * within mdb anyway. 601 */ 602 #define TURNSTILE_HASH_SIZE 128 /* must be power of 2 */ 603 #define TURNSTILE_HASH_MASK (TURNSTILE_HASH_SIZE - 1) 604 #define TURNSTILE_SOBJ_HASH(sobj) \ 605 ((((int)sobj >> 2) + ((int)sobj >> 9)) & TURNSTILE_HASH_MASK) 606 607 typedef struct turnstile_chain { 608 turnstile_t *tc_first; /* first turnstile on hash chain */ 609 disp_lock_t tc_lock; /* lock for this hash chain */ 610 } turnstile_chain_t; 611 612 /* 613 * Given the address of a blocked-upon synchronization object, return 614 * the address of its turnstile. 615 */ 616 617 /*ARGSUSED*/ 618 int 619 sobj2ts(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 620 { 621 GElf_Sym sym; 622 int isupi; 623 int ttoff; 624 uintptr_t ttable; 625 turnstile_t ts, *tsp; 626 turnstile_chain_t tc; 627 628 if (!(flags & DCMD_ADDRSPEC) || argc != 0) 629 return (DCMD_USAGE); 630 631 if (mdb_lookup_by_name("upimutextab", &sym) == -1) { 632 mdb_warn("unable to reference upimutextab\n"); 633 return (DCMD_ERR); 634 } 635 isupi = addr - (uintptr_t)sym.st_value < sym.st_size; 636 ttoff = (isupi ? 0 : TURNSTILE_HASH_SIZE) + TURNSTILE_SOBJ_HASH(addr); 637 638 if (mdb_lookup_by_name("turnstile_table", &sym) == -1) { 639 mdb_warn("unable to reference turnstile_table"); 640 return (DCMD_ERR); 641 } 642 ttable = (uintptr_t)sym.st_value + sizeof (turnstile_chain_t) * ttoff; 643 644 if (mdb_vread(&tc, sizeof (turnstile_chain_t), ttable) == -1) { 645 mdb_warn("unable to read turnstile_chain_t at %#lx", ttable); 646 return (DCMD_ERR); 647 } 648 649 for (tsp = tc.tc_first; tsp != NULL; tsp = ts.ts_next) { 650 if (mdb_vread(&ts, sizeof (turnstile_t), 651 (uintptr_t)tsp) == -1) { 652 mdb_warn("unable to read turnstile_t at %#p", tsp); 653 return (DCMD_ERR); 654 } 655 if ((uintptr_t)ts.ts_sobj == addr) { 656 mdb_printf("%p\n", tsp); 657 break; 658 } 659 } 660 661 return (DCMD_OK); 662 } 663