Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755340AbbKRGt7 (ORCPT ); Wed, 18 Nov 2015 01:49:59 -0500 Received: from mail7.hitachi.co.jp ([133.145.228.42]:39295 "EHLO mail7.hitachi.co.jp" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754756AbbKRGrS (ORCPT ); Wed, 18 Nov 2015 01:47:18 -0500 X-AuditID: 85900ec0-9cdc8b9000001a57-8f-564c1ef25878 Subject: [PATCH perf/core 03/13] perf: Introduce generic refcount APIs with debug feature From: Masami Hiramatsu To: Arnaldo Carvalho de Melo Cc: Peter Zijlstra , linux-kernel@vger.kernel.org, Adrian Hunter , Ingo Molnar , Namhyung Kim , Jiri Olsa Date: Wed, 18 Nov 2015 15:40:16 +0900 Message-ID: <20151118064016.30709.10199.stgit@localhost.localdomain> In-Reply-To: <20151118064009.30709.74354.stgit@localhost.localdomain> References: <20151118064009.30709.74354.stgit@localhost.localdomain> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit X-Brightmail-Tracker: AAAAAA== Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 7979 Lines: 275 This is a kind of debugging feature for atomic reference counter. The reference counters are widely used in perf tools but not well debugged. It sometimes causes memory leaks but no one has noticed the issue, since it is hard to debug such un-reclaimed objects. This refcnt interface provides fully backtrace debug feature to analyze such issue. User just replace atomic_t ops with refcnt APIs and add refcnt__exit() where the object is released. /* At object initializing */ refcnt__init(obj, refcnt); /* <- atomic_set(&obj->refcnt, 1); */ /* At object get method */ refcnt__get(obj, refcnt); /* <- atomic_inc(&obj->refcnt); */ /* At object put method */ if (obj && refcnt__put(obj, refcnt)) /* <-atmoic_dec_and_test(&obj->refcnt)*/ /* At object releasing */ refcnt__exit(obj, refcnt); /* <- Newly added */ The debugging feature is enabled when building perf with REFCNT_DEBUG=1. Otherwides it is just translated as normal atomic ops. Debugging binary warns you if it finds leaks when the perf exits. If you give -v (or --verbose) to the perf, it also shows backtrace logs on all refcnt operations of leaked objects. Signed-off-by: Masami Hiramatsu --- tools/perf/config/Makefile | 5 ++ tools/perf/util/Build | 1 tools/perf/util/refcnt.c | 125 ++++++++++++++++++++++++++++++++++++++++++++ tools/perf/util/refcnt.h | 65 +++++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 tools/perf/util/refcnt.c create mode 100644 tools/perf/util/refcnt.h diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile index de89ec5..efc2e03 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -147,6 +147,11 @@ ifdef PARSER_DEBUG $(call detected_var,PARSER_DEBUG_FLEX) endif +ifdef REFCNT_DEBUG + CFLAGS += -DREFCNT_DEBUG + $(call detected,CONFIG_REFCNT_DEBUG) +endif + ifndef NO_LIBPYTHON # Try different combinations to accommodate systems that only have # python[2][-config] in weird combinations but always preferring diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 591b3fe..f64d2a4 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -86,6 +86,7 @@ libperf-$(CONFIG_AUXTRACE) += intel-pt.o libperf-$(CONFIG_AUXTRACE) += intel-bts.o libperf-y += parse-branch-options.o libperf-y += parse-regs-options.o +libperf-$(CONFIG_REFCNT_DEBUG) += refcnt.o libperf-$(CONFIG_LIBBPF) += bpf-loader.o libperf-$(CONFIG_LIBELF) += symbol-elf.o diff --git a/tools/perf/util/refcnt.c b/tools/perf/util/refcnt.c new file mode 100644 index 0000000..f828419 --- /dev/null +++ b/tools/perf/util/refcnt.c @@ -0,0 +1,125 @@ +/* Refcount backtrace for debugging leaks */ +#include "../perf.h" + +#include +#include +#include /* For backtrace */ + +#include "event.h" +#include "debug.h" +#include "util.h" +#include "refcnt.h" + +/* A root of backtrace */ +static LIST_HEAD(refcnt_root); /* List head of refcnt object */ + +static void __refcnt_object__delete(struct refcnt_object *ref) +{ + struct refcnt_buffer *buf; + + while (!list_empty(&ref->head)) { + buf = list_entry(ref->head.next, struct refcnt_buffer, list); + list_del_init(&buf->list); + free(buf); + } + list_del_init(&ref->list); + free(ref); +} + +static struct refcnt_object *refcnt__find_object(void *obj) +{ + struct refcnt_object *ref; + + /* TODO: use hash list */ + list_for_each_entry(ref, &refcnt_root, list) + if (ref->obj == obj) + return ref; + + return NULL; +} + +void refcnt_object__delete(void *addr) +{ + struct refcnt_object *ref = refcnt__find_object(addr); + + if (!ref) { + pr_debug("REFCNT: Delete uninitialized refcnt: %p\n", addr); + return; + } + __refcnt_object__delete(ref); +} + +void refcnt_object__record(void *obj, const char *name, int count) +{ + struct refcnt_object *ref = refcnt__find_object(obj); + struct refcnt_buffer *buf; + + /* If no entry, allocate new one */ + if (!ref) { + ref = malloc(sizeof(*ref)); + if (!ref) { + pr_debug("REFCNT: Out of memory for %p (%s)\n", + obj, name); + return; + } + INIT_LIST_HEAD(&ref->list); + INIT_LIST_HEAD(&ref->head); + ref->name = name; + ref->obj = obj; + list_add_tail(&ref->list, &refcnt_root); + } + + buf = malloc(sizeof(*buf)); + if (!buf) { + pr_debug("REFCNT: Out of memory for %p (%s)\n", obj, ref->name); + return; + } + + INIT_LIST_HEAD(&buf->list); + buf->count = count; + buf->nr = backtrace(buf->buf, BACKTRACE_SIZE); + list_add_tail(&buf->list, &ref->head); +} + +static void pr_refcnt_buffer(struct refcnt_buffer *buf) +{ + char **symbuf; + int i; + + if (!buf) + return; + symbuf = backtrace_symbols(buf->buf, buf->nr); + /* Skip the first one because it is always btrace__record */ + for (i = 1; i < buf->nr; i++) + pr_debug(" %s\n", symbuf[i]); + free(symbuf); +} + +void refcnt__dump_unreclaimed(void) __attribute__((destructor)); +void refcnt__dump_unreclaimed(void) +{ + struct refcnt_object *ref, *n; + struct refcnt_buffer *buf; + int i = 0; + + if (list_empty(&refcnt_root)) + return; + + pr_warning("REFCNT: BUG: Unreclaimed objects found.\n"); + list_for_each_entry_safe(ref, n, &refcnt_root, list) { + pr_debug("==== [%d] ====\nUnreclaimed %s: %p\n", i, + ref->name ? ref->name : "(object)", ref->obj); + list_for_each_entry(buf, &ref->head, list) { + pr_debug("Refcount %s => %d at\n", + buf->count > 0 ? "+1" : "-1", + buf->count > 0 ? buf->count : -buf->count - 1); + pr_refcnt_buffer(buf); + } + __refcnt_object__delete(ref); + i++; + } + pr_warning("REFCNT: Total %d objects are not reclaimed.\n", i); + if (!verbose) + pr_warning(" To see all backtraces, rerun with -v option\n"); +} + diff --git a/tools/perf/util/refcnt.h b/tools/perf/util/refcnt.h new file mode 100644 index 0000000..a8e2d29 --- /dev/null +++ b/tools/perf/util/refcnt.h @@ -0,0 +1,65 @@ +/* + * Atomic reference counter API with debugging feature + */ +#ifndef __PERF_REFCNT_H +#define __PERF_REFCNT_H + +#include + +#ifdef REFCNT_DEBUG + +struct refcnt_object { + struct list_head list; /* List of objects */ + void *obj; /* Object address which has refcnt */ + const char *name; /* Object class name */ + struct list_head head; /* List head for buffers */ +}; + +#define BACKTRACE_SIZE 32 +struct refcnt_buffer { + struct list_head list; /* List of buffers */ + int count; /* Count number at recording point */ + int nr; /* Number of recorded buffer entries */ + void *buf[BACKTRACE_SIZE]; /* Backtrace buffer */ +}; + +void refcnt_object__record(void *obj, const char *name, int count); +void refcnt_object__delete(void *obj); + +static inline void __refcnt__init(atomic_t *refcnt, void *obj, const char *name) +{ + atomic_set(refcnt, 1); + refcnt_object__record(obj, name, 1); +} + +static inline void __refcnt__get(atomic_t *refcnt, void *obj) +{ + atomic_inc(refcnt); + refcnt_object__record(obj, NULL, atomic_read(refcnt)); +} + +static inline int __refcnt__put(atomic_t *refcnt, void *obj) +{ + refcnt_object__record(obj, NULL, -atomic_read(refcnt)); + return atomic_dec_and_test(refcnt); +} + +#define refcnt__init(obj, member) \ + __refcnt__init(&obj->member, obj, #obj) +#define refcnt__exit(obj, member) \ + refcnt_object__delete(obj) +#define refcnt__get(obj, member) \ + __refcnt__get(&obj->member, obj) +#define refcnt__put(obj, member) \ + __refcnt__put(&obj->member, obj) + +#else /* !REFCNT_DEBUG */ + +#define refcnt__init(obj, member) atomic_set(&obj->member, 1) +#define refcnt__exit(obj, member) do { } while (0) +#define refcnt__get(obj, member) atomic_inc(&obj->member) +#define refcnt__put(obj, member) atomic_dec_and_test(&obj->member) + +#endif /* !REFCNT_DEBUG */ + +#endif /* __PERF_REFCNT_H */ -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/