[Ltrace-devel] [PATCH] Add support for using elfutils as unwinder.

Mark Wielaard mjw at redhat.com
Mon Jan 6 14:23:30 UTC 2014


This adds support for using elfutils as unwinder with -w. Since elfutils
0.158 elfutils contains a simple unwinder interface that matches nicely
on the ltrace backtrace support.

The code reuses the libunwind infrastructure already in ltrace where
possible (by defining HAVE_UNWINDER which is 1 if either libunwind or
elfutils is used). It also reuses the ltrace proc_add_library callback
to keep track of the ELF files mapped for the unwinder.

The current implementation just matches the output as if libunwind was
used. But elfutils can also provide some more information since it can
lookup the DWARF debuginfo (as an example it could also show the source
lines of the backtrace, but this is disabled for now).
---
 Makefile.am              |  1 +
 configure.ac             | 54 ++++++++++++++++++++++++++++++++++
 ltrace.1                 |  3 +-
 options.c                | 18 ++++++------
 options.h                |  4 +--
 output.c                 | 75 ++++++++++++++++++++++++++++++++++++++++++++++++
 proc.c                   | 62 +++++++++++++++++++++++++++++++++++++++
 proc.h                   |  9 ++++++
 testsuite/Makefile.am    |  1 +
 testsuite/lib/ltrace.exp |  4 +++
 10 files changed, 219 insertions(+), 12 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index efcf18a..ed469f4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,6 +40,7 @@ libltrace_la_LIBADD = \
 	$(liberty_LIBS) \
 	$(libsupcxx_LIBS) \
 	$(libstdcxx_LIBS) \
+	$(libdw_LIBS) \
 	$(libunwind_LIBS) \
 	sysdeps/libos.la
 
diff --git a/configure.ac b/configure.ac
index da3b4cb..2135e36 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,52 @@ dnl Check security_get_boolean_active availability.
 AC_CHECK_HEADERS(selinux/selinux.h)
 AC_CHECK_LIB(selinux, security_get_boolean_active)
 
+dnl Whether (and which) elfutils libdw.so to use for unwinding.
+AC_ARG_WITH(elfutils,
+  AS_HELP_STRING([--with-elfutils], [Use elfutils libdwfl unwinding support]),
+  [case "${withval}" in
+  (yes|no) enable_elfutils=$withval;;
+  (*) enable_elfutils=yes
+    AM_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include"
+    AM_LDFLAGS="${AM_LDFLAGS} -L${withval}/lib"
+    elfutils_LD_LIBRARY_PATH="${withval}/lib:${withval}/lib/elfutils"
+    ;;
+esac],[enable_elfutils=maybe])
+
+dnl Check whether we have the elfutils libdwfl.h header installed.
+saved_CPPFLAGS="${CPPFLAGS}"
+CPPFLAGS="${CPPFLAGS} ${AM_CPPFLAGS}"
+AC_CHECK_HEADERS([elfutils/libdwfl.h],[have_libdwfl_h=yes])
+CPPFLAGS="${saved_CPPFLAGS}"
+
+dnl And whether libdw.so provides the unwinding functions.
+saved_LDFLAGS="${LDFLAGS}"
+LDFLAGS="${LDFLAGS} ${AM_LDFLAGS}"
+AC_CHECK_LIB([dw], [dwfl_getthread_frames], [have_libdw_dwfl_frames=yes])
+  AC_SUBST(libdw_LIBS)
+  AC_DEFINE([HAVE_LIBDW], [1], [we have elfutils libdw])
+  LDFLAGS="${saved_LDFLAGS}"
+AC_MSG_CHECKING([whether to use elfutils libdwfl unwinding support])
+case "${enable_elfutils}" in
+(yes|maybe)
+  if test x$have_libdwfl_h = xyes -a x$have_libdw_dwfl_frames = xyes; then
+    enable_elfutils=yes
+  elif test $enable_elfutils = maybe; then
+    enable_elfutils=no
+  else
+    AC_MSG_RESULT([$enable_elfutils])
+    AC_MSG_ERROR([Missing elfutils/libdwfl.h or dwfl_getthread_frames not in libdw.so])	
+  fi
+  ;;
+(*) ;;
+esac
+AC_MSG_RESULT([$enable_elfutils])
+
+if test x"$enable_elfutils" = xyes; then
+  libdw_LIBS=-ldw
+  AC_SUBST(libdw_LIBS)
+  AC_DEFINE([HAVE_LIBDW], [1], [we have elfutils libdw])
+fi
 
 # HAVE_LIBUNWIND
 AC_ARG_WITH(libunwind,
@@ -193,6 +239,13 @@ if test x"$enable_libunwind" = xyes; then
   LDFLAGS="${saved_LDFLAGS}"
 fi
 
+if test x"$enable_elfutils" = xyes -a x"$enable_libunwind" = xyes; then
+  AC_MSG_ERROR([Cannot enable both --with-libunwind and --with-elfutils])
+fi
+
+if test x"$enable_elfutils" = xyes -o x"$enable_libunwind" = xyes; then
+  AC_DEFINE([HAVE_UNWINDER], [1], [we have an unwinder available])
+fi
 
 saved_CPPFLAGS="${CPPFLAGS}"
 saved_LDFLAGS="${LDFLAGS}"
@@ -340,6 +393,7 @@ AC_SUBST(AM_CPPFLAGS)
 AC_SUBST(AM_CFLAGS)
 AC_SUBST(AM_LDFLAGS)
 AC_SUBST(libelf_LD_LIBRARY_PATH)
+AC_SUBST(elfutils_LD_LIBRARY_PATH)
 AC_SUBST(libunwind_LD_LIBRARY_PATH)
 
 AC_CONFIG_FILES([
diff --git a/ltrace.1 b/ltrace.1
index 9e8577e..6e6fc24 100644
--- a/ltrace.1
+++ b/ltrace.1
@@ -195,7 +195,8 @@ This option is only useful when running as root and enables the
 correct execution of setuid and/or setgid binaries.
 .IP "\-w, \-\-where \fInr"
 Show backtrace of \fInr\fR stack frames for each traced function. This
-option enabled only if libunwind support was enabled at compile time.
+option enabled only if elfutils or libunwind support was enabled at compile
+time.
 .IP "\-x \fIfilter"
 A qualifying expression which modifies which symbol table entry points
 to trace.  The format of the filter expression is described in the
diff --git a/options.c b/options.c
index 8a2fe5d..3d5dfaa 100644
--- a/options.c
+++ b/options.c
@@ -107,9 +107,9 @@ usage(void) {
 		"  -T                  show the time spent inside each call.\n"
 		"  -u USERNAME         run command with the userid, groupid of username.\n"
 		"  -V, --version       output version information and exit.\n"
-#if defined(HAVE_LIBUNWIND)
+#if defined(HAVE_UNWINDER)
 		"  -w, --where=NR      print backtrace showing NR stack frames at most.\n"
-#endif /* defined(HAVE_LIBUNWIND) */
+#endif /* defined(HAVE_UNWINDER) */
 		"  -x FILTER           modify which static functions to trace.\n"
 		"\nReport bugs to ltrace-devel at lists.alioth.debian.org\n",
 		progname);
@@ -517,9 +517,9 @@ process_options(int argc, char **argv)
 	progname = argv[0];
 	options.output = stderr;
 	options.no_signals = 0;
-#if defined(HAVE_LIBUNWIND)
+#if defined(HAVE_UNWINDER)
 	options.bt_depth = -1;
-#endif /* defined(HAVE_LIBUNWIND) */
+#endif /* defined(HAVE_UNWINDER) */
 
 	guess_cols();
 
@@ -543,9 +543,9 @@ process_options(int argc, char **argv)
 			{"output", 1, 0, 'o'},
 			{"version", 0, 0, 'V'},
 			{"no-signals", 0, 0, 'b'},
-# if defined(HAVE_LIBUNWIND)
+# if defined(HAVE_UNWINDER)
 			{"where", 1, 0, 'w'},
-# endif /* defined(HAVE_LIBUNWIND) */
+# endif /* defined(HAVE_UNWINDER) */
 			{0, 0, 0, 0}
 		};
 #endif
@@ -554,7 +554,7 @@ process_options(int argc, char **argv)
 #ifdef USE_DEMANGLE
 			"C"
 #endif
-#if defined(HAVE_LIBUNWIND)
+#if defined(HAVE_UNWINDER)
 			"w:"
 #endif
 			"cfhiLrStTVba:A:D:e:F:l:n:o:p:s:u:x:X:";
@@ -679,11 +679,11 @@ process_options(int argc, char **argv)
 			       "There is NO WARRANTY, to the extent permitted by law.\n");
 			exit(0);
 			break;
-#if defined(HAVE_LIBUNWIND)
+#if defined(HAVE_UNWINDER)
 		case 'w':
 			options.bt_depth = parse_int(optarg, 'w', 1, 0);
 			break;
-#endif /* defined(HAVE_LIBUNWIND) */
+#endif /* defined(HAVE_UNWINDER) */
 
 		case 'x':
 			parse_filter_chain(optarg, &options.static_filter);
diff --git a/options.h b/options.h
index d0df3a7..35c3d89 100644
--- a/options.h
+++ b/options.h
@@ -44,9 +44,9 @@ struct options_t {
 	size_t strlen;     /* default maximum # of bytes printed in strings */
 	int follow;     /* trace child processes */
 	int no_signals; /* don't print signals */
-#if defined(HAVE_LIBUNWIND)
+#if defined(HAVE_UNWINDER)
 	int bt_depth;	 /* how may levels of stack frames to show */
-#endif /* defined(HAVE_LIBUNWIND) */
+#endif /* defined(HAVE_UNWINDER) */
 	struct filter *plt_filter;
 	struct filter *static_filter;
 
diff --git a/output.c b/output.c
index f804cd0..0cbd651 100644
--- a/output.c
+++ b/output.c
@@ -33,6 +33,7 @@
 #include <unistd.h>
 #include <errno.h>
 #include <assert.h>
+#include <inttypes.h>
 
 #include "output.h"
 #include "demangle.h"
@@ -567,6 +568,69 @@ output_left(enum tof type, struct process *proc,
 	stel->out.need_delim = need_delim;
 }
 
+#if defined(HAVE_LIBDW)
+static int
+frame_callback (Dwfl_Frame *state, void *arg)
+{
+	Dwarf_Addr pc;
+	bool isactivation;
+
+	int *frames = (int *) arg;
+
+	if (! dwfl_frame_pc (state, &pc, &isactivation))
+		return -1;
+
+	if (! isactivation)
+		pc--;
+
+	Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state));
+	Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc);
+	const char *modname = NULL;
+	const char *symname = NULL;
+	GElf_Off off = 0;
+	if (mod) {
+		GElf_Sym sym;
+		modname = dwfl_module_info(mod, NULL, NULL, NULL, NULL,
+					   NULL, NULL, NULL);
+		symname = dwfl_module_addrinfo(mod, pc, &off, &sym,
+					       NULL, NULL, NULL);
+	}
+
+	/* This mimics the output produced by libunwind below. */
+	fprintf(options.output, " > %s(%s+0x%" PRIx64 ") [%" PRIx64 "]\n",
+		modname, symname, off, pc);
+
+	/* If we wanted fancy we could try to extract the source line too...
+	if (mod) {
+		Dwfl_Line *l = dwfl_module_getsrc(mod, pc);
+		if (l) {
+			int line, col;
+			const char* src;
+			line = col = -1;
+			src = dwfl_lineinfo (l, NULL, &line, &col, NULL, NULL);
+			if (src != NULL) {
+				fprintf (options.output, "\n\t%s", src);
+				if (line > 0) {
+					fprintf (options.output, ":%d", line);
+					if (col > 0)
+			                        fprintf (options.output,
+							":%d", col);
+				}
+				fprintf (options.output, "\n");
+			}
+
+		}
+	}
+	... not fancy for now. */
+
+	/* Max number of frames to print reached? */
+	if ((*frames)-- == 0)
+		return DWARF_CB_ABORT;
+
+	return DWARF_CB_OK;
+}
+#endif /* defined(HAVE_LIBDW) */
+
 void
 output_right(enum tof type, struct process *proc, struct library_symbol *libsym,
 	     struct timedelta *spent)
@@ -702,6 +766,17 @@ output_right(enum tof type, struct process *proc, struct library_symbol *libsym,
 	}
 #endif /* defined(HAVE_LIBUNWIND) */
 
+#if defined(HAVE_LIBDW)
+	if (options.bt_depth > 0 && proc->leader->dwfl != NULL) {
+		int frames = options.bt_depth;
+		if (dwfl_getthread_frames(proc->leader->dwfl, proc->pid,
+					  frame_callback, &frames) < 0)
+			fprintf(stderr, "dwfl_getthread_frames tid %d: %s\n",
+				proc->pid, dwfl_errmsg(-1));
+		fprintf(options.output, "\n");
+	  }
+#endif /* defined(HAVE_LIBDW) */
+
 	current_proc = NULL;
 	current_column = 0;
 }
diff --git a/proc.c b/proc.c
index 97bcb60..e89da6e 100644
--- a/proc.c
+++ b/proc.c
@@ -106,6 +106,11 @@ destroy_unwind(struct process *proc)
 	if (proc->unwind_as != NULL)
 		unw_destroy_addr_space(proc->unwind_as);
 #endif /* defined(HAVE_LIBUNWIND) */
+
+#if defined(HAVE_LIBDW)
+	if (proc->dwfl != NULL)
+		dwfl_end(proc->dwfl);
+#endif /* defined(HAVE_LIBDW) */
 }
 
 static int
@@ -167,6 +172,10 @@ process_bare_init(struct process *proc, const char *filename,
 	}
 #endif /* defined(HAVE_LIBUNWIND) */
 
+#if defined(HAVE_LIBDW)
+	proc->dwfl = NULL; /* Initialize for leader only on first library. */
+#endif /* defined(HAVE_LIBDW) */
+
 	return 0;
 }
 
@@ -880,6 +889,59 @@ proc_add_library(struct process *proc, struct library *lib)
 	debug(DEBUG_PROCESS, "added library %s@%p (%s) to %d",
 	      lib->soname, lib->base, lib->pathname, proc->pid);
 
+#if defined(HAVE_LIBDW)
+	if (options.bt_depth > 0) {
+		/* Setup module tracking for libdwfl unwinding.  */
+		struct process *leader = proc->leader;
+		Dwfl *dwfl = leader->dwfl;
+		if (dwfl == NULL) {
+			static const Dwfl_Callbacks proc_callbacks = {
+				.find_elf = dwfl_linux_proc_find_elf,
+				.find_debuginfo = dwfl_standard_find_debuginfo
+			};
+			dwfl = dwfl_begin(&proc_callbacks);
+			if (dwfl == NULL)
+				fprintf(stderr,
+					"Couldn't initialize libdwfl unwinding "
+					"for process %d: %s\n", leader->pid,
+					dwfl_errmsg (-1));
+		}
+
+		if (dwfl != NULL) {
+			dwfl_report_begin_add (dwfl);
+			if (dwfl_report_elf (dwfl, lib->soname,
+					     lib->pathname, -1,
+					     (GElf_Addr) lib->base,
+					     false) == NULL)
+				fprintf(stderr,
+					"dwfl_report_elf %s@%p (%s) %d: %s\n",
+					lib->soname, lib->base, lib->pathname,
+					proc->pid, dwfl_errmsg (-1));
+			dwfl_report_end (dwfl, NULL, NULL);
+
+			if (leader->dwfl == NULL) {
+				int r;
+				r = dwfl_linux_proc_attach (dwfl, leader->pid,
+							    true);
+				if (r == 0)
+					leader->dwfl = dwfl;
+				else {
+					const char *msg;
+					dwfl_end (dwfl);
+					if (r < 0)
+						msg = dwfl_errmsg (-1);
+					else
+						msg = strerror (r);
+					fprintf(stderr, "Couldn't initialize "
+						"libdwfl unwinding for "
+						"process %d: %s\n",
+						leader->pid, msg);
+				}
+			}
+		}
+	}
+#endif /* defined(HAVE_LIBDW) */
+
 	/* Insert breakpoints for all active (non-latent) symbols.  */
 	struct library_symbol *libsym = NULL;
 	while ((libsym = library_each_symbol(lib, libsym,
diff --git a/proc.h b/proc.h
index 35943c3..ccdf5f5 100644
--- a/proc.h
+++ b/proc.h
@@ -28,6 +28,10 @@
 #include <sys/time.h>
 #include <stdint.h>
 
+#if defined(HAVE_LIBDW)
+# include <elfutils/libdwfl.h>
+#endif
+
 #if defined(HAVE_LIBUNWIND)
 # include <libunwind.h>
 # include <libunwind-ptrace.h>
@@ -114,6 +118,11 @@ struct process {
 	short e_machine;
 	char e_class;
 
+#if defined(HAVE_LIBDW)
+	/* Unwind info for leader, NULL for non-leader procs. */
+	Dwfl *dwfl;
+#endif /* defined(HAVE_LIBDW) */
+
 #if defined(HAVE_LIBUNWIND)
 	/* libunwind address space */
 	unw_addr_space_t unwind_as;
diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am
index f9b6c0d..d999b2e 100644
--- a/testsuite/Makefile.am
+++ b/testsuite/Makefile.am
@@ -38,6 +38,7 @@ BUILT_SOURCES = env.exp
 env.exp: Makefile
 	rm -f env.exp
 	echo set libelf_LD_LIBRARY_PATH '"$(libelf_LD_LIBRARY_PATH)"' >> $@
+	echo set elfutils_LD_LIBRARY_PATH '"$(elfutils_LD_LIBRARY_PATH)"' >> $@
 	echo set libunwind_LD_LIBRARY_PATH '"$(libunwind_LD_LIBRARY_PATH)"' >> $@
 
 CLEANFILES = *.o *.so *.log *.sum *.ltrace site.bak setval.tmp site.exp env.exp
diff --git a/testsuite/lib/ltrace.exp b/testsuite/lib/ltrace.exp
index abb32f6..9931794 100644
--- a/testsuite/lib/ltrace.exp
+++ b/testsuite/lib/ltrace.exp
@@ -742,6 +742,10 @@ proc ld_library_path { args } {
 	if {[string length $libelf_LD_LIBRARY_PATH] > 0} {
 		lappend ALL_LIBRARY_PATHS $libelf_LD_LIBRARY_PATH
 	}
+	global elfutils_LD_LIBRARY_PATH
+	if {[string length $elfutils_LD_LIBRARY_PATH] > 0} {
+		lappend ALL_LIBRARY_PATHS $elfutils_LD_LIBRARY_PATH
+	}
 	global libunwind_LD_LIBRARY_PATH
 	if {[string length $libunwind_LD_LIBRARY_PATH] > 0} {
 		lappend ALL_LIBRARY_PATHS $libunwind_LD_LIBRARY_PATH
-- 
1.8.4.2




More information about the Ltrace-devel mailing list