From d09742e5c1b1183ebdfc4235eec961ca20feb23b Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 20 Nov 2014 16:36:21 +0100 Subject: [PATCH] Add a method to trace call paths Useful to debug reference count leaks where a lot objects are still held just because a single object was not released elsewhere... Totally not optimized and slows down a lot, but it gets the job done. --- Makefile | 6 +++ README | 1 + bt-tree.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ bt-tree.h | 7 +++ gobject-list.c | 89 ++++++++++++++++++++++++++++++- 5 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 bt-tree.c create mode 100644 bt-tree.h diff --git a/Makefile b/Makefile index 684cde8..53d6289 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ HAVE_LIBUNWIND=1 +WITH_ORIGINS_TRACE=1 ifeq ($(HAVE_LIBUNWIND), 1) optional_libs=libunwind @@ -13,6 +14,11 @@ LIBS=`pkg-config --libs gobject-2.0 $(optional_libs)` OBJS = gobject-list.o +ifeq ($(WITH_ORIGINS_TRACE), 1) + BUILD_OPTIONS+=-DWITH_ORIGINS_TRACE + OBJS += bt-tree.o +endif + all: libgobject-list.so .PHONY: all clean clean: diff --git a/README b/README index 2853364..15e2307 100644 --- a/README +++ b/README @@ -39,6 +39,7 @@ GOBJECT_LIST_DISPLAY: • ‘refs’: Print information about every reference increment and decrement on objects. • ‘backtrace’: Include backtraces with every printed message. + • ‘tracerefs’: At exit, for each object still alive, print a call tree. • ‘all’: All of the above. GOBJECT_LIST_FILTER: diff --git a/bt-tree.c b/bt-tree.c new file mode 100644 index 0000000..9586bd8 --- /dev/null +++ b/bt-tree.c @@ -0,0 +1,141 @@ +/** + * Stores a call trace ("backtrace") for later inspection. + * + * Copyright (C) 2014 Peter Wu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +/* TODO + * - faster lookups. The path lookup can surely be optimized (cached?) + * - This code allocates memory which may or may not be a problem depending on + * context. + */ + +#include +#include "bt-tree.h" + +enum { + COUNT_REF = 0, + COUNT_UNREF, + + COUNT_LAST +}; + +typedef struct BtTrie { + GHashTable *children; + char *label; + unsigned count[COUNT_LAST]; +} BtTrie; + +BtTrie * +bt_create (char *label) +{ + BtTrie *bt_trie = g_malloc0 (sizeof(BtTrie)); + bt_trie->label = label; + bt_trie->children = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) bt_free); + return bt_trie; +} + +void +bt_free (BtTrie *bt_trie) +{ + g_free (bt_trie->label); + g_hash_table_unref (bt_trie->children); + g_free (bt_trie); +} + +/* returns the child of bt_trie with the item at position i inserted. The memory + * is freed if such a child already exists. */ +static inline BtTrie * +find_child (BtTrie *bt_trie, const GPtrArray *items, guint i) +{ + BtTrie *child = NULL; + char *label = g_ptr_array_index (items, i); + g_hash_table_lookup_extended (bt_trie->children, label, + NULL, (gpointer *)&child); + if (child == NULL) { + child = bt_create (label); + g_hash_table_insert (bt_trie->children, child->label, child); + } else { + /* unused label */ + g_free (label); + } + return child; +} + +/** + * Inserts a trace described by items into a trie. Memory can be allocated if a + * node is missing. + * @bt_trie: root of the tree. + * @items: the items to insert (in reverse order: the first element is the leaf, + * the last element is the root). Must not be empty. The control of the contents + * is transferred from the caller. + */ +void +bt_insert (BtTrie *bt_trie, const GPtrArray *items, gboolean is_ref) +{ + guint i = items->len; + ++bt_trie->count[is_ref ? COUNT_REF : COUNT_UNREF]; /* mark root */ + while (i-- > 0) { + bt_trie = find_child (bt_trie, items, i); + ++bt_trie->count[is_ref ? COUNT_REF : COUNT_UNREF]; + } +} + +static void +_bt_print_tree (gpointer key, gpointer value, gpointer user_data) +{ + const char *label = key; + BtTrie *tree = value; + guint indent = GPOINTER_TO_INT (user_data), i; + gint diff = tree->count[COUNT_REF] - tree->count[COUNT_UNREF]; + const char + *color_default = "\e[1;34m", /* blue */ + *color_unref = "\e[0;31m", /* red */ + *color_ref = "\e[0;33m", /* yellow */ + *color_diff; + + if (diff == 0) /* not important */ + color_default = color_unref = color_ref = color_diff = + "\e[1;30m"; /* gray */ + else if (diff < 0) /* more unrefs than refs */ + color_diff = "\e[1;31m"; /* red */ + else /* diff > 0, more refs than unrefs */ + color_diff = "\e[1;33m"; /* yellow */ + + for (i = 0; i < indent; i++) + g_print("| "); + g_print ("%s# %s ", color_default, label); /* name */ + g_print ("(" + "%s+%u%s" /* refs */ + "/" + "%s-%u%s" /* unrefs */ + " = %s%d%s", /* diff */ + color_ref, tree->count[COUNT_REF], color_default, + color_unref, tree->count[COUNT_UNREF], color_default, + color_diff, diff, color_default); + g_print (")\e[m\n"); + g_hash_table_foreach (tree->children, _bt_print_tree, + GINT_TO_POINTER (indent + 1)); +} + +void +bt_print_tree (BtTrie *root, guint indent) +{ + _bt_print_tree (root->label, root, GINT_TO_POINTER (indent)); +} diff --git a/bt-tree.h b/bt-tree.h new file mode 100644 index 0000000..206bd5e --- /dev/null +++ b/bt-tree.h @@ -0,0 +1,7 @@ + +typedef struct BtTrie BtTrie; + +BtTrie *bt_create (char *label); +void bt_free (BtTrie *bt_trie); +void bt_insert (BtTrie *root, const GPtrArray *items, gboolean is_ref); +void bt_print_tree (BtTrie *root, guint indent); diff --git a/gobject-list.c b/gobject-list.c index 891a1ce..0dbebe8 100644 --- a/gobject-list.c +++ b/gobject-list.c @@ -34,14 +34,20 @@ #include #endif +#ifdef WITH_ORIGINS_TRACE +#include "bt-tree.h" +#endif + typedef enum { DISPLAY_FLAG_NONE = 0, DISPLAY_FLAG_CREATE = 1, DISPLAY_FLAG_REFS = 1 << 2, DISPLAY_FLAG_BACKTRACE = 1 << 3, + DISPLAY_FLAG_TRACEREFS = 1 << 4, DISPLAY_FLAG_ALL = - DISPLAY_FLAG_CREATE | DISPLAY_FLAG_REFS | DISPLAY_FLAG_BACKTRACE, + DISPLAY_FLAG_CREATE | DISPLAY_FLAG_REFS | DISPLAY_FLAG_BACKTRACE | + DISPLAY_FLAG_TRACEREFS, DISPLAY_FLAG_DEFAULT = DISPLAY_FLAG_CREATE, } DisplayFlags; @@ -57,6 +63,7 @@ DisplayFlagsMapItem display_flags_map[] = { "create", DISPLAY_FLAG_CREATE }, { "refs", DISPLAY_FLAG_REFS }, { "backtrace", DISPLAY_FLAG_BACKTRACE }, + { "tracerefs", DISPLAY_FLAG_TRACEREFS }, { "all", DISPLAY_FLAG_ALL }, }; @@ -71,6 +78,11 @@ typedef struct { * We keep the string representing the type of the object as we won't be able * to get it when displaying later as the object would have been destroyed. */ GHashTable *removed; /* owned */ + +#ifdef WITH_ORIGINS_TRACE + /* GObject -> BtTrie */ + GHashTable *origins; /* owned */ +#endif } ObjectData; /* Global static state, which must be accessed with the @gobject_list mutex @@ -128,6 +140,10 @@ display_filter (DisplayFlags flags) if (display_flags & DISPLAY_FLAG_BACKTRACE) g_print ("Warning: backtrace is not available, it needs libunwind\n"); #endif +#ifndef WITH_ORIGINS_TRACE + if (display_flags & DISPLAY_FLAG_TRACEREFS) + g_print ("Warning: tracerefs is not available, it needs libunwind\n"); +#endif parsed = TRUE; } @@ -146,6 +162,49 @@ object_filter (const char *obj_name) return (strncmp (filter, obj_name, strlen (filter)) == 0); } +static void +save_trace (const char *key, gboolean is_ref) +{ +#if defined(HAVE_LIBUNWIND) && defined(WITH_ORIGINS_TRACE) + unw_context_t uc; + unw_cursor_t cursor; + GPtrArray *trace; + BtTrie *root = NULL; + gboolean found; + + if (!display_filter (DISPLAY_FLAG_TRACEREFS)) + return; + + trace = g_ptr_array_sized_new (10); + + unw_getcontext (&uc); + unw_init_local (&cursor, &uc); + + while (unw_step (&cursor) > 0) + { + gchar name[129]; + unw_word_t off; + int result; + + result = unw_get_proc_name (&cursor, name, sizeof (name), &off); + if (result < 0 && result != -UNW_ENOMEM) + break; + + g_ptr_array_insert (trace, -1, g_strdup (name)); + } + + found = g_hash_table_lookup_extended (gobject_list_state.origins, + (gpointer) key, + NULL, (gpointer *)&root); + if (!found) { + root = bt_create (g_strdup (key)); + g_hash_table_insert (gobject_list_state.origins, (gpointer) key, root); + } + bt_insert (root, trace, is_ref); + g_ptr_array_unref (trace); +#endif +} + static void print_trace (void) { @@ -234,6 +293,17 @@ _sig_usr2_handler (G_GNUC_UNUSED int signal) G_UNLOCK (gobject_list); } +#ifdef WITH_ORIGINS_TRACE +static void +print_refs (G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data) +{ + gint *no = (gpointer) user_data; + BtTrie *bt_trie = value; + g_print ("#%d\n", ++*no); + bt_print_tree (bt_trie, 0); +} +#endif + static void print_still_alive (void) { @@ -241,6 +311,13 @@ print_still_alive (void) G_LOCK (gobject_list); _dump_object_list (gobject_list_state.objects); +#ifdef WITH_ORIGINS_TRACE + if (display_filter (DISPLAY_FLAG_TRACEREFS)) { + guint no = 0; + g_print ("\nReferences:\n"); + g_hash_table_foreach (gobject_list_state.origins, print_refs, (gpointer) &no); + } +#endif G_UNLOCK (gobject_list); } @@ -291,6 +368,10 @@ get_func (const char *func_name) gobject_list_state.objects = g_hash_table_new (NULL, NULL); gobject_list_state.added = g_hash_table_new (NULL, NULL); gobject_list_state.removed = g_hash_table_new_full (NULL, NULL, NULL, g_free); +#ifdef WITH_ORIGINS_TRACE + gobject_list_state.origins = + g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) bt_free); +#endif /* Set up exit handler */ atexit (_exiting); @@ -338,6 +419,9 @@ _object_finalized (G_GNUC_UNUSED gpointer data, g_hash_table_remove (gobject_list_state.objects, obj); g_hash_table_remove (gobject_list_state.added, obj); +#ifdef WITH_ORIGINS_TRACE + g_hash_table_remove (gobject_list_state.origins, G_OBJECT_TYPE_NAME (obj)); +#endif G_UNLOCK (gobject_list); } @@ -371,6 +455,7 @@ g_object_new (GType type, g_print (" ++ Created object %p, %s\n", obj, obj_name); print_trace(); + save_trace (obj_name, TRUE); g_mutex_unlock(&output_mutex); } @@ -424,6 +509,7 @@ g_object_ref (gpointer object) g_print (" + Reffed object %p, %s; ref_count: %d -> %d\n", obj, obj_name, ref_count, obj->ref_count); print_trace(); + save_trace (obj_name, TRUE); g_mutex_unlock(&output_mutex); } @@ -449,6 +535,7 @@ g_object_unref (gpointer object) g_print (" - Unreffed object %p, %s; ref_count: %d -> %d\n", obj, obj_name, obj->ref_count, obj->ref_count - 1); print_trace(); + save_trace (obj_name, FALSE); g_mutex_unlock(&output_mutex); }