Add support for using elfutils as unwinder.
authorMark Wielaard <mjw@redhat.com>
Tue, 7 Jan 2014 20:00:44 +0000 (21:00 +0100)
committerChanho Park <chanho61.park@samsung.com>
Fri, 22 Aug 2014 11:38:24 +0000 (20:38 +0900)
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 matches the output as if libunwind was used.
But elfutils can also provide some more information since it can lookup
the DWARF debuginfo. So if the source info of an address can be found
through elfutils the backtrace will also include this as an additional
output line per frame.

Makefile.am
configure.ac
ltrace.1
options.c
options.h
output.c
proc.c
proc.h
testsuite/Makefile.am
testsuite/lib/ltrace.exp

index efcf18a8a12d44116f2d1f28d050d4b472c11cc9..ed469f425b6fda4ad230492cd3a5a7d7687bee80 100644 (file)
@@ -40,6 +40,7 @@ libltrace_la_LIBADD = \
        $(liberty_LIBS) \
        $(libsupcxx_LIBS) \
        $(libstdcxx_LIBS) \
+       $(libdw_LIBS) \
        $(libunwind_LIBS) \
        sysdeps/libos.la
 
index da3b4cb1c54c4e62f36aecb13aa198f49995f523..c6e6bf023d71c176ed552e21e74bd1dda9663eb7 100644 (file)
@@ -128,6 +128,51 @@ 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])
+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 +238,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 +392,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([
index 1d5ee6337f73f9b437a897a22fa6eba647250c47..331c223079146f0c1b610db4d942bce68772f3da 100644 (file)
--- 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
index 8a2fe5da91a30f93995dc00be59b23a2504a3aca..3d5dfaa551085d35531595a57c4132147c91b31b 100644 (file)
--- 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@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);
index d0df3a705b7c3c07107506a8542170f90ad8e2f2..35c3d89efc97491c52c72a6825f2ef9a24eda27e 100644 (file)
--- 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;
 
index c8c79187de1503381d2e900e0a9beb112bd76c0f..0cec6538d5c386e2875636c1c6509c2173d2199a 100644 (file)
--- 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,73 @@ output_left(enum tof type, struct process *proc,
        stel->out.need_delim = need_delim;
 }
 
+#if defined(HAVE_LIBDW)
+/* Prints information about one frame of a thread.  Called by
+   dwfl_getthread_frames in output_right.  Returns 1 when done (max
+   number of frames reached).  Returns -1 on error.  Returns 0 on
+   success (if there are more frames in the thread, call us again).  */
+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 != NULL) {
+               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);
+
+       /* See if we can extract the source line too and print it on
+          the next line if we can find it.  */
+       if (mod != NULL) {
+               Dwfl_Line *l = dwfl_module_getsrc(mod, pc);
+               if (l != NULL) {
+                       int line, col;
+                       line = col = -1;
+                       const char *src = dwfl_lineinfo(l, NULL, &line, &col,
+                                                       NULL, NULL);
+                       if (src != NULL) {
+                               fprintf(options.output, "\t%s", src);
+                               if (line > 0) {
+                                       fprintf(options.output, ":%d", line);
+                                       if (col > 0)
+                                               fprintf(options.output,
+                                                       ":%d", col);
+                               }
+                               fprintf(options.output, "\n");
+                       }
+
+               }
+       }
+
+       /* Max number of frames to print reached? */
+       if ((*frames)-- == 0)
+               return 1;
+
+       return 0;
+}
+#endif /* defined(HAVE_LIBDW) */
+
 void
 output_right(enum tof type, struct process *proc, struct library_symbol *libsym,
             struct timedelta *spent)
@@ -707,6 +775,24 @@ 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) {
+                       // Only print an error if we couldn't show anything.
+                       // Otherwise just show there might be more...
+                       if (frames == options.bt_depth)
+                               fprintf(stderr,
+                                       "dwfl_getthread_frames tid %d: %s\n",
+                                       proc->pid, dwfl_errmsg(-1));
+                       else
+                               fprintf(options.output, " > [...]\n");
+               }
+               fprintf(options.output, "\n");
+         }
+#endif /* defined(HAVE_LIBDW) */
+
        current_proc = NULL;
        current_column = 0;
 }
diff --git a/proc.c b/proc.c
index 09e6d9b6392ff6893b6367d4c501142fe3038089..6f4f64ef1c7abc284a6e8e561bdc2e319d81646b 100644 (file)
--- 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 = 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 a88546e6e684849d2cd5f53346521799a6542230..659c78661fa35964764a60766215bf63989f307e 100644 (file)
--- a/proc.h
+++ b/proc.h
 #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;
index f9b6c0df9258a5c732d3244b3655fd207c58f88f..d999b2e413ad582c02f187d2374890f55499cc97 100644 (file)
@@ -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
index abb32f66debe4532279ba17b135e391ab14b10b8..99317948a2762715243f42cff79c123d68152a5b 100644 (file)
@@ -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