aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wielaard <mjw@redhat.com>2014-01-07 21:00:44 +0100
committerPetr Machata <pmachata@redhat.com>2014-01-10 00:15:55 +0100
commitdfefa9f057857735a073ea655f5cb34351032c8e (patch)
treeb9f2d6815e8ea27e1af718450250a83f53379b47
parent6bcc092da6b7e20c6c2a9a5846536fbd3d77dbb8 (diff)
downloadltrace-dfefa9f057857735a073ea655f5cb34351032c8e.tar.gz
Add support for using elfutils as unwinder.
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.
-rw-r--r--Makefile.am1
-rw-r--r--configure.ac53
-rw-r--r--ltrace.13
-rw-r--r--options.c18
-rw-r--r--options.h4
-rw-r--r--output.c86
-rw-r--r--proc.c62
-rw-r--r--proc.h9
-rw-r--r--testsuite/Makefile.am1
-rw-r--r--testsuite/lib/ltrace.exp4
10 files changed, 229 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..c6e6bf0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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([
diff --git a/ltrace.1 b/ltrace.1
index 1d5ee63..331c223 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@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 c8c7918..0cec653 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,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 09e6d9b..6f4f64e 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 = 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 a88546e..659c786 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