findstack.c revision 346799e8230991f56d5fd66fb1b895ed057feedc
/*
* 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.
*/
#include <mdb/mdb_modapi.h>
#include "findstack.h"
#include "thread.h"
#include "sobj.h"
typedef struct findstack_info {
#define FSI_FAIL_BADTHREAD 1
#define FSI_FAIL_NOTINMEMORY 2
#define FSI_FAIL_THREADCORRUPT 3
#define FSI_FAIL_STACKNOTFOUND 4
#ifndef STACK_BIAS
#define STACK_BIAS 0
#endif
#define fs_dprintf(x) \
if (findstack_debug_on) { \
mdb_printf("findstack debug: "); \
/*CSTYLED*/ \
mdb_printf x ; \
}
static int findstack_debug_on = 0;
struct rwindow {
};
#endif
#define CRAWL_FOUNDALL (-1)
/*
* Given a stack pointer, try to crawl down it to the bottom.
* "frame" is a VA in MDB's address space.
*
* Returns the number of frames successfully crawled down, or
* CRAWL_FOUNDALL if it got to the bottom of the stack.
*/
static int
{
int levels = 0;
fsip->fsi_overflow = 0;
fs_dprintf(("<0> frame = %p, kbase = %p, ktop = %p, ubase = %p\n",
for (;;) {
break;
else
return (CRAWL_FOUNDALL);
fs_dprintf(("<3> not at base\n"));
fs_dprintf(("<4> found base\n"));
return (CRAWL_FOUNDALL);
}
#endif
fs_dprintf(("<5> fp = %p, kbase = %p, ktop - size = %p\n",
break;
/*
* NULL out the old %fp so we don't go down this stack
* more than once.
*/
if (kill_fp) {
}
levels++;
}
return (levels);
}
/*
* "sp" is a kernel VA.
*/
static int
{
return (DCMD_USAGE);
mdb_printf("stack pointer for thread %p%s: %p\n",
if (pc != 0)
mdb_inc_indent(2);
if (argc == 1)
else if (showargs)
else
mdb_dec_indent(2);
}
/*ARGSUSED*/
static int
{
fsip->fsi_failed = 0;
fsip->fsi_overflow = 0;
MDB_CTF_VREAD_IGNORE_ALL) == -1) {
if (print_warnings)
return (DCMD_ERR);
}
if (print_warnings)
return (DCMD_ERR);
}
if (print_warnings)
"stack base or stack top corrupt for thread %p\n",
addr);
return (DCMD_ERR);
}
#ifdef __amd64
/*
* The stack on amd64 is intentionally misaligned, so ignore the top
* half-frame. See thread_stk_init(). When handling traps, the frame
* is automatically aligned by the hardware, so we only alter ktop if
* needed.
*/
#endif
/*
* If the stack size is larger than a meg, assume that it's bogus.
*/
if (stksz > TOO_BIG_FOR_A_STACK) {
if (print_warnings)
mdb_warn("stack size for thread %p is too big to be "
"reasonable\n", addr);
return (DCMD_ERR);
}
/*
* This could be (and was) a UM_GC allocation. Unfortunately,
* stksz tends to be very large. As currently implemented, dcmds
* invoked as part of pipelines don't have their UM_GC-allocated
* memory freed until the pipeline completes. With stksz in the
* neighborhood of 20k, the popular ::walk thread |::findstack
* pipeline can easily run memory-constrained debuggers (kmdb) out
* of memory. This can be changed back to a gc-able allocation when
* the debugger is changed to free UM_GC memory more promptly.
*/
if (print_warnings)
mdb_warn("couldn't read entire stack for thread %p\n",
addr);
return (DCMD_ERR);
}
/*
* Try the saved %sp first, if it looks reasonable.
*/
#if !defined(__i386)
#endif
goto found;
}
}
/*
* Now walk through the whole stack, starting at the base,
* trying every possible "window".
*/
goto found;
}
}
/*
* We didn't conclusively find the stack. So we'll take another lap,
* and print out anything that looks possible.
*/
if (print_warnings)
int levels;
if (print_warnings)
} else if (levels == CRAWL_FOUNDALL) {
/*
* If this is a live system, the stack could change
* between the two mdb_vread(ubase, utop, kbase)'s,
* and we could have a fully valid stack here.
*/
goto found;
}
}
fsip->fsi_overflow = 0;
return (DCMD_ERR);
return (DCMD_OK);
}
int
{
int retval;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
return (retval);
}
/*ARGSUSED*/
int
{
findstack_debug_on ^= 1;
mdb_printf("findstack: debugging is now %s\n",
return (DCMD_OK);
}
static void
uppercase(char *p)
{
for (; *p != '\0'; p++) {
if (*p >= 'a' && *p <= 'z')
*p += 'A' - 'a';
}
}
static void
{
}
#define SOBJ_ALL 1
static int
{
return (0);
}
}
#define TSTATE_PANIC -2U
static int
{
*out = TSTATE_PANIC;
return (-1);
}
return (0);
}
static void
{
if (paniced)
else
}
typedef struct stacks_entry {
struct stacks_entry *se_next;
#define STACKS_HSIZE 127
/* Maximum stack depth reported in stacks */
#define STACKS_MAX_DEPTH 254
typedef struct stacks_info {
/* global state cached between invocations */
#define STACKS_STATE_CLEAN 0
#define STACKS_STATE_DIRTY 1
#define STACKS_STATE_DONE 2
static stacks_entry_t **stacks_hash;
static stacks_entry_t **stacks_array;
static size_t stacks_array_size;
{
while (depth > 0) {
}
return (total % STACKS_HSIZE);
}
/*
* This is used to both compare stacks for equality and to sort the final
* list of unique stacks. forsort specifies the latter behavior, which
* additionally:
* compares se_count, and
* sorts the stacks by text function name.
*
* The equality test is independent of se_count, and doesn't care about
* relative ordering, so we don't do the extra work of looking up symbols
* for the stack addresses.
*/
int
{
int idx;
/* no matter what, panic stacks come last. */
return (1);
return (-1);
if (forsort) {
/* put large counts earlier */
return (-1);
return (1);
}
return (1);
return (-1);
return (1);
return (-1);
char lbuf[MDB_SYM_NAMLEN];
char rbuf[MDB_SYM_NAMLEN];
int rval;
continue;
if (forsort &&
return (rval);
return (1);
return (-1);
}
if (l->se_overflow > r->se_overflow)
return (-1);
if (l->se_overflow < r->se_overflow)
return (1);
return (1);
return (-1);
if (l->se_sobj_ops > r->se_sobj_ops)
return (1);
if (l->se_sobj_ops < r->se_sobj_ops)
return (-1);
return (0);
}
int
{
}
void
stacks_cleanup(int force)
{
int idx = 0;
if (stacks_state == STACKS_STATE_CLEAN)
return;
return;
/*
* Until the array is sorted and stable, stacks_hash will be non-NULL.
* This way, we can get at all of the data, even if qsort() was
* interrupted while mucking with the array.
*/
if (stacks_hash != NULL) {
}
}
}
if (stacks_array != NULL)
stacks_array_size * sizeof (*stacks_array));
} else if (stacks_array != NULL) {
}
}
}
stacks_array_size * sizeof (*stacks_array));
}
stacks_array_size = 0;
}
/*ARGSUSED*/
int
{
int idx;
return (WALK_NEXT);
}
continue;
return (WALK_NEXT);
}
sip->si_entries++;
return (WALK_NEXT);
}
int
stacks_run(int verbose)
{
si.si_entries = 0;
if (verbose)
mdb_warn("stacks: processing kernel threads\n");
mdb_warn("cannot walk \"thread\"");
return (DCMD_ERR);
}
if (verbose)
mdb_warn("stacks: %d unique stacks / %d threads\n",
cur = stacks_array;
}
mdb_warn("stacks: miscounted array size (%d != size: %d)\n",
return (DCMD_ERR);
}
/* Now that we're done, free the hash table */
stacks_hash = NULL;
if (verbose)
mdb_warn("stacks: done\n");
return (DCMD_OK);
}
static int
{
int idx;
char c[MDB_SYM_NAMLEN];
c, sizeof (c), &sym) != -1 &&
}
return (1);
return (0);
}
static int
{
return (1);
return (-1);
return (0);
}
/*ARGSUSED*/
static void
{
}
/*ARGSUSED*/
static void
{
}
void
stacks_help(void)
{
"::stacks processes all of the thread stacks on the system, grouping\n"
"together threads which have the same:\n"
"\n"
" * Thread state,\n"
" * Sync object type, and\n"
" * PCs in their stack trace.\n"
"\n"
"The default output (no address or options) is just a dump of the thread\n"
"groups in the system. For a view of active threads, use \"::stacks -i\",\n"
"which filters out FREE threads (interrupt threads which are currently\n"
"inactive) and threads sleeping on a CV. (Note that those threads may still\n"
"be noteworthy; this is just for a first glance.) More general filtering\n"
"options are described below, in the \"FILTERS\" section.\n"
"\n"
"::stacks can be used in a pipeline. The input to ::stacks is one or more\n"
"thread pointers. For example, to get a summary of threads in a process,\n"
"you can do:\n"
"\n"
" %<b>procp%</b>::walk thread | ::stacks\n"
"\n"
"When output into a pipe, ::stacks prints all of the threads input,\n"
"filtered by the given filtering options. This means that multiple\n"
"::stacks invocations can be piped together to achieve more complicated\n"
"filters. For example, to get threads which have both 'fop_read' and\n"
"'cv_wait_sig_swap' in their stack trace, you could do:\n"
"\n"
" ::stacks -c fop_read | ::stacks -c cv_wait_sig_swap_core\n"
"\n"
"To get the full list of threads in each group, use the '-a' flag:\n"
"\n"
" ::stacks -a\n"
"\n");
mdb_dec_indent(2);
mdb_printf("%<b>OPTIONS%</b>\n");
mdb_inc_indent(2);
mdb_printf("%s",
" -a Print all of the grouped threads, instead of just a count.\n"
" -f Force a re-run of the thread stack gathering.\n"
" -v Be verbose about thread stack gathering.\n"
"\n");
mdb_dec_indent(2);
mdb_printf("%<b>FILTERS%</b>\n");
mdb_inc_indent(2);
mdb_printf("%s",
" -i Show active threads; equivalent to '-S CV -T FREE'.\n"
" -c func[+offset]\n"
" -C func[+offset]\n"
" -s {type | ALL}\n"
" Only print threads which are on a 'type' synchronization object\n"
" (SOBJ).\n"
" -S {type | ALL}\n"
" Only print threads which are not on a 'type' SOBJ.\n"
" -t tstate\n"
" Only print threads which are in thread state 'tstate'.\n"
" -T tstate\n"
" Only print threads which are not in thread state 'tstate'.\n"
"\n");
mdb_printf(" SOBJ types:");
mdb_printf("\n");
mdb_printf("Thread states:");
mdb_printf(" panic\n");
}
/*ARGSUSED*/
int
{
const char *caller_str = NULL;
const char *excl_caller_str = NULL;
const char *tstate_str = NULL;
const char *excl_tstate_str = NULL;
uint_t interesting = 0;
/*
* We have a slight behavior difference between having piped
* input and 'addr::stacks'. Without a pipe, we assume the
* thread pointer given is a representative thread, and so
* we include all similar threads in the system in our output.
*
* With a pipe, we filter down to just the threads in our
* input.
*/
mdb_pipe_t p;
return (DCMD_USAGE);
if (interesting) {
"stacks: -i is incompatible with -[sStT]\n");
return (DCMD_USAGE);
}
excl_sobj = "CV";
excl_tstate_str = "FREE";
}
if (caller_str != NULL) {
mdb_set_dot(0);
if (mdb_eval(caller_str) != 0) {
mdb_warn("stacks: evaluation of \"%s\" failed",
return (DCMD_ABORT);
}
caller = mdb_get_dot();
}
if (excl_caller_str != NULL) {
mdb_set_dot(0);
if (mdb_eval(excl_caller_str) != 0) {
mdb_warn("stacks: evaluation of \"%s\" failed",
return (DCMD_ABORT);
}
excl_caller = mdb_get_dot();
}
return (DCMD_USAGE);
return (DCMD_USAGE);
if (sobj_ops != 0 && excl_sobj_ops != 0) {
mdb_warn("stacks: only one of -s and -S can be specified\n");
return (DCMD_USAGE);
}
if (tstate_str &&
return (DCMD_USAGE);
if (excl_tstate_str &&
return (DCMD_USAGE);
mdb_warn("stacks: only one of -t and -T can be specified\n");
return (DCMD_USAGE);
}
/*
* Force a cleanup if we're connected to a live system. Never
* do a cleanup after the first invocation around the loop.
*/
force = 0;
if (stacks_state == STACKS_STATE_CLEAN) {
return (res);
}
mdb_printf("%<u>%-?s %-8s %-?s %8s%</u>\n",
"THREAD", "STATE", "SOBJ", "COUNT");
}
/*
* If there's an address specified, we're going to further filter
* to only entries which have an address in the input. To reduce
* overhead (and make the sorted output come out right), we
* use mdb_get_pipe() to grab the entire pipeline of input, then
* use qsort() and bsearch() to speed up the search.
*/
if (addrspec) {
mdb_get_pipe(&p);
p.pipe_len = 1;
}
/* remove any duplicates in the data */
idx = 0;
p.pipe_len--;
continue; /* repeat without incrementing idx */
}
idx++;
}
}
int frame;
if (addrspec) {
size_t foundcount = 0;
/*
* We use the now-unused hash chain field se_next to
* link together the dups which match our list.
*/
foundcount++;
else
}
}
continue; /* no match, skip entry */
if (only_matching) {
count = foundcount;
}
}
continue;
continue;
if (tstate != -1U) {
if (tstate == TSTATE_PANIC) {
continue;
continue;
}
if (excl_tstate != -1U) {
if (excl_tstate == TSTATE_PANIC) {
continue;
continue;
}
if (sep->se_sobj_ops == 0)
continue;
} else if (sobj_ops != 0) {
continue;
}
if (excl_sobj_ops == SOBJ_ALL) {
if (sep->se_sobj_ops != 0)
continue;
} else if (excl_sobj_ops != 0) {
continue;
}
}
if (flags & DCMD_PIPE_OUT) {
sep = only_matching ?
}
continue;
}
if (all) {
mdb_printf("%<u>%-?s %-8s %-?s %8s%</u>\n",
"THREAD", "STATE", "SOBJTYPE", "COUNT");
}
do {
char state[20];
char sobj[100];
mdb_printf("%?p %-8s %-?s %8d\n",
else
mdb_printf("%?p %-8s %-?s %8s\n",
char *reason;
case FSI_FAIL_NOTINMEMORY:
reason = "thread not in memory";
break;
case FSI_FAIL_THREADCORRUPT:
reason = "thread structure stack info corrupt";
break;
case FSI_FAIL_STACKNOTFOUND:
reason = "no consistent stack found";
break;
default:
reason = "unknown failure";
break;
}
}
if (sep->se_overflow)
mdb_printf("\n");
}
if (flags & DCMD_ADDRSPEC) {
mdb_warn("stacks: %p not in thread list\n",
}
return (DCMD_OK);
}