Browse Source

Introduce leak detection code for MALLOC_STATS

OPENBSD_5_0
otto 13 years ago
parent
commit
fc442e2a61
1 changed files with 165 additions and 25 deletions
  1. +165
    -25
      src/lib/libc/stdlib/malloc.c

+ 165
- 25
src/lib/libc/stdlib/malloc.c View File

@ -1,4 +1,4 @@
/* $OpenBSD: malloc.c,v 1.131 2011/05/08 07:08:13 otto Exp $ */
/* $OpenBSD: malloc.c,v 1.132 2011/05/12 09:29:30 otto Exp $ */
/* /*
* Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
* *
@ -43,6 +43,7 @@
#include <unistd.h> #include <unistd.h>
#ifdef MALLOC_STATS #ifdef MALLOC_STATS
#include <sys/tree.h>
#include <fcntl.h> #include <fcntl.h>
#endif #endif
@ -94,6 +95,9 @@
struct region_info { struct region_info {
void *p; /* page; low bits used to mark chunks */ void *p; /* page; low bits used to mark chunks */
uintptr_t size; /* size for pages, or chunk_info pointer */ uintptr_t size; /* size for pages, or chunk_info pointer */
#ifdef MALLOC_STATS
void *f; /* where allocated from */
#endif
}; };
LIST_HEAD(chunk_head, chunk_info); LIST_HEAD(chunk_head, chunk_info);
@ -125,9 +129,11 @@ struct dir_info {
size_t cheap_reallocs; size_t cheap_reallocs;
#define STATS_INC(x) ((x)++) #define STATS_INC(x) ((x)++)
#define STATS_ZERO(x) ((x) = 0) #define STATS_ZERO(x) ((x) = 0)
#define STATS_SETF(x,y) ((x)->f = (y))
#else #else
#define STATS_INC(x) /* nothing */ #define STATS_INC(x) /* nothing */
#define STATS_ZERO(x) /* nothing */ #define STATS_ZERO(x) /* nothing */
#define STATS_SETF(x,y) /* nothing */
#endif /* MALLOC_STATS */ #endif /* MALLOC_STATS */
u_int32_t canary2; u_int32_t canary2;
}; };
@ -195,6 +201,9 @@ extern char *__progname;
#ifdef MALLOC_STATS #ifdef MALLOC_STATS
void malloc_dump(int); void malloc_dump(int);
static void malloc_exit(void); static void malloc_exit(void);
#define CALLER __builtin_return_address(0)
#else
#define CALLER NULL
#endif #endif
/* low bits of r->p determine size: 0 means >= page size and p->size holding /* low bits of r->p determine size: 0 means >= page size and p->size holding
@ -696,7 +705,7 @@ alloc_chunk_info(struct dir_info *d)
} }
static int static int
insert(struct dir_info *d, void *p, size_t sz)
insert(struct dir_info *d, void *p, size_t sz, void *f)
{ {
size_t index; size_t index;
size_t mask; size_t mask;
@ -717,6 +726,9 @@ insert(struct dir_info *d, void *p, size_t sz)
} }
d->r[index].p = p; d->r[index].p = p;
d->r[index].size = sz; d->r[index].size = sz;
#ifdef MALLOC_STATS
d->r[index].f = f;
#endif
d->regions_free--; d->regions_free--;
return 0; return 0;
} }
@ -839,7 +851,7 @@ omalloc_make_chunks(struct dir_info *d, int bits)
if ((uintptr_t)pp & bits) if ((uintptr_t)pp & bits)
wrterror("pp & bits", pp); wrterror("pp & bits", pp);
insert(d, (void *)((uintptr_t)pp | bits), (uintptr_t)bp);
insert(d, (void *)((uintptr_t)pp | bits), (uintptr_t)bp, NULL);
return bp; return bp;
} }
@ -848,7 +860,7 @@ omalloc_make_chunks(struct dir_info *d, int bits)
* Allocate a chunk * Allocate a chunk
*/ */
static void * static void *
malloc_bytes(struct dir_info *d, size_t size)
malloc_bytes(struct dir_info *d, size_t size, void *f)
{ {
int i, j; int i, j;
size_t k; size_t k;
@ -908,6 +920,12 @@ malloc_bytes(struct dir_info *d, size_t size)
i = 0; i = 0;
} }
d->chunk_start += i + 1; d->chunk_start += i + 1;
#ifdef MALLOC_STATS
if (i == 0) {
struct region_info *r = find(d, bp->page);
r->f = f;
}
#endif
*lp ^= u; *lp ^= u;
@ -980,7 +998,7 @@ free_bytes(struct dir_info *d, struct region_info *r, void *ptr)
static void * static void *
omalloc(size_t sz, int zero_fill)
omalloc(size_t sz, int zero_fill, void *f)
{ {
void *p; void *p;
size_t psz; size_t psz;
@ -997,7 +1015,7 @@ omalloc(size_t sz, int zero_fill)
errno = ENOMEM; errno = ENOMEM;
return NULL; return NULL;
} }
if (insert(g_pool, p, sz)) {
if (insert(g_pool, p, sz, f)) {
unmap(g_pool, p, psz); unmap(g_pool, p, psz);
errno = ENOMEM; errno = ENOMEM;
return NULL; return NULL;
@ -1034,7 +1052,7 @@ omalloc(size_t sz, int zero_fill)
} else { } else {
/* takes care of SOME_JUNK */ /* takes care of SOME_JUNK */
p = malloc_bytes(g_pool, sz);
p = malloc_bytes(g_pool, sz, f);
if (zero_fill && p != NULL && sz > 0) if (zero_fill && p != NULL && sz > 0)
memset(p, 0, sz); memset(p, 0, sz);
} }
@ -1090,7 +1108,7 @@ malloc(size_t size)
malloc_recurse(); malloc_recurse();
return NULL; return NULL;
} }
r = omalloc(size, mopts.malloc_zero);
r = omalloc(size, mopts.malloc_zero, CALLER);
malloc_active--; malloc_active--;
_MALLOC_UNLOCK(); _MALLOC_UNLOCK();
if (r == NULL && mopts.malloc_xmalloc) { if (r == NULL && mopts.malloc_xmalloc) {
@ -1198,14 +1216,14 @@ free(void *ptr)
static void * static void *
orealloc(void *p, size_t newsz)
orealloc(void *p, size_t newsz, void *f)
{ {
struct region_info *r; struct region_info *r;
size_t oldsz, goldsz, gnewsz; size_t oldsz, goldsz, gnewsz;
void *q; void *q;
if (p == NULL) if (p == NULL)
return omalloc(newsz, 0);
return omalloc(newsz, 0, f);
r = find(g_pool, p); r = find(g_pool, p);
if (r == NULL) { if (r == NULL) {
@ -1245,6 +1263,7 @@ orealloc(void *p, size_t newsz)
memset(q, SOME_JUNK, memset(q, SOME_JUNK,
rnewsz - roldsz); rnewsz - roldsz);
r->size = newsz; r->size = newsz;
STATS_SETF(r, f);
STATS_INC(g_pool->cheap_reallocs); STATS_INC(g_pool->cheap_reallocs);
return p; return p;
} else if (q != MAP_FAILED) } else if (q != MAP_FAILED)
@ -1263,29 +1282,34 @@ orealloc(void *p, size_t newsz)
} }
unmap(g_pool, (char *)p + rnewsz, roldsz - rnewsz); unmap(g_pool, (char *)p + rnewsz, roldsz - rnewsz);
r->size = gnewsz; r->size = gnewsz;
STATS_SETF(r, f);
return p; return p;
} else { } else {
if (newsz > oldsz && mopts.malloc_junk) if (newsz > oldsz && mopts.malloc_junk)
memset((char *)p + newsz, SOME_JUNK, memset((char *)p + newsz, SOME_JUNK,
rnewsz - mopts.malloc_guard - newsz); rnewsz - mopts.malloc_guard - newsz);
r->size = gnewsz; r->size = gnewsz;
STATS_SETF(r, f);
return p; return p;
} }
} }
if (newsz <= oldsz && newsz > oldsz / 2 && !mopts.malloc_realloc) { if (newsz <= oldsz && newsz > oldsz / 2 && !mopts.malloc_realloc) {
if (mopts.malloc_junk && newsz > 0) if (mopts.malloc_junk && newsz > 0)
memset((char *)p + newsz, SOME_JUNK, oldsz - newsz); memset((char *)p + newsz, SOME_JUNK, oldsz - newsz);
STATS_SETF(r, f);
return p; return p;
} else if (newsz != oldsz || mopts.malloc_realloc) { } else if (newsz != oldsz || mopts.malloc_realloc) {
q = omalloc(newsz, 0);
q = omalloc(newsz, 0, f);
if (q == NULL) if (q == NULL)
return NULL; return NULL;
if (newsz != 0 && oldsz != 0) if (newsz != 0 && oldsz != 0)
memcpy(q, p, oldsz < newsz ? oldsz : newsz); memcpy(q, p, oldsz < newsz ? oldsz : newsz);
ofree(p); ofree(p);
return q; return q;
} else
} else {
STATS_SETF(r, f);
return p; return p;
}
} }
void * void *
@ -1304,7 +1328,7 @@ realloc(void *ptr, size_t size)
malloc_recurse(); malloc_recurse();
return NULL; return NULL;
} }
r = orealloc(ptr, size);
r = orealloc(ptr, size, CALLER);
malloc_active--; malloc_active--;
_MALLOC_UNLOCK(); _MALLOC_UNLOCK();
@ -1347,7 +1371,7 @@ calloc(size_t nmemb, size_t size)
} }
size *= nmemb; size *= nmemb;
r = omalloc(size, 1);
r = omalloc(size, 1, CALLER);
malloc_active--; malloc_active--;
_MALLOC_UNLOCK(); _MALLOC_UNLOCK();
@ -1386,17 +1410,110 @@ posix_memalign(void **memptr, size_t alignment, size_t size)
} }
#ifdef MALLOC_STATS #ifdef MALLOC_STATS
struct malloc_leak {
void (*f)();
size_t total_size;
int count;
};
struct leaknode {
RB_ENTRY(leaknode) entry;
struct malloc_leak d;
};
static int
leakcmp(struct leaknode *e1, struct leaknode *e2)
{
return e1->d.f < e2->d.f ? -1 : e1->d.f > e2->d.f;
}
static RB_HEAD(leaktree, leaknode) leakhead;
RB_GENERATE_STATIC(leaktree, leaknode, entry, leakcmp)
static void
putleakinfo(void *f, size_t sz, int cnt)
{
struct leaknode key, *p;
static struct leaknode *page;
static int used;
if (cnt == 0)
return;
key.d.f = f;
p = RB_FIND(leaktree, &leakhead, &key);
if (p == NULL) {
if (page == NULL ||
used >= MALLOC_PAGESIZE / sizeof(struct leaknode)) {
page = MMAP(MALLOC_PAGESIZE);
if (page == MAP_FAILED)
return;
used = 0;
}
p = &page[used++];
p->d.f = f;
p->d.total_size = sz * cnt;
p->d.count = cnt;
RB_INSERT(leaktree, &leakhead, p);
} else {
p->d.total_size += sz * cnt;
p->d.count += cnt;
}
}
static struct malloc_leak *malloc_leaks;
static void
dump_leaks(int fd)
{
struct leaknode *p;
char buf[64];
int i = 0;
snprintf(buf, sizeof(buf), "Leak report\n");
write(fd, buf, strlen(buf));
snprintf(buf, sizeof(buf), " f sum # avg\n");
write(fd, buf, strlen(buf));
/* XXX only one page of summary */
if (malloc_leaks == NULL)
malloc_leaks = MMAP(MALLOC_PAGESIZE);
if (malloc_leaks != MAP_FAILED)
memset(malloc_leaks, 0, MALLOC_PAGESIZE);
RB_FOREACH(p, leaktree, &leakhead) {
snprintf(buf, sizeof(buf), "%12p %7zu %6u %6zu\n", p->d.f,
p->d.total_size, p->d.count, p->d.total_size / p->d.count);
write(fd, buf, strlen(buf));
if (malloc_leaks == MAP_FAILED ||
i >= MALLOC_PAGESIZE / sizeof(struct malloc_leak))
continue;
malloc_leaks[i].f = p->d.f;
malloc_leaks[i].total_size = p->d.total_size;
malloc_leaks[i].count = p->d.count;
i++;
}
}
static void static void
dump_chunk(int fd, struct chunk_info *p, int fromfreelist)
dump_chunk(int fd, struct chunk_info *p, void *f, int fromfreelist)
{ {
char buf[64]; char buf[64];
while (p != NULL) { while (p != NULL) {
snprintf(buf, sizeof(buf), "chunk %d %d/%d %p\n", p->size,
p->free, p->total, p->page);
snprintf(buf, sizeof(buf), "chunk %12p %12p %4d %d/%d\n",
p->page, ((p->bits[0] & 1) ? NULL : f),
p->size, p->free, p->total);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
if (!fromfreelist)
if (!fromfreelist) {
if (p->bits[0] & 1)
putleakinfo(NULL, p->size, p->total - p->free);
else {
putleakinfo(f, p->size, 1);
putleakinfo(NULL, p->size,
p->total - p->free - 1);
}
break; break;
}
p = LIST_NEXT(p, entries); p = LIST_NEXT(p, entries);
if (p != NULL) { if (p != NULL) {
snprintf(buf, sizeof(buf), " "); snprintf(buf, sizeof(buf), " ");
@ -1418,7 +1535,7 @@ dump_free_chunk_info(int fd, struct dir_info *d)
if (p != NULL) { if (p != NULL) {
snprintf(buf, sizeof(buf), "%2d) ", i); snprintf(buf, sizeof(buf), "%2d) ", i);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
dump_chunk(fd, p, 1);
dump_chunk(fd, p, NULL, 1);
} }
} }
@ -1472,35 +1589,58 @@ malloc_dump1(int fd, struct dir_info *d)
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
snprintf(buf, sizeof(buf), "Regions slots free %zu\n", d->regions_free); snprintf(buf, sizeof(buf), "Regions slots free %zu\n", d->regions_free);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
dump_free_chunk_info(fd, d);
dump_free_page_info(fd, d);
snprintf(buf, sizeof(buf),
"slot) hash d type page f size [free/n]\n");
write(fd, buf, strlen(buf));
for (i = 0; i < d->regions_total; i++) { for (i = 0; i < d->regions_total; i++) {
if (d->r[i].p != NULL) { if (d->r[i].p != NULL) {
size_t h = hash(d->r[i].p) & size_t h = hash(d->r[i].p) &
(d->regions_total - 1); (d->regions_total - 1);
snprintf(buf, sizeof(buf), "%4zx) #%zx %zd ",
snprintf(buf, sizeof(buf), "%4zx) #%4zx %zd ",
i, h, h - i); i, h, h - i);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
REALSIZE(realsize, &d->r[i]); REALSIZE(realsize, &d->r[i]);
if (realsize > MALLOC_MAXCHUNK) { if (realsize > MALLOC_MAXCHUNK) {
putleakinfo(d->r[i].f, realsize, 1);
snprintf(buf, sizeof(buf), snprintf(buf, sizeof(buf),
"%p: %zu\n", d->r[i].p, realsize);
"pages %12p %12p %zu\n", d->r[i].p,
d->r[i].f, realsize);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
} else } else
dump_chunk(fd, dump_chunk(fd,
(struct chunk_info *)d->r[i].size, 0);
(struct chunk_info *)d->r[i].size,
d->r[i].f, 0);
} }
} }
dump_free_chunk_info(fd, d);
dump_free_page_info(fd, d);
snprintf(buf, sizeof(buf), "In use %zu\n", malloc_used); snprintf(buf, sizeof(buf), "In use %zu\n", malloc_used);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
snprintf(buf, sizeof(buf), "Guarded %zu\n", malloc_guarded); snprintf(buf, sizeof(buf), "Guarded %zu\n", malloc_guarded);
write(fd, buf, strlen(buf)); write(fd, buf, strlen(buf));
dump_leaks(fd);
write(fd, "\n", 1);
} }
void void
malloc_dump(int fd) malloc_dump(int fd)
{ {
int i;
void *p;
struct region_info *r;
for (i = 0; i <= MALLOC_DELAYED_CHUNKS; i++) {
p = g_pool->delayed_chunks[i];
if (p == NULL)
continue;
r = find(g_pool, p);
if (r == NULL)
wrterror("bogus pointer in malloc_dump", p);
free_bytes(g_pool, r, p);
g_pool->delayed_chunks[i] = NULL;
}
/* XXX leak when run multiple times */
RB_INIT(&leakhead);
malloc_dump1(fd, g_pool); malloc_dump1(fd, g_pool);
} }


Loading…
Cancel
Save