aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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