Skip to content

Commit

Permalink
Add a method to trace call paths
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Lekensteyn committed Aug 10, 2015
1 parent 26698ea commit d09742e
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 1 deletion.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
HAVE_LIBUNWIND=1
WITH_ORIGINS_TRACE=1

ifeq ($(HAVE_LIBUNWIND), 1)
optional_libs=libunwind
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
141 changes: 141 additions & 0 deletions bt-tree.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Stores a call trace ("backtrace") for later inspection.
*
* Copyright (C) 2014 Peter Wu <[email protected]>
*
* 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 <glib.h>
#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));
}
7 changes: 7 additions & 0 deletions bt-tree.h
Original file line number Diff line number Diff line change
@@ -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);
89 changes: 88 additions & 1 deletion gobject-list.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@
#include <libunwind.h>
#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;

Expand All @@ -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 },
};

Expand 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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
{
Expand Down Expand Up @@ -234,13 +293,31 @@ _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)
{
g_print ("\nStill Alive:\n");

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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down

0 comments on commit d09742e

Please sign in to comment.