diff options
| -rw-r--r-- | Makefile.am | 9 | ||||
| -rw-r--r-- | configure.ac | 89 | ||||
| -rw-r--r-- | defs.h | 19 | ||||
| -rw-r--r-- | mem.c | 17 | ||||
| -rw-r--r-- | process.c | 7 | ||||
| -rw-r--r-- | strace.1 | 5 | ||||
| -rw-r--r-- | strace.c | 33 | ||||
| -rw-r--r-- | syscall.c | 5 | ||||
| -rw-r--r-- | unwind.c | 282 |
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 @@ -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. @@ -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; } @@ -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; } @@ -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 @@ -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; @@ -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); +} |
