/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* A generic memory leak detector. The target interface, defined in
* <leaky_impl.h>, is implemented by the genunix and libumem dmods to fill
* in the details of operation.
*/
#include <mdb/mdb_modapi.h>
#include "leaky.h"
#include "leaky_impl.h"
/*
* We re-use the low bit of the lkm_addr as the 'marked' bit.
*/
/*
* Possible values for lk_state.
*/
static volatile int lk_state;
typedef struct leak_state {
} leak_state_t;
typedef struct leak_beans {
int lkb_dups;
int lkb_follows;
int lkb_misses;
int lkb_dismissals;
int lkb_pushes;
int lkb_deepest;
} leak_beans_t;
typedef struct leak_type {
int lt_type;
} leak_type_t;
typedef struct leak_walk {
int lkw_ndx;
} leak_walk_t;
#ifndef _KMDB
#endif
static void
{
if (lk_verbose == FALSE)
return;
mdb_printf("findleaks: ");
mdb_printf("\n");
return;
}
}
static void
{
if (lk_verbose == FALSE)
return;
mdb_printf("findleaks: %*s => %-13lld (%2d.%1d%%)\n",
}
static void
leaky_verbose_begin(void)
{
/* kmdb can't tell time */
#ifndef _KMDB
extern hrtime_t gethrvtime(void);
lk_vbegin = gethrvtime();
#endif
lk_memusage = 0;
}
static void
leaky_verbose_end(void)
{
/* kmdb can't tell time */
#ifndef _KMDB
extern hrtime_t gethrvtime(void);
#endif
if (lk_verbose == FALSE)
return;
mdb_printf("findleaks: %*s => %lu kB\n",
#ifndef _KMDB
mdb_printf("findleaks: %*s => %lld.%lld seconds\n",
mdb_printf("findleaks: %*s => %lld.%lld seconds\n",
#endif
leaky_verbose(NULL, 0);
}
static void *
{
lk_memusage += sz;
return (buf);
}
static void *
{
lk_memusage += sz;
return (buf);
}
static int
leaky_mtabcmp(const void *l, const void *r)
{
return (-1);
return (1);
return (0);
}
static leak_ndx_t
{
continue;
}
continue;
}
return (guess);
}
return (-1);
}
void
{
return;
state_idx = 0;
/*
* Our main loop, led by the 'pop' label:
* 1) read in a buffer piece by piece,
* 2) mark all unmarked mtab entries reachable from it, and
* either scan them in-line or push them onto our stack of
* unfinished work.
* 3) pop the top mtab entry off the stack, and loop.
*/
pop:
/*
* If our address isn't pointer-aligned, we need to align it and
* whack the size appropriately.
*/
size = 0;
}
while (size > 0) {
mdb_warn("[%p, %p): couldn't read %ld bytes at %p",
break;
}
/*
* The buffer looks like: ('+'s are unscanned data)
*
* -----------------------------++++++++++++++++
* | | |
* buf cur end
*
* cur scans forward. When we encounter a new buffer, and
* it will fit behind "cur", we read it in and back up cur,
* processing it immediately.
*/
dismissals++;
continue;
}
misses++;
continue;
}
dups++; /* already seen */
continue;
}
/*
* Found an unmarked buffer. Mark it, then either
* read it in, or add it to the stack of pending work.
*/
follows++;
continue;
}
/*
* couldn't process it in-place -- add it to the
* stack.
*/
else
state_idx = 0;
}
pushes++;
}
}
/*
* Retrieve the next mtab index, extract its info, and loop around
* to process it.
*/
}
if (depth > 0) {
depth--;
goto pop;
}
/*
* update the beans
*/
}
static void
{
return;
}
return;
}
} else {
if (process)
}
}
void
{
}
void
{
leaky_do_grep_ptr(loc, 0);
}
/*
* This may be used to manually process a marked buffer.
*/
int
{
return (0);
return (1);
}
void
{
int i;
mdb_warn("invalid arguments to leaky_add_leak()\n");
return;
}
for (i = 0; i < depth; i++) {
}
return;
}
for (;;) {
goto no_match;
for (i = 0; i < depth; i++)
goto no_match;
/*
* If we're here, we've found a matching stack; link it in.
* Note that the volatile cast assures that these stores
* will occur in program order (thus assuring that we can
* take an interrupt and still be in a sane enough state to
* throw away the data structure later, in leaky_cleanup()).
*/
/*
* If we're older, swap places so that we are the
* representative leak.
*/
}
break;
break;
}
}
}
int
leaky_ctlcmp(const void *l, const void *r)
{
}
void
leaky_sort(void)
{
int type, i, j;
continue;
sizeof (leak_bufctl_t *), UM_SLEEP);
j = 0;
for (i = 0; i < LK_BUFCTLHSIZE; i++) {
}
}
j);
}
}
void
{
int i;
/*
* State structures are allocated UM_GC, so we just need to nuke
* the freelist pointer.
*/
switch (lk_state) {
case LK_CLEAN:
return; /* nothing to do */
case LK_CLEANING:
mdb_warn("interrupted during ::findleaks cleanup; some mdb "
"memory will be leaked\n");
for (i = 0; i < LK_BUFCTLHSIZE; i++)
for (i = 0; i < LK_NUM_TYPES; i++) {
}
return;
case LK_SWEEPING:
break; /* must clean up */
case LK_DONE:
default:
if (!force)
return;
break; /* only clean up if forced */
}
for (i = 0; i < LK_NUM_TYPES; i++) {
}
}
for (i = 0; i < LK_BUFCTLHSIZE; i++) {
}
}
}
}
int
{
int i;
char c;
return (1);
for (i = 0; i < depth; i++) {
return (1);
&c, sizeof (c), &sym) == -1)
continue;
return (1);
}
return (0);
}
void
{
int i;
int seen = 0;
for (i = 0; i < LK_NUM_TYPES; i++) {
while (leaks-- > 0) {
filter))
continue;
seen = 1;
leaky_subr_dump(lkb, 0);
}
}
if (!seen) {
"findleaks: no memory leaks matching %a found\n",
filter);
else
"findleaks: no memory leaks detected\n");
}
if (!dump_verbose || !seen)
return;
mdb_printf("\n");
for (i = 0; i < LK_NUM_TYPES; i++) {
while (leaks-- > 0) {
filter))
continue;
}
}
}
static const char *const findleaks_desc =
"Does a conservative garbage collection of the heap in order to find\n"
"potentially leaked buffers. Similar leaks are coalesced by stack\n"
"trace, with the oldest leak picked as representative. The leak\n"
"table is cached between invocations.\n"
"\n"
"addr, if provided, should be a function or PC location. Reported\n"
"leaks will then be limited to those with that function or PC in\n"
"their stack trace.\n"
"\n"
"The 'leak' and 'leakbuf' walkers can be used to retrieve coalesced\n"
"leaks.\n";
static const char *const findleaks_args =
" -d detail each representative leak (long)\n"
" -f throw away cached state, and do a full run\n"
" -v report verbose information about the findleaks run\n";
void
findleaks_help(void)
{
mdb_dec_indent(2);
mdb_printf("%<b>OPTIONS%</b>\n");
mdb_inc_indent(2);
}
/*ARGSUSED*/
int
{
leak_ndx_t i;
int ret;
if (flags & DCMD_ADDRSPEC)
return (DCMD_USAGE);
/*
* Clean any previous ::findleaks.
*/
if (lk_verbose)
mdb_printf("findleaks: using cached results "
"(use '-f' to force a full run)\n");
goto dump;
}
return (ret);
/*
* Now we have an upper bound on the number of buffers. Allocate
* our mtab array.
*/
return (ret);
/*
* validate the mtab table now that it is sorted
*/
for (i = 0; i < lk_nbuffers; i++) {
mdb_warn("[%p, %p): invalid mtab\n",
return (DCMD_ERR);
}
if (i < lk_nbuffers - 1 &&
mdb_warn("[%p, %p) and [%p, %p): overlapping mtabs\n",
return (DCMD_ERR);
}
}
return (ret);
for (i = 0; i < lk_nbuffers; i++) {
continue;
leaky_subr_add_leak(&lk_mtab[i]);
}
leaky_verbose(NULL, 0);
leaky_verbose(NULL, 0);
leaky_sort();
dump:
return (DCMD_OK);
}
int
{
int i;
mdb_warn("::findleaks must be run %sbefore leaks can be"
return (WALK_ERR);
}
goto found;
}
/*
* Search the representative leaks first, since that's what we
* report in the table. If that fails, search everything.
*
* Note that we goto found with lkb as the head of desired dup list.
*/
for (i = 0; i < LK_BUFCTLHSIZE; i++) {
goto found;
}
for (i = 0; i < LK_BUFCTLHSIZE; i++) {
goto found;
}
return (WALK_ERR);
return (WALK_NEXT);
}
{
return (NULL);
return (NULL);
}
}
return (lk);
}
int
{
return (WALK_DONE);
wsp->walk_cbdata));
}
void
{
}
int
{
return (WALK_DONE);
}