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 */

Reply via email to