mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-19 23:06:49 +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
|
||||
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}
|
||||
|
|
|
@ -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
|
||||
===============
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue