/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
*/
#include <mdb/mdb_modapi.h>
#include <sys/taskq_impl.h>
#ifndef STACK_BIAS
#define STACK_BIAS 0
#endif
typedef struct thread_walk {
int
{
mdb_warn("failed to read 'allthreads'");
return (WALK_ERR);
}
} else {
return (WALK_ERR);
}
}
return (WALK_NEXT);
}
int
{
int status;
return (WALK_DONE); /* Proc has 0 threads or allthreads = 0 */
return (WALK_DONE); /* We've wrapped around */
return (WALK_DONE);
}
wsp->walk_cbdata);
else
return (status);
}
void
{
}
int
{
mdb_warn("couldn't walk 'thread_deathrow'");
return (WALK_ERR);
}
mdb_warn("couldn't walk 'lwp_deathrow'");
return (WALK_ERR);
}
return (WALK_NEXT);
}
int
{
kthread_t t;
return (WALK_DONE);
return (WALK_ERR);
}
}
int
{
mdb_warn("couldn't read symbol 'thread_deathrow'");
return (WALK_ERR);
}
return (WALK_NEXT);
}
int
{
mdb_warn("couldn't read symbol 'lwp_deathrow'");
return (WALK_ERR);
}
return (WALK_NEXT);
}
typedef struct dispq_walk {
int dw_npri;
} dispq_walk_t;
int
{
mdb_warn("cpu_dispq walk needs a cpu_t address\n");
return (WALK_ERR);
}
return (WALK_ERR);
}
return (WALK_ERR);
}
return (WALK_ERR);
}
return (WALK_NEXT);
}
int
{
mdb_warn("cpupart_dispq walk needs a cpupart_t address\n");
return (WALK_ERR);
}
return (WALK_ERR);
}
mdb_warn("failed to read dispq_t at %p",
return (WALK_ERR);
}
return (WALK_NEXT);
}
int
{
kthread_t t;
return (WALK_DONE);
return (WALK_ERR);
}
}
return (WALK_ERR);
}
else
}
void
{
}
struct thread_state {
const char *ts_name;
} thread_states[] = {
{ TS_FREE, "free" },
{ TS_SLEEP, "sleep" },
{ TS_RUN, "run" },
{ TS_ONPROC, "onproc" },
{ TS_ZOMB, "zomb" },
{ TS_STOPPED, "stopped" },
{ TS_WAIT, "wait" }
};
void
{
int idx;
return;
}
}
}
int
{
int idx;
return (0);
}
}
return (-1);
}
void
{
int idx;
}
}
/*
* Display a kthread_t.
* This is a little complicated, as there is a lot of information that
* the user could be interested in. The flags "ipbsd" are used to
* indicate which subset of the thread's members are to be displayed
* ('i' is the default). If multiple options are specified, multiple
* sets of data will be displayed in a vaguely readable format. If the
* 'm' option is specified, all the selected sets will be merged onto a
* single line for the benefit of those using wider-than-normal
* terminals. Having a generic mechanism for doing this would be
* really useful, but is a project best left to another day.
*/
int
{
kthread_t t;
int first;
/*
* "Gracefully" handle printing a boatload of stuff to the
* screen. If we are not printing our first set of data, and
* we haven't been instructed to merge sets together, output a
* newline and indent such that the thread addresses form a
* column of their own.
*/
#define SPACER() \
if (first) { \
}
if (!(flags & DCMD_ADDRSPEC)) {
mdb_warn("can't walk threads");
return (DCMD_ERR);
}
return (DCMD_OK);
}
return (DCMD_USAGE);
/*
* If no sets were specified, choose the 'i' set.
*/
#ifdef _LP64
#else
#endif
/*
* Print the relevant headers; note use of SPACER().
*/
if (DCMD_HDRSPEC(flags)) {
mdb_flush();
SPACER();
mdb_printf("%<u> %?s %?s %?s%</u>",
"PROC", "LWP", "CRED");
}
SPACER();
mdb_printf("%<u> %8s %4s %4s %4s %5s %5s %3s %?s%</u>",
"STATE", "FLG", "PFLG",
"SFLG", "PRI", "EPRI", "PIL", "INTR");
}
SPACER();
mdb_printf("%<u> %?s %?s %?s %11s%</u>",
"WCHAN", "TS", "PITS", "SOBJ OPS");
}
SPACER();
mdb_printf("%<u> %?s %16s %16s%</u>",
"SIGQUEUE", "SIG PEND", "SIG HELD");
}
SPACER();
mdb_printf("%<u> %?s %5s %2s %-6s%</u>",
"DISPTIME", "BOUND", "PR", "SWITCH");
}
mdb_printf("\n");
}
return (DCMD_ERR);
}
return (DCMD_OK);
/* process information */
SPACER();
}
SPACER();
mdb_printf(" %-8s %4x %4x %4x %5d %5d %3d %?s",
} else {
mdb_printf(" %-8s %4x %4x %4x %5d %5d %3d %?p",
}
}
/* blocking information */
SPACER();
mdb_printf(" %?p %?p %?p %11s",
}
/* signal information */
SPACER();
mdb_printf(" %?p %016llx %016llx",
}
/* dispatcher stuff */
SPACER();
mdb_printf(" %?lx %5d %2d ",
if (t.t_disp_time != 0)
mdb_printf("t-%-4d",
else
}
mdb_printf("\n");
return (DCMD_OK);
}
void
thread_help(void)
{
"The flags -ipbsd control which information is displayed. When\n"
"combined, the fields are displayed on separate lines unless the\n"
"-m option is given.\n"
"\n"
"\t-b\tprint blocked thread state\n"
"\t-d\tprint dispatcher state\n"
"\t-f\tignore freed threads\n"
"\t-i\tprint basic thread state (default)\n"
"\t-m\tdisplay results on a single line\n"
"\t-p\tprint process and lwp state\n"
"\t-s\tprint signal state\n");
}
/*
* List a combination of kthread_t and proc_t. Add stack traces in verbose mode.
*/
int
{
int i;
kthread_t t;
proc_t p;
if (!(flags & DCMD_ADDRSPEC)) {
mdb_warn("can't walk threads");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (i != argc) {
return (DCMD_USAGE);
else
}
if (DCMD_HDRSPEC(flags)) {
if (verbose)
mdb_printf("%<u>%?s %?s %?s %3s %3s %?s%</u>\n",
"ADDR", "PROC", "LWP", "CLS", "PRI", "WCHAN");
else
mdb_printf("%<u>%?s %?s %?s %s/%s%</u>\n",
"ADDR", "PROC", "LWP", "CMD", "LWPID");
}
return (DCMD_ERR);
}
return (DCMD_OK);
return (DCMD_OK);
return (DCMD_ERR);
}
if (verbose) {
mdb_printf("%0?p %?p %?p %3u %3d %?p\n",
mdb_inc_indent(2);
if (t.t_tid == 0) {
else
} else {
}
mdb_dec_indent(2);
mdb_printf("\n");
} else {
if (t.t_tid == 0) {
else
} else {
}
}
return (DCMD_OK);
}
void
threadlist_help(void)
{
" -v print verbose output including C stack trace\n"
" -t skip threads belonging to a taskq\n"
" count print no more than count arguments (default 0)\n");
}
static size_t
{
size_t s;
/* stack grows down */
return (0);
}
return (100);
}
} else {
/* stack grows up */
return (0);
}
return (100);
}
}
if (percent > 100) {
percent = 100;
}
return (percent);
}
/*
* Display kthread stack infos.
*/
int
{
kthread_t t;
proc_t p;
int i = 0;
unsigned int ukmem_stackinfo;
/* handle options */
return (DCMD_USAGE);
}
/* walk all kthread if needed */
mdb_warn("can't walk threads");
return (DCMD_ERR);
}
return (DCMD_OK);
}
/* read 'kmem_stackinfo' */
"kmem_stackinfo") == -1) {
mdb_warn("failed to read 'kmem_stackinfo'\n");
ukmem_stackinfo = 0;
}
/* read 'allthreads' */
"allthreads") == -1) {
mdb_warn("failed to read 'allthreads'\n");
allthreads = NULL;
}
mdb_printf("Dead kthreads stack usage history:\n");
if (ukmem_stackinfo == 0) {
mdb_printf("Tunable kmem_stackinfo is unset, history ");
mdb_printf("feature is off.\nUse ::help stackinfo ");
mdb_printf("for more details.\n");
return (DCMD_OK);
}
mdb_printf("\n");
"kmem_stkinfo_log") == -1) {
mdb_warn("failed to read 'kmem_stkinfo_log'\n");
return (DCMD_ERR);
}
return (DCMD_OK);
}
return (DCMD_ERR);
}
for (i = 0; i < KMEM_STKINFO_LOG_SIZE; i++) {
continue;
}
mdb_printf("%0?p %0?p %6x %3d%%",
mdb_printf(" %s/%u\n",
} else {
}
}
return (DCMD_OK);
}
/* display header */
if (DCMD_HDRSPEC(flags)) {
if (ukmem_stackinfo == 0) {
mdb_printf("Tunable kmem_stackinfo is unset, ");
mdb_printf("MAX value is not available.\n");
mdb_printf("Use ::help stackinfo for more details.\n");
}
mdb_printf("\n");
}
/* read kthread */
return (DCMD_ERR);
}
return (DCMD_OK);
}
/* read proc */
return (DCMD_ERR);
}
/*
* Stack grows up or down, see thread_create(),
* compute stack memory aera start and end (start < end).
*/
/* stack grows down */
} else {
/* stack grows up */
}
/* display stack info */
/* (end - start), kernel stack size as found in kthread_t */
/* negative or stack size > 1 meg, assume bogus */
return (DCMD_ERR);
}
/* display stack size */
/* display current stack usage */
percent = 0;
if (ukmem_stackinfo == 0) {
mdb_printf(" n/a");
if (t.t_tid == 0) {
} else {
}
mdb_printf("\n");
return (DCMD_OK);
}
}
/* size to scan in userland copy of kernel stack */
/*
* Stackinfo pattern size is 8 bytes. Ensure proper 8 bytes
* alignement for ustart and uend, in boundaries.
*/
}
/* read the kernel stack */
mdb_printf("\n");
mdb_warn("couldn't read entire stack\n");
return (DCMD_ERR);
}
/* scan the stack */
/* stack grows down */
/*
* 6 longs are pushed on stack, see thread_load(). Skip
* them, so if kthread has never run, percent is zero.
* 8 bytes alignement is preserved for a 32 bit kernel,
* 6 x 4 = 24, 24 is a multiple of 8.
*/
uend -= (6 * sizeof (long));
#endif
if (*ptr != KMEM_STKINFO_PATTERN) {
break;
}
ptr++;
}
} else {
/* stack grows up */
ptr--;
if (*ptr != KMEM_STKINFO_PATTERN) {
break;
}
ptr--;
}
}
/* thread 't0' stack is not created by thread_create() */
if (addr == allthreads) {
percent = 0;
}
if (percent != 0) {
} else {
mdb_printf(" n/a");
}
if (t.t_tid == 0) {
} else {
}
mdb_printf("\n");
return (DCMD_OK);
}
void
stackinfo_help(void)
{
"kmem_stackinfo tunable\n");
"(an unsigned integer) is non zero at kthread creation time. ");
mdb_printf("For example:\n");
"ffffff014f5f2c20 ffffff0004153000 4f00 4%% 43%% init/1\n");
"The stack size utilization for this kthread is at 4%%"
" of its maximum size,\n");
"but has already used up to 43%%, stack size is 4f00 bytes.\n");
"MAX value can be shown as n/a (not available):\n");
" - for the very first kthread (sched/1)\n");
" - kmem_stackinfo was zero at kthread creation time\n");
" - kthread has not yet run\n");
mdb_printf("\n");
mdb_printf("Options:\n");
"-a shows also TS_FREE kthreads (interrupt kthreads)\n");
"-h shows history, dead kthreads that used their "
"kernel stack the most\n");
"\nSee Solaris Modular Debugger Guide for detailed usage.\n");
mdb_flush();
}