/*
* 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"
#include "umem.h"
#include <sys/vmem_impl_user.h>
#include <umem_impl.h>
#include <alloca.h>
#include <libproc.h>
#include <stdio.h>
#include <string.h>
#include "leaky_impl.h"
#include "misc.h"
#include "proc_kludges.h"
#include "umem_pagesize.h"
/*
*
* See ../genunix/leaky_impl.h for the target interface definition.
*/
/*
* leaky_subr_dump_start()/_end() depend on the ordering of TYPE_VMEM,
* TYPE_MMAP and TYPE_SBRK.
*/
/*
* create a lkm_bufctl from a pointer and a type
*/
typedef struct leaky_seg_info {
typedef struct leaky_maps {
} leaky_maps_t;
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
}
static int
{
return (WALK_NEXT);
}
static int
{
return (WALK_NEXT);
addr);
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
return (WALK_NEXT);
}
static int
leaky_seg_cmp(const void *l, const void *r)
{
return (-1);
return (1);
return (0);
}
static ssize_t
{
continue;
}
continue;
}
return (guess);
}
return (-1);
}
/*ARGSUSED*/
static int
{
++*total;
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
{
return (WALK_NEXT);
return (WALK_ERR);
return (WALK_NEXT);
}
/* ARGSUSED */
static int
{
int has_brk = 0;
int in_vmem = 0;
/*
* This checks if there is any overlap between the segment and the brk.
*/
has_brk = 1;
in_vmem = 1;
/*
* We only want anonymous, mmaped memory. That means:
*
* 1. Must be read-write
* 2. Cannot be shared
* 3. Cannot have backing
* 4. Cannot be in the brk
* 5. Cannot be part of the vmem heap.
*/
(pmp->pr_mapname[0] == 0) &&
!has_brk &&
!in_vmem) {
}
return (WALK_NEXT);
}
static void
{
for (x = 0; x < lmp->lm_seg_count; x++) {
if (first == -1)
first = x;
last = x;
}
}
dprintf(("empty brk -- do nothing\n"));
} else if (first == -1) {
} else {
dprintf(("adding [%p, %p) in brk, before first seg\n",
curbrk));
}
}
}
dprintf(("adding [%p, %p) in brk, after last seg\n",
}
}
}
static int
{
mdb_warn("couldn't read pstatus xdata");
return (DCMD_ERR);
}
mdb_warn("couldn't read heap_arena");
return (DCMD_ERR);
}
if (heap_arena == NULL) {
mdb_warn("heap_arena is NULL.\n");
return (DCMD_ERR);
}
return (DCMD_ERR);
}
}
lm.lm_seg_count = 0;
lm.lm_seg_max = 0;
return (DCMD_ERR);
}
mdb_warn("couldn't walk vmem_span for vmem %p",
heap_top);
return (DCMD_ERR);
}
return (DCMD_ERR);
}
return (DCMD_ERR);
}
return (DCMD_OK);
}
static int
{
mdb_warn("cannot read arena %p for cache '%s'",
return (0);
}
/*
* If this cache isn't allocating from either the umem_default or
* umem_firewall vmem arena, we're not interested.
*/
dprintf(("Skipping cache '%s' with arena '%s'\n",
return (0);
}
return (1);
}
/*ARGSUSED*/
static int
{
if (!leaky_interested(c))
return (WALK_NEXT);
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
{
const char *walk;
if (!leaky_interested(c))
return (WALK_NEXT);
if (audit) {
walk = "bufctl";
} else {
walk = "umem";
}
c->cache_name);
return (WALK_DONE);
}
if (!audit)
}
return (WALK_NEXT);
}
static void
leaky_mappings_header(void)
{
}
/* ARGSUSED */
static int
{
const char *map_libname_ptr;
if (map_libname_ptr != NULL)
else
db_mp_name));
IGNORE("read-only");
IGNORE("in brk");
IGNORE("stack");
USE("a.out data");
IGNORE("part of umem");
} else if (pmp->pr_mapname[0] != 0) {
IGNORE("anon");
} else {
}
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
{
return (0);
}
/*ARGSUSED*/
static int
{
int i;
for (i = 0; i < R_SP; i++)
leaky_grep_ptr(regs[i]);
for (; i < NPRGREG; i++)
leaky_grep_ptr(regs[i]);
return (0);
}
/*
* Handles processing various proc-related things:
* 1. calls leaky_process_lwp on each the LWP
*/
static int
leaky_process_proc(void)
{
mdb_warn("couldn't read pstatus xdata");
return (DCMD_ERR);
}
dprintf(("pstatus says:\n"));
dprintf(("\tbrk: base %p size %p\n",
dprintf(("\tstk: base %p size %p\n",
mdb_warn("couldn't read pshandle xdata");
return (DCMD_ERR);
}
mdb_warn("findleaks: Failed to iterate lwps\n");
return (DCMD_ERR);
}
mdb_warn("findleaks: Failed to iterate lwps\n");
return (DCMD_ERR);
}
&Ps) == -1) {
return (-1);
}
return (0);
}
static void
{
int i;
buf[0] = 0;
for (i = 0; i < depth; i++) {
if (mdb_lookup_by_addr(pc,
continue;
continue;
return;
}
/*
* We're only here if the entire call chain is in libumem.so;
* this shouldn't happen, but we'll just use the last caller.
*/
}
int
{
int rval;
return (rval);
return (-1);
return (1);
return (-1);
return (1);
return (0);
}
/*ARGSUSED*/
int
{
if (umem_ready == 0) {
"findleaks: umem is not loaded in the address space\n");
return (DCMD_ERR);
}
if (umem_ready == UMEM_READY_INIT_FAILED) {
mdb_warn("findleaks: umem initialization failed -- no "
"possible leaks.\n");
return (DCMD_ERR);
}
if (umem_ready != UMEM_READY) {
mdb_warn("findleaks: No allocations have occured -- no "
"possible leaks.\n");
return (DCMD_ERR);
}
mdb_warn("couldn't walk 'umem_cache'");
return (DCMD_ERR);
}
mdb_warn("couldn't walk 'vmem'");
return (DCMD_ERR);
}
if (*estp == 0) {
mdb_warn("findleaks: No allocated buffers found.\n");
return (DCMD_ERR);
}
estp) == -1) {
return (DCMD_ERR);
}
return (DCMD_OK);
}
int
{
mdb_warn("unable to process mappings\n");
return (DCMD_ERR);
}
mdb_warn("couldn't walk 'vmem'");
return (DCMD_ERR);
}
mdb_warn("couldn't walk 'umem_cache'");
return (DCMD_ERR);
}
return (DCMD_OK);
}
int
leaky_subr_run(void)
{
if (leaky_process_proc() == DCMD_ERR) {
mdb_warn("failed to process proc");
return (DCMD_ERR);
}
return (DCMD_OK);
}
void
{
case LKM_CTL_BUFCTL:
mdb_warn("couldn't read leaked bufctl at addr %p",
addr);
return;
}
/*
* The top of the stack will be in umem_cache_alloc().
* Since the offset in umem_cache_alloc() isn't interesting
* we skip that frame for the purposes of uniquifying stacks.
*
* Also, we use the cache pointer as the leaks's cid, to
* prevent the coalescing of leaks from different caches.
*/
if (depth > 0)
depth--;
break;
case LKM_CTL_VMSEG:
mdb_warn("couldn't read leaked vmem_seg at addr %p",
addr);
return;
}
break;
case LKM_CTL_MEMORY:
if (LEAKY_INBRK(addr))
else
break;
case LKM_CTL_CACHE:
break;
default:
mdb_warn("internal error: invalid leak_bufctl_t\n");
break;
}
}
static int lk_vmem_seen;
static int lk_cache_seen;
static int lk_umem_seen;
void
{
switch (type) {
case TYPE_MMAP:
lk_vmem_seen = 0;
break;
case TYPE_SBRK:
case TYPE_VMEM:
return; /* don't zero counts */
case TYPE_CACHE:
lk_cache_seen = 0;
break;
case TYPE_UMEM:
lk_umem_seen = 0;
break;
default:
break;
}
lk_ttl = 0;
lk_bytes = 0;
}
void
{
char c[MDB_SYM_NAMLEN];
if (verbose) {
lk_ttl = 0;
lk_bytes = 0;
lk_vmem_seen = 1;
mdb_printf("%-16s %7s %?s %s\n",
"BYTES", "LEAKED", "VMEM_SEG", "CALLER");
}
case TYPE_MMAP:
case TYPE_SBRK:
if (!verbose)
else
mdb_printf("%s leak: [%p, %p), %ld bytes\n",
lk_ttl++;
}
return;
case TYPE_VMEM:
lk_ttl++;
}
else
if (!verbose) {
c, &caller);
} else {
mdb_arg_t v;
if (lk_ttl == 1)
mdb_printf("umem_oversize leak: 1 vmem_seg, "
"%ld bytes\n", lk_bytes);
else
mdb_printf("umem_oversize leak: %d vmem_segs, "
"%s bytes each, %ld bytes total\n",
v.a_type = MDB_TYPE_STRING;
mdb_warn("'%p::vmem_seg -v' failed",
}
}
return;
case TYPE_CACHE:
if (!lk_cache_seen) {
lk_cache_seen = 1;
if (lk_vmem_seen)
mdb_printf("\n");
mdb_printf("%-?s %7s %?s %s\n",
"CACHE", "LEAKED", "BUFFER", "CALLER");
}
/*
* This _really_ shouldn't happen; we shouldn't
* have been able to get this far if this
* cache wasn't readable.
*/
mdb_warn("can't read cache %p for leaked "
return;
}
if (caller != 0) {
} else {
(void) mdb_snprintf(c, sizeof (c), "%s",
}
if (!verbose) {
} else {
if (lk_ttl == 1)
mdb_printf("%s leak: 1 buffer, %ld bytes,\n",
else
mdb_printf("%s leak: %d buffers, "
"%ld bytes each, %ld bytes total,\n",
mdb_printf(" %s%s%ssample addr %p\n",
}
return;
case TYPE_UMEM:
if (!lk_umem_seen) {
lk_umem_seen = 1;
if (lk_vmem_seen || lk_cache_seen)
mdb_printf("\n");
mdb_printf("%-?s %7s %?s %s\n",
"CACHE", "LEAKED", "BUFCTL", "CALLER");
}
/*
* This _really_ shouldn't happen; we shouldn't
* have been able to get this far if this
* cache wasn't readable.
*/
mdb_warn("can't read cache %p for leaked "
return;
}
if (!verbose) {
&caller);
} else {
mdb_arg_t v;
if (lk_ttl == 1)
mdb_printf("%s leak: 1 buffer, %ld bytes\n",
else
mdb_printf("%s leak: %d buffers, "
"%ld bytes each, %ld bytes total\n",
v.a_type = MDB_TYPE_STRING;
mdb_warn("'%p::bufctl -v' failed",
}
}
return;
default:
return;
}
}
void
{
int i;
int width;
const char *leak;
switch (type) {
case TYPE_VMEM:
if (!lk_vmem_seen)
return;
width = 16;
leak = "oversized leak";
break;
case TYPE_CACHE:
if (!lk_cache_seen)
return;
leak = "buffer";
break;
case TYPE_UMEM:
if (!lk_umem_seen)
return;
leak = "buffer";
break;
default:
return;
}
for (i = 0; i < 72; i++)
mdb_printf("-");
mdb_printf("\n%*s %7ld %s%s, %ld byte%s\n",
}
int
void *cbdata)
{
case TYPE_VMEM:
mdb_warn("unable to read vmem_seg at %p",
return (WALK_NEXT);
}
case TYPE_UMEM:
mdb_warn("unable to read bufctl at %p",
return (WALK_NEXT);
}
default:
}
}