From 242ddf1724ff8a5e53afccd48b8610bdcf613ddc Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 12:11:29 +1100 Subject: [PATCH 01/17] simple leak tracer, using libexecinfo --- Makefile | 28 +++++++++ leakmalloc.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ leakmalloc.h | 37 ++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 Makefile create mode 100644 leakmalloc.c create mode 100644 leakmalloc.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ad01bc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# $OpenBSD$ + +LIB= leakmalloc +SRCS= leakmalloc.c +HDRS= leakmalloc.h +#MAN= leakmalloc.3 +NOMAN=1 +NOPIC=1 +NOPROFILE=1 + +CDIAGFLAGS= -Wall +CDIAGFLAGS+= -Werror +CDIAGFLAGS+= -Wstrict-prototypes +CDIAGFLAGS+= -Wmissing-prototypes +CDIAGFLAGS+= -Wmissing-declarations +CDIAGFLAGS+= -Wshadow +CDIAGFLAGS+= -Wpointer-arith +CDIAGFLAGS+= -Wcast-qual +CDIAGFLAGS+= -Wsign-compare +CDIAGFLAGS+= -Wcast-align +CDIAGFLAGS+= -Wbad-function-cast + +CPPFLAGS+= -I/usr/local/include +DEBUG=-ggdb3 +COPTS=-O0 +INSTALL_STRIP= + +.include diff --git a/leakmalloc.c b/leakmalloc.c new file mode 100644 index 0000000..63feebe --- /dev/null +++ b/leakmalloc.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2012 Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define LEAKMALLOC_NO_REDIRECT +#include "leakmalloc.h" + +static int initialised; + +struct alloc { + RB_ENTRY(alloc) entry; + void *addr; + size_t len; + void *bt[BT_MAX_DEPTH]; + int depth; +}; + +static int +alloc_cmp(struct alloc *a, struct alloc *b) +{ + return (a->addr == b->addr) ? 0 : (a->addr < b->addr ? -1 : 1); +} + +RB_HEAD(alloc_tree, alloc); +RB_GENERATE_STATIC(alloc_tree, alloc, entry, alloc_cmp); +static struct alloc_tree alloc_tree = RB_INITIALIZER(&alloc_tree); + +/* Called atexit to dump unfreed leak objects */ +static void +dump_leaks(void) +{ + struct alloc *alloc; + int i; + + RB_FOREACH(alloc, alloc_tree, &alloc_tree) { + printf("LEAK %p %zu TRACE", alloc->addr, alloc->len); + for (i = 0; i < alloc->depth; i++) + printf(" %p", alloc->bt[i]); + printf("\n"); + } +} + +void __record_leak(void *addr, size_t len, void *oaddr); +void +__record_leak(void *addr, size_t len, void *oaddr) +{ + struct alloc oalloc, *alloc; + + if (!initialised) { + atexit(dump_leaks); + initialised = 1; + } + + if (addr == NULL || addr == oaddr) + return; + if (oaddr == NULL) { + /* + * malloc/calloc: allocate a leak object and fill in the + * trace. + */ + if ((alloc = calloc(1, sizeof(*alloc))) == NULL) + errx(1, "%s: calloc failed", __func__); + alloc->addr = addr; + alloc->len = len; + if ((alloc->depth = backtrace(alloc->bt, BT_MAX_DEPTH)) == -1) + errx(1, "%s: backtrace failed", __func__); + if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) + errx(1, "%s: alloc for %p already exists", + __func__, addr); + } else { + oalloc.addr = oaddr; + alloc = RB_FIND(alloc_tree, &alloc_tree, &oalloc); + if (addr == NULL) { + /* + * free: delete the tracked leak. + */ + if (alloc == NULL) + return; /* Ignore untracked memory */ + RB_REMOVE(alloc_tree, &alloc_tree, alloc); + free(alloc); + } else { + /* + * realloc: update the original address so we can + * trace it when it is freed. + */ + if (alloc == NULL) + errx(1, "%s: original addr missing", __func__); + alloc->addr = addr; + alloc->len = len; + } + } +} + +char * +leak_strdup(const char *s) +{ + char *ret = strdup(s); + + __record_leak(ret, ret == NULL ? 0 : strlen(ret), NULL); + return ret; +} + +void * +leak_malloc(size_t len) +{ + void *ret = malloc(len); + + __record_leak(ret, len, NULL); + return ret; +} + +void * +leak_calloc(size_t nmemb, size_t size) +{ + void *ret = calloc(nmemb, size); + + __record_leak(ret, nmemb * size, NULL); + return ret; +} + +void * +leak_realloc(void *s, size_t len) +{ + void *ret = realloc(s, len); + + __record_leak(ret, len, s); + return ret; +} + +void +leak_free(void *s) +{ + free(s); + __record_leak(NULL, 0, s); +} + diff --git a/leakmalloc.h b/leakmalloc.h new file mode 100644 index 0000000..5a0248b --- /dev/null +++ b/leakmalloc.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012 Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LEAKMALLOC_H +#define _LEAKMALLOC_H + +#include + +char *leak_strdup(const char *s); +void *leak_malloc(size_t len); +void *leak_calloc(size_t nmemb, size_t size); +void *leak_realloc(void *s, size_t len); +void leak_free(void *s); + +#ifndef LEAKMALLOC_NO_REDIRECT +#define malloc leak_malloc +#define strdup leak_strdup +#define calloc leak_calloc +#define realloc leak_realloc +#define free leak_free +#endif /* LEAKMALLOC_NO_REDIRECT */ + +#endif /* _LEAKMALLOC_H */ + From b9fc9037a96ce2a2b24bad690e3c6a8e63fae16d Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 13:23:31 +1100 Subject: [PATCH 02/17] tool to resolve leak dumps to nicer reports using addr2line --- leakresolve.py | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100755 leakresolve.py diff --git a/leakresolve.py b/leakresolve.py new file mode 100755 index 0000000..f87c8e3 --- /dev/null +++ b/leakresolve.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 Damien Miller +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# Resolve leak dump to backtraces + +import sys +import getopt +import subprocess + +TRIM_TRACE=True + +def usage(): + print >> sys.stderr, "leakresolve.py -p executable < trace" + sys.exit(1); + +class LineResolver: + """Resolves addresses to source lines""" + def __init__(self, executable): + self.resolver = subprocess.Popen( + ["addr2line", "-e", executable, '-C', '-f'], + bufsize=1, # line-buffered + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + self.cache = {} + def resolve(self, addr): + if addr not in self.cache: + self.resolver.stdin.write(addr + "\n") + func = self.resolver.stdout.readline() + fileline = self.resolver.stdout.readline() + result = fileline.strip() + " in " + func.strip() + "()" + self.cache[addr] = result + return self.cache[addr] + +class Leak: + """Represents a memory leak site""" + def __init__(self, backtrace, resolver): + self.backtrace = backtrace; + self.resolver = resolver + self.nleaks = 0 + self.nbytes = 0; + def leak(self, nbytes): + self.nbytes += nbytes + self.nleaks += 1 + def __str__(self): + s = "memory leak: %d objects totalling %d bytes\n" % \ + (self.nleaks, self.nbytes) + for addr in self.backtrace: + s += " " + self.resolver.resolve(addr) + "\n" + return s + +class LeakTracker: + """Tracks all memory leaks""" + def __init__(self, executable): + self.resolver = LineResolver(executable) + self.leaks = {} + def addleak(self, nbytes, trace): + if TRIM_TRACE: + trace = trace[:-1] + trace = tuple(trace) + if trace not in self.leaks: + leak = Leak(trace, self.resolver) + self.leaks[trace] = leak + self.leaks[trace].leak(nbytes) + def _leakcmp(self, a, b): + return cmp(self.leaks[a].nbytes, self.leaks[b].nbytes) + def __str__(self): + s = "Memory leaks\n" + s+= "------------\n" + for trace in sorted(self.leaks.keys(), cmp=self._leakcmp): + s += str(self.leaks[trace]) + "\n" + return s + +def main(): + executable = None + try: + opts, args = getopt.getopt(sys.argv[1:], 'hp:') + except getopt.GetoptError: + print >> sys.stderr, "Invalid commandline arguments" + usage() + for o, a in opts: + if o in ('-h', '--help'): + usage() + if o in ('-p', '--program'): + executable = a + continue + + if not executable: + print >> sys.stderr, "Missing executable name" + usage(); + + leaks = LeakTracker(executable) + for line in sys.stdin: + if line.startswith("LEAK "): + leakinfo = line.split() + if len(leakinfo) < 4 or leakinfo[3] != "TRACE": + sys.stdout.write(line); + continue + backtrace = leakinfo[4:] + nbytes = int(leakinfo[2]) + leaks.addleak(nbytes, backtrace) + + print leaks + +if __name__ == '__main__': main() + From 2d05fae6fe8ebdec76e713d9dc4c31ceb5e413c5 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 14:44:09 +1100 Subject: [PATCH 03/17] sort by number of leaks, then by bytes --- leakresolve.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/leakresolve.py b/leakresolve.py index f87c8e3..087f3f1 100755 --- a/leakresolve.py +++ b/leakresolve.py @@ -76,6 +76,9 @@ class LeakTracker: self.leaks[trace] = leak self.leaks[trace].leak(nbytes) def _leakcmp(self, a, b): + r = cmp(self.leaks[a].nleaks, self.leaks[b].nleaks) + if r: + return r return cmp(self.leaks[a].nbytes, self.leaks[b].nbytes) def __str__(self): s = "Memory leaks\n" From f07479d57cb8ac284eba672a03e023acde69e7ee Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 14:44:32 +1100 Subject: [PATCH 04/17] ensure malloc() and friends are in the namespace --- leakmalloc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/leakmalloc.h b/leakmalloc.h index 5a0248b..8134c0b 100644 --- a/leakmalloc.h +++ b/leakmalloc.h @@ -18,6 +18,8 @@ #define _LEAKMALLOC_H #include +#include +#include char *leak_strdup(const char *s); void *leak_malloc(size_t len); From 776483fdf549bc748da8605242656367000e1391 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 14:44:45 +1100 Subject: [PATCH 05/17] fix tracking bugs, kludge to dump backtrace on error --- leakmalloc.c | 92 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/leakmalloc.c b/leakmalloc.c index 63feebe..3841409 100644 --- a/leakmalloc.c +++ b/leakmalloc.c @@ -40,55 +40,89 @@ struct alloc { static int alloc_cmp(struct alloc *a, struct alloc *b) { - return (a->addr == b->addr) ? 0 : (a->addr < b->addr ? -1 : 1); + if (a->addr == b->addr) + return 0; + if (a->addr > b->addr) + return 1; + else + return -1; } RB_HEAD(alloc_tree, alloc); RB_GENERATE_STATIC(alloc_tree, alloc, entry, alloc_cmp); static struct alloc_tree alloc_tree = RB_INITIALIZER(&alloc_tree); +static void +dump_leak(struct alloc *alloc) +{ + int i; + + printf("LEAK %p %zu TRACE", alloc->addr, alloc->len); + for (i = 1; i < alloc->depth; i++) + printf(" %p", alloc->bt[i]); + printf("\n"); +} + /* Called atexit to dump unfreed leak objects */ static void dump_leaks(void) { struct alloc *alloc; - int i; - RB_FOREACH(alloc, alloc_tree, &alloc_tree) { - printf("LEAK %p %zu TRACE", alloc->addr, alloc->len); - for (i = 0; i < alloc->depth; i++) - printf(" %p", alloc->bt[i]); - printf("\n"); - } + if (initialised != 1) + return; + RB_FOREACH(alloc, alloc_tree, &alloc_tree) + dump_leak(alloc); } -void __record_leak(void *addr, size_t len, void *oaddr); -void +static void +internal_error(struct alloc *alloc) +{ + alloc->addr = NULL; + alloc->len = -1; + dump_leak(alloc); + initialised = -1; +} + +static struct alloc * +new_alloc(void *addr, size_t len) +{ + struct alloc *alloc; + + if ((alloc = calloc(1, sizeof(*alloc))) == NULL) + errx(1, "%s: calloc failed", __func__); + alloc->addr = addr; + alloc->len = len; + if ((alloc->depth = backtrace(alloc->bt, BT_MAX_DEPTH)) == -1) + errx(1, "%s: backtrace failed", __func__); + return alloc; +} + +static void __record_leak(void *addr, size_t len, void *oaddr) { struct alloc oalloc, *alloc; - if (!initialised) { + if (initialised == -1) + return; + else if (initialised == 0) { atexit(dump_leaks); initialised = 1; } - if (addr == NULL || addr == oaddr) - return; if (oaddr == NULL) { /* - * malloc/calloc: allocate a leak object and fill in the - * trace. + * malloc/calloc/realloc(NULL,...): allocate a leak object + * and fill in the trace. */ - if ((alloc = calloc(1, sizeof(*alloc))) == NULL) - errx(1, "%s: calloc failed", __func__); - alloc->addr = addr; - alloc->len = len; - if ((alloc->depth = backtrace(alloc->bt, BT_MAX_DEPTH)) == -1) - errx(1, "%s: backtrace failed", __func__); - if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) + if (addr == NULL) + return; /* alloc failed */ + alloc = new_alloc(addr, len); + if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) { + internal_error(alloc); errx(1, "%s: alloc for %p already exists", __func__, addr); + } } else { oalloc.addr = oaddr; alloc = RB_FIND(alloc_tree, &alloc_tree, &oalloc); @@ -105,10 +139,20 @@ __record_leak(void *addr, size_t len, void *oaddr) * realloc: update the original address so we can * trace it when it is freed. */ - if (alloc == NULL) - errx(1, "%s: original addr missing", __func__); + if (alloc == NULL) { + alloc = new_alloc(NULL, -1); + internal_error(alloc); + errx(1, "%s: realloc original addr %p missing", + __func__, oaddr); + } + RB_REMOVE(alloc_tree, &alloc_tree, alloc); alloc->addr = addr; alloc->len = len; + if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) { + internal_error(alloc); + errx(1, "%s: alloc for %p already exists", + __func__, addr); + } } } } From 9570b61230497465e3cd12616de6f32ca1c54831 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 15:04:45 +1100 Subject: [PATCH 06/17] add example --- example/README | 4 ++++ example/example.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 example/README create mode 100644 example/example.c diff --git a/example/README b/example/README new file mode 100644 index 0000000..0ab9b1c --- /dev/null +++ b/example/README @@ -0,0 +1,4 @@ +Here's how you do it: + +gcc -Wall -O0 -ggdb3 -I.. -c example.c && gcc -ggdb3 -Wall -I.. -c example.c && gcc -L ../obj -L /usr/local/lib example.o -lleakmalloc -lexecinfo +./a.out | ../leakresolve.py -p a.out diff --git a/example/example.c b/example/example.c new file mode 100644 index 0000000..02474ab --- /dev/null +++ b/example/example.c @@ -0,0 +1,60 @@ +#include +#include + +#include "leakmalloc.h" + +static void *a, *b, *c, *d, *e, *f; + +void +f6(void) +{ + d = strdup("hello"); + e = malloc(789); +} + +void +f5(void) +{ + c = calloc(1, 678); + f6(); +} + +void +f4(void) +{ + b = malloc(456); + f5(); + free(e); +} + +void +f3(void) +{ + a = malloc(123); + f = realloc(NULL, 321); +} + +void +f2(void) +{ + f4(); + b = realloc(b, 567); +} + +void +f1(void) +{ + int i; + + f2(); + for (i = 0; i < 10; i++) + f3(); +} + +int +main(void) +{ + f1(); + free(f); + return 0; +} From f9e93c4b6436fa62489add737e1c488dff7fd4aa Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 15:08:59 +1100 Subject: [PATCH 07/17] README --- README | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..770e094 --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +This is a very simple memory leak detector using libexecinfo (in +OpenBSD ports as devel/libexecinfo). It could be adapted to use the +backtrace function in glibc too. + +It tracks memory allocations by redirecting malloc/free and friends +to its own functions using preprocessor defines. When an allocation +is made, leakmalloc grabs the call chain from the stack and stores it. +When the program exits, leakmalloc dumps all unfreed allocations. + +leakresolve.py consumes one of these dumps and produces a fairly +readable report with leaks coalesced by stack trace and sorted by +number of leak instances. Usually the last entry on this report is +the one to care most about. + +The example in example/ shows how you might use it. Note that everything +needs to be compiled without optimisation and with debugging symbols. From 42546380875ac0b47d1334723b7f95aad29da89b Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 16:50:27 +1100 Subject: [PATCH 08/17] better comment --- leakmalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leakmalloc.c b/leakmalloc.c index 3841409..52143f0 100644 --- a/leakmalloc.c +++ b/leakmalloc.c @@ -116,7 +116,7 @@ __record_leak(void *addr, size_t len, void *oaddr) * and fill in the trace. */ if (addr == NULL) - return; /* alloc failed */ + return; /* alloc failed or free(NULL) */ alloc = new_alloc(addr, len); if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) { internal_error(alloc); From dadde6f62ba896e6c8824e90799767f93ec23afe Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 Mar 2012 17:51:14 +1100 Subject: [PATCH 09/17] print totals --- leakresolve.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/leakresolve.py b/leakresolve.py index 087f3f1..0e0ae39 100755 --- a/leakresolve.py +++ b/leakresolve.py @@ -83,8 +83,14 @@ class LeakTracker: def __str__(self): s = "Memory leaks\n" s+= "------------\n" + total_leaks = 0 + total_bytes = 0 for trace in sorted(self.leaks.keys(), cmp=self._leakcmp): s += str(self.leaks[trace]) + "\n" + total_leaks += 1 + total_bytes += self.leaks[trace].nbytes + s+= "\nTotal: %d leaks containing %d bytes\n" % \ + (total_leaks, total_bytes) return s def main(): From bac1cfe2291c172d84eb2d44ac8d5253db863ff9 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 08:24:20 +1100 Subject: [PATCH 10/17] make output useful as vim error file, based on patch from markus@ --- leakresolve.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/leakresolve.py b/leakresolve.py index 0e0ae39..64ce813 100755 --- a/leakresolve.py +++ b/leakresolve.py @@ -40,8 +40,8 @@ class LineResolver: if addr not in self.cache: self.resolver.stdin.write(addr + "\n") func = self.resolver.stdout.readline() - fileline = self.resolver.stdout.readline() - result = fileline.strip() + " in " + func.strip() + "()" + loc = self.resolver.stdout.readline() + result = "%s: in %s()" % (loc.strip(), func.strip()) self.cache[addr] = result return self.cache[addr] @@ -56,10 +56,9 @@ class Leak: self.nbytes += nbytes self.nleaks += 1 def __str__(self): - s = "memory leak: %d objects totalling %d bytes\n" % \ + s = "Leaked %d objects totalling %d bytes\n" % \ (self.nleaks, self.nbytes) - for addr in self.backtrace: - s += " " + self.resolver.resolve(addr) + "\n" + s += "\n".join(map(self.resolver.resolve, self.backtrace)) return s class LeakTracker: @@ -86,10 +85,10 @@ class LeakTracker: total_leaks = 0 total_bytes = 0 for trace in sorted(self.leaks.keys(), cmp=self._leakcmp): - s += str(self.leaks[trace]) + "\n" + s += str(self.leaks[trace]) + "\n\n" total_leaks += 1 total_bytes += self.leaks[trace].nbytes - s+= "\nTotal: %d leaks containing %d bytes\n" % \ + s+= "Total: %d leaks containing %d bytes\n" % \ (total_leaks, total_bytes) return s From 989d18730928579878cef8df8995a8ff8abc30dc Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 14:39:30 +1100 Subject: [PATCH 11/17] improve summary --- leakresolve.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/leakresolve.py b/leakresolve.py index 64ce813..e219d08 100755 --- a/leakresolve.py +++ b/leakresolve.py @@ -82,14 +82,16 @@ class LeakTracker: def __str__(self): s = "Memory leaks\n" s+= "------------\n" + total_sites = 0 total_leaks = 0 total_bytes = 0 for trace in sorted(self.leaks.keys(), cmp=self._leakcmp): s += str(self.leaks[trace]) + "\n\n" - total_leaks += 1 + total_sites += 1 + total_leaks += self.leaks[trace].nleaks total_bytes += self.leaks[trace].nbytes - s+= "Total: %d leaks containing %d bytes\n" % \ - (total_leaks, total_bytes) + s+= "Total: %d leaks from %d sites, containing %d bytes\n" % \ + (total_leaks, total_sites, total_bytes) return s def main(): From fed32729da032185fb1798f1211898a8690fd521 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 14:42:34 +1100 Subject: [PATCH 12/17] reorg directory structure --- Makefile => leakmalloc/Makefile | 0 leakmalloc.c => leakmalloc/leakmalloc.c | 0 leakmalloc.h => leakmalloc/leakmalloc.h | 0 {example => regress}/README | 0 {example => regress}/example.c | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename Makefile => leakmalloc/Makefile (100%) rename leakmalloc.c => leakmalloc/leakmalloc.c (100%) rename leakmalloc.h => leakmalloc/leakmalloc.h (100%) rename {example => regress}/README (100%) rename {example => regress}/example.c (100%) diff --git a/Makefile b/leakmalloc/Makefile similarity index 100% rename from Makefile rename to leakmalloc/Makefile diff --git a/leakmalloc.c b/leakmalloc/leakmalloc.c similarity index 100% rename from leakmalloc.c rename to leakmalloc/leakmalloc.c diff --git a/leakmalloc.h b/leakmalloc/leakmalloc.h similarity index 100% rename from leakmalloc.h rename to leakmalloc/leakmalloc.h diff --git a/example/README b/regress/README similarity index 100% rename from example/README rename to regress/README diff --git a/example/example.c b/regress/example.c similarity index 100% rename from example/example.c rename to regress/example.c From e41307b545166d4c90b23e34f05c20c6eca4a39d Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 14:43:11 +1100 Subject: [PATCH 13/17] add some options via LEAKMALLOC_OPTIONS environment X = exit with status 99 on leaks Q = quiet (useful with X) D = dump leaks to leakmalloc.out instead of stdout --- leakmalloc/leakmalloc.c | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/leakmalloc/leakmalloc.c b/leakmalloc/leakmalloc.c index 52143f0..3e2979f 100644 --- a/leakmalloc/leakmalloc.c +++ b/leakmalloc/leakmalloc.c @@ -27,7 +27,13 @@ #define LEAKMALLOC_NO_REDIRECT #include "leakmalloc.h" +#define OPT_EXIT_ON_LEAKS 0x01 +#define OPT_DUMP_TO_FILE 0x02 +#define OPT_QUIET 0x04 + +u_int leakmalloc_options = 0; static int initialised; +static FILE *dumpfile; struct alloc { RB_ENTRY(alloc) entry; @@ -53,14 +59,14 @@ RB_GENERATE_STATIC(alloc_tree, alloc, entry, alloc_cmp); static struct alloc_tree alloc_tree = RB_INITIALIZER(&alloc_tree); static void -dump_leak(struct alloc *alloc) +dump_leak(FILE *f, const char *tag, struct alloc *alloc) { int i; - printf("LEAK %p %zu TRACE", alloc->addr, alloc->len); + fprintf(f, "%s %p %zu TRACE", tag, alloc->addr, alloc->len); for (i = 1; i < alloc->depth; i++) - printf(" %p", alloc->bt[i]); - printf("\n"); + fprintf(f, " %p", alloc->bt[i]); + fprintf(f, "\n"); } /* Called atexit to dump unfreed leak objects */ @@ -68,11 +74,19 @@ static void dump_leaks(void) { struct alloc *alloc; + int i = 0; if (initialised != 1) return; - RB_FOREACH(alloc, alloc_tree, &alloc_tree) - dump_leak(alloc); + RB_FOREACH(alloc, alloc_tree, &alloc_tree) { + if ((leakmalloc_options & OPT_QUIET) == 0) + dump_leak(dumpfile ? dumpfile : stdout, "LEAK", alloc); + i++; + } + if (dumpfile) + fclose(dumpfile); + if ((leakmalloc_options & OPT_EXIT_ON_LEAKS) != 0) + _exit(99); } static void @@ -80,7 +94,7 @@ internal_error(struct alloc *alloc) { alloc->addr = NULL; alloc->len = -1; - dump_leak(alloc); + dump_leak(stderr, "ERROR", alloc); initialised = -1; } @@ -102,11 +116,24 @@ static void __record_leak(void *addr, size_t len, void *oaddr) { struct alloc oalloc, *alloc; + char *cp; if (initialised == -1) return; else if (initialised == 0) { atexit(dump_leaks); + if (!issetugid() && + (cp = getenv("LEAKMALLOC_OPTIONS")) != NULL) { + if (strchr(cp, 'X') != NULL) + leakmalloc_options |= OPT_EXIT_ON_LEAKS; + if (strchr(cp, 'D') != NULL) + leakmalloc_options |= OPT_DUMP_TO_FILE; + if (strchr(cp, 'Q') != NULL) + leakmalloc_options |= OPT_QUIET; + } + if ((leakmalloc_options & OPT_DUMP_TO_FILE) != 0 && + (dumpfile = fopen("leakmalloc.out", "w+")) == NULL) + err(1, "fopen(\"leakmalloc.out\")"); initialised = 1; } From de82036ecfaf645e07f6cf69bf682b9a602d5df3 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 14:45:00 +1100 Subject: [PATCH 14/17] rename example.c => leaky.c --- regress/README | 2 +- regress/{example.c => leaky.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename regress/{example.c => leaky.c} (100%) diff --git a/regress/README b/regress/README index 0ab9b1c..ae4934c 100644 --- a/regress/README +++ b/regress/README @@ -1,4 +1,4 @@ Here's how you do it: -gcc -Wall -O0 -ggdb3 -I.. -c example.c && gcc -ggdb3 -Wall -I.. -c example.c && gcc -L ../obj -L /usr/local/lib example.o -lleakmalloc -lexecinfo +gcc -Wall -O0 -ggdb3 -I.. -c leaky.c && gcc -ggdb3 -Wall -I.. -c leaky.c && gcc -L ../obj -L /usr/local/lib leaky.o -lleakmalloc -lexecinfo ./a.out | ../leakresolve.py -p a.out diff --git a/regress/example.c b/regress/leaky.c similarity index 100% rename from regress/example.c rename to regress/leaky.c From 70606ded9df78ad55aec37da051316a514dffe02 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 14:45:38 +1100 Subject: [PATCH 15/17] make into a proper regress test --- regress/Makefile | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ regress/README | 4 ---- regress/leaky.c | 12 +++++----- 3 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 regress/Makefile delete mode 100644 regress/README diff --git a/regress/Makefile b/regress/Makefile new file mode 100644 index 0000000..491715c --- /dev/null +++ b/regress/Makefile @@ -0,0 +1,57 @@ +# $OpenBSD$ + +.include +.include + +CDIAGFLAGS= -Wall +CDIAGFLAGS+= -Werror +CDIAGFLAGS+= -Wpointer-arith +CDIAGFLAGS+= -Wstrict-prototypes +CDIAGFLAGS+= -Wmissing-prototypes +CDIAGFLAGS+= -Wunused +CDIAGFLAGS+= -Wsign-compare +CDIAGFLAGS+= -Wshadow +CDIAGFLAGS+= -Wformat +.if (${CC:L} == "gcc" || ${CC:L} == "cc") +CDIAGFLAGS+= -Wbounded +.endif +DEBUG=-g +COPTS=-O0 + +CPPFLAGS+=-I${.CURDIR}/../leakmalloc + +.if exists(${.CURDIR}/../leakmalloc/${__objdir}) +LDADD+=-L${.CURDIR}/../leakmalloc/${__objdir} -lleakmalloc +DPADD+=${.CURDIR}/../leakmalloc/${__objdir}/libleakmalloc.a +.else +LDADD+=-L${.CURDIR}/../leakmalloc -lleakmalloc +DPADD+=${.CURDIR}/../leakmalloc/libleakmalloc.a +.endif + +LDFLAGS+=-L/usr/local/lib +LDADD+=-lexecinfo + +PROG=leaky +SRCS=leaky.c +REGRESS_TARGETS=does-leak outfile exit-leak summary + +does-leak: ${PROG} + ./${PROG} | grep -q "^LEAK " + +outfile: ${PROG} + rm -f leakmalloc.out + env LEAKMALLOC_OPTIONS=D ./${PROG} + grep -q "^LEAK " leakmalloc.out + +exit-leak: ${PROG} + sh -c 'export LEAKMALLOC_OPTIONS=XQ ; ./${PROG} ; test $$? -eq 99' + +summary: ${PROG} + test `./${PROG} | ${.CURDIR}/../leakresolve.py -p ${PROG} | \ + grep "^Total:" | cut -d' ' -f 2` -eq 22 + +clean: + rm ${PROG} *.o *.core leakmalloc.out + +.include + diff --git a/regress/README b/regress/README deleted file mode 100644 index ae4934c..0000000 --- a/regress/README +++ /dev/null @@ -1,4 +0,0 @@ -Here's how you do it: - -gcc -Wall -O0 -ggdb3 -I.. -c leaky.c && gcc -ggdb3 -Wall -I.. -c leaky.c && gcc -L ../obj -L /usr/local/lib leaky.o -lleakmalloc -lexecinfo -./a.out | ../leakresolve.py -p a.out diff --git a/regress/leaky.c b/regress/leaky.c index 02474ab..9f37797 100644 --- a/regress/leaky.c +++ b/regress/leaky.c @@ -5,21 +5,21 @@ static void *a, *b, *c, *d, *e, *f; -void +static void f6(void) { d = strdup("hello"); e = malloc(789); } -void +static void f5(void) { c = calloc(1, 678); f6(); } -void +static void f4(void) { b = malloc(456); @@ -27,21 +27,21 @@ f4(void) free(e); } -void +static void f3(void) { a = malloc(123); f = realloc(NULL, 321); } -void +static void f2(void) { f4(); b = realloc(b, 567); } -void +static void f1(void) { int i; From 4e552cbf8001468bc8865e3fbb09026ec0cff86e Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 7 Mar 2012 15:34:41 +1100 Subject: [PATCH 16/17] adjust for new paths --- README | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README b/README index 770e094..e531460 100644 --- a/README +++ b/README @@ -12,5 +12,8 @@ readable report with leaks coalesced by stack trace and sorted by number of leak instances. Usually the last entry on this report is the one to care most about. -The example in example/ shows how you might use it. Note that everything -needs to be compiled without optimisation and with debugging symbols. +leaky.c in regress/ shows how you might use it (run 'make' in that directory +and run it manually, or look at the Makefile for tips). + +Note that everything needs to be compiled without optimisation and with +debugging symbols. From 6161ebb123d0e57b46c64c7d69c7d8fe5c160da7 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Thu, 15 Mar 2012 12:23:01 +1100 Subject: [PATCH 17/17] move everything into subdirectory in preparation for merge to libopenssh --- README => leakmalloc/README | 0 leakmalloc/{ => leakmalloc}/Makefile | 0 leakmalloc/{ => leakmalloc}/leakmalloc.c | 0 leakmalloc/{ => leakmalloc}/leakmalloc.h | 0 leakresolve.py => leakmalloc/leakresolve.py | 0 {regress => leakmalloc/regress}/Makefile | 0 {regress => leakmalloc/regress}/leaky.c | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename README => leakmalloc/README (100%) rename leakmalloc/{ => leakmalloc}/Makefile (100%) rename leakmalloc/{ => leakmalloc}/leakmalloc.c (100%) rename leakmalloc/{ => leakmalloc}/leakmalloc.h (100%) rename leakresolve.py => leakmalloc/leakresolve.py (100%) rename {regress => leakmalloc/regress}/Makefile (100%) rename {regress => leakmalloc/regress}/leaky.c (100%) diff --git a/README b/leakmalloc/README similarity index 100% rename from README rename to leakmalloc/README diff --git a/leakmalloc/Makefile b/leakmalloc/leakmalloc/Makefile similarity index 100% rename from leakmalloc/Makefile rename to leakmalloc/leakmalloc/Makefile diff --git a/leakmalloc/leakmalloc.c b/leakmalloc/leakmalloc/leakmalloc.c similarity index 100% rename from leakmalloc/leakmalloc.c rename to leakmalloc/leakmalloc/leakmalloc.c diff --git a/leakmalloc/leakmalloc.h b/leakmalloc/leakmalloc/leakmalloc.h similarity index 100% rename from leakmalloc/leakmalloc.h rename to leakmalloc/leakmalloc/leakmalloc.h diff --git a/leakresolve.py b/leakmalloc/leakresolve.py similarity index 100% rename from leakresolve.py rename to leakmalloc/leakresolve.py diff --git a/regress/Makefile b/leakmalloc/regress/Makefile similarity index 100% rename from regress/Makefile rename to leakmalloc/regress/Makefile diff --git a/regress/leaky.c b/leakmalloc/regress/leaky.c similarity index 100% rename from regress/leaky.c rename to leakmalloc/regress/leaky.c