diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/mdb/common/modules/genunix/sobj.c | |
download | illumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/mdb/common/modules/genunix/sobj.c')
-rw-r--r-- | usr/src/cmd/mdb/common/modules/genunix/sobj.c | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/usr/src/cmd/mdb/common/modules/genunix/sobj.c b/usr/src/cmd/mdb/common/modules/genunix/sobj.c new file mode 100644 index 0000000000..4ee86a54b8 --- /dev/null +++ b/usr/src/cmd/mdb/common/modules/genunix/sobj.c @@ -0,0 +1,662 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <mdb/mdb_modapi.h> +#include <sys/types.h> +#include <sys/mutex.h> +#include <sys/thread.h> +#include <sys/condvar.h> +#include <sys/sleepq.h> +#include <sys/sobject.h> +#include <sys/rwlock_impl.h> +#include <sys/turnstile.h> +#include <sys/proc.h> +#include <sys/mutex_impl.h> + +#include <stdio.h> + + +typedef struct wchan_walk_data { + caddr_t *ww_seen; + int ww_seen_size; + int ww_seen_ndx; + uintptr_t ww_thr; + sleepq_head_t ww_sleepq[NSLEEPQ]; + int ww_sleepq_ndx; + uintptr_t ww_compare; +} wchan_walk_data_t; + +int +wchan_walk_init(mdb_walk_state_t *wsp) +{ + wchan_walk_data_t *ww = + mdb_zalloc(sizeof (wchan_walk_data_t), UM_SLEEP); + + if (mdb_readvar(&ww->ww_sleepq[0], "sleepq_head") == -1) { + mdb_warn("failed to read sleepq"); + mdb_free(ww, sizeof (wchan_walk_data_t)); + return (WALK_ERR); + } + + if ((ww->ww_compare = wsp->walk_addr) == NULL) { + if (mdb_readvar(&ww->ww_seen_size, "nthread") == -1) { + mdb_warn("failed to read nthread"); + mdb_free(ww, sizeof (wchan_walk_data_t)); + return (WALK_ERR); + } + + ww->ww_seen = mdb_alloc(ww->ww_seen_size * + sizeof (caddr_t), UM_SLEEP); + } else { + ww->ww_sleepq_ndx = SQHASHINDEX(wsp->walk_addr); + } + + wsp->walk_data = ww; + return (WALK_NEXT); +} + +int +wchan_walk_step(mdb_walk_state_t *wsp) +{ + wchan_walk_data_t *ww = wsp->walk_data; + sleepq_head_t *sq; + kthread_t thr; + uintptr_t t; + int i; + +again: + /* + * Get the address of the first thread on the next sleepq in the + * sleepq hash. If ww_compare is set, ww_sleepq_ndx is already + * set to the appropriate sleepq index for the desired cv. + */ + for (t = ww->ww_thr; t == NULL; ) { + if (ww->ww_sleepq_ndx == NSLEEPQ) + return (WALK_DONE); + + sq = &ww->ww_sleepq[ww->ww_sleepq_ndx++]; + t = (uintptr_t)sq->sq_queue.sq_first; + + /* + * If we were looking for a specific cv and we're at the end + * of its sleepq, we're done walking. + */ + if (t == NULL && ww->ww_compare != NULL) + return (WALK_DONE); + } + + /* + * Read in the thread. If it's t_wchan pointer is NULL, the thread has + * woken up since we took a snapshot of the sleepq (i.e. we are probably + * being applied to a live system); we can't believe the t_link pointer + * anymore either, so just skip to the next sleepq index. + */ + if (mdb_vread(&thr, sizeof (thr), t) != sizeof (thr)) { + mdb_warn("failed to read thread at %p", t); + return (WALK_ERR); + } + + if (thr.t_wchan == NULL) { + ww->ww_thr = NULL; + goto again; + } + + /* + * Set ww_thr to the address of the next thread in the sleepq list. + */ + ww->ww_thr = (uintptr_t)thr.t_link; + + /* + * If we're walking a specific cv, invoke the callback if we've + * found a match, or loop back to the top and read the next thread. + */ + if (ww->ww_compare != NULL) { + if (ww->ww_compare == (uintptr_t)thr.t_wchan) + return (wsp->walk_callback(t, &thr, wsp->walk_cbdata)); + + if (ww->ww_thr == NULL) + return (WALK_DONE); + + goto again; + } + + /* + * If we're walking all cvs, seen if we've already encountered this one + * on the current sleepq. If we have, skip to the next thread. + */ + for (i = 0; i < ww->ww_seen_ndx; i++) { + if (ww->ww_seen[i] == thr.t_wchan) + goto again; + } + + /* + * If we're not at the end of a sleepq, save t_wchan; otherwise reset + * the seen index so our array is empty at the start of the next sleepq. + * If we hit seen_size this is a live kernel and nthread is now larger, + * cope by replacing the final element in our memory. + */ + if (ww->ww_thr != NULL) { + if (ww->ww_seen_ndx < ww->ww_seen_size) + ww->ww_seen[ww->ww_seen_ndx++] = thr.t_wchan; + else + ww->ww_seen[ww->ww_seen_size - 1] = thr.t_wchan; + } else + ww->ww_seen_ndx = 0; + + return (wsp->walk_callback((uintptr_t)thr.t_wchan, + NULL, wsp->walk_cbdata)); +} + +void +wchan_walk_fini(mdb_walk_state_t *wsp) +{ + wchan_walk_data_t *ww = wsp->walk_data; + + mdb_free(ww->ww_seen, ww->ww_seen_size * sizeof (uintptr_t)); + mdb_free(ww, sizeof (wchan_walk_data_t)); +} + +struct wcdata { + sobj_ops_t sobj; + int nwaiters; +}; + +/*ARGSUSED*/ +static int +wchaninfo_twalk(uintptr_t addr, const kthread_t *t, struct wcdata *wc) +{ + if (wc->sobj.sobj_type == SOBJ_NONE) { + (void) mdb_vread(&wc->sobj, sizeof (sobj_ops_t), + (uintptr_t)t->t_sobj_ops); + } + + wc->nwaiters++; + return (WALK_NEXT); +} + +static int +wchaninfo_vtwalk(uintptr_t addr, const kthread_t *t, int *first) +{ + proc_t p; + + (void) mdb_vread(&p, sizeof (p), (uintptr_t)t->t_procp); + + if (*first) { + *first = 0; + mdb_printf(": %0?p %s\n", addr, p.p_user.u_comm); + } else { + mdb_printf("%*s%0?p %s\n", (int)(sizeof (uintptr_t) * 2 + 17), + "", addr, p.p_user.u_comm); + } + + return (WALK_NEXT); +} + +/*ARGSUSED*/ +static int +wchaninfo_walk(uintptr_t addr, void *ignored, uint_t *verbose) +{ + struct wcdata wc; + int first = 1; + + bzero(&wc, sizeof (wc)); + wc.sobj.sobj_type = SOBJ_NONE; + + if (mdb_pwalk("wchan", (mdb_walk_cb_t)wchaninfo_twalk, &wc, addr) < 0) { + mdb_warn("failed to walk wchan %p", addr); + return (WALK_NEXT); + } + + mdb_printf("%0?p %4s %8d%s", addr, + wc.sobj.sobj_type == SOBJ_CV ? "cond" : + wc.sobj.sobj_type == SOBJ_SEMA ? "sema" : "??", + wc.nwaiters, (*verbose) ? "" : "\n"); + + if (*verbose != 0 && wc.nwaiters != 0 && mdb_pwalk("wchan", + (mdb_walk_cb_t)wchaninfo_vtwalk, &first, addr) == -1) { + mdb_warn("failed to walk waiters for wchan %p", addr); + mdb_printf("\n"); + } + + return (WALK_NEXT); +} + +int +wchaninfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + uint_t v = FALSE; + + if (mdb_getopts(argc, argv, + 'v', MDB_OPT_SETBITS, TRUE, &v, NULL) != argc) + return (DCMD_USAGE); + + if (v == TRUE) { + mdb_printf("%-?s %-4s %8s %-?s %s\n", + "ADDR", "TYPE", "NWAITERS", "THREAD", "PROC"); + } else + mdb_printf("%-?s %-4s %8s\n", "ADDR", "TYPE", "NWAITERS"); + + if (flags & DCMD_ADDRSPEC) { + if (wchaninfo_walk(addr, NULL, &v) == WALK_ERR) + return (DCMD_ERR); + } else if (mdb_walk("wchan", (mdb_walk_cb_t)wchaninfo_walk, &v) == -1) { + mdb_warn("failed to walk wchans"); + return (DCMD_ERR); + } + + return (DCMD_OK); +} + +int +blocked_walk_init(mdb_walk_state_t *wsp) +{ + if ((wsp->walk_data = (void *)wsp->walk_addr) == NULL) { + mdb_warn("must specify a sobj * for blocked walk"); + return (WALK_ERR); + } + + wsp->walk_addr = NULL; + + if (mdb_layered_walk("thread", wsp) == -1) { + mdb_warn("couldn't walk 'thread'"); + return (WALK_ERR); + } + + return (WALK_NEXT); +} + +int +blocked_walk_step(mdb_walk_state_t *wsp) +{ + uintptr_t addr = (uintptr_t)((const kthread_t *)wsp->walk_layer)->t_ts; + uintptr_t taddr = wsp->walk_addr; + turnstile_t ts; + + if (mdb_vread(&ts, sizeof (ts), addr) == -1) { + mdb_warn("couldn't read %p's turnstile at %p", taddr, addr); + return (WALK_ERR); + } + + if (ts.ts_waiters == 0 || ts.ts_sobj != wsp->walk_data) + return (WALK_NEXT); + + return (wsp->walk_callback(taddr, wsp->walk_layer, wsp->walk_cbdata)); +} + +typedef struct rwlock_block { + struct rwlock_block *rw_next; + int rw_qnum; + uintptr_t rw_thread; +} rwlock_block_t; + +static int +rwlock_walk(uintptr_t taddr, const kthread_t *t, rwlock_block_t **rwp) +{ + turnstile_t ts; + uintptr_t addr = (uintptr_t)t->t_ts; + rwlock_block_t *rw; + int state, i; + + if (mdb_vread(&ts, sizeof (ts), addr) == -1) { + mdb_warn("couldn't read %p's turnstile at %p", taddr, addr); + return (WALK_ERR); + } + + for (i = 0; i < TS_NUM_Q; i++) { + if ((uintptr_t)t->t_sleepq == + (uintptr_t)&ts.ts_sleepq[i] - (uintptr_t)&ts + addr) + break; + } + + if (i == TS_NUM_Q) { + if ((state = mdb_get_state()) == MDB_STATE_DEAD || + state == MDB_STATE_STOPPED) { + /* + * This shouldn't happen post-mortem or under kmdb; + * the blocked walk returned a thread which wasn't + * actually blocked on its turnstile. This may happen + * in-situ if the thread wakes up during the ::rwlock. + */ + mdb_warn("thread %p isn't blocked on ts %p\n", + taddr, addr); + return (WALK_ERR); + } + + return (WALK_NEXT); + } + + rw = mdb_alloc(sizeof (rwlock_block_t), UM_SLEEP | UM_GC); + + rw->rw_next = *rwp; + rw->rw_qnum = i; + rw->rw_thread = taddr; + *rwp = rw; + + return (WALK_NEXT); +} + +/* + * > rwd_rwlock::rwlock + * ADDR OWNER/COUNT FLAGS WAITERS + * 7835dee8 READERS=1 B011 30004393d20 (W) + * || + * WRITE_WANTED -------+| + * HAS_WAITERS --------+ + * + * |--ADDR_WIDTH--| |--OWNR_WIDTH--| + * |--LBL_OFFSET--||-LBL_WIDTH| + * |--------------LONG-------------| + * |------------WAITER_OFFSET------------| + */ + +#ifdef _LP64 +#define RW_ADDR_WIDTH 16 +#define RW_OWNR_WIDTH 16 +#else +#define RW_ADDR_WIDTH 8 +#define RW_OWNR_WIDTH 11 +#endif + +#define RW_LONG (RW_ADDR_WIDTH + 1 + RW_OWNR_WIDTH) +#define RW_LBL_WIDTH 12 +#define RW_LBL_OFFSET (RW_ADDR_WIDTH + RW_OWNR_WIDTH - 3 - RW_LBL_WIDTH) +#define RW_WAITER_OFFSET (RW_LONG + 6) + +/* Access rwlock bits */ +#define RW_BIT(n, offon) (wwwh & (1 << (n)) ? offon[1] : offon[0]) +#define RW_BIT_SET(n) (wwwh & (1 << (n))) + +/* Print a waiter (if any) and a newline */ +#define RW_NEWLINE \ + if (rw != NULL) { \ + int q = rw->rw_qnum; \ + mdb_printf(" %?p (%s)", rw->rw_thread, \ + q == TS_READER_Q ? "R" : q == TS_WRITER_Q ? "W" : "?"); \ + rw = rw->rw_next; \ + } \ + mdb_printf("\n"); + +/*ARGSUSED*/ +int +rwlock(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + rwlock_impl_t lock; + rwlock_block_t *rw = NULL; + uintptr_t wwwh; + + if (!(flags & DCMD_ADDRSPEC) || addr == NULL || argc != 0) + return (DCMD_USAGE); + + if (mdb_vread(&lock, sizeof (lock), addr) == -1) { + mdb_warn("failed to read rwlock at 0x%p", addr); + return (DCMD_ERR); + } + + if (mdb_pwalk("blocked", (mdb_walk_cb_t)rwlock_walk, &rw, addr) == -1) { + mdb_warn("couldn't walk 'blocked' for sobj %p", addr); + return (WALK_ERR); + } + + mdb_printf("%?s %*s %5s %?s\n", "ADDR", + RW_OWNR_WIDTH, "OWNER/COUNT", "FLAGS", "WAITERS"); + + mdb_printf("%?p ", addr); + + if (((wwwh = lock.rw_wwwh) & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK) + mdb_printf("%*s", RW_OWNR_WIDTH, "1"); + else if ((wwwh = lock.rw_wwwh) & RW_WRITE_LOCKED) + mdb_printf("%*p", RW_OWNR_WIDTH, wwwh & RW_OWNER); + else { + uintptr_t count = (wwwh & RW_HOLD_COUNT) >> RW_HOLD_COUNT_SHIFT; + char c[20]; + + mdb_snprintf(c, 20, "READERS=%ld", count); + mdb_printf("%*s", RW_OWNR_WIDTH, count ? c : "-"); + } + + mdb_printf(" B%c%c%c", + RW_BIT(2, "01"), RW_BIT(1, "01"), RW_BIT(0, "01")); + RW_NEWLINE; + + mdb_printf("%*s%c %c%c%c", RW_LONG - 1, "", + " |"[(wwwh & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK], + RW_BIT(2, " |"), RW_BIT(1, " |"), RW_BIT(0, " |")); + RW_NEWLINE; + + if ((wwwh & RW_DOUBLE_LOCK) == RW_DOUBLE_LOCK) { + mdb_printf("%*s%*s --+---+", RW_LBL_OFFSET, "", RW_LBL_WIDTH, + "DESTROYED"); + goto no_zero; + } + + if (!RW_BIT_SET(2)) + goto no_two; + + mdb_printf("%*s%*s ------+%c%c", RW_LBL_OFFSET, "", RW_LBL_WIDTH, + "WRITE_LOCKED", RW_BIT(1, " |"), RW_BIT(0, " |")); + RW_NEWLINE; + +no_two: + if (!RW_BIT_SET(1)) + goto no_one; + + mdb_printf("%*s%*s -------+%c", RW_LBL_OFFSET, "", RW_LBL_WIDTH, + "WRITE_WANTED", RW_BIT(0, " |")); + RW_NEWLINE; + +no_one: + if (!RW_BIT_SET(0)) + goto no_zero; + + mdb_printf("%*s%*s --------+", RW_LBL_OFFSET, "", RW_LBL_WIDTH, + "HAS_WAITERS"); + RW_NEWLINE; + +no_zero: + while (rw != NULL) { + mdb_printf("%*s", RW_WAITER_OFFSET, ""); + RW_NEWLINE; + } + + return (DCMD_OK); +} + +int +mutex(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + mutex_impl_t lock; + uint_t force = FALSE; + + if (!(flags & DCMD_ADDRSPEC)) { + return (DCMD_USAGE); + } + + if (mdb_getopts(argc, argv, + 'f', MDB_OPT_SETBITS, TRUE, &force, NULL) != argc) { + return (DCMD_USAGE); + } + + if (mdb_vread(&lock, sizeof (lock), addr) == -1) { + mdb_warn("failed to read mutex at 0x%0?p", addr); + return (DCMD_ERR); + } + + if (DCMD_HDRSPEC(flags)) { + mdb_printf("%<u>%?s %5s %?s %6s %6s %7s%</u>\n", + "ADDR", "TYPE", "HELD", "MINSPL", "OLDSPL", "WAITERS"); + } + + if (MUTEX_TYPE_SPIN(&lock)) { + struct spin_mutex *sp = &lock.m_spin; + + if (!force && (sp->m_filler != 0 || + sp->m_minspl > PIL_MAX || sp->m_oldspl > PIL_MAX || + (sp->m_spinlock != 0 && sp->m_spinlock != 0xff))) { + mdb_warn("%a: invalid spin lock " + "(-f to dump anyway)\n", addr); + return (DCMD_ERR); + } + + if (sp->m_spinlock == 0xff) { + mdb_printf("%0?p %5s %?s %6d %6d %7s\n", + addr, "spin", "yes", sp->m_minspl, sp->m_oldspl, + "-"); + } else { + mdb_printf("%0?p %5s %?s %6d %6s %7s\n", + addr, "spin", "no", sp->m_minspl, "-", "-"); + } + + } else { + kthread_t *owner = MUTEX_OWNER(&lock); + char *waiters = MUTEX_HAS_WAITERS(&lock) ? "yes" : "no"; + + if (!force && (!MUTEX_TYPE_ADAPTIVE(&lock) || + (owner == NULL && MUTEX_HAS_WAITERS(&lock)))) { + mdb_warn("%a: invalid adaptive mutex " + "(-f to dump anyway)\n", addr); + return (DCMD_ERR); + } + + if (owner != NULL) { + mdb_printf("%0?p %5s %?p %6s %6s %7s\n", + addr, "adapt", owner, "-", "-", waiters); + } else { + mdb_printf("%0?p %5s %?s %6s %6s %7s\n", + addr, "adapt", "no", "-", "-", waiters); + } + } + return (DCMD_OK); +} + +void +mutex_help(void) +{ + mdb_printf("Options:\n" + " -f force printing even if the data seems to be" + " inconsistent\n"); +} + +int +turnstile(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + turnstile_t t; + + if (argc != 0) + return (DCMD_USAGE); + + if (!(flags & DCMD_ADDRSPEC)) { + if (mdb_walk_dcmd("turnstile_cache", "turnstile", argc, argv) + == -1) { + mdb_warn("can't walk turnstiles"); + return (DCMD_ERR); + } + return (DCMD_OK); + } + + if (DCMD_HDRSPEC(flags)) + mdb_printf("%<u>%?s %?s %5s %4s %?s %?s%</u>\n", + "ADDR", "SOBJ", "WTRS", "EPRI", "ITOR", "PRIOINV"); + + if (mdb_vread(&t, sizeof (turnstile_t), addr) == -1) { + mdb_warn("can't read turnstile_t at %p", addr); + return (DCMD_ERR); + } + + mdb_printf("%0?p %?p %5d %4d %?p %?p\n", + addr, t.ts_sobj, t.ts_waiters, t.ts_epri, + t.ts_inheritor, t.ts_prioinv); + + return (DCMD_OK); +} + +/* + * Macros and structure definition copied from turnstile.c. + * This is unfortunate, but half the macros we need aren't usable from + * within mdb anyway. + */ +#define TURNSTILE_HASH_SIZE 128 /* must be power of 2 */ +#define TURNSTILE_HASH_MASK (TURNSTILE_HASH_SIZE - 1) +#define TURNSTILE_SOBJ_HASH(sobj) \ + ((((int)sobj >> 2) + ((int)sobj >> 9)) & TURNSTILE_HASH_MASK) + +typedef struct turnstile_chain { + turnstile_t *tc_first; /* first turnstile on hash chain */ + disp_lock_t tc_lock; /* lock for this hash chain */ +} turnstile_chain_t; + +/* + * Given the address of a blocked-upon synchronization object, return + * the address of its turnstile. + */ + +/*ARGSUSED*/ +int +sobj2ts(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + GElf_Sym sym; + int isupi; + int ttoff; + uintptr_t ttable; + turnstile_t ts, *tsp; + turnstile_chain_t tc; + + if (!(flags & DCMD_ADDRSPEC) || argc != 0) + return (DCMD_USAGE); + + if (mdb_lookup_by_name("upimutextab", &sym) == -1) { + mdb_warn("unable to reference upimutextab\n"); + return (DCMD_ERR); + } + isupi = addr - (uintptr_t)sym.st_value < sym.st_size; + ttoff = (isupi ? 0 : TURNSTILE_HASH_SIZE) + TURNSTILE_SOBJ_HASH(addr); + + if (mdb_lookup_by_name("turnstile_table", &sym) == -1) { + mdb_warn("unable to reference turnstile_table"); + return (DCMD_ERR); + } + ttable = (uintptr_t)sym.st_value + sizeof (turnstile_chain_t) * ttoff; + + if (mdb_vread(&tc, sizeof (turnstile_chain_t), ttable) == -1) { + mdb_warn("unable to read turnstile_chain_t at %#lx", ttable); + return (DCMD_ERR); + } + + for (tsp = tc.tc_first; tsp != NULL; tsp = ts.ts_next) { + if (mdb_vread(&ts, sizeof (turnstile_t), + (uintptr_t)tsp) == -1) { + mdb_warn("unable to read turnstile_t at %#p", tsp); + return (DCMD_ERR); + } + if ((uintptr_t)ts.ts_sobj == addr) { + mdb_printf("%p\n", tsp); + break; + } + } + + return (DCMD_OK); +} |