xref: /illumos-gate/usr/src/cmd/mdb/common/modules/genunix/sobj.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
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