leaks tracer: add creation stack trace support

This allow us to provide the trace of leaked objects making it easier
to debug.

https://bugzilla.gnome.org/show_bug.cgi?id=767862
This commit is contained in:
Guillaume Desmottes 2016-06-22 16:25:16 +02:00 committed by Sebastian Dröge
parent 3bb5c1e73a
commit 17c37efa83
5 changed files with 144 additions and 10 deletions

View file

@ -804,6 +804,20 @@ if test "x$BUILD_EXAMPLES" = "xyes"; then
fi
AM_CONDITIONAL(HAVE_GTK, test "x$HAVE_GTK" = "xyes")
dnl libunwind is optionally used by the leaks tracer
PKG_CHECK_MODULES(UNWIND, libunwind, HAVE_UNWIND=yes, HAVE_UNWIND=no)
dnl Check for backtrace() from libc
AC_CHECK_FUNC(backtrace, [
AC_CHECK_HEADERS([execinfo.h], [
AC_DEFINE(HAVE_BACKTRACE,1,[Have backtrace])
], [], [])
])
if test "x$HAVE_UNWIND" = "xyes"; then
AC_DEFINE(HAVE_UNWIND, 1, [libunwind available])
fi
dnl building of unit test libraries
AC_ARG_ENABLE(check,
AS_HELP_STRING([--disable-check],[disable building unit test libraries]),
@ -1131,6 +1145,7 @@ Configuration
Static plugins : ${enable_static_plugins}
Unit testing support : ${BUILD_CHECK}
PTP clock support : ${HAVE_PTP}
libunwind support : ${HAVE_UNWIND}
Debug : ${USE_DEBUG}
Profiling : ${USE_PROFILING}

View file

@ -268,6 +268,9 @@ leaks
- log alive objects when receiving the SIGUSR1 signal.
- create a checkpoint and print a list of objects created and destroyed since
the previous checkpoint.
- If the GST_LEAKS_TRACER_STACK_TRACE env variable is defined log the creation
stack trace of leaked objects. This may significantly increase memory
consumption.
User interfaces
===============

View file

@ -28,7 +28,8 @@ libgstcoretracers_la_CFLAGS = $(GST_OBJ_CFLAGS) \
-DGST_USE_UNSTABLE_API
libgstcoretracers_la_LIBADD = \
$(GST_PRINTF_LA) \
$(GST_OBJ_LIBS)
$(GST_OBJ_LIBS) \
$(UNWIND_LIBS)
libgstcoretracers_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
libgstcoretracers_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)

View file

@ -32,6 +32,17 @@
# include "config.h"
#endif
#ifdef HAVE_UNWIND
/* No need for remote debugging so turn on the 'local only' optimizations in
* libunwind */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#endif /* HAVE_UNWIND */
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif /* HAVE_BACKTRACE */
#include "gstleaks.h"
#ifdef HAVE_POSIX_SIGNALS
@ -166,10 +177,84 @@ mini_object_weak_cb (gpointer data, GstMiniObject * object)
handle_object_destroyed (self, object);
}
#ifdef HAVE_UNWIND
#define BT_NAME_SIZE 256
static gchar *
generate_unwind_trace (void)
{
unw_context_t ctx;
unw_cursor_t cursor;
GString *trace;
trace = g_string_new (NULL);
unw_getcontext (&ctx);
unw_init_local (&cursor, &ctx);
while (unw_step (&cursor) > 0) {
char name[BT_NAME_SIZE];
unw_word_t offp;
int ret;
ret = unw_get_proc_name (&cursor, name, BT_NAME_SIZE, &offp);
/* -UNW_ENOMEM is returned if name has been truncated */
if (ret != 0 && ret != -UNW_ENOMEM)
break;
g_string_append_printf (trace, "%s\n", name);
}
return g_string_free (trace, FALSE);
}
#endif /* HAVE_UNWIND */
#ifdef HAVE_BACKTRACE
#define BT_BUF_SIZE 100
static gchar *
generate_backtrace_trace (void)
{
int j, nptrs;
void *buffer[BT_BUF_SIZE];
char **strings;
GString *trace;
trace = g_string_new (NULL);
nptrs = backtrace (buffer, BT_BUF_SIZE);
strings = backtrace_symbols (buffer, nptrs);
if (!strings)
return NULL;
for (j = 0; j < nptrs; j++)
g_string_append_printf (trace, "%s\n", strings[j]);
return g_string_free (trace, FALSE);
}
#endif /* HAVE_BACKTRACE */
static gchar *
generate_trace (void)
{
gchar *trace = NULL;
#ifdef HAVE_UNWIND
trace = generate_unwind_trace ();
if (trace)
return trace;
#endif /* HAVE_UNWIND */
#ifdef HAVE_BACKTRACE
trace = generate_backtrace_trace ();
#endif /* HAVE_BACKTRACE */
return trace;
}
static void
handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
gboolean gobject)
{
gchar *trace = NULL;
if (!should_handle_object_type (self, type))
return;
@ -180,7 +265,12 @@ handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
mini_object_weak_cb, self);
GST_OBJECT_LOCK (self);
g_hash_table_add (self->objects, object);
if (self->log_stack_trace) {
trace = generate_trace ();
}
g_hash_table_insert (self->objects, object, trace);
#ifdef HAVE_POSIX_SIGNALS
if (self->added)
g_hash_table_add (self->added, object_log_new (object));
@ -213,7 +303,20 @@ object_created_cb (GstTracer * tracer, GstClockTime ts, GstObject * object)
static void
gst_leaks_tracer_init (GstLeaksTracer * self)
{
self->objects = g_hash_table_new (NULL, NULL);
self->objects = g_hash_table_new_full (NULL, NULL, NULL, g_free);
if (g_getenv ("GST_LEAKS_TRACER_STACK_TRACE")) {
gchar *trace;
/* Test if we can retrieve backtrace */
trace = generate_trace ();
if (trace) {
self->log_stack_trace = TRUE;
g_free (trace);
} else {
g_warning ("Can't retrieve backtrace on this system");
}
}
g_queue_push_tail (&instances, self);
}
@ -244,10 +347,13 @@ typedef struct
const gchar *type_name;
guint ref_count;
gchar *desc;
const gchar *trace;
} Leak;
/* The content of the returned Leak struct is valid until the self->objects
* hash table has been modified. */
static Leak *
leak_new (gpointer obj, GType type, guint ref_count)
leak_new (gpointer obj, GType type, guint ref_count, const gchar * trace)
{
Leak *leak = g_slice_new (Leak);
@ -255,6 +361,7 @@ leak_new (gpointer obj, GType type, guint ref_count)
leak->type_name = g_type_name (type);
leak->ref_count = ref_count;
leak->desc = gst_info_strdup_printf ("%" GST_PTR_FORMAT, obj);
leak->trace = trace;
return leak;
}
@ -279,10 +386,10 @@ create_leaks_list (GstLeaksTracer * self)
{
GList *l = NULL;
GHashTableIter iter;
gpointer obj;
gpointer obj, trace;
g_hash_table_iter_init (&iter, self->objects);
while (g_hash_table_iter_next (&iter, &obj, NULL)) {
while (g_hash_table_iter_next (&iter, &obj, &trace)) {
GType type;
guint ref_count;
@ -300,7 +407,7 @@ create_leaks_list (GstLeaksTracer * self)
ref_count = ((GstMiniObject *) obj)->refcount;
}
l = g_list_prepend (l, leak_new (obj, type, ref_count));
l = g_list_prepend (l, leak_new (obj, type, ref_count, trace));
}
/* Sort leaks by type name so they are grouped together making the output
@ -324,7 +431,7 @@ log_leaked (GstLeaksTracer * self)
Leak *leak = l->data;
gst_tracer_record_log (tr_alive, leak->type_name, leak->obj, leak->desc,
leak->ref_count);
leak->ref_count, leak->trace ? leak->trace : "");
}
g_list_free_full (leaks, (GDestroyNotify) leak_free);
@ -389,6 +496,11 @@ gst_leaks_tracer_finalize (GObject * object)
"type", G_TYPE_GTYPE, G_TYPE_UINT, \
"related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
NULL)
#define RECORD_FIELD_TRACE \
"trace", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
"type", G_TYPE_GTYPE, G_TYPE_STRING, \
"related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
NULL)
#ifdef HAVE_POSIX_SIGNALS
static void
@ -488,7 +600,7 @@ gst_leaks_tracer_class_init (GstLeaksTracerClass * klass)
tr_alive = gst_tracer_record_new ("object-alive.class",
RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, RECORD_FIELD_DESC,
RECORD_FIELD_REF_COUNT, NULL);
RECORD_FIELD_REF_COUNT, RECORD_FIELD_TRACE, NULL);
GST_OBJECT_FLAG_SET (tr_alive, GST_OBJECT_FLAG_MAY_BE_LEAKED);
if (g_getenv ("GST_LEAKS_TRACER_SIG")) {

View file

@ -51,7 +51,8 @@ struct _GstLeaksTracer {
GstTracer parent;
/*< private >*/
/* Set of objects currently alive. Protected by object lock */
/* gpointer (object currently alive) -> gchar * (its creation trace, or
* NULL). Protected by object lock */
GHashTable *objects;
/* array of GType used as filtering */
GArray *filter;
@ -59,6 +60,8 @@ struct _GstLeaksTracer {
GHashTable *added;
/* Set of owned ObjectLog. Protected by object lock */
GHashTable *removed;
gboolean log_stack_trace;
};
struct _GstLeaksTracerClass {