diff --git a/validate/config.h.meson b/validate/config.h.meson index 0dd7a600b8..26c3d9a8fd 100644 --- a/validate/config.h.meson +++ b/validate/config.h.meson @@ -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 diff --git a/validate/configure.ac b/validate/configure.ac index cab877d743..8851b52ec5 100644 --- a/validate/configure.ac +++ b/validate/configure.ac @@ -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) diff --git a/validate/gst/validate/Makefile.am b/validate/gst/validate/Makefile.am index 389d532752..becbc05a85 100644 --- a/validate/gst/validate/Makefile.am +++ b/validate/gst/validate/Makefile.am @@ -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 = diff --git a/validate/gst/validate/gst-validate-internal.h b/validate/gst/validate/gst-validate-internal.h index b846d50fce..ffae7ff7fa 100644 --- a/validate/gst/validate/gst-validate-internal.h +++ b/validate/gst/validate/gst-validate-internal.h @@ -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 diff --git a/validate/gst/validate/gst-validate-report.c b/validate/gst/validate/gst-validate-report.c index 47545b491a..5186b226f6 100644 --- a/validate/gst/validate/gst-validate-report.c +++ b/validate/gst/validate/gst-validate-report.c @@ -25,6 +25,14 @@ # 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 +#endif /* HAVE_UNWIND */ + + #include /* fprintf */ #include #include @@ -50,6 +58,136 @@ GOutputStream *server_ostream = NULL; GType _gst_validate_report_type = 0; +#ifdef HAVE_UNWIND +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_DW +#include +#include +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 +#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); diff --git a/validate/gst/validate/gst-validate-report.h b/validate/gst/validate/gst-validate-report.h index b7713f675a..7c18835cce 100644 --- a/validate/gst/validate/gst-validate-report.h +++ b/validate/gst/validate/gst-validate-report.h @@ -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, diff --git a/validate/gst/validate/gst-validate-runner.c b/validate/gst/validate/gst-validate-runner.c index 06d63c149d..f99a674008 100644 --- a/validate/gst/validate/gst-validate-runner.c +++ b/validate/gst/validate/gst-validate-runner.c @@ -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) diff --git a/validate/gst/validate/meson.build b/validate/gst/validate/meson.build index 4f568d7460..2d7ab19dee 100644 --- a/validate/gst/validate/meson.build +++ b/validate/gst/validate/meson.build @@ -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, diff --git a/validate/meson.build b/validate/meson.build index 504ef98a13..9093a2b214 100644 --- a/validate/meson.build +++ b/validate/meson.build @@ -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)