validate: Add backtraces in the reports
authorThibault Saunier <thibault.saunier@osg.samsung.com>
Thu, 8 Sep 2016 15:53:30 +0000 (12:53 -0300)
committerThibault Saunier <thibault.saunier@osg.samsung.com>
Tue, 20 Sep 2016 11:49:04 +0000 (08:49 -0300)
Printing them when the reporting all the details only

validate/config.h.meson
validate/configure.ac
validate/gst/validate/Makefile.am
validate/gst/validate/gst-validate-internal.h
validate/gst/validate/gst-validate-report.c
validate/gst/validate/gst-validate-report.h
validate/gst/validate/gst-validate-runner.c
validate/gst/validate/meson.build
validate/meson.build

index 0dd7a60..26c3d9a 100644 (file)
@@ -9,8 +9,12 @@
 
 /* directory where plugins are located */
 #mesondefine VALIDATEPLUGINDIR
+#mesondefine HAVE_UNWIND
+#mesondefine HAVE_BACKTRACE
+#mesondefine HAVE_DW
 #mesondefine GST_LICENSE
 #mesondefine VERSION
 #mesondefine PACKAGE
+#mesondefine PACKAGE_VERSION
 #mesondefine GST_PACKAGE_NAME
 #mesondefine GST_PACKAGE_ORIGIN
index cab877d..8851b52 100644 (file)
@@ -205,6 +205,26 @@ PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0)
 AC_SUBST(JSON_GLIB_LIBS)
 AC_SUBST(JSON_GLIB_CFLAGS)
 
+dnl libunwind is optionally used to generate backtraces
+PKG_CHECK_MODULES(UNWIND, libunwind, HAVE_UNWIND=yes, HAVE_UNWIND=no)
+if test "x$HAVE_UNWIND" = "xyes"; then
+  AC_DEFINE(HAVE_UNWIND, 1, [libunwind available])
+fi
+
+dnl libdw is optionally used to add source lines and numbers to backtraces
+PKG_CHECK_MODULES(DW, libdw, HAVE_DW=yes, HAVE_DW=no)
+if test "x$HAVE_DW" = "xyes"; then
+  AC_DEFINE(HAVE_DW, 1, [libdw available])
+fi
+
+dnl Check for backtrace() from libc
+AC_CHECK_FUNC(backtrace, [
+  AC_CHECK_HEADERS([execinfo.h], [
+    AC_DEFINE(HAVE_BACKTRACE,1,[Have backtrace])
+  ], [], [])
+])
+
+
 dnl checks for gstreamer
 
 AG_GST_CHECK_GST_CHECK($GST_API_VERSION, [$GST_REQ], no)
index 389d532..becbc05 100644 (file)
@@ -57,7 +57,7 @@ libgstvalidate_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLA
 libgstvalidate_@GST_API_VERSION@_la_LIBADD = \
        $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \
        $(GST_ALL_LIBS) $(GIO_LIBS) $(GST_PBUTILS_LIBS) \
-       $(JSON_GLIB_LIBS) $(GLIB_LIBS) $(LIBM)
+       $(JSON_GLIB_LIBS) $(GLIB_LIBS) $(LIBM) $(UNWIND_LIBS)
 
 libgstvalidate_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/validate
 
@@ -73,7 +73,7 @@ libgstvalidateplugin_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL
 libgstvalidateplugin_@GST_API_VERSION@_la_LIBADD = \
        $(JSON_GLIB_CFLAGS) $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \
        $(GST_ALL_LIBS) $(GIO_LIBS) $(GST_PBUTILS_LIBS) \
-       $(JSON_GLIB_LIBS) $(GLIB_LIBS) $(LIBM)
+       $(JSON_GLIB_LIBS) $(GLIB_LIBS) $(LIBM) $(UNWIND_LIBS)
 
 CLEANFILES =
 
index b846d50..ffae7ff 100644 (file)
@@ -47,9 +47,11 @@ G_GNUC_INTERNAL gboolean _action_check_and_set_printed (GstValidateAction *actio
 G_GNUC_INTERNAL gboolean gst_validate_action_is_subaction (GstValidateAction *action);
 G_GNUC_INTERNAL void _priv_validate_override_registry_deinit (void);
 
+G_GNUC_INTERNAL GstValidateReportingDetails gst_validate_runner_get_default_reporting_details (GstValidateRunner *runner);
+
 G_GNUC_INTERNAL GstValidateMonitor * gst_validate_get_monitor (GObject *object);
 G_GNUC_INTERNAL void gst_validate_init_runner (void);
 G_GNUC_INTERNAL void gst_validate_deinit_runner (void);
 G_GNUC_INTERNAL void gst_validate_report_deinit (void);
-gboolean gst_validate_send (JsonNode * root);
+G_GNUC_INTERNAL gboolean gst_validate_send (JsonNode * root);
 #endif
index 47545b4..5186b22 100644 (file)
 #  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 */
+
+
 #include <stdio.h>              /* fprintf */
 #include <glib/gstdio.h>
 #include <errno.h>
@@ -50,6 +58,136 @@ GOutputStream *server_ostream = NULL;
 
 GType _gst_validate_report_type = 0;
 
+#ifdef HAVE_UNWIND
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#ifdef HAVE_DW
+#include <elfutils/libdwfl.h>
+#include <libunwind.h>
+static void
+append_debug_info (GString * trace, const void *ip)
+{
+
+  char *debuginfo_path = NULL;
+
+  Dwfl_Callbacks callbacks = {
+    .find_elf = dwfl_linux_proc_find_elf,
+    .find_debuginfo = dwfl_standard_find_debuginfo,
+    .debuginfo_path = &debuginfo_path,
+  };
+
+  Dwfl *dwfl = dwfl_begin (&callbacks);
+  assert (dwfl != NULL);
+
+  assert (dwfl_linux_proc_report (dwfl, getpid ()) == 0);
+  assert (dwfl_report_end (dwfl, NULL, NULL) == 0);
+
+  Dwarf_Addr addr = (uintptr_t) ip;
+
+  Dwfl_Module *module = dwfl_addrmodule (dwfl, addr);
+
+  const char *function_name = dwfl_module_addrname (module, addr);
+
+  g_string_append_printf (trace, "%s(", function_name ? function_name : "??");
+
+  Dwfl_Line *line = dwfl_getsrc (dwfl, addr);
+  if (line != NULL) {
+    int nline;
+    Dwarf_Addr addr;
+    const char *filename = dwfl_lineinfo (line, &addr,
+        &nline, NULL, NULL, NULL);
+    g_string_append_printf (trace, "%s:%d", strrchr (filename, '/') + 1, nline);
+  } else {
+    const gchar *eflfile = NULL;
+
+    dwfl_module_info (module, NULL, NULL, NULL, NULL, NULL, &eflfile, NULL);
+    g_string_append_printf (trace, "%s:%p", eflfile ? eflfile : "??", ip);
+  }
+}
+#endif
+
+static gchar *
+generate_unwind_trace ()
+{
+  unw_context_t uc;
+  GString *trace = g_string_new (NULL);
+
+  unw_getcontext (&uc);
+  unw_cursor_t cursor;
+  unw_init_local (&cursor, &uc);
+
+  while (unw_step (&cursor) > 0) {
+#ifdef HAVE_DW
+    unw_word_t ip;
+
+    unw_get_reg (&cursor, UNW_REG_IP, &ip);
+    append_debug_info (trace, (void *) (ip - 4));
+    g_string_append (trace, ")\n");
+#else
+    char name[32];
+
+    unw_word_t offset;
+    unw_get_proc_name (&cursor, name, sizeof (name), &offset);
+    g_string_append_printf (trace, "%s (0x%lx)\n", name, offset);
+#endif
+  }
+
+  return g_string_free (trace, FALSE);
+}
+
+#endif /* HAVE_UNWIND */
+
+#ifdef HAVE_BACKTRACE
+#include <execinfo.h>
+#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 JsonNode *
 gst_validate_report_serialize (GstValidateReport * report)
 {
@@ -576,6 +714,8 @@ gst_validate_report_level_get_name (GstValidateReportLevel level)
     default:
       return "unknown";
   }
+
+  return NULL;
 }
 
 GstValidateReportLevel
@@ -658,6 +798,7 @@ gst_validate_report_new (GstValidateIssue * issue,
     GstValidateReporter * reporter, const gchar * message)
 {
   GstValidateReport *report = g_slice_new0 (GstValidateReport);
+  GstValidateReportingDetails reporter_level;
 
   gst_mini_object_init (((GstMiniObject *) report), 0,
       _gst_validate_report_type, NULL, NULL,
@@ -679,6 +820,15 @@ gst_validate_report_new (GstValidateIssue * issue,
   report->level = issue->default_level;
   report->reporting_level = GST_VALIDATE_SHOW_UNKNOWN;
 
+  reporter_level = gst_validate_reporter_get_reporting_level (reporter);
+  if (reporter_level == GST_VALIDATE_SHOW_ALL ||
+      (reporter_level == GST_VALIDATE_SHOW_UNKNOWN
+          &&
+          gst_validate_runner_get_default_reporting_details
+          (gst_validate_reporter_get_runner (reporter)) ==
+          GST_VALIDATE_SHOW_ALL))
+    report->trace = generate_trace ();
+
   return report;
 }
 
@@ -1012,6 +1162,20 @@ gst_validate_report_print_details (GstValidateReport * report)
   }
 }
 
+static void
+gst_validate_report_print_trace (GstValidateReport * report)
+{
+  if (report->trace) {
+    gint i;
+    gchar **lines = g_strsplit (report->trace, "\n", -1);
+
+    gst_validate_printf (NULL, "%*s backtrace :\n", 12, "");
+    for (i = 0; lines[i]; i++)
+      gst_validate_printf (NULL, "%*s%s\n", 15, "", lines[i]);
+  }
+}
+
+
 void
 gst_validate_report_print_description (GstValidateReport * report)
 {
@@ -1028,6 +1192,7 @@ gst_validate_report_printf (GstValidateReport * report)
   gst_validate_report_print_level (report);
   gst_validate_report_print_detected_on (report);
   gst_validate_report_print_details (report);
+  gst_validate_report_print_trace (report);
 
   for (tmp = report->repeated_reports; tmp; tmp = tmp->next) {
     gst_validate_report_print_details (report);
index b7713f6..7c18835 100644 (file)
@@ -182,8 +182,9 @@ struct _GstValidateReport {
 
   GstValidateReportingDetails reporting_level;
   gchar *reporter_name;
+  gchar *trace;
 
-  gpointer _gst_reserved[GST_PADDING];
+  gpointer _gst_reserved[GST_PADDING - 1];
 };
 
 void gst_validate_report_add_message (GstValidateReport *report,
index 06d63c1..f99a674 100644 (file)
@@ -540,6 +540,7 @@ gst_validate_runner_add_report (GstValidateRunner * runner,
 
   gst_validate_send (json_boxed_serialize (GST_MINI_OBJECT_TYPE (report),
           report));
+
   /* Let's use our own reporting strategy */
   if (reporter_level == GST_VALIDATE_SHOW_UNKNOWN) {
     gst_validate_report_set_reporting_level (report,
@@ -751,6 +752,12 @@ gst_validate_deinit_runner (void)
   g_clear_object (&first_runner);
 }
 
+GstValidateReportingDetails
+gst_validate_runner_get_default_reporting_details (GstValidateRunner * runner)
+{
+  return runner->priv->default_level;
+}
+
 #ifdef __GST_VALIDATE_PLUGIN
 static gboolean
 plugin_init (GstPlugin * plugin)
index 4f568d7..2d7ab19 100644 (file)
@@ -44,18 +44,20 @@ gstvalidate = shared_library('gstvalidate-1.0',
     soversion : soversion,
     include_directories : [inc_dirs],
     install: true,
-    c_args : [gst_c_args],
+    c_args : [gst_c_args] + ['-D_GNU_SOURCE'],
     dependencies : [gst_dep, glib_dep, gio_dep, gmodule_dep,
-                    gst_pbutils_dep, mathlib, json_dep])
+                    gst_pbutils_dep, mathlib, json_dep,
+                    unwind_dep, dw_dep])
 
 gstvalidate = shared_library('gstvalidateplugin',
     sources: gstvalidate_sources,
     include_directories : [inc_dirs],
     install: true,
-    c_args : [gst_c_args] + ['-D__GST_VALIDATE_PLUGIN'],
+    c_args : [gst_c_args] + ['-D__GST_VALIDATE_PLUGIN', '-D_GNU_SOURCE'],
     install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
     dependencies : [gst_dep, glib_dep, gio_dep, gmodule_dep,
-                    gst_pbutils_dep, mathlib, json_dep])
+                    gst_pbutils_dep, mathlib, json_dep, unwind_dep,
+                    dw_dep])
 
 validate_gen_sources = []
 if build_gir
@@ -64,9 +66,9 @@ if build_gir
       # FIXME: There must be a better way to do this
       # Need to pass the include path to find gst/gst.h and gst/gstenumtypes.h (built)
       gst_validate_gir_extra_args += ['--cflags-begin',
-         '-I' + meson.current_source_dir() + '/../../',
-         '-I' + meson.current_build_dir() + '/../../',
-         '--cflags-end']
+      '-I' + meson.current_source_dir() + '/../../',
+      '-I' + meson.current_build_dir() + '/../../',
+      '--cflags-end']
     endif
     validate_gen_sources = [gnome.generate_gir(gstvalidate,
             sources : gstvalidate_sources,
index 504ef98..9093a2b 100644 (file)
@@ -2,6 +2,25 @@ inc_dirs = include_directories('.')
 
 json_dep = dependency('json-glib-1.0')
 cdata = configuration_data()
+
+unwind_dep = dependency('libunwind', required : false)
+dw_dep = dependency('libdw', required: false)
+if unwind_dep.found()
+  cdata.set('HAVE_UNWIND', 1)
+  if dw_dep.found()
+    cdata.set('HAVE_DW', 1)
+  else
+    message('Support for backtraces is partial only.')
+  endif
+else
+  if cc.has_function('backtrace')
+    cdata.set('HAVE_BACKTRACE', 1)
+  else
+      message('NO backtraces support.')
+  endif
+endif
+
+
 cdata.set('GST_LICENSE', '"LGPL"')
 cdata.set('VERSION', '"@0@"'.format(gst_version))
 cdata.set('PACKAGE', '"gst-validate"')
@@ -11,6 +30,7 @@ cdata.set('GST_API_VERSION', '"@0@"'.format(apiversion))
 cdata.set('VALIDATEPLUGINDIR', '"@0@/@1@/gstreamer-1.0/validate"'.format(get_option('prefix'),get_option('libdir')))
 cdata.set('GST_DATADIR', '"@0@/@1@"'.format(prefix, get_option('datadir')))
 cdata.set('PACKAGE_NAME', '"GStreamer Validate"')
+cdata.set('PACKAGE_VERSION', '"@0@"'.format(gst_version))
 configure_file(input : 'config.h.meson',
   output : 'config.h',
   configuration : cdata)