kmem.c revision 0c3b83b18601d5b276d2586e180f1e6929247469
/*
* 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_param.h>
#include <mdb/mdb_modapi.h>
#include <sys/kmem_impl.h>
#include <sys/vmem_impl.h>
#include <sys/sysmacros.h>
#include "avl.h"
#include "combined.h"
#include "dist.h"
#include "kmem.h"
#include "list.h"
#define dprintf(x) if (mdb_debug_level) { \
mdb_printf("kmem debug: "); \
/*CSTYLED*/\
mdb_printf x ;\
}
#define KM_ALLOCATED 0x01
#define KM_FREE 0x02
#define KM_BUFCTL 0x04
#define KM_HASH 0x10
static int mdb_debug_level = 0;
/*ARGSUSED*/
static int
{
mdb_walker_t w;
char descr[64];
"walk the %s cache", c->cache_name);
w.walk_name = c->cache_name;
w.walk_descr = descr;
w.walk_init = kmem_walk_init;
w.walk_step = kmem_walk_step;
w.walk_fini = kmem_walk_fini;
w.walk_init_arg = (void *)addr;
if (mdb_add_walker(&w) == -1)
return (WALK_NEXT);
}
/*ARGSUSED*/
int
{
mdb_debug_level ^= 1;
mdb_printf("kmem: debugging is now %s\n",
return (DCMD_OK);
}
int
{
mdb_warn("couldn't find kmem_caches");
return (WALK_ERR);
}
}
int
{
mdb_warn("kmem_cpu_cache doesn't support global walks");
return (WALK_ERR);
}
mdb_warn("couldn't walk 'cpu'");
return (WALK_ERR);
}
return (WALK_NEXT);
}
int
{
return (WALK_ERR);
}
}
static int
{
kmem_slab_t *sp = p;
mdb_warn("slab %p isn't in cache %p (in cache %p)\n",
return (-1);
}
return (0);
}
static int
{
kmem_slab_t *sp = p;
if (rc != 0) {
return (rc);
}
if (!KMEM_SLAB_IS_PARTIAL(sp)) {
return (-1);
}
return (0);
}
static int
{
kmem_slab_t *sp = p;
if (rc != 0) {
return (rc);
}
if (!KMEM_SLAB_IS_ALL_USED(sp)) {
return (-1);
}
return (0);
}
typedef struct {
int kns_nslabs;
static int
{
if (rc != 0) {
return (rc);
}
}
static int
{
kmem_complete_slab_check, (void *)caddr));
}
static int
{
kmem_partial_slab_check, (void *)caddr));
}
int
{
mdb_warn("kmem_slab doesn't support global walks\n");
return (WALK_ERR);
}
return (WALK_NEXT);
}
static int
{
}
int
{
kmem_cache_t c;
mdb_warn("kmem_slab_partial doesn't support global walks\n");
return (WALK_ERR);
}
return (WALK_ERR);
}
/*
* Some consumers (umem_walk_step(), in particular) require at
* least one callback if there are any buffers in the cache. So
* if there are *no* partial slabs, report the first full slab, if
* any.
*
* Yes, this is ugly, but it's cleaner than the other possibilities.
*/
if (c.cache_partial_slabs.avl_numnodes == 0) {
} else {
}
return (WALK_NEXT);
}
int
{
kmem_cache_t c;
return (DCMD_USAGE);
}
if (!(flags & DCMD_ADDRSPEC)) {
mdb_warn("can't walk kmem_cache");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (DCMD_HDRSPEC(flags))
"FLAG", "CFLAG", "BUFSIZE", "BUFTOTL");
return (DCMD_ERR);
}
return (DCMD_OK);
return (DCMD_OK);
}
void
kmem_cache_help(void)
{
mdb_dec_indent(2);
mdb_printf("%<b>OPTIONS%</b>\n");
mdb_inc_indent(2);
mdb_printf("%s",
" -n name\n"
" name of kmem cache (or matching partial name)\n"
"\n"
"Column\tDescription\n"
"\n"
"ADDR\t\taddress of kmem cache\n"
"NAME\t\tname of kmem cache\n"
"FLAG\t\tvarious cache state flags\n"
"CFLAG\t\tcache creation flags\n"
"BUFSIZE\tobject size in bytes\n"
"BUFTOTL\tcurrent total buffers in cache (allocated and free)\n");
}
#define LABEL_WIDTH 11
static void
{
int buckets;
int i;
const int *distarray;
int complete[2];
total = 0;
for (i = 0; i <= buffers_per_slab; i++)
if (maxbuckets > 1)
if (minbucketsize > 1) {
/*
* minbucketsize does not apply to the first bucket reserved
* for completely allocated slabs
*/
buckets = 2;
}
}
/*
* The first printed bucket is reserved for completely allocated slabs.
* Passing (buckets - 1) excludes that bucket from the generated
* distribution, since we're handling it as a special case.
*/
complete[0] = buffers_per_slab;
/*
* Print bucket ranges in descending order after the first bucket for
* completely allocated slabs, so a person can see immediately whether
* or not there is fragmentation without having to scan possibly
* multiple screens of output. Starting at (buckets - 2) excludes the
* extra terminating bucket.
*/
for (i = buckets - 2; i >= 0; i--) {
}
mdb_printf("\n");
}
/*ARGSUSED*/
static int
{
return (WALK_DONE);
}
/*ARGSUSED*/
static int
{
/*
* The "kmem_partial_slab" walker reports the first full slab if there
* are no partial slabs (for the sake of consumers that require at least
* one callback if there are any buffers in the cache).
*/
return (WALK_DONE);
}
typedef struct kmem_slab_usage {
int ksu_refcnt; /* count of allocated buffers on slab */
typedef struct kmem_slab_stats {
const kmem_cache_t *ks_cp;
int ks_slabs; /* slabs in cache */
int ks_partial_slabs; /* partially allocated slabs in cache */
int ks_max_buffers_per_slab; /* max buffers per slab */
int ks_usage_len; /* ks_usage array length */
/*ARGSUSED*/
static int
{
long unused;
if (unused == 0) {
return (WALK_NEXT);
}
ks->ks_partial_slabs++;
}
}
return (WALK_NEXT);
}
static void
{
mdb_printf("%-25s %8s %8s %9s %9s %6s\n",
"", "", "Partial", "", "Unused", "");
mdb_printf("%-25s %8s %8s %9s %9s %6s\n",
"Cache Name", "Slabs", "Slabs", "Buffers", "Buffers", "Waste");
mdb_printf("%-25s %8s %8s %9s %9s %6s\n",
"-------------------------", "--------", "--------", "---------",
"---------", "------");
}
int
{
kmem_cache_t c;
int pct;
int tenths_pct;
size_t minbucketsize = 0;
return (DCMD_USAGE);
}
}
if (!(flags & DCMD_ADDRSPEC)) {
argv) == -1) {
mdb_warn("can't walk kmem_cache");
return (DCMD_ERR);
}
return (DCMD_OK);
}
return (DCMD_ERR);
}
} else {
/* match either -n or -N */
}
if (DCMD_HDRSPEC(flags)) {
} else {
const char *walker_name;
if (opt_v) {
walker_name = "kmem_slab_partial";
} else {
walker_name = "kmem_slab";
}
if (is_slab) {
}
}
}
if (skip) {
return (DCMD_OK);
}
/* +1 to include a zero bucket */
if (c.cache_buftotal == 0) {
pct = 0;
tenths_pct = 0;
} else {
pct = (int)(n / c.cache_buftotal);
if (tenths_pct == 10) {
pct += 100;
tenths_pct = 0;
}
}
pct /= 100;
if (maxbuckets == 0) {
}
mdb_printf("\n");
}
if (!opt_v) {
return (DCMD_OK);
}
int i;
mdb_printf(" %d complete (%d), %d partial:",
for (i = 0; i < stats.ks_partial_slabs; i++) {
}
mdb_printf("\n\n");
}
if (stats.ks_usage_len > 0) {
}
return (DCMD_OK);
}
void
kmem_slabs_help(void)
{
mdb_printf("%s",
"Display slab usage per kmem cache.\n\n");
mdb_dec_indent(2);
mdb_printf("%<b>OPTIONS%</b>\n");
mdb_inc_indent(2);
mdb_printf("%s",
" -n name\n"
" name of kmem cache (or matching partial name)\n"
" -N name\n"
" exact name of kmem cache\n"
" -b maxbins\n"
" Print a distribution of allocated buffers per slab using at\n"
" most maxbins bins. The first bin is reserved for completely\n"
" allocated slabs. Setting maxbins to zero (-b 0) has the same\n"
" effect as specifying the maximum allocated buffers per slab\n"
" or setting minbinsize to 1 (-B 1).\n"
" -B minbinsize\n"
" Print a distribution of allocated buffers per slab, making\n"
" all bins (except the first, reserved for completely allocated\n"
" slabs) at least minbinsize buffers apart.\n"
" -v verbose output: List the allocated buffer count of each partial\n"
" slab on the free list in order from front to back to show how\n"
" closely the slabs are ordered by usage. For example\n"
"\n"
" 10 complete, 3 partial (8): 7 3 1\n"
"\n"
" means there are thirteen slabs with eight buffers each, including\n"
" three partially allocated slabs with less than all eight buffers\n"
" allocated.\n"
"\n"
" Buffer allocations are always from the front of the partial slab\n"
" list. When a buffer is freed from a completely used slab, that\n"
" slab is added to the front of the partial slab list. Assuming\n"
" that all buffers are equally likely to be freed soon, the\n"
" desired order of partial slabs is most-used at the front of the\n"
" list and least-used at the back (as in the example above).\n"
" However, if a slab contains an allocated buffer that will not\n"
" soon be freed, it would be better for that slab to be at the\n"
" front where all of its buffers can be allocated. Taking a slab\n"
" off the partial slab list (either with all buffers freed or all\n"
" buffers allocated) reduces cache fragmentation.\n"
"\n"
" A slab's allocated buffer count representing a partial slab (9 in\n"
" the example below) may be marked as follows:\n"
"\n"
" 9* An asterisk indicates that kmem has marked the slab non-\n"
" reclaimable because the kmem client refused to move one of the\n"
" slab's buffers. Since kmem does not expect to completely free the\n"
" slab, it moves it to the front of the list in the hope of\n"
" completely allocating it instead. A slab marked with an asterisk\n"
" stays marked for as long as it remains on the partial slab list.\n"
"\n"
"Column\t\tDescription\n"
"\n"
"Cache Name\t\tname of kmem cache\n"
"Slabs\t\t\ttotal slab count\n"
"Partial Slabs\t\tcount of partially allocated slabs on the free list\n"
"Buffers\t\ttotal buffer count (Slabs * (buffers per slab))\n"
"Unused Buffers\tcount of unallocated buffers across all partial slabs\n"
"Waste\t\t\t(Unused Buffers / Buffers) does not include space\n"
"\t\t\t for accounting structures (debug mode), slab\n"
"\t\t\t coloring (incremental small offsets to stagger\n"
"\t\t\t buffer alignment), or the per-CPU magazine layer\n");
}
static int
{
return (-1);
return (1);
return (0);
}
static int
{
return (-1);
return (1);
return (0);
}
typedef struct kmem_hash_walk {
int
{
kmem_cache_t c;
mdb_warn("kmem_hash doesn't support global walks\n");
return (WALK_ERR);
}
return (WALK_ERR);
}
if (!(c.cache_flags & KMF_HASH)) {
return (WALK_DONE); /* nothing to do */
}
return (WALK_ERR);
}
return (WALK_NEXT);
}
int
{
break;
}
}
return (WALK_DONE);
return (WALK_ERR);
}
}
void
{
return;
}
/*
* Find the address of the bufctl structure for the address 'buf' in cache
* 'cp', which is at address caddr, and place it in *out.
*/
static int
{
mdb_warn("unable to read hash bucket for %p in cache %p",
return (-1);
}
return (-1);
}
return (0);
}
}
return (-1);
}
int
{
int res;
/*
* if cpu 0 has a non-zero magsize, it must be correct. caches
* with KMF_NOMAGAZINE have disabled their magazine layers, so
* it is okay to return 0 for them.
*/
return (res);
mdb_warn("unable to read 'kmem_magtype'");
mdb_warn("cache '%s' has invalid magtype pointer (%p)\n",
return (0);
}
return (0);
}
return (mt.mt_magsize);
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
}
/*
* Returns an upper bound on the number of allocated buffers in a given
* cache.
*/
{
int magsize;
(void) mdb_pwalk("kmem_slab_partial",
} else {
mdb_warn("cache %p's magazine layer holds more buffers "
"than the slab layer.\n", addr);
}
}
return (cache_est);
}
#define READMAG_ROUNDS(rounds) { \
goto fail; \
} \
for (i = 0; i < rounds; i++) { \
mdb_warn("%d magazines exceeds fudge factor\n", \
magcnt); \
goto fail; \
} \
} \
}
int
{
int i, cpu;
/*
* Read the magtype out of the cache, after verifying the pointer's
* correctness.
*/
if (magsize == 0) {
*magcntp = 0;
*magmaxp = 0;
return (WALK_NEXT);
}
/*
* There are several places where we need to go buffer hunting:
* the per-CPU loaded magazine, the per-CPU spare full magazine,
* and the full magazine list in the depot.
*
* For an upper bound on the number of buffers in the magazine
* layer, we have the number of magazines on the cache_full
* list plus at most two magazines per CPU (the loaded and the
* spare). Toss in 100 magazines as a fudge factor in case this
* is live (the number "100" comes from the same fudge factor in
* crash(1M)).
*/
mdb_warn("magazine size for cache %p unreasonable (%x)\n",
return (WALK_ERR);
}
goto fail;
/*
* First up: the magazines in the depot (i.e. on the cache_full list).
*/
break; /* cache_full list loop detected */
}
dprintf(("cache_full list done\n"));
/*
* Now whip through the CPUs, snagging the loaded magazines
* and full spares.
*/
dprintf(("reading cpu cache %p\n",
}
if (ccp->cc_prounds > 0 &&
dprintf(("reading %d previously loaded rounds\n",
ccp->cc_prounds));
}
}
if (!(alloc_flags & UM_GC))
return (WALK_NEXT);
fail:
if (!(alloc_flags & UM_GC)) {
if (mp)
if (maglist)
}
return (WALK_ERR);
}
static int
{
}
static int
{
/*
* if KMF_AUDIT is not set, we know that we're looking at a
* kmem_bufctl_t.
*/
(void) memset(&b, 0, sizeof (b));
return (WALK_ERR);
}
}
}
typedef struct kmem_walk {
int kmw_type;
int kmw_addr; /* cache address */
/*
* magazine layer
*/
void **kmw_maglist;
/*
* slab layer
*/
char *kmw_valid; /* to keep track of freed buffers */
char *kmw_ubase; /* buffer for slab data */
} kmem_walk_t;
static int
{
const char *layered;
mdb_warn("kmem walk doesn't support global walks\n");
return (WALK_ERR);
}
/*
* First we need to figure out how many CPUs are configured in the
* system to know how much to slurp out.
*/
goto out2;
}
/*
* It's easy for someone to hand us an invalid cache address.
* Unfortunately, it is hard for this walker to survive an
* invalid cache cleanly. So we make sure that:
*
* 1. the vmem arena for the cache is readable,
* 2. the vmem arena's quantum is a power of 2,
* 3. our slabsize is a multiple of the quantum, and
* 4. our chunksize is >0 and less than our slabsize.
*/
vm_quantum == 0 ||
cp->cache_chunksize == 0 ||
goto out2;
}
if (cp->cache_buftotal == 0) {
return (WALK_DONE);
}
/*
* If they ask for bufctls, but it's a small-slab cache,
* there is nothing to report.
*/
dprintf(("bufctl requested, not KMF_HASH (flags: %p)\n",
cp->cache_flags));
return (WALK_DONE);
}
/*
* If they want constructed buffers, but there's no constructor or
* the cache has DEADBEEF checking enabled, there is nothing to report.
*/
return (WALK_DONE);
}
/*
* Read in the contents of the magazine layer
*/
goto out2;
/*
* We have all of the buffers from the magazines; if we are walking
* allocated buffers, sort them so we can bsearch them later.
*/
if (type & KM_ALLOCATED)
/*
* When walking allocated buffers in a KMF_HASH cache, we walk the
* hash table instead of the slab layer.
*/
layered = "kmem_hash";
} else {
/*
* If we are walking freed buffers, we only need the
* magazine layer plus the partially allocated slabs.
* To walk allocated buffers, we need all of the slabs.
*/
if (type & KM_ALLOCATED)
layered = "kmem_slab";
else
layered = "kmem_slab_partial";
/*
* for small-slab caches, we read in the entire slab. For
* freed buffers, we can just walk the freelist. For
* allocated buffers, we use a 'valid' array to track
* the freed buffers.
*/
sizeof (kmem_bufctl_t), UM_SLEEP);
if (type & KM_ALLOCATED)
}
}
}
out1:
sizeof (kmem_bufctl_t));
if (kmw->kmw_maglist)
}
out2:
return (status);
}
int
{
const kmem_slab_t *sp;
const kmem_bufctl_t *bcp;
int chunks;
char *kbase;
void *buf;
int i, ret;
/*
* first, handle the 'kmem_hash' layered walk case
*/
/*
* We have a buffer which has been allocated out of the
* global layer. We need to make sure that it's not
* actually sitting in a magazine before we report it as
* an allocated buffer.
*/
if (magcnt > 0 &&
return (WALK_NEXT);
}
/*
* If we're walking freed buffers, report everything in the
* magazine layer before processing the first slab.
*/
for (i = 0; i < magcnt; i++) {
/* LINTED - alignment */
mdb_warn("reading buftag for "
continue;
}
} else {
&out) == -1)
continue;
}
} else {
}
return (ret);
}
}
/*
* If they want constructed buffers, we're finished, since the
* magazine layer holds them all.
*/
if (type & KM_CONSTRUCTED)
return (WALK_DONE);
/*
* Handle the buffers in the current slab
*/
return (WALK_ERR);
}
/*
* Set up the valid map as fully allocated -- we'll punch
* out the freelist.
*/
if (type & KM_ALLOCATED)
} else {
}
/*
* walk the slab's freelist
*/
/*
* since we could be in the middle of allocating a buffer,
* our refcnt could be one higher than it aught. So we
* check one further on the freelist than the count allows.
*/
if (i == chunks)
break;
"slab %p in cache %p freelist too short by %d\n",
break;
}
mdb_warn("failed to read bufctl ptr at %p",
bcp);
break;
}
} else {
/*
* Otherwise the buffer is in the slab which
* we've read in; we just need to determine
* its offset in the slab to find the
* kmem_bufctl_t.
*/
bc = *((kmem_bufctl_t *)
}
/*
* This is very wrong; we have managed to find
* a buffer in the slab which shouldn't
* actually be here. Emit a warning, and
* try to continue.
*/
mdb_warn("buf %p is out of range for "
} else if (type & KM_ALLOCATED) {
/*
* we have found a buffer on the slab's freelist;
* clear its entry
*/
} else {
/*
* Report this freed buffer
*/
} else {
}
return (ret);
}
}
dprintf(("slab %p in cache %p freelist too long (%p)\n",
}
/*
* If we are walking freed buffers, the loop above handled reporting
* them.
*/
return (WALK_NEXT);
mdb_warn("impossible situation: small-slab KM_BUFCTL walk for "
"cache %p\n", addr);
return (WALK_ERR);
}
/*
* Report allocated buffers, skipping buffers in the magazine layer.
* We only get this far for small-slab caches.
*/
if (!valid[i])
continue; /* on slab freelist */
if (magcnt > 0 &&
continue; /* in magazine layer */
}
return (ret);
}
void
{
return;
}
/*ARGSUSED*/
static int
{
/*
* Buffers allocated from NOTOUCH caches can also show up as freed
* memory in other caches. This can be a little confusing, so we
* don't walk NOTOUCH caches when walking all caches (thereby assuring
* that "::walk kmem" and "::walk freemem" yield disjoint output).
*/
if (c->cache_cflags & KMC_NOTOUCH)
return (WALK_NEXT);
return (WALK_DONE);
return (WALK_NEXT);
}
return (WALK_ERR); \
return (WALK_DONE); \
}
int
{
}
int
{
}
int
{
}
int
{
}
int
{
}
int
{
return (kmem_walk_init_common(wsp,
}
typedef struct bufctl_history_walk {
void *bhw_next;
int
{
mdb_warn("bufctl_history walk doesn't support global walks\n");
return (WALK_ERR);
}
return (WALK_ERR);
}
bhw->bhw_timestamp = 0;
/*
* sometimes the first log entry matches the base bufctl; in that
* case, skip the base bufctl.
*/
else
return (WALK_NEXT);
}
int
{
return (WALK_DONE);
return (WALK_ERR);
}
/*
* The bufctl is only valid if the address, cache, and slab are
* correct. We also check that the timestamp is decreasing, to
* prevent infinite loops.
*/
return (WALK_DONE);
}
void
{
}
typedef struct kmem_log_walk {
int
{
int maxndx, i, j, k;
/*
* By default (global walk), walk the kmem_transaction_log. Otherwise
* read the log whose kmem_log_header_t is stored at walk_addr.
*/
mdb_warn("failed to read 'kmem_transaction_log'");
return (WALK_ERR);
}
mdb_warn("log is disabled\n");
return (WALK_ERR);
}
return (WALK_ERR);
}
return (WALK_ERR);
}
sizeof (kmem_bufctl_audit_t *), UM_SLEEP);
for (i = 0, k = 0; i < lhp->lh_nchunks; i++) {
for (j = 0; j < maxndx; j++)
}
(int(*)(const void *, const void *))bufctlcmp);
klw->klw_maxndx = k;
return (WALK_NEXT);
}
int
{
return (WALK_DONE);
}
void
{
sizeof (kmem_bufctl_audit_t *));
}
typedef struct allocdby_bufctl {
typedef struct allocdby_walk {
const char *abw_walk;
int
{
return (WALK_NEXT);
}
return (WALK_NEXT);
}
/*ARGSUSED*/
int
{
return (WALK_DONE);
}
return (WALK_NEXT);
}
static int
{
return (1);
return (-1);
return (0);
}
static int
{
mdb_warn("allocdby walk doesn't support global walks\n");
return (WALK_ERR);
}
if (mdb_walk("kmem_cache",
mdb_warn("couldn't walk kmem_cache");
return (WALK_ERR);
}
(int(*)(const void *, const void *))allocdby_cmp);
return (WALK_NEXT);
}
int
{
}
int
{
}
int
{
return (WALK_DONE);
return (WALK_DONE);
}
}
void
{
}
/*ARGSUSED*/
int
{
char c[MDB_SYM_NAMLEN];
int i;
continue;
continue;
mdb_printf("%s+0x%lx",
break;
}
mdb_printf("\n");
return (WALK_NEXT);
}
static int
{
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
return (DCMD_ERR);
}
return (DCMD_OK);
}
/*ARGSUSED*/
int
{
}
/*ARGSUSED*/
int
{
}
/*
* Return a string describing the address in relation to the given thread's
* stack.
*
* - If the thread state is TS_FREE, return " (inactive interrupt thread)".
*
* - If the address is above the stack pointer, return an empty string
* signifying that the address is active.
*
* - If the address is below the stack pointer, and the thread is not on proc,
* return " (below sp)".
*
* - If the address is below the stack pointer, and the thread is on proc,
* return " (possibly below sp)". Depending on context, we may or may not
* have an accurate t_sp.
*/
static const char *
{
return (" (inactive interrupt thread)");
/*
* Check to see if we're on the panic stack. If so, ignore t_sp, as it
* no longer relates to the thread's real stack.
*/
return ("");
}
return ("");
return (" (possibly below sp)");
return (" (below sp)");
}
typedef struct whatis {
const kmem_cache_t *w_cache;
int w_slab_found;
int w_found;
int w_kmem_lite_count;
} whatis_t;
/* nicely report pointers as offsets from a base */
static void
{
mdb_printf("%p is %s",
addr, description);
else
mdb_printf("%p is %p+%p, %s",
}
/* call one of our dcmd functions with "-v" and the provided address */
static void
{
mdb_arg_t a;
a.a_type = MDB_TYPE_STRING;
}
static void
{
/* LINTED pointer cast may result in improper alignment */
int call_printer;
int count = 0;
int i;
goto done;
goto done;
/*
* provide the bufctl ptr if it has useful information
*/
count = w->w_kmem_lite_count;
count = 0;
if (count > 0 &&
btaddr +
count = 0;
/*
* skip unused callers
*/
count--;
}
}
done:
if (baddr != 0 && !call_printer)
mdb_printf("%s from %s%s\n",
if (call_printer)
mdb_inc_indent(8);
for (i = 1; i < count; i++)
mdb_dec_indent(8);
}
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
whatis_print_kmem(addr, 0, w);
w->w_found++;
}
static int
{
return (WALK_NEXT);
/*
* If we're not printing it seperately, provide the vmem_seg
* pointer if it has a stack trace.
*/
}
mdb_printf("%s from %s vmem arena%s\n",
if (!w->w_quiet)
w->w_found++;
}
static int
{
return (WALK_NEXT);
if (w->w_verbose)
if (mdb_pwalk("vmem_alloc",
return (WALK_NEXT);
}
return (WALK_DONE);
if (w->w_verbose)
if (mdb_pwalk("vmem_free",
return (WALK_NEXT);
}
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
return (WALK_NEXT);
w->w_found++;
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
w->w_slab_found++;
return (WALK_DONE);
}
static int
{
return (WALK_NEXT);
/* For caches with auditing info, we always walk the bufctls */
walk = "bufctl";
freewalk = "freectl";
} else {
walk = "kmem";
freewalk = "freemem";
}
w->w_cache = c;
if (w->w_verbose)
/*
* Verify that the address is in one of the cache's slabs. If not,
* we can skip the more expensive walkers. (this is purely a
* heuristic -- as long as there are no false-negatives, we'll be fine)
*
* We try to get the cache's arena's quantum, since to accurately
* get the base of a slab, you have to align it to the quantum. If
* it doesn't look sensible, we fall back to not aligning.
*/
mdb_warn("unable to read %p->cache_arena->vm_quantum", c);
w->w_slab_align = 1;
}
mdb_warn("%p's arena has invalid quantum (0x%p)\n", c,
w->w_slab_align);
w->w_slab_align = 1;
}
w->w_slab_found = 0;
addr) == -1) {
mdb_warn("can't find kmem_slab walker");
return (WALK_DONE);
}
if (w->w_slab_found == 0)
return (WALK_NEXT);
if (c->cache_flags & KMF_LITE) {
if (mdb_readvar(&w->w_kmem_lite_count,
w->w_kmem_lite_count = 0;
}
if (w->w_verbose)
return (WALK_DONE);
}
return (WALK_DONE);
/*
* We have searched for allocated memory; now search for freed memory.
*/
if (w->w_verbose)
return (WALK_DONE);
}
}
static int
{
if (c->cache_cflags & KMC_NOTOUCH)
return (WALK_NEXT);
return (whatis_walk_cache(addr, c, w));
}
static int
{
if (!(c->cache_cflags & KMC_NOTOUCH))
return (WALK_NEXT);
return (whatis_walk_cache(addr, c, w));
}
static int
{
/*
* Often, one calls ::whatis on an address from a thread structure.
* We use this opportunity to short circuit this case...
*/
"allocated as a thread structure\n");
w->w_found++;
}
return (WALK_NEXT);
return (WALK_NEXT);
stack_active(t, w->w_addr));
w->w_found++;
}
static int
{
return (WALK_NEXT);
return (WALK_NEXT);
}
where = "text segment";
goto found;
}
where = "data segment";
goto found;
}
where = "bss";
goto found;
}
return (WALK_NEXT);
}
where = "symtab";
goto found;
}
where = "symspace";
goto found;
}
return (WALK_NEXT);
/*
* If we found this address in a module, then there's a chance that
* it's actually a named symbol. Try the symbol lookup.
*/
}
w->w_found++;
}
/*ARGSUSED*/
static int
{
static int machsize = 0;
if (machsize == 0) {
else {
mdb_warn("could not get size of page_t");
}
}
return (WALK_NEXT);
"allocated as a page structure\n");
w->w_found++;
}
int
{
whatis_t w;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
return (DCMD_USAGE);
w.w_found = 0;
if (w.w_verbose)
mdb_printf("Searching modules...\n");
if (!w.w_idspace) {
== -1) {
mdb_warn("couldn't find modctl walker");
return (DCMD_ERR);
}
return (DCMD_OK);
/*
* Now search all thread stacks. Yes, this is a little weak; we
* can save a lot of work by first checking to see if the
* address is in segkp vs. segkmem. But hey, computers are
* fast.
*/
if (w.w_verbose)
mdb_printf("Searching threads...\n");
== -1) {
mdb_warn("couldn't find thread walker");
return (DCMD_ERR);
}
return (DCMD_OK);
if (w.w_verbose)
mdb_printf("Searching page structures...\n");
== -1) {
mdb_warn("couldn't find page walker");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (mdb_walk("kmem_cache",
mdb_warn("couldn't find kmem_cache walker");
return (DCMD_ERR);
}
return (DCMD_OK);
if (mdb_walk("kmem_cache",
mdb_warn("couldn't find kmem_cache walker");
return (DCMD_ERR);
}
return (DCMD_OK);
if (mdb_walk("vmem_postfix",
mdb_warn("couldn't find vmem_postfix walker");
return (DCMD_ERR);
}
if (w.w_found == 0)
return (DCMD_OK);
}
void
whatis_help(void)
{
"Given a virtual address, attempt to determine where it came\n"
"from.\n"
"\n"
"\t-a\tFind all possible sources. Default behavior is to stop at\n"
"\t\tthe first (most specific) source.\n"
"\t-b\tReport bufctls and vmem_segs for matches in kmem and vmem,\n"
"\t\trespectively. Warning: if the buffer exists, but does not\n"
"\t\thave a bufctl, it will not be reported.\n"
"\t-i\tSearch only identifier arenas and caches. By default\n"
"\t\tthese are ignored.\n"
"\t-q\tDon't print multi-line reports (stack traces, etc.)\n"
"\t\tsearched\n");
}
typedef struct kmem_log_cpu {
typedef struct kmem_log_data {
int
{
int i;
for (i = 0; i < NCPU; i++) {
break;
}
return (WALK_NEXT);
"failed to read cache_bufsize for cache at %p",
b->bc_cache);
return (WALK_ERR);
}
return (WALK_NEXT);
}
if (i == NCPU)
mdb_printf(" ");
else
mdb_printf("%3d", i);
b->bc_timestamp, b->bc_thread);
return (WALK_NEXT);
}
/*ARGSUSED*/
int
{
int ncpus;
int i;
return (DCMD_USAGE);
mdb_warn("failed to read 'kmem_transaction_log'");
return (DCMD_ERR);
}
mdb_warn("no kmem transaction log\n");
return (DCMD_ERR);
}
return (DCMD_ERR);
}
mdb_warn("couldn't find 'cpu' array");
return (DCMD_ERR);
}
mdb_warn("expected 'cpu' to be of size %d; found %d\n",
return (DCMD_ERR);
}
return (DCMD_ERR);
}
for (i = 0; i < NCPU; i++) {
continue;
mdb_warn("cannot read cpu %d's log header at %p",
i, clhp);
return (DCMD_ERR);
}
clhp += sizeof (kmem_cpu_log_header_t);
}
"TIMESTAMP", "THREAD");
/*
* If we have been passed an address, print out only log entries
* corresponding to that address. If opt_b is specified, then interpret
* the address as a bufctl.
*/
if (flags & DCMD_ADDRSPEC) {
if (opt_b) {
} else {
if (mdb_vread(&b,
return (DCMD_ERR);
}
return (DCMD_OK);
}
}
mdb_warn("can't find kmem log walker");
return (DCMD_ERR);
}
return (DCMD_OK);
}
typedef struct bufctl_history_cb {
int bhc_flags;
int bhc_argc;
int bhc_ret;
/*ARGSUSED*/
static int
{
}
void
bufctl_help(void)
{
mdb_printf("%s",
"Display the contents of kmem_bufctl_audit_ts, with optional filtering.\n\n");
mdb_dec_indent(2);
mdb_printf("%<b>OPTIONS%</b>\n");
mdb_inc_indent(2);
mdb_printf("%s",
" -v Display the full content of the bufctl, including its stack trace\n"
" -h retrieve the bufctl's transaction history, if available\n"
" -a addr\n"
" filter out bufctls not involving the buffer at addr\n"
" -c caller\n"
" -e earliest\n"
" filter out bufctls timestamped before earliest\n"
" -l latest\n"
" filter out bufctls timestamped after latest\n"
" -t thread\n"
" filter out bufctls not involving thread\n");
}
int
{
int i, depth;
char c[MDB_SYM_NAMLEN];
return (DCMD_USAGE);
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
if (in_history && !history)
return (DCMD_USAGE);
if (history && !in_history) {
for (i = 0; i < argc; i++)
/*
* When in history mode, we treat each element as if it
* were in a seperate loop, so that the headers group
* bufctls with similar histories.
*/
addr) == -1) {
mdb_warn("unable to walk bufctl_history");
return (DCMD_ERR);
}
mdb_printf("\n");
}
if (verbose) {
mdb_printf("%16s %16s %16s %16s\n"
"%<u>%16s %16s %16s %16s%</u>\n",
"ADDR", "BUFADDR", "TIMESTAMP", "THREAD",
"", "CACHE", "LASTLOG", "CONTENTS");
} else {
mdb_printf("%<u>%-?s %-?s %-12s %-?s %s%</u>\n",
"ADDR", "BUFADDR", "TIMESTAMP", "THREAD", "CALLER");
}
}
return (DCMD_ERR);
}
/*
* Guard against bogus bc_depth in case the bufctl is corrupt or
* the address does not really refer to a bufctl.
*/
/*
* We were provided an exact symbol value; any
* address in the function is valid.
*/
}
for (i = 0; i < depth; i++)
break;
if (i == depth)
return (DCMD_OK);
}
return (DCMD_OK);
return (DCMD_OK);
return (DCMD_OK);
return (DCMD_OK);
if (flags & DCMD_PIPE_OUT) {
return (DCMD_OK);
}
if (verbose) {
"%<b>%16p%</b> %16p %16llx %16p\n"
"%16s %16p %16p %16p\n",
mdb_inc_indent(17);
for (i = 0; i < depth; i++)
mdb_dec_indent(17);
mdb_printf("\n");
} else {
for (i = 0; i < depth; i++) {
continue;
continue;
break;
}
if (i >= depth)
mdb_printf("\n");
}
return (DCMD_OK);
}
typedef struct kmem_verify {
int kmv_corruption; /* > 0 if corruption found. */
int kmv_besilent; /* report actual corruption sites */
/*
* verify_pattern()
* verify that buf is filled with the pattern pat.
*/
static int64_t
{
/*LINTED*/
return (-1);
}
/*
* verify_buftag()
* verify that btp->bt_bxstat == (bcp ^ pat)
*/
static int
{
}
/*
* verify_free()
* verify the integrity of a free block of memory by checking
* that it is filled with 0xdeadbeef and that its buftag is sane.
*/
/*ARGSUSED1*/
static int
{
/*LINTED*/
/*
* Read the buffer to check.
*/
if (!besilent)
return (WALK_NEXT);
}
KMEM_FREE_PATTERN)) >= 0) {
if (!besilent)
mdb_printf("buffer %p (free) seems corrupted, at %p\n",
goto corrupt;
}
/*
* When KMF_LITE is set, buftagp->bt_redzone is used to hold
* the first bytes of the buffer, hence we cannot check for red
* zone corruption.
*/
if (!besilent)
mdb_printf("buffer %p (free) seems to "
"have a corrupt redzone pattern\n", addr);
goto corrupt;
}
/*
* confirm bufctl pointer integrity.
*/
if (!besilent)
mdb_printf("buffer %p (free) has a corrupt "
"buftag\n", addr);
goto corrupt;
}
return (WALK_NEXT);
kmv->kmv_corruption++;
return (WALK_NEXT);
}
/*
* verify_alloc()
* Verify that the buftag of an allocated buffer makes sense with respect
* to the buffer.
*/
/*ARGSUSED1*/
static int
{
/*LINTED*/
/*
* Read the buffer to check.
*/
if (!besilent)
return (WALK_NEXT);
}
/*
* There are two cases to handle:
* 1. If the buf was alloc'd using kmem_cache_alloc, it will have
* 0xfeedfacefeedface at the end of it
* 2. If the buf was alloc'd using kmem_alloc, it will have
* 0xbb just past the end of the region in use. At the buftag,
* it will have 0xfeedface (or, if the whole buffer is in use,
* 0xfeedface & bb000000 or 0xfeedfacf & 000000bb depending on
* endianness), followed by 32 bits containing the offset of the
* 0xbb byte in the buffer.
*
* Finally, the two 32-bit words that comprise the second half of the
* buftag should xor to KMEM_BUFTAG_ALLOC
*/
looks_ok = 1;
size_ok = 0;
looks_ok = 1;
else
size_ok = 0;
if (!size_ok) {
if (!besilent)
mdb_printf("buffer %p (allocated) has a corrupt "
"redzone size encoding\n", addr);
goto corrupt;
}
if (!looks_ok) {
if (!besilent)
mdb_printf("buffer %p (allocated) has a corrupt "
"redzone signature\n", addr);
goto corrupt;
}
if (!besilent)
mdb_printf("buffer %p (allocated) has a "
"corrupt buftag\n", addr);
goto corrupt;
}
return (WALK_NEXT);
kmv->kmv_corruption++;
return (WALK_NEXT);
}
/*ARGSUSED2*/
int
{
if (flags & DCMD_ADDRSPEC) {
int check_alloc = 0, check_free = 0;
addr) == -1) {
return (DCMD_ERR);
}
sizeof (kmem_buftag_t);
kmv.kmv_corruption = 0;
check_alloc = 1;
check_free = 1;
} else {
mdb_warn("cache %p (%s) does not have "
"redzone checking enabled\n", addr,
}
return (DCMD_ERR);
}
/*
* table mode, don't print out every corrupt buffer
*/
} else {
mdb_printf("Summary for cache '%s'\n",
mdb_inc_indent(2);
kmv.kmv_besilent = 0;
}
if (check_alloc)
if (check_free)
if (kmv.kmv_corruption == 0) {
mdb_printf("%-*s %?p clean\n",
} else {
char *s = ""; /* optional s in "buffer[s]" */
s = "s";
mdb_printf("%-*s %?p %d corrupt buffer%s\n",
kmv.kmv_corruption, s);
}
} else {
/*
* This is the more verbose mode, when the user has
* type addr::kmem_verify. If the cache was clean,
* nothing will have yet been printed. So say something.
*/
if (kmv.kmv_corruption == 0)
mdb_printf("clean\n");
mdb_dec_indent(2);
}
} else {
/*
* If the user didn't specify a cache to verify, we'll walk all
* kmem_cache's, specifying ourself as a callback for each...
* this is the equivalent of '::walk kmem_cache .::kmem_verify'
*/
"Cache Name", "Addr", "Cache Integrity");
}
return (DCMD_OK);
}
typedef struct vmem_node {
struct vmem_node *vn_sibling;
struct vmem_node *vn_children;
int vn_marked;
} vmem_node_t;
typedef struct vmem_walk {
} vmem_walk_t;
int
{
mdb_warn("couldn't read 'vmem_list'");
return (WALK_ERR);
}
goto err;
}
}
continue;
}
continue;
break;
}
mdb_warn("couldn't find %p's parent (%p)\n",
goto err;
}
}
else
return (WALK_NEXT);
err:
}
return (WALK_ERR);
}
int
{
int rval;
return (WALK_DONE);
return (rval);
}
do {
return (rval);
}
/*
* The "vmem_postfix" walk walks the vmem arenas in post-fix order; all
* children are visited before their parent. We perform the postfix walk
* iteratively (rather than recursively) to allow mdb to regain control
* after each callback.
*/
int
{
int rval;
/*
* If this node is marked, then we know that we have already visited
* all of its children. If the node has any siblings, they need to
* be visited next; otherwise, we need to visit the parent. Note
* that vp->vn_marked will only be zero on the first invocation of
* the step function.
*/
else {
/*
* We have neither a parent, nor a sibling, and we
* have already been visited; we're done.
*/
return (WALK_DONE);
}
}
/*
* Before we visit this node, visit its children.
*/
return (rval);
}
void
{
int done;
return;
if (done) {
} else {
}
}
typedef struct vmem_seg_walk {
/*ARGSUSED*/
int
{
return (WALK_ERR);
}
return (WALK_NEXT);
}
/*
* vmem segments can't have type 0 (this should be added to vmem_impl.h).
*/
#define VMEM_NONE 0
int
{
}
int
{
}
int
{
}
int
{
}
int
{
int rval;
if (!seg_size) {
mdb_warn("failed to read 'vmem_seg_size'");
seg_size = sizeof (vmem_seg_t);
}
}
return (WALK_ERR);
}
} else {
}
return (WALK_DONE);
return (rval);
}
void
{
}
#define VMEM_NAMEWIDTH 22
int
{
int ident = 0;
char c[VMEM_NAMEWIDTH];
if (!(flags & DCMD_ADDRSPEC)) {
mdb_warn("can't walk vmem");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (DCMD_HDRSPEC(flags))
mdb_printf("%-?s %-*s %10s %12s %9s %5s\n",
"TOTAL", "SUCCEED", "FAIL");
return (DCMD_ERR);
}
ident = 0;
break;
}
}
mdb_printf("%0?p %-*s %10llu %12llu %9llu %5llu\n",
addr, VMEM_NAMEWIDTH, c,
return (DCMD_OK);
}
void
vmem_seg_help(void)
{
mdb_printf("%s",
"Display the contents of vmem_seg_ts, with optional filtering.\n\n"
"\n"
"A vmem_seg_t represents a range of addresses (or arbitrary numbers),\n"
"representing a single chunk of data. Only ALLOC segments have debugging\n"
"information.\n");
mdb_dec_indent(2);
mdb_printf("%<b>OPTIONS%</b>\n");
mdb_inc_indent(2);
mdb_printf("%s",
" -v Display the full content of the vmem_seg, including its stack trace\n"
" -s report the size of the segment, instead of the end address\n"
" -c caller\n"
" -e earliest\n"
" filter out segments timestamped before earliest\n"
" -l latest\n"
" filter out segments timestamped after latest\n"
" -m minsize\n"
" filer out segments smaller than minsize\n"
" -M maxsize\n"
" filer out segments larger than maxsize\n"
" -t thread\n"
" filter out segments not involving thread\n"
" -T type\n"
" filter out segments not of type 'type'\n"
}
/*ARGSUSED*/
int
{
uint8_t t;
char c[MDB_SYM_NAMLEN];
int no_debug;
int i;
int depth;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
return (DCMD_USAGE);
if (verbose) {
mdb_printf("%16s %4s %16s %16s %16s\n"
"%<u>%16s %4s %16s %16s %16s%</u>\n",
"ADDR", "TYPE", "START", "END", "SIZE",
"", "", "THREAD", "TIMESTAMP", "");
} else {
}
}
return (DCMD_ERR);
}
t = VMEM_ALLOC;
t = VMEM_FREE;
t = VMEM_SPAN;
t = VMEM_ROTOR;
t = VMEM_WALKER;
else {
mdb_warn("\"%s\" is not a recognized vmem_seg type\n",
type);
return (DCMD_ERR);
}
return (DCMD_OK);
}
return (DCMD_OK);
return (DCMD_OK);
/*
* debug info, when present, is only accurate for VMEM_ALLOC segments
*/
no_debug = (t != VMEM_ALLOC) ||
if (no_debug) {
latest != 0)
return (DCMD_OK); /* not enough info */
} else {
sizeof (c), &sym) != -1 &&
/*
* We were provided an exact symbol value; any
* address in the function is valid.
*/
}
for (i = 0; i < depth; i++)
break;
if (i == depth)
return (DCMD_OK);
}
return (DCMD_OK);
return (DCMD_OK);
return (DCMD_OK);
}
t == VMEM_FREE ? "FREE" :
t == VMEM_SPAN ? "SPAN" :
t == VMEM_ROTOR ? "ROTR" :
t == VMEM_WALKER ? "WLKR" :
"????");
if (flags & DCMD_PIPE_OUT) {
return (DCMD_OK);
}
if (verbose) {
mdb_printf("%<b>%16p%</b> %4s %16p %16p %16d\n",
if (no_debug)
return (DCMD_OK);
mdb_printf("%16s %4s %16p %16llx\n",
mdb_inc_indent(17);
for (i = 0; i < depth; i++) {
}
mdb_dec_indent(17);
mdb_printf("\n");
} else {
if (no_debug) {
mdb_printf("\n");
return (DCMD_OK);
}
for (i = 0; i < depth; i++) {
c, sizeof (c), &sym) == -1)
continue;
continue;
break;
}
}
return (DCMD_OK);
}
typedef struct kmalog_data {
/*ARGSUSED*/
static int
{
int i, depth;
if (bcp->bc_timestamp == 0)
return (WALK_DONE);
if (kma->kma_newest == 0)
"failed to read cache_bufsize for cache at %p",
return (WALK_ERR);
}
return (WALK_NEXT);
}
mdb_printf("\nT-%lld.%09lld addr=%p %s\n",
for (i = 0; i < depth; i++)
return (WALK_NEXT);
}
int
{
const char *logname = "kmem_transaction_log";
if (argc > 1)
return (DCMD_USAGE);
kma.kma_newest = 0;
if (flags & DCMD_ADDRSPEC)
else
if (argc > 0) {
return (DCMD_USAGE);
logname = "kmem_failure_log";
logname = "kmem_slab_log";
else
return (DCMD_USAGE);
}
mdb_warn("failed to read %s log header pointer");
return (DCMD_ERR);
}
mdb_warn("failed to walk kmem log");
return (DCMD_ERR);
}
return (DCMD_OK);
}
/*
* As the final lure for die-hard crash(1M) users, we provide ::kmausers here.
* The first piece is a structure which we use to accumulate kmem_cache_t
* addresses of interest. The kmc_add is used as a callback for the kmem_cache
* walker; we either add all caches, or ones named explicitly as arguments.
*/
typedef struct kmclist {
const char *kmc_name; /* Name to match (or NULL) */
int kmc_nelems; /* Num entries in kmc_caches */
int kmc_size; /* Size of kmc_caches array */
} kmclist_t;
static int
{
void *p;
int s;
/*
* If we have a match, grow our array (if necessary), and then
* add the virtual address of the matching cache to our list.
*/
kmc->kmc_caches = p;
}
}
return (WALK_NEXT);
}
/*
* The second piece of ::kmausers is a hash table of allocations. Each
* allocation owner is identified by its stack trace and data_size. We then
* track the total bytes of all such allocations, and the number of allocations
* to report at the end. Once we have a list of caches, we walk through the
* allocated bufctls of each, and update our hash table accordingly.
*/
typedef struct kmowner {
int kmo_depth; /* Depth of stack trace */
} kmowner_t;
typedef struct kmusers {
int kmu_nelems; /* Number of entries in use */
int kmu_size; /* Total number of entries */
} kmusers_t;
static void
{
/*
* If the hash table is full, double its size and rehash everything.
*/
}
}
/*
* Finish computing the hash signature from the stack trace, and then
* see if the owner is in the hash table. If so, update our stats.
*/
for (i = 0; i < depth; i++)
size_t difference = 0;
for (i = 0; i < depth; i++) {
}
if (difference == 0) {
return;
}
}
}
/*
* If the owner is not yet hashed, grab the next element and fill it
* in based on the allocation information.
*/
for (i = 0; i < depth; i++)
}
/*
* When ::kmausers is invoked without the -f flag, we simply update our hash
* table with the information from each allocated bufctl.
*/
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
}
/*
* When ::kmausers is invoked with the -f flag, we print out the information
* for each bufctl as well as updating the hash table.
*/
static int
{
return (WALK_NEXT);
}
mdb_printf("size %d, addr %p, thread %p, cache %s\n",
for (i = 0; i < depth; i++)
return (WALK_NEXT);
}
/*
* We sort our results by allocation size before printing them.
*/
static int
{
}
/*
* The main engine of ::kmausers is relatively straightforward: First we
* accumulate our list of kmem_cache_t addresses into the kmclist_t. Next we
* iterate over the allocated bufctls of each cache in the list. Finally,
* we sort and print our results.
*/
/*ARGSUSED*/
int
{
int audited_caches = 0; /* Number of KMF_AUDIT caches found */
int i, oelems;
argv += i; /* skip past options we just processed */
argc -= i; /* adjust argc */
return (DCMD_USAGE);
return (DCMD_ERR);
}
do_all_caches = 0;
argv++;
argc--;
}
if (flags & DCMD_ADDRSPEC) {
} else {
}
if (opt_e)
mem_threshold = cnt_threshold = 0;
if (opt_f)
if (do_all_caches) {
}
for (i = 0; i < kmc.kmc_nelems; i++) {
kmem_cache_t c;
continue;
}
if (!(c.cache_flags & KMF_AUDIT)) {
if (!do_all_caches) {
mdb_warn("KMF_AUDIT is not enabled for %s\n",
c.cache_name);
}
continue;
}
}
if (audited_caches == 0 && do_all_caches) {
mdb_warn("KMF_AUDIT is not enabled for any caches\n");
return (DCMD_ERR);
}
continue;
mdb_printf("%lu bytes for %u allocations with data size %lu:\n",
}
return (DCMD_OK);
}
void
kmausers_help(void)
{
"Displays the largest users of the kmem allocator, sorted by \n"
"trace. If one or more caches is specified, only those caches\n"
"will be searched. By default, all caches are searched. If an\n"
"address is specified, then only those allocations which include\n"
"the given address are displayed. Specifying an address implies\n"
"-f.\n"
"\n"
"\t-e\tInclude all users, not just the largest\n"
"\t-f\tDisplay individual allocations. By default, users are\n"
"\t\tgrouped by stack\n");
}
static int
kmem_ready_check(void)
{
int ready;
return (-1); /* errno is set for us */
return (ready);
}
void
kmem_statechange(void)
{
static int been_ready = 0;
if (been_ready)
return;
if (kmem_ready_check() <= 0)
return;
been_ready = 1;
}
void
kmem_init(void)
{
mdb_walker_t w = {
};
/*
* If kmem is ready, we'll need to invoke the kmem_cache walker
* immediately. Walkers in the linkage structure won't be ready until
* _mdb_init returns, so we'll need to add this one manually. If kmem
* is ready, we'll use the walker to initialize the caches. If kmem
* isn't ready, we'll register a callback that will allow us to defer
* cache walking until it is.
*/
if (mdb_add_walker(&w) != 0) {
mdb_warn("failed to add kmem_cache walker");
return;
}
}
typedef struct whatthread {
int wt_verbose;
} whatthread_t;
static int
{
return (WALK_NEXT);
/*
* Warn about swapped out threads, but drive on anyway
*/
if (!(t->t_schedflag & TS_LOAD)) {
return (WALK_NEXT);
}
/*
* Search the thread's stack for the given pointer. Note that it would
* be more efficient to follow ::kgrep's lead and read in page-sized
* chunks, but this routine is already fast and simple.
*/
mdb_warn("couldn't read thread %p's stack at %p",
return (WALK_ERR);
}
if (w->wt_verbose) {
mdb_printf("%p in thread %p's stack%s\n",
} else {
return (WALK_NEXT);
}
}
}
return (WALK_NEXT);
}
int
{
whatthread_t w;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
w.wt_verbose = FALSE;
return (DCMD_USAGE);
== -1) {
mdb_warn("couldn't walk threads");
return (DCMD_ERR);
}
return (DCMD_OK);
}