Skip to content

Commit b4ad639

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 41d05ab commit b4ad639

File tree

4 files changed

+242
-1
lines changed

4 files changed

+242
-1
lines changed

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=-lunwind
@@ -13,6 +14,11 @@ LIBS=`pkg-config --libs glib-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
clean:
1824
rm -f libgobject-list.so $(OBJS)

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
@@ -123,6 +135,10 @@ display_filter (DisplayFlags flags)
123135
if (display_flags & DISPLAY_FLAG_BACKTRACE)
124136
g_print ("Warning: backtrace is not available, it needs libunwind\n");
125137
#endif
138+
#ifndef WITH_ORIGINS_TRACE
139+
if (display_flags & DISPLAY_FLAG_TRACEREFS)
140+
g_print ("Warning: tracerefs is not available, it needs libunwind\n");
141+
#endif
126142

127143
parsed = TRUE;
128144
}
@@ -141,6 +157,49 @@ object_filter (const char *obj_name)
141157
return (strncmp (filter, obj_name, strlen (filter)) == 0);
142158
}
143159

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

291+
#ifdef WITH_ORIGINS_TRACE
292+
static void
293+
print_refs (G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data)
294+
{
295+
gint *no = (gpointer) user_data;
296+
BtTrie *bt_trie = value;
297+
g_print ("#%d\n", ++*no);
298+
bt_print_tree (bt_trie, 0);
299+
}
300+
#endif
301+
232302
static void
233303
print_still_alive (void)
234304
{
235305
g_print ("\nStill Alive:\n");
236306

237307
G_LOCK (gobject_list);
238308
_dump_object_list (gobject_list_state.objects);
309+
#ifdef WITH_ORIGINS_TRACE
310+
if (display_filter (DISPLAY_FLAG_TRACEREFS)) {
311+
guint no = 0;
312+
g_print ("\nReferences:\n");
313+
g_hash_table_foreach (gobject_list_state.origins, print_refs, (gpointer) &no);
314+
}
315+
#endif
239316
G_UNLOCK (gobject_list);
240317
}
241318

@@ -286,6 +363,10 @@ get_func (const char *func_name)
286363
gobject_list_state.objects = g_hash_table_new (NULL, NULL);
287364
gobject_list_state.added = g_hash_table_new (NULL, NULL);
288365
gobject_list_state.removed = g_hash_table_new_full (NULL, NULL, NULL, g_free);
366+
#ifdef WITH_ORIGINS_TRACE
367+
gobject_list_state.origins =
368+
g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) bt_free);
369+
#endif
289370

290371
/* Set up exit handler */
291372
atexit (_exiting);
@@ -329,6 +410,9 @@ _object_finalized (G_GNUC_UNUSED gpointer data,
329410

330411
g_hash_table_remove (gobject_list_state.objects, obj);
331412
g_hash_table_remove (gobject_list_state.added, obj);
413+
#ifdef WITH_ORIGINS_TRACE
414+
g_hash_table_remove (gobject_list_state.origins, obj);
415+
#endif
332416

333417
G_UNLOCK (gobject_list);
334418
}
@@ -360,6 +444,7 @@ g_object_new (GType type,
360444
{
361445
g_print (" ++ Created object %p, %s\n", obj, obj_name);
362446
print_trace();
447+
save_trace (obj_name, TRUE);
363448
}
364449

365450
/* FIXME: For thread safety, GWeakRef should be used here, except it
@@ -409,6 +494,7 @@ g_object_ref (gpointer object)
409494
g_print (" + Reffed object %p, %s; ref_count: %d -> %d\n",
410495
obj, obj_name, ref_count, obj->ref_count);
411496
print_trace();
497+
save_trace (obj_name, TRUE);
412498
}
413499

414500
return ret;
@@ -430,6 +516,7 @@ g_object_unref (gpointer object)
430516
g_print (" - Unreffed object %p, %s; ref_count: %d -> %d\n",
431517
obj, obj_name, obj->ref_count, obj->ref_count - 1);
432518
print_trace();
519+
save_trace (obj_name, FALSE);
433520
}
434521

435522
real_g_object_unref (object);

0 commit comments

Comments
 (0)