Skip to content

Commit d09742e

Browse files
committed
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.
1 parent 26698ea commit d09742e

5 files changed

Lines changed: 243 additions & 1 deletion

File tree

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
HAVE_LIBUNWIND=1
2+
WITH_ORIGINS_TRACE=1
23

34
ifeq ($(HAVE_LIBUNWIND), 1)
45
optional_libs=libunwind
@@ -13,6 +14,11 @@ LIBS=`pkg-config --libs gobject-2.0 $(optional_libs)`
1314

1415
OBJS = gobject-list.o
1516

17+
ifeq ($(WITH_ORIGINS_TRACE), 1)
18+
BUILD_OPTIONS+=-DWITH_ORIGINS_TRACE
19+
OBJS += bt-tree.o
20+
endif
21+
1622
all: libgobject-list.so
1723
.PHONY: all clean
1824
clean:

README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ GOBJECT_LIST_DISPLAY:
3939
• ‘refs’: Print information about every reference increment and
4040
decrement on objects.
4141
• ‘backtrace’: Include backtraces with every printed message.
42+
• ‘tracerefs’: At exit, for each object still alive, print a call tree.
4243
• ‘all’: All of the above.
4344

4445
GOBJECT_LIST_FILTER:

bt-tree.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Stores a call trace ("backtrace") for later inspection.
3+
*
4+
* Copyright (C) 2014 Peter Wu <peter@lekensteyn.nl>
5+
*
6+
* This library is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 2.1 of the License, or (at your option) any later version.
10+
*
11+
* This library is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public
17+
* License along with this library; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19+
* USA
20+
*/
21+
22+
/* TODO
23+
* - faster lookups. The path lookup can surely be optimized (cached?)
24+
* - This code allocates memory which may or may not be a problem depending on
25+
* context.
26+
*/
27+
28+
#include <glib.h>
29+
#include "bt-tree.h"
30+
31+
enum {
32+
COUNT_REF = 0,
33+
COUNT_UNREF,
34+
35+
COUNT_LAST
36+
};
37+
38+
typedef struct BtTrie {
39+
GHashTable *children;
40+
char *label;
41+
unsigned count[COUNT_LAST];
42+
} BtTrie;
43+
44+
BtTrie *
45+
bt_create (char *label)
46+
{
47+
BtTrie *bt_trie = g_malloc0 (sizeof(BtTrie));
48+
bt_trie->label = label;
49+
bt_trie->children = g_hash_table_new_full (g_str_hash, g_str_equal,
50+
NULL, (GDestroyNotify) bt_free);
51+
return bt_trie;
52+
}
53+
54+
void
55+
bt_free (BtTrie *bt_trie)
56+
{
57+
g_free (bt_trie->label);
58+
g_hash_table_unref (bt_trie->children);
59+
g_free (bt_trie);
60+
}
61+
62+
/* returns the child of bt_trie with the item at position i inserted. The memory
63+
* is freed if such a child already exists. */
64+
static inline BtTrie *
65+
find_child (BtTrie *bt_trie, const GPtrArray *items, guint i)
66+
{
67+
BtTrie *child = NULL;
68+
char *label = g_ptr_array_index (items, i);
69+
g_hash_table_lookup_extended (bt_trie->children, label,
70+
NULL, (gpointer *)&child);
71+
if (child == NULL) {
72+
child = bt_create (label);
73+
g_hash_table_insert (bt_trie->children, child->label, child);
74+
} else {
75+
/* unused label */
76+
g_free (label);
77+
}
78+
return child;
79+
}
80+
81+
/**
82+
* Inserts a trace described by items into a trie. Memory can be allocated if a
83+
* node is missing.
84+
* @bt_trie: root of the tree.
85+
* @items: the items to insert (in reverse order: the first element is the leaf,
86+
* the last element is the root). Must not be empty. The control of the contents
87+
* is transferred from the caller.
88+
*/
89+
void
90+
bt_insert (BtTrie *bt_trie, const GPtrArray *items, gboolean is_ref)
91+
{
92+
guint i = items->len;
93+
++bt_trie->count[is_ref ? COUNT_REF : COUNT_UNREF]; /* mark root */
94+
while (i-- > 0) {
95+
bt_trie = find_child (bt_trie, items, i);
96+
++bt_trie->count[is_ref ? COUNT_REF : COUNT_UNREF];
97+
}
98+
}
99+
100+
static void
101+
_bt_print_tree (gpointer key, gpointer value, gpointer user_data)
102+
{
103+
const char *label = key;
104+
BtTrie *tree = value;
105+
guint indent = GPOINTER_TO_INT (user_data), i;
106+
gint diff = tree->count[COUNT_REF] - tree->count[COUNT_UNREF];
107+
const char
108+
*color_default = "\e[1;34m", /* blue */
109+
*color_unref = "\e[0;31m", /* red */
110+
*color_ref = "\e[0;33m", /* yellow */
111+
*color_diff;
112+
113+
if (diff == 0) /* not important */
114+
color_default = color_unref = color_ref = color_diff =
115+
"\e[1;30m"; /* gray */
116+
else if (diff < 0) /* more unrefs than refs */
117+
color_diff = "\e[1;31m"; /* red */
118+
else /* diff > 0, more refs than unrefs */
119+
color_diff = "\e[1;33m"; /* yellow */
120+
121+
for (i = 0; i < indent; i++)
122+
g_print("| ");
123+
g_print ("%s# %s ", color_default, label); /* name */
124+
g_print ("("
125+
"%s+%u%s" /* refs */
126+
"/"
127+
"%s-%u%s" /* unrefs */
128+
" = %s%d%s", /* diff */
129+
color_ref, tree->count[COUNT_REF], color_default,
130+
color_unref, tree->count[COUNT_UNREF], color_default,
131+
color_diff, diff, color_default);
132+
g_print (")\e[m\n");
133+
g_hash_table_foreach (tree->children, _bt_print_tree,
134+
GINT_TO_POINTER (indent + 1));
135+
}
136+
137+
void
138+
bt_print_tree (BtTrie *root, guint indent)
139+
{
140+
_bt_print_tree (root->label, root, GINT_TO_POINTER (indent));
141+
}

bt-tree.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
typedef struct BtTrie BtTrie;
3+
4+
BtTrie *bt_create (char *label);
5+
void bt_free (BtTrie *bt_trie);
6+
void bt_insert (BtTrie *root, const GPtrArray *items, gboolean is_ref);
7+
void bt_print_tree (BtTrie *root, guint indent);

gobject-list.c

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,20 @@
3434
#include <libunwind.h>
3535
#endif
3636

37+
#ifdef WITH_ORIGINS_TRACE
38+
#include "bt-tree.h"
39+
#endif
40+
3741
typedef enum
3842
{
3943
DISPLAY_FLAG_NONE = 0,
4044
DISPLAY_FLAG_CREATE = 1,
4145
DISPLAY_FLAG_REFS = 1 << 2,
4246
DISPLAY_FLAG_BACKTRACE = 1 << 3,
47+
DISPLAY_FLAG_TRACEREFS = 1 << 4,
4348
DISPLAY_FLAG_ALL =
44-
DISPLAY_FLAG_CREATE | DISPLAY_FLAG_REFS | DISPLAY_FLAG_BACKTRACE,
49+
DISPLAY_FLAG_CREATE | DISPLAY_FLAG_REFS | DISPLAY_FLAG_BACKTRACE |
50+
DISPLAY_FLAG_TRACEREFS,
4551
DISPLAY_FLAG_DEFAULT = DISPLAY_FLAG_CREATE,
4652
} DisplayFlags;
4753

@@ -57,6 +63,7 @@ DisplayFlagsMapItem display_flags_map[] =
5763
{ "create", DISPLAY_FLAG_CREATE },
5864
{ "refs", DISPLAY_FLAG_REFS },
5965
{ "backtrace", DISPLAY_FLAG_BACKTRACE },
66+
{ "tracerefs", DISPLAY_FLAG_TRACEREFS },
6067
{ "all", DISPLAY_FLAG_ALL },
6168
};
6269

@@ -71,6 +78,11 @@ typedef struct {
7178
* We keep the string representing the type of the object as we won't be able
7279
* to get it when displaying later as the object would have been destroyed. */
7380
GHashTable *removed; /* owned */
81+
82+
#ifdef WITH_ORIGINS_TRACE
83+
/* GObject -> BtTrie */
84+
GHashTable *origins; /* owned */
85+
#endif
7486
} ObjectData;
7587

7688
/* Global static state, which must be accessed with the @gobject_list mutex
@@ -128,6 +140,10 @@ display_filter (DisplayFlags flags)
128140
if (display_flags & DISPLAY_FLAG_BACKTRACE)
129141
g_print ("Warning: backtrace is not available, it needs libunwind\n");
130142
#endif
143+
#ifndef WITH_ORIGINS_TRACE
144+
if (display_flags & DISPLAY_FLAG_TRACEREFS)
145+
g_print ("Warning: tracerefs is not available, it needs libunwind\n");
146+
#endif
131147

132148
parsed = TRUE;
133149
}
@@ -146,6 +162,49 @@ object_filter (const char *obj_name)
146162
return (strncmp (filter, obj_name, strlen (filter)) == 0);
147163
}
148164

165+
static void
166+
save_trace (const char *key, gboolean is_ref)
167+
{
168+
#if defined(HAVE_LIBUNWIND) && defined(WITH_ORIGINS_TRACE)
169+
unw_context_t uc;
170+
unw_cursor_t cursor;
171+
GPtrArray *trace;
172+
BtTrie *root = NULL;
173+
gboolean found;
174+
175+
if (!display_filter (DISPLAY_FLAG_TRACEREFS))
176+
return;
177+
178+
trace = g_ptr_array_sized_new (10);
179+
180+
unw_getcontext (&uc);
181+
unw_init_local (&cursor, &uc);
182+
183+
while (unw_step (&cursor) > 0)
184+
{
185+
gchar name[129];
186+
unw_word_t off;
187+
int result;
188+
189+
result = unw_get_proc_name (&cursor, name, sizeof (name), &off);
190+
if (result < 0 && result != -UNW_ENOMEM)
191+
break;
192+
193+
g_ptr_array_insert (trace, -1, g_strdup (name));
194+
}
195+
196+
found = g_hash_table_lookup_extended (gobject_list_state.origins,
197+
(gpointer) key,
198+
NULL, (gpointer *)&root);
199+
if (!found) {
200+
root = bt_create (g_strdup (key));
201+
g_hash_table_insert (gobject_list_state.origins, (gpointer) key, root);
202+
}
203+
bt_insert (root, trace, is_ref);
204+
g_ptr_array_unref (trace);
205+
#endif
206+
}
207+
149208
static void
150209
print_trace (void)
151210
{
@@ -234,13 +293,31 @@ _sig_usr2_handler (G_GNUC_UNUSED int signal)
234293
G_UNLOCK (gobject_list);
235294
}
236295

296+
#ifdef WITH_ORIGINS_TRACE
297+
static void
298+
print_refs (G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data)
299+
{
300+
gint *no = (gpointer) user_data;
301+
BtTrie *bt_trie = value;
302+
g_print ("#%d\n", ++*no);
303+
bt_print_tree (bt_trie, 0);
304+
}
305+
#endif
306+
237307
static void
238308
print_still_alive (void)
239309
{
240310
g_print ("\nStill Alive:\n");
241311

242312
G_LOCK (gobject_list);
243313
_dump_object_list (gobject_list_state.objects);
314+
#ifdef WITH_ORIGINS_TRACE
315+
if (display_filter (DISPLAY_FLAG_TRACEREFS)) {
316+
guint no = 0;
317+
g_print ("\nReferences:\n");
318+
g_hash_table_foreach (gobject_list_state.origins, print_refs, (gpointer) &no);
319+
}
320+
#endif
244321
G_UNLOCK (gobject_list);
245322
}
246323

@@ -291,6 +368,10 @@ get_func (const char *func_name)
291368
gobject_list_state.objects = g_hash_table_new (NULL, NULL);
292369
gobject_list_state.added = g_hash_table_new (NULL, NULL);
293370
gobject_list_state.removed = g_hash_table_new_full (NULL, NULL, NULL, g_free);
371+
#ifdef WITH_ORIGINS_TRACE
372+
gobject_list_state.origins =
373+
g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) bt_free);
374+
#endif
294375

295376
/* Set up exit handler */
296377
atexit (_exiting);
@@ -338,6 +419,9 @@ _object_finalized (G_GNUC_UNUSED gpointer data,
338419

339420
g_hash_table_remove (gobject_list_state.objects, obj);
340421
g_hash_table_remove (gobject_list_state.added, obj);
422+
#ifdef WITH_ORIGINS_TRACE
423+
g_hash_table_remove (gobject_list_state.origins, G_OBJECT_TYPE_NAME (obj));
424+
#endif
341425

342426
G_UNLOCK (gobject_list);
343427
}
@@ -371,6 +455,7 @@ g_object_new (GType type,
371455

372456
g_print (" ++ Created object %p, %s\n", obj, obj_name);
373457
print_trace();
458+
save_trace (obj_name, TRUE);
374459

375460
g_mutex_unlock(&output_mutex);
376461
}
@@ -424,6 +509,7 @@ g_object_ref (gpointer object)
424509
g_print (" + Reffed object %p, %s; ref_count: %d -> %d\n",
425510
obj, obj_name, ref_count, obj->ref_count);
426511
print_trace();
512+
save_trace (obj_name, TRUE);
427513

428514
g_mutex_unlock(&output_mutex);
429515
}
@@ -449,6 +535,7 @@ g_object_unref (gpointer object)
449535
g_print (" - Unreffed object %p, %s; ref_count: %d -> %d\n",
450536
obj, obj_name, obj->ref_count, obj->ref_count - 1);
451537
print_trace();
538+
save_trace (obj_name, FALSE);
452539

453540
g_mutex_unlock(&output_mutex);
454541
}

0 commit comments

Comments
 (0)