mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 09:40:37 +00:00
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:
parent
3bb5c1e73a
commit
17c37efa83
5 changed files with 144 additions and 10 deletions
15
configure.ac
15
configure.ac
|
@ -804,6 +804,20 @@ if test "x$BUILD_EXAMPLES" = "xyes"; then
|
||||||
fi
|
fi
|
||||||
AM_CONDITIONAL(HAVE_GTK, test "x$HAVE_GTK" = "xyes")
|
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
|
dnl building of unit test libraries
|
||||||
AC_ARG_ENABLE(check,
|
AC_ARG_ENABLE(check,
|
||||||
AS_HELP_STRING([--disable-check],[disable building unit test libraries]),
|
AS_HELP_STRING([--disable-check],[disable building unit test libraries]),
|
||||||
|
@ -1131,6 +1145,7 @@ Configuration
|
||||||
Static plugins : ${enable_static_plugins}
|
Static plugins : ${enable_static_plugins}
|
||||||
Unit testing support : ${BUILD_CHECK}
|
Unit testing support : ${BUILD_CHECK}
|
||||||
PTP clock support : ${HAVE_PTP}
|
PTP clock support : ${HAVE_PTP}
|
||||||
|
libunwind support : ${HAVE_UNWIND}
|
||||||
|
|
||||||
Debug : ${USE_DEBUG}
|
Debug : ${USE_DEBUG}
|
||||||
Profiling : ${USE_PROFILING}
|
Profiling : ${USE_PROFILING}
|
||||||
|
|
|
@ -268,6 +268,9 @@ leaks
|
||||||
- log alive objects when receiving the SIGUSR1 signal.
|
- log alive objects when receiving the SIGUSR1 signal.
|
||||||
- create a checkpoint and print a list of objects created and destroyed since
|
- create a checkpoint and print a list of objects created and destroyed since
|
||||||
the previous checkpoint.
|
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
|
User interfaces
|
||||||
===============
|
===============
|
||||||
|
|
|
@ -28,7 +28,8 @@ libgstcoretracers_la_CFLAGS = $(GST_OBJ_CFLAGS) \
|
||||||
-DGST_USE_UNSTABLE_API
|
-DGST_USE_UNSTABLE_API
|
||||||
libgstcoretracers_la_LIBADD = \
|
libgstcoretracers_la_LIBADD = \
|
||||||
$(GST_PRINTF_LA) \
|
$(GST_PRINTF_LA) \
|
||||||
$(GST_OBJ_LIBS)
|
$(GST_OBJ_LIBS) \
|
||||||
|
$(UNWIND_LIBS)
|
||||||
libgstcoretracers_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
libgstcoretracers_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||||
libgstcoretracers_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
|
libgstcoretracers_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,17 @@
|
||||||
# include "config.h"
|
# include "config.h"
|
||||||
#endif
|
#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"
|
#include "gstleaks.h"
|
||||||
|
|
||||||
#ifdef HAVE_POSIX_SIGNALS
|
#ifdef HAVE_POSIX_SIGNALS
|
||||||
|
@ -166,10 +177,84 @@ mini_object_weak_cb (gpointer data, GstMiniObject * object)
|
||||||
handle_object_destroyed (self, 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
|
static void
|
||||||
handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
|
handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
|
||||||
gboolean gobject)
|
gboolean gobject)
|
||||||
{
|
{
|
||||||
|
gchar *trace = NULL;
|
||||||
|
|
||||||
if (!should_handle_object_type (self, type))
|
if (!should_handle_object_type (self, type))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -180,7 +265,12 @@ handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
|
||||||
mini_object_weak_cb, self);
|
mini_object_weak_cb, self);
|
||||||
|
|
||||||
GST_OBJECT_LOCK (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
|
#ifdef HAVE_POSIX_SIGNALS
|
||||||
if (self->added)
|
if (self->added)
|
||||||
g_hash_table_add (self->added, object_log_new (object));
|
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
|
static void
|
||||||
gst_leaks_tracer_init (GstLeaksTracer * self)
|
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);
|
g_queue_push_tail (&instances, self);
|
||||||
}
|
}
|
||||||
|
@ -244,10 +347,13 @@ typedef struct
|
||||||
const gchar *type_name;
|
const gchar *type_name;
|
||||||
guint ref_count;
|
guint ref_count;
|
||||||
gchar *desc;
|
gchar *desc;
|
||||||
|
const gchar *trace;
|
||||||
} Leak;
|
} Leak;
|
||||||
|
|
||||||
|
/* The content of the returned Leak struct is valid until the self->objects
|
||||||
|
* hash table has been modified. */
|
||||||
static Leak *
|
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);
|
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->type_name = g_type_name (type);
|
||||||
leak->ref_count = ref_count;
|
leak->ref_count = ref_count;
|
||||||
leak->desc = gst_info_strdup_printf ("%" GST_PTR_FORMAT, obj);
|
leak->desc = gst_info_strdup_printf ("%" GST_PTR_FORMAT, obj);
|
||||||
|
leak->trace = trace;
|
||||||
|
|
||||||
return leak;
|
return leak;
|
||||||
}
|
}
|
||||||
|
@ -279,10 +386,10 @@ create_leaks_list (GstLeaksTracer * self)
|
||||||
{
|
{
|
||||||
GList *l = NULL;
|
GList *l = NULL;
|
||||||
GHashTableIter iter;
|
GHashTableIter iter;
|
||||||
gpointer obj;
|
gpointer obj, trace;
|
||||||
|
|
||||||
g_hash_table_iter_init (&iter, self->objects);
|
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;
|
GType type;
|
||||||
guint ref_count;
|
guint ref_count;
|
||||||
|
|
||||||
|
@ -300,7 +407,7 @@ create_leaks_list (GstLeaksTracer * self)
|
||||||
ref_count = ((GstMiniObject *) obj)->refcount;
|
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
|
/* 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;
|
Leak *leak = l->data;
|
||||||
|
|
||||||
gst_tracer_record_log (tr_alive, leak->type_name, leak->obj, leak->desc,
|
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);
|
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, \
|
"type", G_TYPE_GTYPE, G_TYPE_UINT, \
|
||||||
"related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
|
"related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
|
||||||
NULL)
|
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
|
#ifdef HAVE_POSIX_SIGNALS
|
||||||
static void
|
static void
|
||||||
|
@ -488,7 +600,7 @@ gst_leaks_tracer_class_init (GstLeaksTracerClass * klass)
|
||||||
|
|
||||||
tr_alive = gst_tracer_record_new ("object-alive.class",
|
tr_alive = gst_tracer_record_new ("object-alive.class",
|
||||||
RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, RECORD_FIELD_DESC,
|
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);
|
GST_OBJECT_FLAG_SET (tr_alive, GST_OBJECT_FLAG_MAY_BE_LEAKED);
|
||||||
|
|
||||||
if (g_getenv ("GST_LEAKS_TRACER_SIG")) {
|
if (g_getenv ("GST_LEAKS_TRACER_SIG")) {
|
||||||
|
|
|
@ -51,7 +51,8 @@ struct _GstLeaksTracer {
|
||||||
GstTracer parent;
|
GstTracer parent;
|
||||||
|
|
||||||
/*< private >*/
|
/*< 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;
|
GHashTable *objects;
|
||||||
/* array of GType used as filtering */
|
/* array of GType used as filtering */
|
||||||
GArray *filter;
|
GArray *filter;
|
||||||
|
@ -59,6 +60,8 @@ struct _GstLeaksTracer {
|
||||||
GHashTable *added;
|
GHashTable *added;
|
||||||
/* Set of owned ObjectLog. Protected by object lock */
|
/* Set of owned ObjectLog. Protected by object lock */
|
||||||
GHashTable *removed;
|
GHashTable *removed;
|
||||||
|
|
||||||
|
gboolean log_stack_trace;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _GstLeaksTracerClass {
|
struct _GstLeaksTracerClass {
|
||||||
|
|
Loading…
Reference in a new issue