aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuca Clementi <luca.clementi@gmail.com>2013-07-23 00:11:35 -0700
committerDmitry V. Levin <ldv@altlinux.org>2014-05-30 22:24:31 +0000
commit327064b63722a4400058e0e7e9b39d9e34b14b57 (patch)
tree392475e202d7d45aca86d75d9fc9864fbe33bbfe
parent6dbbe0737a76c1bae5b0995025a9b0843781a393 (diff)
downloadandroid_external_strace-327064b63722a4400058e0e7e9b39d9e34b14b57.tar.gz
android_external_strace-327064b63722a4400058e0e7e9b39d9e34b14b57.tar.bz2
android_external_strace-327064b63722a4400058e0e7e9b39d9e34b14b57.zip
Add -k option to print stack trace after each syscall
Print the stack trace of the traced process after each system call when -k option is specified. It is implemented using libunwind to unwind the stack and to obtain the function name pointed by the IP. Based on the code that was originally taken from strace-plus of Philip J. Guo. * configure.ac: Add --with-libunwind option. Check libunwind support. * Makefile.am: Add libunwind support. * defs.h (struct tcb) [USE_LIBUNWIND]: Append libunwind specific fields. [USE_LIBUNWIND] (stack_trace_enabled, alloc_mmap_cache, delete_mmap_cache, print_stacktrace): New prototypes. * mem.c (print_mmap, sys_munmap, sys_mprotect): Add libunwind support. * process.c (sys_execve): Likewise. * strace.c (usage, alloctcb, droptcb, init): Likewise. * syscall.c (trace_syscall_exiting): Likewise. * unwind.c: New file. * strace.1: Document -k option.
-rw-r--r--Makefile.am9
-rw-r--r--configure.ac89
-rw-r--r--defs.h19
-rw-r--r--mem.c17
-rw-r--r--process.c7
-rw-r--r--strace.15
-rw-r--r--strace.c33
-rw-r--r--syscall.c5
-rw-r--r--unwind.c282
9 files changed, 465 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 7052157f..be059462 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -54,6 +54,15 @@ strace_SOURCES = \
util.c \
vsprintf.c
+if USE_LIBUNWIND
+strace_SOURCES += unwind.c
+strace_CPPFLAGS = $(AM_CPPFLAGS) $(libunwind_CPPFLAGS)
+strace_LDFLAGS = $(libunwind_LDFLAGS)
+strace_LDADD = $(libunwind_LIBS)
+else
+strace_CPPFLAGS = $(AM_CPPFLAGS)
+endif
+
noinst_HEADERS = defs.h
# Enable this to get link map generated
#strace_CFLAGS = $(AM_CFLAGS) -Wl,-Map=strace.mapfile
diff --git a/configure.ac b/configure.ac
index ec8451a2..9aeb3a69 100644
--- a/configure.ac
+++ b/configure.ac
@@ -649,5 +649,94 @@ fi
AC_PATH_PROG([PERL], [perl])
+dnl stack trace with libunwind
+libunwind_CPPFLAGS=
+libunwind_LDFLAGS=
+libunwind_LIBS=
+AC_ARG_WITH([libunwind],
+ [AS_HELP_STRING([--with-libunwind],
+ [use libunwind to implement stack tracing support])],
+ [case "${withval}" in
+ yes|no|check) ;;
+ *) with_libunwind=yes
+ libunwind_CPPFLAGS="-I${withval}/include"
+ libunwind_LDFLAGS="-L${withval}/lib" ;;
+ esac],
+ [with_libunwind=check]
+)
+
+use_libunwind=no
+AS_IF([test "x$with_libunwind" != xno],
+ [saved_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $libunwind_CPPFLAGS"
+
+ AC_CHECK_HEADERS([libunwind-ptrace.h],
+ [saved_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $libunwind_LDFLAGS"
+
+ AC_CHECK_LIB([unwind], [backtrace],
+ [libunwind_LIBS="-lunwind $libunwind_LIBS"
+
+ AC_MSG_CHECKING([for unw_create_addr_space in libunwind-generic])
+ saved_LIBS="$LIBS"
+ LIBS="-lunwind-generic $libunwind_LIBS $LIBS"
+
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM([[#include <libunwind-ptrace.h>]],
+ [[return !unw_create_addr_space(0, 0)]])
+ ],
+ [AC_MSG_RESULT([yes])
+ libunwind_LIBS="-lunwind-generic $libunwind_LIBS"
+
+ AC_CHECK_LIB([unwind-ptrace], [_UPT_create],
+ [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"
+ use_libunwind=yes
+ ],
+ [if test "x$with_libunwind" != xcheck; then
+ AC_MSG_FAILURE([failed to find _UPT_create in libunwind-ptrace])
+ fi
+ ],
+ [$libunwind_LIBS]
+ )
+ ],
+ [AC_MSG_RESULT([no])
+ if test "x$with_libunwind" != xcheck; then
+ AC_MSG_FAILURE([failed to find unw_create_addr_space in libunwind-generic])
+ fi
+ ]
+ )
+
+ LIBS="$saved_LIBS"
+ ],
+ [if test "x$with_libunwind" != xcheck; then
+ AC_MSG_FAILURE([failed to find libunwind])
+ fi
+ ],
+ [$libunwind_LIBS]
+ )
+
+ LDFLAGS="$saved_LDFLAGS"
+ ],
+ [if test "x$with_libunwind" != xcheck; then
+ AC_MSG_FAILURE([failed to find libunwind-ptrace.h])
+ fi
+ ]
+ )
+
+ CPPFLAGS="$saved_CPPFLAGS"
+ ]
+)
+
+dnl enable libunwind
+AC_MSG_CHECKING([whether to enable stack tracing support using libunwind])
+if test "x$use_libunwind" = xyes; then
+ AC_DEFINE([USE_LIBUNWIND], 1, [Compile stack tracing functionality])
+ AC_SUBST(libunwind_LIBS)
+ AC_SUBST(libunwind_LDFLAGS)
+ AC_SUBST(libunwind_CPPFLAGS)
+fi
+AM_CONDITIONAL([USE_LIBUNWIND], [test "x$use_libunwind" = xyes])
+AC_MSG_RESULT([$use_libunwind])
+
AC_CONFIG_FILES([Makefile tests/Makefile])
AC_OUTPUT
diff --git a/defs.h b/defs.h
index 34a88748..825fc797 100644
--- a/defs.h
+++ b/defs.h
@@ -425,6 +425,12 @@ struct tcb {
struct timeval etime; /* Syscall entry time */
/* Support for tracing forked processes: */
long inst[2]; /* Saved clone args (badly named) */
+
+#ifdef USE_LIBUNWIND
+ struct UPT_info* libunwind_ui;
+ struct mmap_cache_t* mmap_cache;
+ unsigned int mmap_cache_size;
+#endif
};
/* TCB flags */
@@ -559,6 +565,10 @@ extern const char **paths_selected;
extern bool need_fork_exec_workarounds;
extern unsigned xflag;
extern unsigned followfork;
+#ifdef USE_LIBUNWIND
+/* if this is true do the stack trace for every system call */
+extern bool stack_trace_enabled;
+#endif
extern unsigned ptrace_setoptions;
extern unsigned max_strlen;
extern unsigned os_release;
@@ -721,6 +731,15 @@ extern void tv_sub(struct timeval *, const struct timeval *, const struct timeva
extern void tv_mul(struct timeval *, const struct timeval *, int);
extern void tv_div(struct timeval *, const struct timeval *, int);
+#ifdef USE_LIBUNWIND
+extern void init_unwind_addr_space(void);
+extern void init_libunwind_ui(struct tcb *tcp);
+extern void free_libunwind_ui(struct tcb *tcp);
+extern void alloc_mmap_cache(struct tcb* tcp);
+extern void delete_mmap_cache(struct tcb* tcp);
+extern void print_stacktrace(struct tcb* tcp);
+#endif
+
/* Strace log generation machinery.
*
* printing_tcp: tcb which has incomplete line being printed right now.
diff --git a/mem.c b/mem.c
index 6ecd363c..2b684b15 100644
--- a/mem.c
+++ b/mem.c
@@ -60,6 +60,11 @@ static int
print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset)
{
if (entering(tcp)) {
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ delete_mmap_cache(tcp);
+#endif
+
/* addr */
if (!u_arg[0])
tprints("NULL, ");
@@ -189,6 +194,12 @@ sys_munmap(struct tcb *tcp)
tprintf("%#lx, %lu",
tcp->u_arg[0], tcp->u_arg[1]);
}
+#ifdef USE_LIBUNWIND
+ else {
+ if (stack_trace_enabled)
+ delete_mmap_cache(tcp);
+ }
+#endif
return 0;
}
@@ -200,6 +211,12 @@ sys_mprotect(struct tcb *tcp)
tcp->u_arg[0], tcp->u_arg[1]);
printflags(mmap_prot, tcp->u_arg[2], "PROT_???");
}
+#ifdef USE_LIBUNWIND
+ else {
+ if (stack_trace_enabled)
+ delete_mmap_cache(tcp);
+ }
+#endif
return 0;
}
diff --git a/process.c b/process.c
index a880f9e3..22886cbf 100644
--- a/process.c
+++ b/process.c
@@ -799,6 +799,13 @@ sys_execve(struct tcb *tcp)
tprints("]");
}
}
+#ifdef USE_LIBUNWIND
+ else {
+ if (stack_trace_enabled)
+ delete_mmap_cache(tcp);
+ }
+#endif
+
return 0;
}
diff --git a/strace.1 b/strace.1
index 4d26be52..9a7f7022 100644
--- a/strace.1
+++ b/strace.1
@@ -39,7 +39,7 @@
strace \- trace system calls and signals
.SH SYNOPSIS
.B strace
-[\fB-CdffhiqrtttTvVxxy\fR]
+[\fB-CdffhikqrtttTvVxxy\fR]
[\fB-I\fIn\fR]
[\fB-b\fIexecve\fR]
[\fB-e\fIexpr\fR]...
@@ -262,6 +262,9 @@ Print the help summary.
.B \-i
Print the instruction pointer at the time of the system call.
.TP
+.B \-k
+Print the execution stack trace of the traced processes after each system call.
+.TP
.B \-q
Suppress messages about attaching, detaching etc. This happens
automatically when output is redirected to a file and the command
diff --git a/strace.c b/strace.c
index 35ee7bed..a8b04b07 100644
--- a/strace.c
+++ b/strace.c
@@ -50,6 +50,10 @@ extern char **environ;
extern int optind;
extern char *optarg;
+#ifdef USE_LIBUNWIND
+/* if this is true do the stack trace for every system call */
+bool stack_trace_enabled = false;
+#endif
#if defined __NR_tkill
# define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
@@ -233,6 +237,10 @@ usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...\n\
-E var -- remove var from the environment for command\n\
-P path -- trace accesses to path\n\
"
+#ifdef USE_LIBUNWIND
+"-k obtain stack trace between each syscall\n\
+"
+#endif
/* ancient, no one should use it
-F -- attempt to follow vforks (deprecated, use -f)\n\
*/
@@ -695,6 +703,12 @@ alloctcb(int pid)
#if SUPPORTED_PERSONALITIES > 1
tcp->currpers = current_personality;
#endif
+
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ init_libunwind_ui(tcp);
+#endif
+
nprocs++;
if (debug_flag)
fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs);
@@ -731,6 +745,12 @@ droptcb(struct tcb *tcp)
if (printing_tcp == tcp)
printing_tcp = NULL;
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled) {
+ delete_mmap_cache(tcp);
+ free_libunwind_ui(tcp);
+ }
+#endif
memset(tcp, 0, sizeof(*tcp));
}
@@ -1653,6 +1673,9 @@ init(int argc, char *argv[])
qualify("signal=all");
while ((c = getopt(argc, argv,
"+b:cCdfFhiqrtTvVwxyz"
+#ifdef USE_LIBUNWIND
+ "k"
+#endif
"D"
"a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
switch (c) {
@@ -1758,6 +1781,11 @@ init(int argc, char *argv[])
case 'u':
username = strdup(optarg);
break;
+#ifdef USE_LIBUNWIND
+ case 'k':
+ stack_trace_enabled = true;
+ break;
+#endif
case 'E':
if (putenv(optarg) < 0)
die_out_of_memory();
@@ -1789,6 +1817,11 @@ init(int argc, char *argv[])
error_msg_and_die("-D and -p are mutually exclusive");
}
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ init_unwind_addr_space();
+#endif
+
if (!followfork)
followfork = optF;
diff --git a/syscall.c b/syscall.c
index ca721abf..50b41ba3 100644
--- a/syscall.c
+++ b/syscall.c
@@ -2706,6 +2706,11 @@ trace_syscall_exiting(struct tcb *tcp)
dumpio(tcp);
line_ended();
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ print_stacktrace(tcp);
+#endif
+
ret:
tcp->flags &= ~TCB_INSYSCALL;
return 0;
diff --git a/unwind.c b/unwind.c
new file mode 100644
index 00000000..0b8f0b01
--- /dev/null
+++ b/unwind.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2013 Luca Clementi <luca.clementi@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "defs.h"
+#include <limits.h>
+#include <libunwind-ptrace.h>
+
+/*
+ * Кeep a sorted array of cache entries,
+ * so that we can binary search through it.
+ */
+struct mmap_cache_t {
+ /**
+ * example entry:
+ * 7fabbb09b000-7fabbb09f000 r--p 00179000 fc:00 1180246 /lib/libc-2.11.1.so
+ *
+ * start_addr is 0x7fabbb09b000
+ * end_addr is 0x7fabbb09f000
+ * mmap_offset is 0x179000
+ * binary_filename is "/lib/libc-2.11.1.so"
+ */
+ unsigned long start_addr;
+ unsigned long end_addr;
+ unsigned long mmap_offset;
+ char* binary_filename;
+};
+
+static unw_addr_space_t libunwind_as;
+
+void
+init_unwind_addr_space(void)
+{
+ libunwind_as = unw_create_addr_space(&_UPT_accessors, 0);
+ if (!libunwind_as)
+ error_msg_and_die("failed to create address space for stack tracing");
+}
+
+void
+init_libunwind_ui(struct tcb *tcp)
+{
+ tcp->libunwind_ui = _UPT_create(tcp->pid);
+ if (!tcp->libunwind_ui)
+ die_out_of_memory();
+}
+
+void
+free_libunwind_ui(struct tcb *tcp)
+{
+ _UPT_destroy(tcp->libunwind_ui);
+ tcp->libunwind_ui = NULL;
+}
+
+/*
+ * caching of /proc/ID/maps for each process to speed up stack tracing
+ *
+ * The cache must be refreshed after some syscall: mmap, mprotect, munmap, execve
+ */
+void
+alloc_mmap_cache(struct tcb* tcp)
+{
+ unsigned long start_addr, end_addr, mmap_offset;
+ char filename[sizeof ("/proc/0123456789/maps")];
+ char buffer[PATH_MAX + 80];
+ char binary_path[PATH_MAX];
+ struct mmap_cache_t *cur_entry, *prev_entry;
+ /* start with a small dynamically-allocated array and then expand it */
+ size_t cur_array_size = 10;
+ struct mmap_cache_t *cache_head;
+ FILE *fp;
+
+ sprintf(filename, "/proc/%d/maps", tcp->pid);
+ fp = fopen(filename, "r");
+ if (!fp) {
+ perror_msg("fopen: %s", filename);
+ return;
+ }
+
+ cache_head = calloc(cur_array_size, sizeof(*cache_head));
+ if (!cache_head)
+ die_out_of_memory();
+
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ binary_path[0] = '\0'; // 'reset' it just to be paranoid
+
+ sscanf(buffer, "%lx-%lx %*c%*c%*c%*c %lx %*x:%*x %*d %[^\n]",
+ &start_addr, &end_addr, &mmap_offset, binary_path);
+
+ /* ignore special 'fake files' like "[vdso]", "[heap]", "[stack]", */
+ if (binary_path[0] == '[') {
+ continue;
+ }
+
+ if (binary_path[0] == '\0') {
+ continue;
+ }
+
+ if (end_addr < start_addr)
+ perror_msg_and_die("%s: unrecognized maps file format",
+ filename);
+
+ cur_entry = &cache_head[tcp->mmap_cache_size];
+ cur_entry->start_addr = start_addr;
+ cur_entry->end_addr = end_addr;
+ cur_entry->mmap_offset = mmap_offset;
+ cur_entry->binary_filename = strdup(binary_path);
+
+ /*
+ * sanity check to make sure that we're storing
+ * non-overlapping regions in ascending order
+ */
+ if (tcp->mmap_cache_size > 0) {
+ prev_entry = &cache_head[tcp->mmap_cache_size - 1];
+ if (prev_entry->start_addr >= cur_entry->start_addr)
+ perror_msg_and_die("Overlaying memory region in %s",
+ filename);
+ if (prev_entry->end_addr > cur_entry->start_addr)
+ perror_msg_and_die("Overlaying memory region in %s",
+ filename);
+ }
+ tcp->mmap_cache_size++;
+
+ /* resize doubling its size */
+ if (tcp->mmap_cache_size >= cur_array_size) {
+ cur_array_size *= 2;
+ cache_head = realloc(cache_head, cur_array_size * sizeof(*cache_head));
+ if (!cache_head)
+ die_out_of_memory();
+ }
+ }
+ fclose(fp);
+ tcp->mmap_cache = cache_head;
+}
+
+/* deleting the cache */
+void
+delete_mmap_cache(struct tcb* tcp)
+{
+ unsigned int i;
+ for (i = 0; i < tcp->mmap_cache_size; i++) {
+ free(tcp->mmap_cache[i].binary_filename);
+ tcp->mmap_cache[i].binary_filename = NULL;
+ }
+ free(tcp->mmap_cache);
+ tcp->mmap_cache = NULL;
+ tcp->mmap_cache_size = 0;
+}
+
+/* use libunwind to unwind the stack and print a backtrace */
+void
+print_stacktrace(struct tcb* tcp)
+{
+ unw_word_t ip;
+ unw_cursor_t cursor;
+ unw_word_t function_off_set;
+ int stack_depth = 0, ret_val;
+ /* these are used for the binary search through the mmap_chace */
+ unsigned int lower, upper, mid;
+ size_t symbol_name_size = 40;
+ char * symbol_name;
+ struct mmap_cache_t* cur_mmap_cache;
+ unsigned long true_offset;
+
+ if (!tcp->mmap_cache)
+ alloc_mmap_cache(tcp);
+ if (!tcp->mmap_cache || !tcp->mmap_cache_size)
+ return;
+
+ symbol_name = malloc(symbol_name_size);
+ if (!symbol_name)
+ die_out_of_memory();
+
+ if (unw_init_remote(&cursor, libunwind_as, tcp->libunwind_ui) < 0)
+ perror_msg_and_die("Can't initiate libunwind");
+
+ do {
+ /* looping on the stack frame */
+ if (unw_get_reg(&cursor, UNW_REG_IP, &ip) < 0) {
+ perror_msg("Can't walk the stack of process %d", tcp->pid);
+ break;
+ }
+
+ lower = 0;
+ upper = tcp->mmap_cache_size - 1;
+
+ while (lower <= upper) {
+ /* find the mmap_cache and print the stack frame */
+ mid = (upper + lower) / 2;
+ cur_mmap_cache = &tcp->mmap_cache[mid];
+
+ if (ip >= cur_mmap_cache->start_addr &&
+ ip < cur_mmap_cache->end_addr) {
+ for (;;) {
+ symbol_name[0] = '\0';
+ ret_val = unw_get_proc_name(&cursor, symbol_name,
+ symbol_name_size, &function_off_set);
+ if (ret_val != -UNW_ENOMEM)
+ break;
+ symbol_name_size *= 2;
+ symbol_name = realloc(symbol_name, symbol_name_size);
+ if (!symbol_name)
+ die_out_of_memory();
+ }
+
+ true_offset = ip - cur_mmap_cache->start_addr +
+ cur_mmap_cache->mmap_offset;
+ if (symbol_name[0]) {
+ /*
+ * we want to keep the format used by backtrace_symbols from the glibc
+ *
+ * ./a.out() [0x40063d]
+ * ./a.out() [0x4006bb]
+ * ./a.out() [0x4006c6]
+ * /lib64/libc.so.6(__libc_start_main+0xed) [0x7fa2f8a5976d]
+ * ./a.out() [0x400569]
+ */
+ tprintf(" > %s(%s+0x%lx) [0x%lx]\n",
+ cur_mmap_cache->binary_filename,
+ symbol_name, function_off_set, true_offset);
+ } else {
+ tprintf(" > %s() [0x%lx]\n",
+ cur_mmap_cache->binary_filename, true_offset);
+ }
+ line_ended();
+ break; /* stack frame printed */
+ }
+ else if (mid == 0) {
+ /*
+ * there is a bug in libunwind >= 1.0
+ * after a set_tid_address syscall
+ * unw_get_reg returns IP == 0
+ */
+ if(ip)
+ tprintf(" > backtracing_error\n");
+ line_ended();
+ goto ret;
+ }
+ else if (ip < cur_mmap_cache->start_addr)
+ upper = mid - 1;
+ else
+ lower = mid + 1;
+
+ }
+ if (lower > upper) {
+ tprintf(" > backtracing_error [0x%lx]\n", ip);
+ line_ended();
+ goto ret;
+ }
+
+ ret_val = unw_step(&cursor);
+
+ if (++stack_depth > 255) {
+ tprintf("> too many stack frames\n");
+ line_ended();
+ break;
+ }
+ } while (ret_val > 0);
+ret:
+ free(symbol_name);
+}