Implement simple memory leak detector.
authorBen Pfaff <blp@nicira.com>
Tue, 13 Jan 2009 21:46:15 +0000 (13:46 -0800)
committerBen Pfaff <blp@nicira.com>
Tue, 13 Jan 2009 22:07:44 +0000 (14:07 -0800)
Initially, make it available only in secchan and vswitchd.  But it's easy
to add it elsewhere too.

16 files changed:
debian/openflow-common.install
lib/automake.mk
lib/leak-checker.c [new file with mode: 0644]
lib/leak-checker.h [new file with mode: 0644]
lib/leak-checker.man [new file with mode: 0644]
lib/queue.c
lib/vconn-ssl.c
lib/vconn-stream.c
lib/vlog-modules.def
m4/libopenflow.m4
secchan/secchan.8.in
secchan/secchan.c
utilities/automake.mk
utilities/ofp-parse-leaks.in [new file with mode: 0755]
vswitchd/vswitchd.8.in
vswitchd/vswitchd.c

index 624bc34430de159fc867885859f838cfdefeb345..eed7413ecb0bc170d8d5563ee9f9a3f24e651058 100644 (file)
@@ -1,2 +1,3 @@
+_debian/utilities/ofp-parse-leaks usr/bin
 _debian/utilities/ofp-pki usr/sbin
 _debian/utilities/vlogconf usr/sbin
index 53b17289324b10938d2926dd9c7ca8d221644a7a..d3f5db002516fa6bcf8141d7490750234d3dd96f 100644 (file)
@@ -29,6 +29,8 @@ lib_libopenflow_a_SOURCES = \
        lib/hash.h \
        lib/hmap.c \
        lib/hmap.h \
+       lib/leak-checker.c \
+       lib/leak-checker.h \
        lib/learning-switch.c \
        lib/learning-switch.h \
        lib/list.c \
diff --git a/lib/leak-checker.c b/lib/leak-checker.c
new file mode 100644 (file)
index 0000000..defc1ed
--- /dev/null
@@ -0,0 +1,312 @@
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include "leak-checker.h"
+#include <inttypes.h>
+
+#define THIS_MODULE VLM_leak_checker
+#include "vlog.h"
+
+#ifndef HAVE_MALLOC_HOOKS
+void
+leak_checker_start(const char *file_name UNUSED)
+{
+    VLOG_WARN("not enabling leak checker because the libc in use does not "
+              "have the required hooks");
+}
+
+void
+leak_checker_set_limit(off_t max_size UNUSED)
+{
+}
+
+void
+leak_checker_claim(const void *p UNUSED)
+{
+}
+
+void
+leak_checker_usage(void)
+{
+    printf("  --check-leaks=FILE      (accepted but ignored in this build)\n");
+}
+#else /* HAVE_MALLOC_HOOKS */
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <sys/stat.h>
+
+typedef void *malloc_hook_type(size_t, const void *);
+typedef void *realloc_hook_type(void *, size_t, const void *);
+typedef void free_hook_type(void *, const void *);
+
+struct hooks {
+    malloc_hook_type *malloc_hook_func;
+    realloc_hook_type *realloc_hook_func;
+    free_hook_type *free_hook_func;
+};
+
+static malloc_hook_type hook_malloc;
+static realloc_hook_type hook_realloc;
+static free_hook_type hook_free;
+
+static struct hooks libc_hooks;
+static const struct hooks our_hooks = { hook_malloc, hook_realloc, hook_free };
+
+static FILE *file;
+static off_t limit = 10 * 1000 * 1000;
+
+static void
+get_hooks(struct hooks *hooks)
+{
+    hooks->malloc_hook_func = __malloc_hook;
+    hooks->realloc_hook_func = __realloc_hook;
+    hooks->free_hook_func = __free_hook;
+}
+
+static void
+set_hooks(const struct hooks *hooks)
+{
+    __malloc_hook = hooks->malloc_hook_func;
+    __realloc_hook = hooks->realloc_hook_func;
+    __free_hook = hooks->free_hook_func;
+}
+
+void
+leak_checker_start(const char *file_name)
+{
+    if (!file) {
+        file = fopen(file_name, "w");
+        if (!file) {
+            VLOG_WARN("failed to create \"%s\": %s",
+                      file_name, strerror(errno));
+            return;
+        }
+        VLOG_WARN("enabled memory leak logging to \"%s\"", file_name);
+        get_hooks(&libc_hooks);
+        set_hooks(&our_hooks);
+    }
+}
+
+void
+leak_checker_stop(void)
+{
+    if (file) {
+        fclose(file);
+        file = NULL;
+        set_hooks(&libc_hooks);
+        VLOG_WARN("disabled memory leak logging");
+    }
+}
+
+void
+leak_checker_set_limit(off_t limit_)
+{
+    limit = limit_;
+}
+
+void
+leak_checker_usage(void)
+{
+    printf("  --check-leaks=FILE      log malloc and free calls to FILE\n");
+}
+
+static uintptr_t UNUSED
+read_maps(void)
+{
+    static const char file_name[] = "/proc/self/maps";
+    char line[1024];
+    int line_number;
+    FILE *f;
+
+    f = fopen(file_name, "r");
+    if (f == NULL) {
+        VLOG_WARN("opening %s failed: %s", file_name, strerror(errno));
+        return -1;
+    }
+
+    for (line_number = 1; fgets(line, sizeof line, f); line_number++) {
+        if (strstr(line, "[stack]")) {
+            uintptr_t end;
+            if (sscanf(line, "%*"SCNxPTR"-%"SCNxPTR, &end) != 1) {
+                VLOG_WARN("%s:%d: parse error", file_name, line_number);
+                continue;
+            }
+            fclose(f);
+            return end;
+        }
+    }
+    fclose(f);
+
+    VLOG_WARN("%s: no stack found", file_name);
+    return -1;
+}
+
+static void
+get_stack_limits(uintptr_t *lowp, uintptr_t *highp)
+{
+#ifndef __x86__
+    static bool warned = false;
+    if (!warned) {
+        warned = true;
+        VLOG_WARN("stack limit detection not implemented on "
+                  "this architecture, possible segfaults from leak checker");
+    }
+    *lowp = 0;
+    *highp = (uintptr_t) -1;
+#else
+    static uintptr_t high;
+    if (!high) {
+        high = get_max_stack();
+    }
+    asm("movl %%esp,%0" : "g" (*lowp));
+    *highp = high;
+#endif
+}
+
+static bool
+in_stack(void *p)
+{
+    uintptr_t address = (uintptr_t) p;
+    uintptr_t low, high;
+    get_stack_limits(&low, &high);
+    return address >= low && address < high;
+}
+
+static void PRINTF_FORMAT(1, 2)
+log_callers(const char *format, ...)
+{
+    va_list args;
+    void **frame;
+
+    va_start(args, format);
+    vfprintf(file, format, args);
+    va_end(args);
+
+    putc(':', file);
+    for (frame = __builtin_frame_address(0);
+         frame != NULL && in_stack(frame) && frame[0] != NULL;
+         frame = frame[0]) {
+        fprintf(file, " %p", frame[1]);
+    }
+    putc('\n', file);
+}
+
+static void
+reset_hooks(void)
+{
+    static int count;
+
+    if (count++ >= 100 && limit && file) {
+        struct stat s;
+        count = 0;
+        if (fstat(fileno(file), &s) < 0) {
+            VLOG_WARN("cannot fstat leak checker log file: %s",
+                      strerror(errno));
+            return;
+        }
+        if (s.st_size > limit) {
+            VLOG_WARN("leak checker log file size exceeded limit");
+            leak_checker_stop();
+            return;
+        }
+    }
+    if (file) {
+        set_hooks(&our_hooks);
+    }
+}
+
+static void *
+hook_malloc(size_t size, const void *caller UNUSED)
+{
+    void *p;
+
+    set_hooks(&libc_hooks);
+    p = malloc(size);
+    get_hooks(&libc_hooks);
+
+    log_callers("malloc(%zu) -> %p", size, p);
+
+    reset_hooks();
+    return p;
+}
+
+void
+leak_checker_claim(const void *p)
+{
+    if (!file) {
+        return;
+    }
+
+    if (p) {
+        set_hooks(&libc_hooks);
+        log_callers("claim(%p)", p);
+        reset_hooks();
+    }
+}
+
+static void
+hook_free(void *p, const void *caller UNUSED)
+{
+    if (!p) {
+        return;
+    }
+
+    set_hooks(&libc_hooks);
+    free(p);
+    get_hooks(&libc_hooks);
+
+    log_callers("free(%p)", p);
+
+    reset_hooks();
+}
+
+static void *
+hook_realloc(void *p, size_t size, const void *caller UNUSED)
+{
+    void *q;
+
+    set_hooks(&libc_hooks);
+    q = realloc(p, size);
+    get_hooks(&libc_hooks);
+
+    if (p != q) {
+        log_callers("realloc(%p, %zu) -> %p", p, size, q);
+    }
+
+    reset_hooks();
+
+    return q;
+}
+#endif /* HAVE_MALLOC_HOOKS */
diff --git a/lib/leak-checker.h b/lib/leak-checker.h
new file mode 100644 (file)
index 0000000..fb16d34
--- /dev/null
@@ -0,0 +1,58 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef LEAK_CHECKER_H
+#define LEAK_CHECKER_H 1
+
+#include <sys/types.h>
+
+#define LEAK_CHECKER_OPTION_ENUMS               \
+    OPT_CHECK_LEAKS,                            \
+    OPT_LEAK_LIMIT
+#define LEAK_CHECKER_LONG_OPTIONS                           \
+    {"check-leaks", required_argument, 0, OPT_CHECK_LEAKS}, \
+    {"leak-limit", required_argument, 0, OPT_LEAK_LIMIT}
+#define LEAK_CHECKER_OPTION_HANDLERS                \
+        case OPT_CHECK_LEAKS:                       \
+            leak_checker_start(optarg);             \
+            break;                                  \
+        case OPT_LEAK_LIMIT:                        \
+            leak_checker_set_limit(atol(optarg));   \
+            break;
+void leak_checker_start(const char *file_name);
+void leak_checker_set_limit(off_t limit);
+void leak_checker_stop(void);
+void leak_checker_claim(const void *);
+void leak_checker_usage(void);
+
+#endif /* dynamic-string.h */
diff --git a/lib/leak-checker.man b/lib/leak-checker.man
new file mode 100644 (file)
index 0000000..7b376e1
--- /dev/null
@@ -0,0 +1,7 @@
+.TP
+\fB--check-leaks=\fIfile\fR
+.
+Logs information about memory allocation and deallocation to
+\fIfile\fR, to allow for debugging memory leaks in \fB\*(PN\fR.  This
+option slows down \fB\*(PN\fR considerably, so it should only be used
+when a memory leak is suspected.
index 24eb01d73ec6ad664a25b0b11bc816bf5b9bbd0c..33e500670661cb34f3293b5eff4de8b51c2caf8f 100644 (file)
@@ -35,6 +35,7 @@
 #include "queue.h"
 #include <assert.h>
 #include "compiler.h"
+#include "leak-checker.h"
 #include "ofpbuf.h"
 
 static void check_queue(struct ofp_queue *q);
@@ -90,6 +91,7 @@ void
 queue_push_tail(struct ofp_queue *q, struct ofpbuf *b)
 {
     check_queue(q);
+    leak_checker_claim(b);
 
     b->next = NULL;
     if (q->n++) {
index 1035442690461145cde15aacfa03cca51532bd3d..c6fde670197549bcef3b5f59722f7c5f277e867a 100644 (file)
@@ -718,6 +718,7 @@ ssl_send(struct vconn *vconn, struct ofpbuf *buffer)
             ssl_clear_txbuf(sslv);
             return 0;
         case EAGAIN:
+            leak_checker_claim(buffer);
             ssl_register_tx_waiter(vconn);
             return 0;
         default:
index cdbea1fb29c1a2fd31979d8ba8d492b3bf1c560d..cdba45f2c03ceb89b09ceb2664babb38b42c0c59 100644 (file)
@@ -213,6 +213,7 @@ stream_send(struct vconn *vconn, struct ofpbuf *buffer)
         ofpbuf_delete(buffer);
         return 0;
     } else if (retval >= 0 || errno == EAGAIN) {
+        leak_checker_claim(buffer);
         s->txbuf = buffer;
         if (retval > 0) {
             ofpbuf_pull(buffer, retval);
index 434bcfa8b5af0647d6823f4883fbb8147dd09b20..a78b04e4cb7bdeb5f3e87cb36fa3215c691a8138 100644 (file)
@@ -18,6 +18,7 @@ VLOG_MODULE(fault)
 VLOG_MODULE(flow)
 VLOG_MODULE(flow_end)
 VLOG_MODULE(in_band)
+VLOG_MODULE(leak_checker)
 VLOG_MODULE(learning_switch)
 VLOG_MODULE(mac_learning)
 VLOG_MODULE(netdev)
index c1787f4fa4e978cf5537a89125fffe50493bb8fc..294932b0ff00bbc02832d6a5eb0b6ef12acdf42e 100644 (file)
@@ -150,6 +150,26 @@ AC_DEFUN([OFP_CHECK_LOGDIR],
      [LOGDIR='${localstatedir}/log/${PACKAGE}'])
    AC_SUBST([LOGDIR])])
 
+dnl Checks for __malloc_hook, etc., supported by glibc.
+AC_DEFUN([OFP_CHECK_MALLOC_HOOKS],
+  [AC_CACHE_CHECK(
+    [whether libc supports hooks for malloc and related functions],
+    [ofp_cv_malloc_hooks],
+    [AC_COMPILE_IFELSE(
+      [AC_LANG_PROGRAM(
+         [#include <malloc.h>
+         ], 
+         [(void) __malloc_hook;
+          (void) __realloc_hook;
+          (void) __free_hook;])],
+      [ofp_cv_malloc_hooks=yes],
+      [ofp_cv_malloc_hooks=no])])
+   if test $ofp_cv_malloc_hooks = yes; then
+     AC_DEFINE([HAVE_MALLOC_HOOKS], [1], 
+               [Define to 1 if you have __malloc_hook, __realloc_hook, and
+                __free_hook in <malloc.h>.])
+   fi])
+
 dnl Runs the checks required to include the headers in include/ and
 dnl link against lib/libopenflow.a.
 AC_DEFUN([OFP_CHECK_LIBOPENFLOW],
@@ -163,5 +183,6 @@ AC_DEFUN([OFP_CHECK_LIBOPENFLOW],
    AC_REQUIRE([OFP_CHECK_PKIDIR])
    AC_REQUIRE([OFP_CHECK_RUNDIR])
    AC_REQUIRE([OFP_CHECK_LOGDIR])
+   AC_REQUIRE([OFP_CHECK_MALLOC_HOOKS])
    AC_CHECK_FUNCS([strlcpy])])
 
index c536f0077db1708577b4805203bd617e4eb61d51..bcb3970c2f982b4a0cf3ac40fce552ba8ffb3cdc 100644 (file)
@@ -434,6 +434,7 @@ require the controller to send the CA certificate, but
 .so lib/daemon.man
 .so lib/vlog.man
 .so lib/common.man
+.so lib/leak-checker.man
 
 .SH "SEE ALSO"
 
index 04c5d9d61ddbeebdeec9797834100c40ac5980d2..03a5b7915d51fc8da5ee8a8de65dd0cda470b73c 100644 (file)
@@ -50,6 +50,7 @@
 #include "fail-open.h"
 #include "fault.h"
 #include "in-band.h"
+#include "leak-checker.h"
 #include "list.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
@@ -590,7 +591,8 @@ parse_options(int argc, char *argv[], struct settings *s)
         OPT_COMMAND_ACL,
         OPT_COMMAND_DIR,
         OPT_NETFLOW,
-        VLOG_OPTION_ENUMS
+        VLOG_OPTION_ENUMS,
+        LEAK_CHECKER_OPTION_ENUMS
     };
     static struct option long_options[] = {
         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
@@ -615,6 +617,7 @@ parse_options(int argc, char *argv[], struct settings *s)
         {"version",     no_argument, 0, 'V'},
         DAEMON_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
+        LEAK_CHECKER_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
         VCONN_SSL_LONG_OPTIONS
         {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
@@ -775,6 +778,8 @@ parse_options(int argc, char *argv[], struct settings *s)
 
         VLOG_OPTION_HANDLERS
 
+        LEAK_CHECKER_OPTION_HANDLERS
+
 #ifdef HAVE_OPENSSL
         VCONN_SSL_OPTION_HANDLERS
 
@@ -878,5 +883,6 @@ usage(void)
     printf("\nOther options:\n"
            "  -h, --help              display this help message\n"
            "  -V, --version           display version information\n");
+    leak_checker_usage();
     exit(EXIT_SUCCESS);
 }
index f9ce28d6a7fe15bd80960425664db753d142e0cc..d6f79a8c1c3b75bf532c26fc4fafe110205ea61f 100644 (file)
@@ -4,12 +4,13 @@ bin_PROGRAMS += \
        utilities/ofp-discover \
        utilities/ofp-kill
 bin_SCRIPTS += utilities/ofp-pki
-noinst_SCRIPTS += utilities/ofp-pki-cgi
+noinst_SCRIPTS += utilities/ofp-pki-cgi utilities/ofp-parse-leaks
 
 EXTRA_DIST += \
        utilities/dpctl.8.in \
        utilities/ofp-discover.8.in \
        utilities/ofp-kill.8.in \
+       utilities/ofp-parse-leaks.in \
        utilities/ofp-pki-cgi.in \
        utilities/ofp-pki.8.in \
        utilities/ofp-pki.in \
@@ -18,6 +19,7 @@ DISTCLEANFILES += \
        utilities/dpctl.8 \
        utilities/ofp-discover.8 \
        utilities/ofp-kill.8 \
+       utilities/ofp-parse-leaks \
        utilities/ofp-pki \
        utilities/ofp-pki.8 \
        utilities/ofp-pki-cgi \
diff --git a/utilities/ofp-parse-leaks.in b/utilities/ofp-parse-leaks.in
new file mode 100755 (executable)
index 0000000..c632a59
--- /dev/null
@@ -0,0 +1,185 @@
+#! @PERL@
+
+use strict;
+use warnings;
+
+if (grep($_ eq '--help', @ARGV)) {
+    print <<EOF;
+$0, for parsing leak checker logs
+usage: $0 [BINARY] < LOG
+where LOG is a log file produced by an OpenFlow program's --check-leaks option
+  and BINARY is the binary that wrote LOG.
+EOF
+    exit 0;
+}
+
+die "$0: zero or one arguments required; use --help for help\n" if @ARGV > 1;
+die "$0: $ARGV[0] does not exist" if @ARGV > 0 && ! -e $ARGV[0];
+
+our ($binary);
+our ($a2l) = search_path("addr2line");
+my ($no_syms) = "symbols will not be translated";
+if (!@ARGV) {
+    print "no binary specified; $no_syms\n";
+} elsif (! -e $ARGV[0]) {
+    print "$ARGV[0] does not exist; $no_syms";
+} elsif (!defined($a2l)) {
+    print "addr2line not found in PATH; $no_syms";
+} else {
+    $binary = $ARGV[0];
+}
+
+our %blocks;
+while (<STDIN>) {
+    my $ptr = "(0x[0-9a-fA-F]+|\\(nil\\))";
+    my $callers = ":((?: $ptr)+)";
+    if (/^malloc\((\d+)\) -> $ptr$callers$/) {
+        allocated($., $2, $1, $3);
+    } elsif (/^claim\($ptr\)$callers$/) {
+        claimed($., $1, $2);
+    } elsif (/realloc\($ptr, (\d+)\) -> $ptr$callers$/) {
+        my ($callers) = $4;
+        freed($., $1, $callers);
+        allocated($., $3, $2, $callers);
+    } elsif (/^free\($ptr\)$callers$/) {
+        freed($., $1, $2);
+    } else {
+        print "stdin:$.: syntax error\n";
+    }
+}
+if (%blocks) {
+    my $n_blocks = scalar(keys(%blocks));
+    my $n_bytes = 0;
+    $n_bytes += $_->{SIZE} foreach values(%blocks);
+    print "$n_bytes bytes in $n_blocks blocks not freed at end of run\n";
+    my %blocks_by_callers;
+    foreach my $block (values(%blocks)) {
+        my ($trimmed_callers) = trim_callers($block->{CALLERS});
+        push (@{$blocks_by_callers{$trimmed_callers}}, $block);
+    }
+    foreach my $callers (sort {@{$b} <=> @{$a}} (values(%blocks_by_callers))) {
+        $n_blocks = scalar(@{$callers});
+        $n_bytes = 0;
+        $n_bytes += $_->{SIZE} foreach @{$callers};
+        print "$n_bytes bytes in these $n_blocks blocks were not freed:\n";
+        my $i = 0;
+        my $max = 5;
+        foreach my $block (sort {$a->{LINE} <=> $b->{LINE}} (@{$callers})) {
+            printf "\t%d-byte block at 0x%08x allocated on stdin:%d\n",
+              $block->{SIZE}, $block->{BASE}, $block->{LINE};
+            last if $i++ > $max;
+        }
+        print "\t...and ", $n_blocks - $max, " others...\n"
+          if $n_blocks > $max;
+        print "The blocks listed above were allocated by:\n";
+        print_callers("\t", ${$callers}[0]->{CALLERS});
+    }
+}
+sub interp_pointer {
+    my ($s_ptr) = @_;
+    return $s_ptr eq '(nil)' ? 0 : hex($s_ptr);
+}
+
+sub allocated {
+    my ($line, $s_base, $size, $callers) = @_;
+    my ($base) = interp_pointer($s_base);
+    return if !$base;
+    my ($info) = {LINE => $line,
+                  BASE => $base,
+                  SIZE => $size,
+                  CALLERS => $callers};
+    if (exists($blocks{$base})) {
+        print "In-use address returned by allocator:\n";
+        print "\tInitial allocation:\n";
+        print_block("\t\t", $blocks{$base});
+        print "\tNew allocation:\n";
+        print_block("\t\t", $info);
+    }
+    $blocks{$base} = $info;
+}
+
+sub claimed {
+    my ($line, $s_base, $callers) = @_;
+    my ($base) = interp_pointer($s_base);
+    return if !$base;
+    if (exists($blocks{$base})) {
+       $blocks{$base}{LINE} = $line;
+       $blocks{$base}{CALLERS} = $callers;
+    } else {
+        printf "Claim asserted on not-in-use block 0x%08x by:\n", $base;
+       print_callers('', $callers);
+    }
+}
+
+sub freed {
+    my ($line, $s_base, $callers) = @_;
+    my ($base) = interp_pointer($s_base);
+    return if !$base;
+
+    if (!delete($blocks{$base})) {
+        printf "Bad free of not-allocated address 0x%08x on stdin:%d by:\n", $base, $line;
+        print_callers('', $callers);
+    }
+}
+
+sub print_block {
+    my ($prefix, $info) = @_;
+    printf '%s%d-byte block at 0x%08x allocated on stdin:%d by:' . "\n",
+      $prefix, $info->{SIZE}, $info->{BASE}, $info->{LINE};
+    print_callers($prefix, $info->{CALLERS});
+}
+
+sub print_callers {
+    my ($prefix, $callers) = @_;
+    foreach my $pc (split(' ', $callers)) {
+        print "$prefix\t", lookup_pc($pc), "\n";
+    }
+}
+
+our (%cache);
+sub lookup_pc {
+    my ($s_pc) = @_;
+    if (defined($binary)) {
+        my ($pc) = hex($s_pc);
+        my ($output) = "$s_pc: ";
+        if (!exists($cache{$pc})) {
+            open(A2L, "$a2l -fe $binary $s_pc|");
+            chomp(my $function = <A2L>);
+            chomp(my $line = <A2L>);
+            close(A2L);
+            $line =~ s/^(\.\.\/)*//;
+            $line = "..." . substr($line, -25) if length($line) > 28;
+            $cache{$pc} = "$s_pc: $function ($line)";
+        }
+        return $cache{$pc};
+    } else {
+        return "$s_pc";
+    }
+}
+
+sub trim_callers {
+    my ($in) = @_;
+    my (@out);
+    foreach my $pc (split(' ', $in)) {
+        my $xlated = lookup_pc($pc);
+        if ($xlated =~ /\?\?/) {
+            push(@out, "...") if !@out || $out[$#out] ne '...';
+        } else {
+            push(@out, $pc);
+        }
+    }
+    return join(' ', @out);
+}
+
+sub search_path {
+    my ($target) = @_;
+    for my $dir (split (':', $ENV{PATH})) {
+       my ($file) = "$dir/$target";
+       return $file if -e $file;
+    }
+    return undef;
+}
+
+# Local Variables:
+# mode: perl
+# End:
index 76ad20e4741b519b33657868858241ea1a3918ee..ec7ac1bed6de56f23bedc4d893e8a6b064806c53 100644 (file)
@@ -97,6 +97,7 @@ loaded.
 .so lib/daemon.man
 .so lib/vlog.man
 .so lib/common.man
+.so lib/leak-checker.man
 .
 .SH "BUGS"
 .
index 65bf041f7ffaca9c491123f55203f1b5b3d62940..dc1cfa1d8b339f30c3e00b54f029ef2f96c3b216 100644 (file)
@@ -41,6 +41,7 @@
 #include "compiler.h"
 #include "daemon.h"
 #include "fault.h"
+#include "leak-checker.h"
 #include "poll-loop.h"
 #include "process.h"
 #include "signals.h"
@@ -117,7 +118,8 @@ parse_options(int argc, char *argv[])
 {
     enum {
         OPT_PEER_CA_CERT,
-        VLOG_OPTION_ENUMS
+        VLOG_OPTION_ENUMS,
+        LEAK_CHECKER_OPTION_ENUMS
     };
     static struct option long_options[] = {
         {"brcompat",    required_argument, 0, 'b'},
@@ -126,6 +128,7 @@ parse_options(int argc, char *argv[])
         {"version",     no_argument, 0, 'V'},
         DAEMON_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
+        LEAK_CHECKER_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
         VCONN_SSL_LONG_OPTIONS
         {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
@@ -170,6 +173,7 @@ parse_options(int argc, char *argv[])
         VLOG_OPTION_HANDLERS
         DAEMON_OPTION_HANDLERS
         VCONN_SSL_OPTION_HANDLERS
+        LEAK_CHECKER_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
         case OPT_PEER_CA_CERT:
@@ -209,5 +213,6 @@ usage(void)
     printf("\nOther options:\n"
            "  -h, --help              display this help message\n"
            "  -V, --version           display version information\n");
+    leak_checker_usage();
     exit(EXIT_SUCCESS);
 }