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.
This commit is contained in:
Luca Clementi 2013-07-23 00:11:35 -07:00 committed by Dmitry V. Levin
parent 6dbbe0737a
commit 327064b637
9 changed files with 465 additions and 1 deletions

View File

@ -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

View File

@ -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

19
defs.h
View File

@ -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.

17
mem.c
View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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;

282
unwind.c Normal file
View File

@ -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);
}