On Thu, May 05, 2011 at 02:29:39PM +0200, Otto Moerbeek wrote: > Hi, > > Now that the latest chunk scan optimization diff is committed, it's > time to present a malloc feature diff. > > This diff adds instrumentation to a MALLOC_STATS enabled malloc.c to > detect and report memory leaks, including the address of the code that > allocated memory that was not freed. With the help of gdb, you can > translate this address into a source file location. > > For instructions and to show some of the possibilities, check > > http://www.drijf.net/malloc/ > > Diff also below. It was good fun working on this, and I hope you'll > enjoy it as well. > > Note that this diff a a bit bigger than strictly needed, I moved the > stats reporting code to the bottom of the file. Something I wanted to > do for a long time.
Updated diff. Kent R. Spillner spotted a glitch that caused a (ironly!) memory leak in the reporting code, -Otto Index: malloc.c =================================================================== RCS file: /cvs/src/lib/libc/stdlib/malloc.c,v retrieving revision 1.130 diff -u -p -r1.130 malloc.c --- malloc.c 5 May 2011 12:11:20 -0000 1.130 +++ malloc.c 5 May 2011 18:15:15 -0000 @@ -43,6 +43,7 @@ #include <unistd.h> #ifdef MALLOC_STATS +#include <sys/tree.h> #include <fcntl.h> #endif @@ -94,6 +95,9 @@ struct region_info { void *p; /* page; low bits used to mark chunks */ 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); @@ -125,9 +129,11 @@ struct dir_info { size_t cheap_reallocs; #define STATS_INC(x) ((x)++) #define STATS_ZERO(x) ((x) = 0) +#define STATS_SETF(x,y) ((x)->f = (y)) #else #define STATS_INC(x) /* nothing */ #define STATS_ZERO(x) /* nothing */ +#define STATS_SETF(x,y) /* nothing */ #endif /* MALLOC_STATS */ u_int32_t canary2; }; @@ -143,6 +149,9 @@ struct dir_info { struct chunk_info { LIST_ENTRY(chunk_info) entries; void *page; /* pointer to the page */ +#ifdef MALLOC_STATS + void *f; +#endif u_int32_t canary; u_short size; /* size of this page's chunks */ u_short shift; /* how far to shift for this size */ @@ -192,6 +201,14 @@ static u_char getrnibble(void); extern char *__progname; +#ifdef MALLOC_STATS +void malloc_dump(int); +static void malloc_exit(void); +#define CALLER __builtin_return_address(0) +#else +#define CALLER NULL +#endif + /* low bits of r->p determine size: 0 means >= page size and p->size holding * real size, otherwise r->size is a shift count, or 1 for malloc(0) */ @@ -217,142 +234,6 @@ hash(void *p) return sum; } -#ifdef MALLOC_STATS -static void -dump_chunk(int fd, struct chunk_info *p, int fromfreelist) -{ - char buf[64]; - - while (p != NULL) { - snprintf(buf, sizeof(buf), "chunk %d %d/%d %p\n", p->size, - p->free, p->total, p->page); - write(fd, buf, strlen(buf)); - if (!fromfreelist) - break; - p = LIST_NEXT(p, entries); - if (p != NULL) { - snprintf(buf, sizeof(buf), " "); - write(fd, buf, strlen(buf)); - } - } -} - -static void -dump_free_chunk_info(int fd, struct dir_info *d) -{ - char buf[64]; - int i; - - snprintf(buf, sizeof(buf), "Free chunk structs:\n"); - write(fd, buf, strlen(buf)); - for (i = 0; i < MALLOC_MAXSHIFT; i++) { - struct chunk_info *p = LIST_FIRST(&d->chunk_dir[i]); - if (p != NULL) { - snprintf(buf, sizeof(buf), "%2d) ", i); - write(fd, buf, strlen(buf)); - dump_chunk(fd, p, 1); - } - } - -} - -static void -dump_free_page_info(int fd, struct dir_info *d) -{ - char buf[64]; - int i; - - snprintf(buf, sizeof(buf), "Free pages cached: %zu\n", - d->free_regions_size); - write(fd, buf, strlen(buf)); - for (i = 0; i < mopts.malloc_cache; i++) { - if (d->free_regions[i].p != NULL) { - snprintf(buf, sizeof(buf), "%2d) ", i); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "free at %p: %zu\n", - d->free_regions[i].p, d->free_regions[i].size); - write(fd, buf, strlen(buf)); - } - } -} - -static void -malloc_dump1(int fd, struct dir_info *d) -{ - char buf[64]; - size_t i, realsize; - - snprintf(buf, sizeof(buf), "Malloc dir of %s at %p\n", __progname, d); - write(fd, buf, strlen(buf)); - if (d == NULL) - return; - snprintf(buf, sizeof(buf), "Regions slots %zu\n", d->regions_total); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "Finds %zu/%zu %f\n", d->finds, - d->find_collisions, - 1.0 + (double)d->find_collisions / d->finds); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "Inserts %zu/%zu %f\n", d->inserts, - d->insert_collisions, - 1.0 + (double)d->insert_collisions / d->inserts); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "Deletes %zu/%zu\n", d->deletes, - d->delete_moves); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "Cheap reallocs %zu/%zu\n", - d->cheap_reallocs, d->cheap_realloc_tries); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "Regions slots free %zu\n", d->regions_free); - write(fd, buf, strlen(buf)); - for (i = 0; i < d->regions_total; i++) { - if (d->r[i].p != NULL) { - size_t h = hash(d->r[i].p) & - (d->regions_total - 1); - snprintf(buf, sizeof(buf), "%4zx) #%zx %zd ", - i, h, h - i); - write(fd, buf, strlen(buf)); - REALSIZE(realsize, &d->r[i]); - if (realsize > MALLOC_MAXCHUNK) { - snprintf(buf, sizeof(buf), - "%p: %zu\n", d->r[i].p, realsize); - write(fd, buf, strlen(buf)); - } else - dump_chunk(fd, - (struct chunk_info *)d->r[i].size, 0); - } - } - dump_free_chunk_info(fd, d); - dump_free_page_info(fd, d); - snprintf(buf, sizeof(buf), "In use %zu\n", malloc_used); - write(fd, buf, strlen(buf)); - snprintf(buf, sizeof(buf), "Guarded %zu\n", malloc_guarded); - write(fd, buf, strlen(buf)); -} - - -void -malloc_dump(int fd) -{ - malloc_dump1(fd, g_pool); -} - -static void -malloc_exit(void) -{ - static const char q[] = "malloc() warning: Couldn't dump stats\n"; - int save_errno = errno, fd; - - fd = open("malloc.out", O_RDWR|O_APPEND); - if (fd != -1) { - malloc_dump(fd); - close(fd); - } else - write(STDERR_FILENO, q, sizeof(q) - 1); - errno = save_errno; -} -#endif /* MALLOC_STATS */ - - static void wrterror(char *msg, void *p) { @@ -827,7 +708,7 @@ alloc_chunk_info(struct dir_info *d) } 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 mask; @@ -848,6 +729,9 @@ insert(struct dir_info *d, void *p, size } d->r[index].p = p; d->r[index].size = sz; +#ifdef MALLOC_STATS + d->r[index].f = f; +#endif d->regions_free--; return 0; } @@ -970,7 +854,7 @@ omalloc_make_chunks(struct dir_info *d, if ((uintptr_t)pp & bits) 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; } @@ -979,7 +863,7 @@ omalloc_make_chunks(struct dir_info *d, * Allocate a chunk */ 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; size_t k; @@ -1039,6 +923,10 @@ malloc_bytes(struct dir_info *d, size_t i = 0; } d->chunk_start += i + 1; +#ifdef MALLOC_STATS + if (i == 0) + bp->f = f; +#endif *lp ^= u; @@ -1111,7 +999,7 @@ free_bytes(struct dir_info *d, struct re static void * -omalloc(size_t sz, int zero_fill) +omalloc(size_t sz, int zero_fill, void *f) { void *p; size_t psz; @@ -1128,7 +1016,7 @@ omalloc(size_t sz, int zero_fill) errno = ENOMEM; return NULL; } - if (insert(g_pool, p, sz)) { + if (insert(g_pool, p, sz, f)) { unmap(g_pool, p, psz); errno = ENOMEM; return NULL; @@ -1165,7 +1053,7 @@ omalloc(size_t sz, int zero_fill) } else { /* 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) memset(p, 0, sz); } @@ -1221,7 +1109,7 @@ malloc(size_t size) malloc_recurse(); return NULL; } - r = omalloc(size, mopts.malloc_zero); + r = omalloc(size, mopts.malloc_zero, CALLER); malloc_active--; _MALLOC_UNLOCK(); if (r == NULL && mopts.malloc_xmalloc) { @@ -1329,14 +1217,14 @@ free(void *ptr) static void * -orealloc(void *p, size_t newsz) +orealloc(void *p, size_t newsz, void *f) { struct region_info *r; size_t oldsz, goldsz, gnewsz; void *q; if (p == NULL) - return omalloc(newsz, 0); + return omalloc(newsz, 0, f); r = find(g_pool, p); if (r == NULL) { @@ -1376,6 +1264,7 @@ orealloc(void *p, size_t newsz) memset(q, SOME_JUNK, rnewsz - roldsz); r->size = newsz; + STATS_SETF(r, f); STATS_INC(g_pool->cheap_reallocs); return p; } else if (q != MAP_FAILED) @@ -1394,29 +1283,34 @@ orealloc(void *p, size_t newsz) } unmap(g_pool, (char *)p + rnewsz, roldsz - rnewsz); r->size = gnewsz; + STATS_SETF(r, f); return p; } else { if (newsz > oldsz && mopts.malloc_junk) memset((char *)p + newsz, SOME_JUNK, rnewsz - mopts.malloc_guard - newsz); r->size = gnewsz; + STATS_SETF(r, f); return p; } } if (newsz <= oldsz && newsz > oldsz / 2 && !mopts.malloc_realloc) { if (mopts.malloc_junk && newsz > 0) memset((char *)p + newsz, SOME_JUNK, oldsz - newsz); + STATS_SETF(r, f); return p; } else if (newsz != oldsz || mopts.malloc_realloc) { - q = omalloc(newsz, 0); + q = omalloc(newsz, 0, f); if (q == NULL) return NULL; if (newsz != 0 && oldsz != 0) memcpy(q, p, oldsz < newsz ? oldsz : newsz); ofree(p); return q; - } else + } else { + STATS_SETF(r, f); return p; + } } void * @@ -1435,7 +1329,7 @@ realloc(void *ptr, size_t size) malloc_recurse(); return NULL; } - r = orealloc(ptr, size); + r = orealloc(ptr, size, CALLER); malloc_active--; _MALLOC_UNLOCK(); @@ -1478,7 +1372,7 @@ calloc(size_t nmemb, size_t size) } size *= nmemb; - r = omalloc(size, 1); + r = omalloc(size, 1, CALLER); malloc_active--; _MALLOC_UNLOCK(); @@ -1516,3 +1410,246 @@ posix_memalign(void **memptr, size_t ali return 0; } +#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; + + 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 + malloc_leaks = MMAP(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 (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 +dump_chunk(int fd, struct chunk_info *p, int fromfreelist) +{ + char buf[64]; + + while (p != NULL) { + snprintf(buf, sizeof(buf), "chunk %12p %12p %4d %d/%d\n", + p->page, ((p->bits[0] & 1) ? NULL : p->f), + p->size, p->free, p->total); + write(fd, buf, strlen(buf)); + if (!fromfreelist) { + if (p->bits[0] & 1) + putleakinfo(NULL, p->size, p->total - p->free); + else { + putleakinfo(p->f, p->size, 1); + putleakinfo(NULL, p->size, + p->total - p->free - 1); + } + break; + } + p = LIST_NEXT(p, entries); + if (p != NULL) { + snprintf(buf, sizeof(buf), " "); + write(fd, buf, strlen(buf)); + } + } +} + +static void +dump_free_chunk_info(int fd, struct dir_info *d) +{ + char buf[64]; + int i; + + snprintf(buf, sizeof(buf), "Free chunk structs:\n"); + write(fd, buf, strlen(buf)); + for (i = 0; i < MALLOC_MAXSHIFT; i++) { + struct chunk_info *p = LIST_FIRST(&d->chunk_dir[i]); + if (p != NULL) { + snprintf(buf, sizeof(buf), "%2d) ", i); + write(fd, buf, strlen(buf)); + dump_chunk(fd, p, 1); + } + } + +} + +static void +dump_free_page_info(int fd, struct dir_info *d) +{ + char buf[64]; + int i; + + snprintf(buf, sizeof(buf), "Free pages cached: %zu\n", + d->free_regions_size); + write(fd, buf, strlen(buf)); + for (i = 0; i < mopts.malloc_cache; i++) { + if (d->free_regions[i].p != NULL) { + snprintf(buf, sizeof(buf), "%2d) ", i); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "free at %p: %zu\n", + d->free_regions[i].p, d->free_regions[i].size); + write(fd, buf, strlen(buf)); + } + } +} + +static void +malloc_dump1(int fd, struct dir_info *d) +{ + char buf[64]; + size_t i, realsize; + + snprintf(buf, sizeof(buf), "Malloc dir of %s at %p\n", __progname, d); + write(fd, buf, strlen(buf)); + if (d == NULL) + return; + snprintf(buf, sizeof(buf), "Regions slots %zu\n", d->regions_total); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "Finds %zu/%zu %f\n", d->finds, + d->find_collisions, + 1.0 + (double)d->find_collisions / d->finds); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "Inserts %zu/%zu %f\n", d->inserts, + d->insert_collisions, + 1.0 + (double)d->insert_collisions / d->inserts); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "Deletes %zu/%zu\n", d->deletes, + d->delete_moves); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "Cheap reallocs %zu/%zu\n", + d->cheap_reallocs, d->cheap_realloc_tries); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "Regions slots free %zu\n", d->regions_free); + 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++) { + if (d->r[i].p != NULL) { + size_t h = hash(d->r[i].p) & + (d->regions_total - 1); + snprintf(buf, sizeof(buf), "%4zx) #%4zx %zd ", + i, h, h - i); + write(fd, buf, strlen(buf)); + REALSIZE(realsize, &d->r[i]); + if (realsize > MALLOC_MAXCHUNK) { + putleakinfo(d->r[i].f, realsize, 1); + snprintf(buf, sizeof(buf), + "pages %12p %12p %zu\n", d->r[i].p, + d->r[i].f, realsize); + write(fd, buf, strlen(buf)); + } else + dump_chunk(fd, + (struct chunk_info *)d->r[i].size, 0); + } + } + snprintf(buf, sizeof(buf), "In use %zu\n", malloc_used); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "Guarded %zu\n", malloc_guarded); + write(fd, buf, strlen(buf)); + dump_leaks(fd); + write(fd, "\n", 1); +} + +void +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; + } + RB_INIT(&leakhead); + malloc_dump1(fd, g_pool); +} + +static void +malloc_exit(void) +{ + static const char q[] = "malloc() warning: Couldn't dump stats\n"; + int save_errno = errno, fd; + + fd = open("malloc.out", O_RDWR|O_APPEND); + if (fd != -1) { + malloc_dump(fd); + close(fd); + } else + write(STDERR_FILENO, q, sizeof(q) - 1); + errno = save_errno; +} + +#endif /* MALLOC_STATS */