From: Luca C. <luc...@gm...> - 2013-06-24 07:18:29
|
This patch prints the stack trace of the traced process after each system call when using -k flag. It uses libunwind to unwind the stack and to obtain the function name pointed by the IP. Tested on Ubuntu 12.04 64 with the distribution version of libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind form the source code. On Ubuntu you need the libunwind and libunwind-dev package to compile the stack trace feature in the code. This is still a development patch, please let me know if you would consider pulling this patch and if so what do you think should be modified (I will also write man page and better autoconf script). Some of this code was originally taken from strace-plus of Philip J. Guo. --- configure.ac | 24 +++++++ defs.h | 38 +++++++++++ mem.c | 12 ++++ process.c | 4 ++ strace.c | 45 +++++++++++++ syscall.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 323 insertions(+) diff --git a/configure.ac b/configure.ac index c4896f3..aa89399 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,30 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> # include <asm/sigcontext.h> #endif]) +dnl libunwind tests +AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) + +if test "x$ac_cv_header_libunwind_ptrace_h" == "xyes" && + test "x$ac_cv_header_libunwind_h" == "xyes"; then : + + AC_CHECK_LIB([unwind], [backtrace],[],[ + AC_MSG_FAILURE([Unable to find libunwind]) + ]) + AC_CHECK_LIB([unwind-$arch], [_U${arch}_create_addr_space],[], [ + AC_MSG_FAILURE([Unable to find libunwind-$arch]) + ]) + AC_CHECK_LIB([unwind-ptrace], [_UPT_create],[], [ + AC_MSG_FAILURE([Unable to find libunwind-ptrace]) + ]) + + dnl everything is find we can activate stack tracing + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) + AC_MSG_NOTICE([Enabled stack tracing]) +fi + +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) + + AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) AC_CHECK_DECLS([sys_errlist]) diff --git a/defs.h b/defs.h index 3f07c4f..83d9a1e 100644 --- a/defs.h +++ b/defs.h @@ -392,6 +392,29 @@ typedef struct ioctlent { unsigned long code; } struct_ioctlent; + +#ifdef LIB_UNWIND +#include <libunwind-ptrace.h> +#include <libunwind.h> + +/* keep 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; +}; +#endif + /* Trace Control Block */ struct tcb { int flags; /* See below for TCB_ values */ @@ -417,6 +440,13 @@ struct tcb { struct timeval etime; /* Syscall entry time */ /* Support for tracing forked processes: */ long inst[2]; /* Saved clone args (badly named) */ + +#ifdef LIB_UNWIND + // keep a cache of /proc/<pid>/mmap contents to avoid unnecessary file reads + struct mmap_cache_t* mmap_cache; + int mmap_cache_size; + struct UPT_info* libunwind_ui; +#endif }; /* TCB flags */ @@ -710,6 +740,14 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); extern void tv_mul(struct timeval *, struct timeval *, int); extern void tv_div(struct timeval *, struct timeval *, int); +#ifdef LIB_UNWIND +/** + * print stack (-k flag) memory allocation and deallocation + */ +extern void alloc_mmap_cache(struct tcb* tcp); +extern void delete_mmap_cache(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 ef273c7..8c0663a 100644 --- a/mem.c +++ b/mem.c @@ -175,6 +175,10 @@ static int print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) { if (entering(tcp)) { +#ifdef LIB_UNWIND + /* clean the cache */ + delete_mmap_cache(tcp); +#endif /* addr */ if (!u_arg[0]) tprints("NULL, "); @@ -304,6 +308,10 @@ sys_munmap(struct tcb *tcp) tprintf("%#lx, %lu", tcp->u_arg[0], tcp->u_arg[1]); } +#ifdef LIB_UNWIND + if (!entering(tcp)) + delete_mmap_cache(tcp); +#endif return 0; } @@ -315,6 +323,10 @@ sys_mprotect(struct tcb *tcp) tcp->u_arg[0], tcp->u_arg[1]); printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); } +#ifdef LIB_UNWIND + if (!entering(tcp)) + delete_mmap_cache(tcp); +#endif return 0; } diff --git a/process.c b/process.c index 799a314..a370ba1 100644 --- a/process.c +++ b/process.c @@ -1197,6 +1197,10 @@ sys_waitid(struct tcb *tcp) printrusage(tcp, tcp->u_arg[4]); } } +#ifdef LIB_UNWIND + if (!entering(tcp)) + delete_mmap_cache(tcp); +#endif return 0; } diff --git a/strace.c b/strace.c index 6eab600..e972d5c 100644 --- a/strace.c +++ b/strace.c @@ -190,6 +190,15 @@ strerror(int err_no) #endif /* HAVE_STERRROR */ + +#ifdef LIB_UNWIND +/* if this is 1, then do the stack trace for every system call + */ +int use_libunwind = 0; +unw_addr_space_t libunwind_as; +#endif + + static void usage(FILE *ofp, int exitval) { @@ -232,6 +241,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 LIB_UNWIND +"-k obtain stack trace between each syscall\n\ +" +#endif /* ancient, no one should use it -F -- attempt to follow vforks (deprecated, use -f)\n\ */ @@ -685,6 +698,15 @@ alloctcb(int pid) #if SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif + +#ifdef LIB_UNWIND + tcp->mmap_cache = NULL; + tcp->mmap_cache_size = 0; + if (use_libunwind) { + tcp->libunwind_ui = _UPT_create(tcp->pid); + } +#endif + nprocs++; if (debug_flag) fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); @@ -721,6 +743,12 @@ droptcb(struct tcb *tcp) if (printing_tcp == tcp) printing_tcp = NULL; +#ifdef LIB_UNWIND + if (use_libunwind) { + delete_mmap_cache(tcp); + _UPT_destroy(tcp->libunwind_ui); + } +#endif memset(tcp, 0, sizeof(*tcp)); } @@ -1578,6 +1606,9 @@ init(int argc, char *argv[]) qualify("signal=all"); while ((c = getopt(argc, argv, "+b:cCdfFhiqrtTvVxyz" +#ifdef LIB_UNWIND + "k" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { @@ -1680,6 +1711,11 @@ init(int argc, char *argv[]) case 'u': username = strdup(optarg); break; +#ifdef LIB_UNWIND + case 'k': + use_libunwind = 1; + break; +#endif case 'E': if (putenv(optarg) < 0) die_out_of_memory(); @@ -1711,6 +1747,15 @@ init(int argc, char *argv[]) error_msg_and_die("-D and -p are mutually exclusive"); } +#ifdef LIB_UNWIND + if (use_libunwind) { + libunwind_as = unw_create_addr_space (&_UPT_accessors, 0); + if (!libunwind_as) { + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); + } + } +#endif + if (!followfork) followfork = optF; diff --git a/syscall.c b/syscall.c index 7efee0e..ff73118 100644 --- a/syscall.c +++ b/syscall.c @@ -1946,6 +1946,194 @@ get_syscall_args(struct tcb *tcp) return 1; } +#ifdef LIB_UNWIND + +extern unw_addr_space_t libunwind_as; + +/* + * 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) { + + if ( tcp->mmap_cache) + perror_msg_and_die("Memory map cache is empty"); + if ( tcp->mmap_cache_size) + perror_msg_and_die("Memory map cache is empty"); + + /* start with a small dynamically-allocated array and then expand it */ + int cur_array_size = 10; + struct mmap_cache_t* cache_head = malloc(cur_array_size * sizeof(*cache_head)); + char filename[sizeof ("/proc/0123456789/maps")]; + + sprintf(filename, "/proc/%d/maps", tcp->pid); + + FILE* f = fopen(filename, "r"); + if ( ! f ) + perror_msg_and_die("Unable to open %s", filename); + char s[300]; + while (fgets(s, sizeof(s), f) != NULL) { + unsigned long start_addr, end_addr, mmap_offset; + char binary_path[512]; + binary_path[0] = '\0'; // 'reset' it just to be paranoid + + sscanf(s, "%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] == '[' && binary_path[strlen(binary_path) - 1] == ']') { + continue; + } + + if (binary_path[0] == '\0') { + continue; + } + + if(end_addr < start_addr) + perror_msg_and_die("Unrecognized maps file format %s", filename); + + struct mmap_cache_t* 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) { + struct mmap_cache_t* 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)); + } + } + fclose(f); + tcp->mmap_cache = cache_head; +} + +/* deleting the cache */ +void delete_mmap_cache(struct tcb* tcp) { + int i; + for (i = 0; i < tcp->mmap_cache_size; i++) { + free(tcp->mmap_cache[i].binary_filename); + } + free(tcp->mmap_cache); + tcp->mmap_cache = NULL; + tcp->mmap_cache_size = 0; +} + + + +/* + * use libunwind to unwind the stack and print a backtrace + * + * Pre-condition: tcp->mmap_cache is already initialized + */ +void print_libunwind_backtrace(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 */ + int lower, upper, mid; + + int symbol_name_size = 40; + char * symbol_name; + struct mmap_cache_t* cur_mmap_cache; + unsigned long true_offset; + + symbol_name = malloc(symbol_name_size); + if ( !symbol_name ) + perror_msg_and_die("Unable to allocate memory to hold symbol name"); + + if ( unw_init_remote(&cursor, libunwind_as, tcp->libunwind_ui) < 0 ) + perror_msg_and_die("Unable to initiate libunwind"); + + do { + /* looping on the stack frame */ + if ( unw_get_reg(&cursor, UNW_REG_IP, &ip) < 0 ) + perror_msg_and_die("Unable to walk the stack of process %d", + tcp->pid); + + lower = 0; + upper = tcp->mmap_cache_size; + + while ( lower <= upper ) { + /* find the mmap_cache and print the stack frame */ + mid = (int)((upper + lower) / 2); + cur_mmap_cache = &tcp->mmap_cache[mid]; + + if (ip >= cur_mmap_cache->start_addr && + ip < cur_mmap_cache->end_addr) { + + do { + 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 ) + perror_msg_and_die("Unable to allocate memory to hold the symbol name"); + } while ( 1 ); + + 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); + line_ended(); + } + else{ + tprintf(" > %s() [0x%lx]\n", cur_mmap_cache->binary_filename, true_offset); + line_ended(); + } + + break; /* stack frame printed */ + } + else if ( ip < cur_mmap_cache->start_addr ) + upper = mid - 1; + + else + lower = mid + 1; + + } + if ( lower > upper ){ + tprintf(" > Unmapped_memory_area:0x%lx\n", ip); + line_ended(); + } + + ret_val = unw_step(&cursor); + + if ( ++stack_depth > 255 ) { + /* guard against bad unwind info in old libraries... */ + perror_msg("libunwind warning: too deeply nested---assuming bogus unwind\n"); + break; + } + } while (ret_val > 0); +} +#endif + static int trace_syscall_entering(struct tcb *tcp) { @@ -2700,6 +2888,18 @@ trace_syscall_exiting(struct tcb *tcp) dumpio(tcp); line_ended(); +#ifdef LIB_UNWIND + extern int use_libunwind; + if (use_libunwind) { + // caching for efficiency ... + if (!tcp->mmap_cache) { + alloc_mmap_cache(tcp); + } + // use libunwind to unwind the stack, which works even for code compiled + print_libunwind_backtrace(tcp); + } +#endif + ret: tcp->flags &= ~TCB_INSYSCALL; return 0; -- 1.7.9.5 |
From: Denys V. <dvl...@re...> - 2013-06-24 10:44:58
|
On 06/24/2013 09:18 AM, Luca Clementi wrote: > This patch prints the stack trace of the traced process after > each system call when using -k flag. It uses libunwind to > unwind the stack and to obtain the function name pointed by > the IP. > > Tested on Ubuntu 12.04 64 with the distribution version of > libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind > form the source code. On Ubuntu you need the libunwind and > libunwind-dev package to compile the stack trace feature in > the code. > > This is still a development patch, please let me know if you > would consider pulling this patch and if so what do you think > should be modified (I will also write man page and better > autoconf script). > +#ifdef LIB_UNWIND > +#include <libunwind-ptrace.h> > +#include <libunwind.h> > + > +/* keep 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; > +}; > +#endif > + > /* Trace Control Block */ > struct tcb { > int flags; /* See below for TCB_ values */ > @@ -417,6 +440,13 @@ struct tcb { > struct timeval etime; /* Syscall entry time */ > /* Support for tracing forked processes: */ > long inst[2]; /* Saved clone args (badly named) */ > + > +#ifdef LIB_UNWIND > + // keep a cache of /proc/<pid>/mmap contents to avoid unnecessary file reads > + struct mmap_cache_t* mmap_cache; > + int mmap_cache_size; > + struct UPT_info* libunwind_ui; > +#endif > }; > > /* TCB flags */ > @@ -710,6 +740,14 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); > extern void tv_mul(struct timeval *, struct timeval *, int); > extern void tv_div(struct timeval *, struct timeval *, int); > > +#ifdef LIB_UNWIND > +/** > + * print stack (-k flag) memory allocation and deallocation > + */ > +extern void alloc_mmap_cache(struct tcb* tcp); > +extern void delete_mmap_cache(struct tcb* tcp); #else # define alloc_mmap_cache(tcp) ((void)0) # define delete_mmap_cache(tcp) ((void)0) > +#endif > print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) > { > if (entering(tcp)) { > +#ifdef LIB_UNWIND > + /* clean the cache */ > + delete_mmap_cache(tcp); > +#endif If you add above defines, you can lose #ifdef here. Comment is redundant - function name is good enough. > +#ifdef LIB_UNWIND > +/* if this is 1, then do the stack trace for every system call > + */ > +int use_libunwind = 0; Maybe bool? > @@ -685,6 +698,15 @@ alloctcb(int pid) > #if SUPPORTED_PERSONALITIES > 1 > tcp->currpers = current_personality; > #endif > + > +#ifdef LIB_UNWIND > + tcp->mmap_cache = NULL; > + tcp->mmap_cache_size = 0; tcp was memset to 0 a few lines above. These assignment are redundant. > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + libunwind_as = unw_create_addr_space (&_UPT_accessors, 0); The rest of strace source uses a style with no space: "func(...)", not "func (...)". Follow the style. > + if (!libunwind_as) { > + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); Just call die_out_of_memory(). > +void alloc_mmap_cache(struct tcb* tcp) { > + > + if ( tcp->mmap_cache) > + perror_msg_and_die("Memory map cache is empty"); > + if ( tcp->mmap_cache_size) > + perror_msg_and_die("Memory map cache is empty"); ?? please explain the code above. > + /* start with a small dynamically-allocated array and then expand it */ > + int cur_array_size = 10; > + struct mmap_cache_t* cache_head = malloc(cur_array_size * sizeof(*cache_head)); > + char filename[sizeof ("/proc/0123456789/maps")]; > + > + sprintf(filename, "/proc/%d/maps", tcp->pid); > + > + FILE* f = fopen(filename, "r"); > + if ( ! f ) > + perror_msg_and_die("Unable to open %s", filename); "Unable to" => "Can't". It's the same but shorter :) > + char s[300]; > + while (fgets(s, sizeof(s), f) != NULL) { > + unsigned long start_addr, end_addr, mmap_offset; > + char binary_path[512]; > + binary_path[0] = '\0'; // 'reset' it just to be paranoid > + > + sscanf(s, "%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] == '[' && binary_path[strlen(binary_path) - 1] == ']') { > + continue; > + } I would drop the check for closing ']' - strlen is a bit expensive, and all legal files are going to start with '/', not '[', anyway. > + /* 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)); Please, die_out_of_memory() if realloc returns NULL. > +void delete_mmap_cache(struct tcb* tcp) { The { should be on the next line, as the rest of the strace source does. > +#ifdef LIB_UNWIND > + extern int use_libunwind; Move this extern to defs.h > + if (use_libunwind) { > + // caching for efficiency ... > + if (!tcp->mmap_cache) { > + alloc_mmap_cache(tcp); > + } > + // use libunwind to unwind the stack, which works even for code compiled I don't understand what "which works even for code compiled" tries to say. > + print_libunwind_backtrace(tcp); > + } -- vda |
From: Luca C. <luc...@gm...> - 2013-06-25 07:14:20
|
On Mon, Jun 24, 2013 at 3:44 AM, Denys Vlasenko <dvl...@re...> wrote: > On 06/24/2013 09:18 AM, Luca Clementi wrote: > >> +void alloc_mmap_cache(struct tcb* tcp) { >> + >> + if ( tcp->mmap_cache) >> + perror_msg_and_die("Memory map cache is empty"); >> + if ( tcp->mmap_cache_size) >> + perror_msg_and_die("Memory map cache is empty"); > > ?? please explain the code above. That's a completely useless piece of code! There is already the same test before the function invocation, and the error message is also misleading. Ill fix it. >> +void delete_mmap_cache(struct tcb* tcp) { > > The { should be on the next line, as the rest of the strace source does. > >> + // use libunwind to unwind the stack, which works even for code compiled > > I don't understand what "which works even for code compiled" > tries to say. > Some old cut&paste left over.... Clem |
From: Mike F. <va...@ge...> - 2013-06-24 15:42:49
Attachments:
signature.asc
|
On Monday 24 June 2013 03:18:58 Luca Clementi wrote: > --- a/configure.ac > +++ b/configure.ac > > +dnl libunwind tests > +AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) add a proper --with/--enable flag for this. the default should be to autodetect the headers and not fail if support isn't found. > +if test "x$ac_cv_header_libunwind_ptrace_h" == "xyes" && > + test "x$ac_cv_header_libunwind_h" == "xyes"; then : "==" is not POSIX shell. use "=" instead. that trailing ":" is pointless > + AC_CHECK_LIB([unwind], [backtrace],[],[ > + AC_MSG_FAILURE([Unable to find libunwind]) > + ]) > + AC_CHECK_LIB([unwind-$arch], [_U${arch}_create_addr_space],[], [ > + AC_MSG_FAILURE([Unable to find libunwind-$arch]) > + ]) is the $arch stuff really necessary ? isn't linking to -lunwind sufficient ? > --- a/defs.h > +++ b/defs.h > > +struct mmap_cache_t { > + // example entry: > + // 7fabbb09b000-7fabbb09f000 r--p 00179000 fc:00 1180246 > /lib/libc-2.11.1.so we use /* comments */ rather than // comments -mike |
From: Luca C. <luc...@gm...> - 2013-06-25 06:41:42
|
On Mon, Jun 24, 2013 at 8:42 AM, Mike Frysinger <va...@ge...> wrote: > On Monday 24 June 2013 03:18:58 Luca Clementi wrote: >> --- a/configure.ac >> +++ b/configure.ac >> >> +dnl libunwind tests >> +AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) > > add a proper --with/--enable flag for this. the default should be to > autodetect the headers and not fail if support isn't found. > >> +if test "x$ac_cv_header_libunwind_ptrace_h" == "xyes" && >> + test "x$ac_cv_header_libunwind_h" == "xyes"; then : > > "==" is not POSIX shell. use "=" instead. that trailing ":" is pointless > >> + AC_CHECK_LIB([unwind], [backtrace],[],[ >> + AC_MSG_FAILURE([Unable to find libunwind]) >> + ]) >> + AC_CHECK_LIB([unwind-$arch], [_U${arch}_create_addr_space],[], [ >> + AC_MSG_FAILURE([Unable to find libunwind-$arch]) >> + ]) > > is the $arch stuff really necessary ? isn't linking to -lunwind sufficient ? Actually what I do need is the libunwind-ptrace, but the problem is that -lunwind-ptrace depends on -lunwind-$arch (basically they put all the arch dependent code in there) so if you test libunwind-ptrace you need to have the -lunwind-$arch in your linker flags if not the test fails. I will all your (Denys and Mike) reviews and send a new patch. Luca > >> --- a/defs.h >> +++ b/defs.h >> >> +struct mmap_cache_t { >> + // example entry: >> + // 7fabbb09b000-7fabbb09f000 r--p 00179000 fc:00 1180246 >> /lib/libc-2.11.1.so > > we use /* comments */ rather than // comments > -mike |
From: Dmitry V. L. <ld...@al...> - 2013-06-26 08:30:57
|
On Mon, Jun 24, 2013 at 12:18:58AM -0700, Luca Clementi wrote: > This patch prints the stack trace of the traced process after > each system call when using -k flag. It uses libunwind to > unwind the stack and to obtain the function name pointed by > the IP. [...] > --- a/defs.h > +++ b/defs.h > @@ -392,6 +392,29 @@ typedef struct ioctlent { > unsigned long code; > } struct_ioctlent; > > + > +#ifdef LIB_UNWIND > +#include <libunwind-ptrace.h> > +#include <libunwind.h> Please avoid including feature-specific header files in defs.h because the latter is included by all translation units. Just include these headers where they are really needed. > +/* keep 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; > +}; > +#endif > + > /* Trace Control Block */ > struct tcb { > int flags; /* See below for TCB_ values */ > @@ -417,6 +440,13 @@ struct tcb { > struct timeval etime; /* Syscall entry time */ > /* Support for tracing forked processes: */ > long inst[2]; /* Saved clone args (badly named) */ > + > +#ifdef LIB_UNWIND > + // keep a cache of /proc/<pid>/mmap contents to avoid unnecessary file reads > + struct mmap_cache_t* mmap_cache; > + int mmap_cache_size; > + struct UPT_info* libunwind_ui; > +#endif > }; This can stay in defs.h without including libunwind header files, just add a forward declaration for struct UPT_info. > /* TCB flags */ > @@ -710,6 +740,14 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); > extern void tv_mul(struct timeval *, struct timeval *, int); > extern void tv_div(struct timeval *, struct timeval *, int); > > +#ifdef LIB_UNWIND > +/** > + * print stack (-k flag) memory allocation and deallocation > + */ > +extern void alloc_mmap_cache(struct tcb* tcp); > +extern void delete_mmap_cache(struct tcb* tcp); > +#endif Please add here all declarations you need, avoid placing them in *.c files. > +#ifdef LIB_UNWIND > + if (!entering(tcp)) > + delete_mmap_cache(tcp); > +#endif In all such places, please use "exiting" instead of "!entering". > + if (use_libunwind) { > + tcp->libunwind_ui = _UPT_create(tcp->pid); > + } If _UPT_create allocates memory, it can fail. Please check return code of all functions that allocate memory. > --- a/syscall.c > +++ b/syscall.c > @@ -1946,6 +1946,194 @@ get_syscall_args(struct tcb *tcp) > return 1; > } > > +#ifdef LIB_UNWIND > + > +extern unw_addr_space_t libunwind_as; > + > +/* > + * 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) { The code you are working on is quite specific, so please create a new translation unit (e.g. unwind.c) and place all new definitions there. -- ldv |
From: Mike F. <va...@ge...> - 2013-06-28 05:41:31
Attachments:
signature.asc
|
On Tuesday 25 June 2013 02:41:21 Luca Clementi wrote: > On Mon, Jun 24, 2013 at 8:42 AM, Mike Frysinger <va...@ge...> wrote: > > On Monday 24 June 2013 03:18:58 Luca Clementi wrote: > >> + AC_CHECK_LIB([unwind], [backtrace],[],[ > >> + AC_MSG_FAILURE([Unable to find libunwind]) > >> + ]) > >> + AC_CHECK_LIB([unwind-$arch], [_U${arch}_create_addr_space],[], > >> [ + AC_MSG_FAILURE([Unable to find > >> libunwind-$arch]) + ]) > > > > is the $arch stuff really necessary ? isn't linking to -lunwind > > sufficient ? > > Actually what I do need is the libunwind-ptrace, but the problem is > that -lunwind-ptrace depends on -lunwind-$arch (basically they put all > the arch dependent code in there) so if you test libunwind-ptrace you > need to have the -lunwind-$arch in your linker flags if not the test > fails. i think what you want is to use PKG_CHECK_MODULES and look up libunwind- generic instead. libunwind takes care of linking that to the right arch. lrwxrwxrwx 1 root root 19 Nov 2 2012 /usr/lib64/libunwind-generic.so -> libunwind-x86_64.so -mike |
From: Luca C. <luc...@gm...> - 2013-07-03 01:54:28
|
On Thu, Jun 27, 2013 at 10:41 PM, Mike Frysinger <va...@ge...> wrote: > On Tuesday 25 June 2013 02:41:21 Luca Clementi wrote: >> On Mon, Jun 24, 2013 at 8:42 AM, Mike Frysinger <va...@ge...> wrote: >> > On Monday 24 June 2013 03:18:58 Luca Clementi wrote: >> >> + AC_CHECK_LIB([unwind], [backtrace],[],[ >> >> + AC_MSG_FAILURE([Unable to find libunwind]) >> >> + ]) >> >> + AC_CHECK_LIB([unwind-$arch], [_U${arch}_create_addr_space],[], >> >> [ + AC_MSG_FAILURE([Unable to find >> >> libunwind-$arch]) + ]) >> > >> > is the $arch stuff really necessary ? isn't linking to -lunwind >> > sufficient ? >> >> Actually what I do need is the libunwind-ptrace, but the problem is >> that -lunwind-ptrace depends on -lunwind-$arch (basically they put all >> the arch dependent code in there) so if you test libunwind-ptrace you >> need to have the -lunwind-$arch in your linker flags if not the test >> fails. > > i think what you want is to use PKG_CHECK_MODULES and look up libunwind- > generic instead. libunwind takes care of linking that to the right arch. > lrwxrwxrwx 1 root root 19 Nov 2 2012 /usr/lib64/libunwind-generic.so -> > libunwind-x86_64.so Hey Mike, Thanks for pointing it out, using libunwind-generic is a cleaner test. Regarding PKG_CHECKS_MODULE I am not too convinced (although I must say that I am not an expert of autoconf). For example Ubuntu 12.04 the system libunwind7 and libunwind7-dev do not include the .pc files needed by PKG_CHECKS_MODULE. I have been looking around and I found that ltrace uses libunwind for the same purpose and it uses AC_CHECK_LIB to do its checks: http://anonscm.debian.org/gitweb/?p=collab-maint/ltrace.git;a=blob;f=configure.ac;h=16c7a615962d23c7fd733e7d2b67145d75e0a9de;hb=HEAD#l132 I will post shortly a solution based on ltrace autoconf script. Please let me know if you have issue with that. Luca |
From: Mike F. <va...@ge...> - 2013-07-03 16:58:47
Attachments:
signature.asc
|
On Tuesday 02 July 2013 21:54:17 Luca Clementi wrote: > Regarding PKG_CHECKS_MODULE I am not too convinced (although I must > say that I am not an expert of autoconf). > For example Ubuntu 12.04 the system libunwind7 and libunwind7-dev do > not include the .pc files needed by PKG_CHECKS_MODULE. looks like they're new to the 1.1 release. you should still default to checking the .pc files. just add fallback logic to support older versions. something like this untested snippet: PKG_CHECK_MODULES([LIBUNWIND], [libunwind-generic], [CFLAGS="$CFLAGS $LIBUNWIND_CFLAGS" LIBS="$LIBS $LIBUNWIND_LIBS"], [... AC_CHECK_LIB fallback logic ...]) -mike |
From: Luca C. <luc...@gm...> - 2013-07-08 01:43:57
|
On Wed, Jul 3, 2013 at 9:58 AM, Mike Frysinger <va...@ge...> wrote: > On Tuesday 02 July 2013 21:54:17 Luca Clementi wrote: >> Regarding PKG_CHECKS_MODULE I am not too convinced (although I must >> say that I am not an expert of autoconf). >> For example Ubuntu 12.04 the system libunwind7 and libunwind7-dev do >> not include the .pc files needed by PKG_CHECKS_MODULE. > > looks like they're new to the 1.1 release. you should still default to > checking the .pc files. just add fallback logic to support older versions. > > something like this untested snippet: > PKG_CHECK_MODULES([LIBUNWIND], [libunwind-generic], > [CFLAGS="$CFLAGS $LIBUNWIND_CFLAGS" LIBS="$LIBS $LIBUNWIND_LIBS"], > [... AC_CHECK_LIB fallback logic ...]) Hey Mike, I did some tests on this, and they are not just new to 1.1 but they are also missing the: Cflags section. So I can't get the include path from them anyway. So far I have a solution with ac_check_lib that works with both distribution installation and local installation and uses the --with-libunwind=/path/to/unwind flag. Provided the .pc file were not missing the Cflags what would be the advantage of having 10 extra line to manage the checks with pkg_check_modules? Luca diff --git a/Makefile.am b/Makefile.am index 9d611f3..c8ad026 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ ARCH = @arch@ ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = $(WARN_CFLAGS) -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) strace_SOURCES = \ bjm.c \ @@ -43,6 +43,14 @@ strace_SOURCES = \ util.c \ vsprintf.c + +if LIB_UNWIND +strace_SOURCES += unwind.c +strace_LDADD = $(libunwind_LIBS) +AM_LDFLAGS = $(libunwind_LDFLAGS) +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 03e49fe..526fbda 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> # include <asm/sigcontext.h> #endif]) + +dnl stack trace with libunwind +AC_ARG_WITH([libunwind], + [AS_HELP_STRING([--with-libunwind], + [libunwind is used to display system call stacktrace])], + [case "${withval}" in + (yes|no) enable_libunwind=$withval;; + (*) enable_libunwind=yes + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" + libunwind_LDFLAGS="-L${withval}/lib" ;; + esac], + [enable_libunwind=maybe]) + +AS_IF([test "x$enable_libunwind" != xno], + [saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) + CPPFLAGS="${saved_CPPFLAGS}" ]) + +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && + test "x$ac_cv_header_libunwind_h" = "xyes"; then + + dnl code is taken from ltrace + case "${host_cpu}" in + arm*|sa110) UNWIND_ARCH="arm" ;; + i?86) UNWIND_ARCH="x86" ;; + powerpc) UNWIND_ARCH="ppc32" ;; + powerpc64) UNWIND_ARCH="ppc64" ;; + mips*) UNWIND_ARCH="mips" ;; + *) UNWIND_ARCH="${host_cpu}" ;; + esac + + saved_LDFLAGS="${LDFLAGS}" + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" + AC_CHECK_LIB([unwind], [backtrace], + [libunwind_LIBS="-lunwind"], + [AC_MSG_FAILURE([Unable to find libunwind])]) + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], + [AC_MSG_FAILURE([Unable to find libunwind-generic])], + [$libunwind_LIBS]) + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], + [$libunwind_LIBS]) + LDFLAGS="${saved_LDFLAGS}" + + dnl we have all the dependencies we need we can activate stack tracing + AC_SUBST(libunwind_LIBS) + AC_SUBST(libunwind_LDFLAGS) + AC_SUBST(libunwind_CPPFLAGS) + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) + AC_MSG_NOTICE([Enabled stack tracing]) +fi +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) + + AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) AC_CHECK_DECLS([sys_errlist]) |
From: Luca C. <luc...@gm...> - 2013-07-08 01:52:23
|
This patch prints the stack trace of the traced process after each system call when using -k flag. It uses libunwind to unwind the stack and to obtain the function name pointed by the IP. Tested on Ubuntu 12.04 64 with the distribution version of libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind form the source code. On Ubuntu you need the libunwind and libunwind-dev package to compile the stack trace feature in the code. Some of this code was originally taken from strace-plus of Philip J. Guo. v2: fixed several identation issues removed a strlen in alloc_mmap_cache (Denys) reload mmap_cache after execv fixed a bug in libunwind_backtrace binary search (Masatake) check return value of _UPT_create use die_out_of_memory after Xalloc use exiting instead of !entering created a new separate unwind.c file removed include from defs.h added --with-libunwind to autoconf script cleaned up defs.h from several inclusions --- Makefile.am | 10 +++++++++- configure.ac | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ defs.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ mem.c | 7 +++++++ process.c | 8 ++++++++ strace.c | 43 +++++++++++++++++++++++++++++++++++++++++++ syscall.c | 9 +++++++++ 7 files changed, 178 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 9d611f3..c8ad026 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ ARCH = @arch@ ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = $(WARN_CFLAGS) -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) strace_SOURCES = \ bjm.c \ @@ -43,6 +43,14 @@ strace_SOURCES = \ util.c \ vsprintf.c + +if LIB_UNWIND +strace_SOURCES += unwind.c +strace_LDADD = $(libunwind_LIBS) +AM_LDFLAGS = $(libunwind_LDFLAGS) +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 03e49fe..526fbda 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> # include <asm/sigcontext.h> #endif]) + +dnl stack trace with libunwind +AC_ARG_WITH([libunwind], + [AS_HELP_STRING([--with-libunwind], + [libunwind is used to display system call stacktrace])], + [case "${withval}" in + (yes|no) enable_libunwind=$withval;; + (*) enable_libunwind=yes + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" + libunwind_LDFLAGS="-L${withval}/lib" ;; + esac], + [enable_libunwind=maybe]) + +AS_IF([test "x$enable_libunwind" != xno], + [saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) + CPPFLAGS="${saved_CPPFLAGS}" ]) + +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && + test "x$ac_cv_header_libunwind_h" = "xyes"; then + + dnl code is taken from ltrace + case "${host_cpu}" in + arm*|sa110) UNWIND_ARCH="arm" ;; + i?86) UNWIND_ARCH="x86" ;; + powerpc) UNWIND_ARCH="ppc32" ;; + powerpc64) UNWIND_ARCH="ppc64" ;; + mips*) UNWIND_ARCH="mips" ;; + *) UNWIND_ARCH="${host_cpu}" ;; + esac + + saved_LDFLAGS="${LDFLAGS}" + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" + AC_CHECK_LIB([unwind], [backtrace], + [libunwind_LIBS="-lunwind"], + [AC_MSG_FAILURE([Unable to find libunwind])]) + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], + [AC_MSG_FAILURE([Unable to find libunwind-generic])], + [$libunwind_LIBS]) + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], + [$libunwind_LIBS]) + LDFLAGS="${saved_LDFLAGS}" + + dnl we have all the dependencies we need we can activate stack tracing + AC_SUBST(libunwind_LIBS) + AC_SUBST(libunwind_LDFLAGS) + AC_SUBST(libunwind_CPPFLAGS) + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) + AC_MSG_NOTICE([Enabled stack tracing]) +fi +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) + + AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) AC_CHECK_DECLS([sys_errlist]) diff --git a/defs.h b/defs.h index 64cfc8d..b314622 100644 --- a/defs.h +++ b/defs.h @@ -405,6 +405,33 @@ typedef struct ioctlent { unsigned long code; } struct_ioctlent; + +#ifdef LIB_UNWIND + +/* keep 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; +}; + + +/* if this is true do the stack trace for every system call */ +extern bool use_libunwind; +#endif + /* Trace Control Block */ struct tcb { int flags; /* See below for TCB_ values */ @@ -430,6 +457,12 @@ struct tcb { struct timeval etime; /* Syscall entry time */ /* Support for tracing forked processes: */ long inst[2]; /* Saved clone args (badly named) */ + +#ifdef LIB_UNWIND + struct mmap_cache_t* mmap_cache; + int mmap_cache_size; + struct UPT_info* libunwind_ui; +#endif }; /* TCB flags */ @@ -721,6 +754,18 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); extern void tv_mul(struct timeval *, struct timeval *, int); extern void tv_div(struct timeval *, struct timeval *, int); +#ifdef LIB_UNWIND +/** + * print stack (-k flag) memory allocation and deallocation + */ +extern void alloc_mmap_cache(struct tcb* tcp); +extern void delete_mmap_cache(struct tcb* tcp); +extern void print_stacktrace(struct tcb* tcp); +#else +# define alloc_mmap_cache(tcp) ((void)0) +# define delete_mmap_cache(tcp) ((void)0) +#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 ef273c7..a82b747 100644 --- a/mem.c +++ b/mem.c @@ -175,6 +175,9 @@ static int print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) { if (entering(tcp)) { + if (use_libunwind) + delete_mmap_cache(tcp); + /* addr */ if (!u_arg[0]) tprints("NULL, "); @@ -304,6 +307,8 @@ sys_munmap(struct tcb *tcp) tprintf("%#lx, %lu", tcp->u_arg[0], tcp->u_arg[1]); } + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); return 0; } @@ -315,6 +320,8 @@ sys_mprotect(struct tcb *tcp) tcp->u_arg[0], tcp->u_arg[1]); printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); } + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); return 0; } diff --git a/process.c b/process.c index e2fa25b..306aa23 100644 --- a/process.c +++ b/process.c @@ -992,6 +992,10 @@ sys_execve(struct tcb *tcp) tprints("]"); } } + + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); + return 0; } @@ -1209,6 +1213,10 @@ sys_waitid(struct tcb *tcp) printrusage(tcp, tcp->u_arg[4]); } } + + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); + return 0; } diff --git a/strace.c b/strace.c index a489db4..5956da6 100644 --- a/strace.c +++ b/strace.c @@ -50,6 +50,13 @@ extern char **environ; extern int optind; extern char *optarg; +#ifdef LIB_UNWIND +# include <libunwind-ptrace.h> + +/* if this is true do the stack trace for every system call */ +bool use_libunwind = false; +unw_addr_space_t libunwind_as; +#endif #if defined __NR_tkill # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) @@ -231,6 +238,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 LIB_UNWIND +"-k obtain stack trace between each syscall\n\ +" +#endif /* ancient, no one should use it -F -- attempt to follow vforks (deprecated, use -f)\n\ */ @@ -685,6 +696,15 @@ alloctcb(int pid) #if SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif + +#ifdef LIB_UNWIND + if (use_libunwind) { + tcp->libunwind_ui = _UPT_create(tcp->pid); + if (!tcp->libunwind_ui) + die_out_of_memory(); + } +#endif + nprocs++; if (debug_flag) fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) if (printing_tcp == tcp) printing_tcp = NULL; +#ifdef LIB_UNWIND + if (use_libunwind) { + delete_mmap_cache(tcp); + _UPT_destroy(tcp->libunwind_ui); + } +#endif memset(tcp, 0, sizeof(*tcp)); } @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) qualify("signal=all"); while ((c = getopt(argc, argv, "+b:cCdfFhiqrtTvVxyz" +#ifdef LIB_UNWIND + "k" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) case 'u': username = strdup(optarg); break; +#ifdef LIB_UNWIND + case 'k': + use_libunwind = true; + break; +#endif case 'E': if (putenv(optarg) < 0) die_out_of_memory(); @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) error_msg_and_die("-D and -p are mutually exclusive"); } +#ifdef LIB_UNWIND + if (use_libunwind) { + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); + if (!libunwind_as) { + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); + } + } +#endif + if (!followfork) followfork = optF; diff --git a/syscall.c b/syscall.c index f524b13..dee4c61 100644 --- a/syscall.c +++ b/syscall.c @@ -2684,6 +2684,15 @@ trace_syscall_exiting(struct tcb *tcp) dumpio(tcp); line_ended(); +#ifdef LIB_UNWIND + if (use_libunwind) { + if (!tcp->mmap_cache) { + alloc_mmap_cache(tcp); + } + print_stacktrace(tcp); + } +#endif + ret: tcp->flags &= ~TCB_INSYSCALL; return 0; -- 1.7.9.5 |
From: Masatake Y. <ya...@re...> - 2013-07-08 05:33:03
|
How can I try this v2 patch? As far as my seeing, I cannot find the definition of print_stacktrace function. Should I apply both v1 and v2 patches? Masatake YAMATO > This patch prints the stack trace of the traced process after > each system call when using -k flag. It uses libunwind to > unwind the stack and to obtain the function name pointed by > the IP. > > Tested on Ubuntu 12.04 64 with the distribution version of > libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind > form the source code. On Ubuntu you need the libunwind and > libunwind-dev package to compile the stack trace feature in > the code. > > Some of this code was originally taken from strace-plus of > Philip J. Guo. > > v2: > fixed several identation issues > removed a strlen in alloc_mmap_cache (Denys) > reload mmap_cache after execv > fixed a bug in libunwind_backtrace binary search (Masatake) > check return value of _UPT_create > use die_out_of_memory after Xalloc > use exiting instead of !entering > created a new separate unwind.c file > removed include from defs.h > added --with-libunwind to autoconf script > cleaned up defs.h from several inclusions > --- > Makefile.am | 10 +++++++++- > configure.ac | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > defs.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ > mem.c | 7 +++++++ > process.c | 8 ++++++++ > strace.c | 43 +++++++++++++++++++++++++++++++++++++++++++ > syscall.c | 9 +++++++++ > 7 files changed, 178 insertions(+), 1 deletion(-) > > diff --git a/Makefile.am b/Makefile.am > index 9d611f3..c8ad026 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -12,7 +12,7 @@ ARCH = @arch@ > > ACLOCAL_AMFLAGS = -I m4 > AM_CFLAGS = $(WARN_CFLAGS) > -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) > +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) > > strace_SOURCES = \ > bjm.c \ > @@ -43,6 +43,14 @@ strace_SOURCES = \ > util.c \ > vsprintf.c > > + > +if LIB_UNWIND > +strace_SOURCES += unwind.c > +strace_LDADD = $(libunwind_LIBS) > +AM_LDFLAGS = $(libunwind_LDFLAGS) > +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 03e49fe..526fbda 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> > # include <asm/sigcontext.h> > #endif]) > > + > +dnl stack trace with libunwind > +AC_ARG_WITH([libunwind], > + [AS_HELP_STRING([--with-libunwind], > + [libunwind is used to display system call stacktrace])], > + [case "${withval}" in > + (yes|no) enable_libunwind=$withval;; > + (*) enable_libunwind=yes > + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" > + libunwind_LDFLAGS="-L${withval}/lib" ;; > + esac], > + [enable_libunwind=maybe]) > + > +AS_IF([test "x$enable_libunwind" != xno], > + [saved_CPPFLAGS="${CPPFLAGS}" > + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" > + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) > + CPPFLAGS="${saved_CPPFLAGS}" ]) > + > +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && > + test "x$ac_cv_header_libunwind_h" = "xyes"; then > + > + dnl code is taken from ltrace > + case "${host_cpu}" in > + arm*|sa110) UNWIND_ARCH="arm" ;; > + i?86) UNWIND_ARCH="x86" ;; > + powerpc) UNWIND_ARCH="ppc32" ;; > + powerpc64) UNWIND_ARCH="ppc64" ;; > + mips*) UNWIND_ARCH="mips" ;; > + *) UNWIND_ARCH="${host_cpu}" ;; > + esac > + > + saved_LDFLAGS="${LDFLAGS}" > + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" > + AC_CHECK_LIB([unwind], [backtrace], > + [libunwind_LIBS="-lunwind"], > + [AC_MSG_FAILURE([Unable to find libunwind])]) > + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], > + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-generic])], > + [$libunwind_LIBS]) > + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], > + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], > + [$libunwind_LIBS]) > + LDFLAGS="${saved_LDFLAGS}" > + > + dnl we have all the dependencies we need we can activate stack tracing > + AC_SUBST(libunwind_LIBS) > + AC_SUBST(libunwind_LDFLAGS) > + AC_SUBST(libunwind_CPPFLAGS) > + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) > + AC_MSG_NOTICE([Enabled stack tracing]) > +fi > +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) > + > + > AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) > > AC_CHECK_DECLS([sys_errlist]) > diff --git a/defs.h b/defs.h > index 64cfc8d..b314622 100644 > --- a/defs.h > +++ b/defs.h > @@ -405,6 +405,33 @@ typedef struct ioctlent { > unsigned long code; > } struct_ioctlent; > > + > +#ifdef LIB_UNWIND > + > +/* keep 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; > +}; > + > + > +/* if this is true do the stack trace for every system call */ > +extern bool use_libunwind; > +#endif > + > /* Trace Control Block */ > struct tcb { > int flags; /* See below for TCB_ values */ > @@ -430,6 +457,12 @@ struct tcb { > struct timeval etime; /* Syscall entry time */ > /* Support for tracing forked processes: */ > long inst[2]; /* Saved clone args (badly named) */ > + > +#ifdef LIB_UNWIND > + struct mmap_cache_t* mmap_cache; > + int mmap_cache_size; > + struct UPT_info* libunwind_ui; > +#endif > }; > > /* TCB flags */ > @@ -721,6 +754,18 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); > extern void tv_mul(struct timeval *, struct timeval *, int); > extern void tv_div(struct timeval *, struct timeval *, int); > > +#ifdef LIB_UNWIND > +/** > + * print stack (-k flag) memory allocation and deallocation > + */ > +extern void alloc_mmap_cache(struct tcb* tcp); > +extern void delete_mmap_cache(struct tcb* tcp); > +extern void print_stacktrace(struct tcb* tcp); > +#else > +# define alloc_mmap_cache(tcp) ((void)0) > +# define delete_mmap_cache(tcp) ((void)0) > +#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 ef273c7..a82b747 100644 > --- a/mem.c > +++ b/mem.c > @@ -175,6 +175,9 @@ static int > print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) > { > if (entering(tcp)) { > + if (use_libunwind) > + delete_mmap_cache(tcp); > + > /* addr */ > if (!u_arg[0]) > tprints("NULL, "); > @@ -304,6 +307,8 @@ sys_munmap(struct tcb *tcp) > tprintf("%#lx, %lu", > tcp->u_arg[0], tcp->u_arg[1]); > } > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > return 0; > } > > @@ -315,6 +320,8 @@ sys_mprotect(struct tcb *tcp) > tcp->u_arg[0], tcp->u_arg[1]); > printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); > } > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > return 0; > } > > diff --git a/process.c b/process.c > index e2fa25b..306aa23 100644 > --- a/process.c > +++ b/process.c > @@ -992,6 +992,10 @@ sys_execve(struct tcb *tcp) > tprints("]"); > } > } > + > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > + > return 0; > } > > @@ -1209,6 +1213,10 @@ sys_waitid(struct tcb *tcp) > printrusage(tcp, tcp->u_arg[4]); > } > } > + > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > + > return 0; > } > > diff --git a/strace.c b/strace.c > index a489db4..5956da6 100644 > --- a/strace.c > +++ b/strace.c > @@ -50,6 +50,13 @@ extern char **environ; > extern int optind; > extern char *optarg; > > +#ifdef LIB_UNWIND > +# include <libunwind-ptrace.h> > + > +/* if this is true do the stack trace for every system call */ > +bool use_libunwind = false; > +unw_addr_space_t libunwind_as; > +#endif > > #if defined __NR_tkill > # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) > @@ -231,6 +238,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 LIB_UNWIND > +"-k obtain stack trace between each syscall\n\ > +" > +#endif > /* ancient, no one should use it > -F -- attempt to follow vforks (deprecated, use -f)\n\ > */ > @@ -685,6 +696,15 @@ alloctcb(int pid) > #if SUPPORTED_PERSONALITIES > 1 > tcp->currpers = current_personality; > #endif > + > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + tcp->libunwind_ui = _UPT_create(tcp->pid); > + if (!tcp->libunwind_ui) > + die_out_of_memory(); > + } > +#endif > + > nprocs++; > if (debug_flag) > fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); > @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) > if (printing_tcp == tcp) > printing_tcp = NULL; > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + delete_mmap_cache(tcp); > + _UPT_destroy(tcp->libunwind_ui); > + } > +#endif > memset(tcp, 0, sizeof(*tcp)); > } > > @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) > qualify("signal=all"); > while ((c = getopt(argc, argv, > "+b:cCdfFhiqrtTvVxyz" > +#ifdef LIB_UNWIND > + "k" > +#endif > "D" > "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { > switch (c) { > @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) > case 'u': > username = strdup(optarg); > break; > +#ifdef LIB_UNWIND > + case 'k': > + use_libunwind = true; > + break; > +#endif > case 'E': > if (putenv(optarg) < 0) > die_out_of_memory(); > @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) > error_msg_and_die("-D and -p are mutually exclusive"); > } > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); > + if (!libunwind_as) { > + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); > + } > + } > +#endif > + > if (!followfork) > followfork = optF; > > diff --git a/syscall.c b/syscall.c > index f524b13..dee4c61 100644 > --- a/syscall.c > +++ b/syscall.c > @@ -2684,6 +2684,15 @@ trace_syscall_exiting(struct tcb *tcp) > dumpio(tcp); > line_ended(); > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + if (!tcp->mmap_cache) { > + alloc_mmap_cache(tcp); > + } > + print_stacktrace(tcp); > + } > +#endif > + > ret: > tcp->flags &= ~TCB_INSYSCALL; > return 0; > -- > 1.7.9.5 > |
From: Luca C. <luc...@gm...> - 2013-07-08 07:24:33
|
On Sun, Jul 7, 2013 at 10:32 PM, Masatake YAMATO <ya...@re...> wrote: > How can I try this v2 patch? > > As far as my seeing, I cannot find the definition of print_stacktrace > function. Should I apply both v1 and v2 patches? > > Masatake YAMATO > > Hey Yamato, Sorry I did not include the new file unwind.c :-( I just resend the patch. Thank you for testing, Luca |
From: Luca C. <luc...@gm...> - 2013-07-08 07:23:22
|
This patch prints the stack trace of the traced process after each system call when using -k flag. It uses libunwind to unwind the stack and to obtain the function name pointed by the IP. Tested on Ubuntu 12.04 64 with the distribution version of libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind form the source code. On Ubuntu you need the libunwind and libunwind-dev package to compile the stack trace feature in the code. Some of this code was originally taken from strace-plus of Philip J. Guo. v2: fixed several identation issues removed a strlen in alloc_mmap_cache (Denys) reload mmap_cache after execv fixed a bug in libunwind_backtrace binary search (Masatake) check return value of _UPT_create use die_out_of_memory after Xalloc use exiting instead of !entering created a new separate unwind.c file removed include from defs.h added --with-libunwind to autoconf script cleaned up defs.h from several inclusions --- Makefile.am | 10 ++- configure.ac | 57 +++++++++++++++ defs.h | 45 ++++++++++++ mem.c | 7 ++ process.c | 8 +++ strace.c | 43 ++++++++++++ syscall.c | 9 +++ unwind.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 unwind.c diff --git a/Makefile.am b/Makefile.am index 9d611f3..c8ad026 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ ARCH = @arch@ ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = $(WARN_CFLAGS) -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) strace_SOURCES = \ bjm.c \ @@ -43,6 +43,14 @@ strace_SOURCES = \ util.c \ vsprintf.c + +if LIB_UNWIND +strace_SOURCES += unwind.c +strace_LDADD = $(libunwind_LIBS) +AM_LDFLAGS = $(libunwind_LDFLAGS) +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 03e49fe..526fbda 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> # include <asm/sigcontext.h> #endif]) + +dnl stack trace with libunwind +AC_ARG_WITH([libunwind], + [AS_HELP_STRING([--with-libunwind], + [libunwind is used to display system call stacktrace])], + [case "${withval}" in + (yes|no) enable_libunwind=$withval;; + (*) enable_libunwind=yes + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" + libunwind_LDFLAGS="-L${withval}/lib" ;; + esac], + [enable_libunwind=maybe]) + +AS_IF([test "x$enable_libunwind" != xno], + [saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) + CPPFLAGS="${saved_CPPFLAGS}" ]) + +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && + test "x$ac_cv_header_libunwind_h" = "xyes"; then + + dnl code is taken from ltrace + case "${host_cpu}" in + arm*|sa110) UNWIND_ARCH="arm" ;; + i?86) UNWIND_ARCH="x86" ;; + powerpc) UNWIND_ARCH="ppc32" ;; + powerpc64) UNWIND_ARCH="ppc64" ;; + mips*) UNWIND_ARCH="mips" ;; + *) UNWIND_ARCH="${host_cpu}" ;; + esac + + saved_LDFLAGS="${LDFLAGS}" + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" + AC_CHECK_LIB([unwind], [backtrace], + [libunwind_LIBS="-lunwind"], + [AC_MSG_FAILURE([Unable to find libunwind])]) + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], + [AC_MSG_FAILURE([Unable to find libunwind-generic])], + [$libunwind_LIBS]) + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], + [$libunwind_LIBS]) + LDFLAGS="${saved_LDFLAGS}" + + dnl we have all the dependencies we need we can activate stack tracing + AC_SUBST(libunwind_LIBS) + AC_SUBST(libunwind_LDFLAGS) + AC_SUBST(libunwind_CPPFLAGS) + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) + AC_MSG_NOTICE([Enabled stack tracing]) +fi +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) + + AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) AC_CHECK_DECLS([sys_errlist]) diff --git a/defs.h b/defs.h index 64cfc8d..b314622 100644 --- a/defs.h +++ b/defs.h @@ -405,6 +405,33 @@ typedef struct ioctlent { unsigned long code; } struct_ioctlent; + +#ifdef LIB_UNWIND + +/* keep 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; +}; + + +/* if this is true do the stack trace for every system call */ +extern bool use_libunwind; +#endif + /* Trace Control Block */ struct tcb { int flags; /* See below for TCB_ values */ @@ -430,6 +457,12 @@ struct tcb { struct timeval etime; /* Syscall entry time */ /* Support for tracing forked processes: */ long inst[2]; /* Saved clone args (badly named) */ + +#ifdef LIB_UNWIND + struct mmap_cache_t* mmap_cache; + int mmap_cache_size; + struct UPT_info* libunwind_ui; +#endif }; /* TCB flags */ @@ -721,6 +754,18 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); extern void tv_mul(struct timeval *, struct timeval *, int); extern void tv_div(struct timeval *, struct timeval *, int); +#ifdef LIB_UNWIND +/** + * print stack (-k flag) memory allocation and deallocation + */ +extern void alloc_mmap_cache(struct tcb* tcp); +extern void delete_mmap_cache(struct tcb* tcp); +extern void print_stacktrace(struct tcb* tcp); +#else +# define alloc_mmap_cache(tcp) ((void)0) +# define delete_mmap_cache(tcp) ((void)0) +#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 ef273c7..a82b747 100644 --- a/mem.c +++ b/mem.c @@ -175,6 +175,9 @@ static int print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) { if (entering(tcp)) { + if (use_libunwind) + delete_mmap_cache(tcp); + /* addr */ if (!u_arg[0]) tprints("NULL, "); @@ -304,6 +307,8 @@ sys_munmap(struct tcb *tcp) tprintf("%#lx, %lu", tcp->u_arg[0], tcp->u_arg[1]); } + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); return 0; } @@ -315,6 +320,8 @@ sys_mprotect(struct tcb *tcp) tcp->u_arg[0], tcp->u_arg[1]); printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); } + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); return 0; } diff --git a/process.c b/process.c index e2fa25b..306aa23 100644 --- a/process.c +++ b/process.c @@ -992,6 +992,10 @@ sys_execve(struct tcb *tcp) tprints("]"); } } + + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); + return 0; } @@ -1209,6 +1213,10 @@ sys_waitid(struct tcb *tcp) printrusage(tcp, tcp->u_arg[4]); } } + + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); + return 0; } diff --git a/strace.c b/strace.c index a489db4..5956da6 100644 --- a/strace.c +++ b/strace.c @@ -50,6 +50,13 @@ extern char **environ; extern int optind; extern char *optarg; +#ifdef LIB_UNWIND +# include <libunwind-ptrace.h> + +/* if this is true do the stack trace for every system call */ +bool use_libunwind = false; +unw_addr_space_t libunwind_as; +#endif #if defined __NR_tkill # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) @@ -231,6 +238,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 LIB_UNWIND +"-k obtain stack trace between each syscall\n\ +" +#endif /* ancient, no one should use it -F -- attempt to follow vforks (deprecated, use -f)\n\ */ @@ -685,6 +696,15 @@ alloctcb(int pid) #if SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif + +#ifdef LIB_UNWIND + if (use_libunwind) { + tcp->libunwind_ui = _UPT_create(tcp->pid); + if (!tcp->libunwind_ui) + die_out_of_memory(); + } +#endif + nprocs++; if (debug_flag) fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) if (printing_tcp == tcp) printing_tcp = NULL; +#ifdef LIB_UNWIND + if (use_libunwind) { + delete_mmap_cache(tcp); + _UPT_destroy(tcp->libunwind_ui); + } +#endif memset(tcp, 0, sizeof(*tcp)); } @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) qualify("signal=all"); while ((c = getopt(argc, argv, "+b:cCdfFhiqrtTvVxyz" +#ifdef LIB_UNWIND + "k" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) case 'u': username = strdup(optarg); break; +#ifdef LIB_UNWIND + case 'k': + use_libunwind = true; + break; +#endif case 'E': if (putenv(optarg) < 0) die_out_of_memory(); @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) error_msg_and_die("-D and -p are mutually exclusive"); } +#ifdef LIB_UNWIND + if (use_libunwind) { + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); + if (!libunwind_as) { + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); + } + } +#endif + if (!followfork) followfork = optF; diff --git a/syscall.c b/syscall.c index f524b13..dee4c61 100644 --- a/syscall.c +++ b/syscall.c @@ -2684,6 +2684,15 @@ trace_syscall_exiting(struct tcb *tcp) dumpio(tcp); line_ended(); +#ifdef LIB_UNWIND + if (use_libunwind) { + if (!tcp->mmap_cache) { + alloc_mmap_cache(tcp); + } + print_stacktrace(tcp); + } +#endif + ret: tcp->flags &= ~TCB_INSYSCALL; return 0; diff --git a/unwind.c b/unwind.c new file mode 100644 index 0000000..b7bd8ba --- /dev/null +++ b/unwind.c @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2013 Luca Clementi <luc...@gm...> + * + * 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 <libunwind.h> + + +extern unw_addr_space_t libunwind_as; +/* + * 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) +{ + /* start with a small dynamically-allocated array and then expand it */ + int cur_array_size = 10; + char filename[sizeof ("/proc/0123456789/maps")]; + struct mmap_cache_t* cache_head = malloc(cur_array_size * sizeof(*cache_head)); + if (!cache_head) + die_out_of_memory(); + + sprintf(filename, "/proc/%d/maps", tcp->pid); + + FILE* f = fopen(filename, "r"); + if (!f) + perror_msg_and_die("Can't open %s", filename); + char s[300]; + while (fgets(s, sizeof(s), f) != NULL) { + unsigned long start_addr, end_addr, mmap_offset; + char binary_path[512]; + binary_path[0] = '\0'; // 'reset' it just to be paranoid + + sscanf(s, "%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("Unrecognized maps file format %s", filename); + + struct mmap_cache_t* 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) { + struct mmap_cache_t* 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(f); + tcp->mmap_cache = cache_head; +} + +/* deleting the cache */ +void +delete_mmap_cache(struct tcb* tcp) +{ + int i; + for (i = 0; i < tcp->mmap_cache_size; i++) { + free(tcp->mmap_cache[i].binary_filename); + } + free(tcp->mmap_cache); + tcp->mmap_cache = NULL; + tcp->mmap_cache_size = 0; +} + + + +/* + * use libunwind to unwind the stack and print a backtrace + * + * Pre-condition: tcp->mmap_cache is already initialized + */ +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 */ + int lower, upper, mid; + int symbol_name_size = 40; + char * symbol_name; + struct mmap_cache_t* cur_mmap_cache; + unsigned long true_offset; + + 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_and_die("Can't walk the stack of process %d", + tcp->pid); + + lower = 0; + upper = tcp->mmap_cache_size - 1; + + + while (lower <= upper) { + /* find the mmap_cache and print the stack frame */ + mid = (int)((upper + lower) / 2); + cur_mmap_cache = &tcp->mmap_cache[mid]; + + if (ip >= cur_mmap_cache->start_addr && + ip < cur_mmap_cache->end_addr) { + + do { + 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(); + } while (1); + + 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); + line_ended(); + } + else{ + tprintf(" > %s() [0x%lx]\n", cur_mmap_cache->binary_filename, true_offset); + line_ended(); + } + + break; /* stack frame printed */ + } + else if (ip < cur_mmap_cache->start_addr) + upper = mid - 1; + + else + lower = mid + 1; + + } + if (lower > upper){ + tprintf(" > Unmapped_memory_area:0x%lx\n", ip); + line_ended(); + } + + ret_val = unw_step(&cursor); + + if (++stack_depth > 255) { + /* guard against bad unwind info in old libraries... */ + perror_msg("libunwind warning: too deeply nested---assuming bogus unwind\n"); + break; + } + } while (ret_val > 0); +} -- 1.7.9.5 |
From: Denys V. <dvl...@re...> - 2013-07-08 08:39:20
|
On 07/08/2013 09:24 AM, Luca Clementi wrote: > This patch prints the stack trace of the traced process after > each system call when using -k flag. It uses libunwind to > unwind the stack and to obtain the function name pointed by > the IP. > > Tested on Ubuntu 12.04 64 with the distribution version of > libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind > form the source code. On Ubuntu you need the libunwind and > libunwind-dev package to compile the stack trace feature in > the code. > > Some of this code was originally taken from strace-plus of > Philip J. Guo. > > v2: > fixed several identation issues > removed a strlen in alloc_mmap_cache (Denys) > reload mmap_cache after execv > fixed a bug in libunwind_backtrace binary search (Masatake) > check return value of _UPT_create > use die_out_of_memory after Xalloc > use exiting instead of !entering > created a new separate unwind.c file > removed include from defs.h > added --with-libunwind to autoconf script > cleaned up defs.h from several inclusions > --- > Makefile.am | 10 ++- > configure.ac | 57 +++++++++++++++ > defs.h | 45 ++++++++++++ > mem.c | 7 ++ > process.c | 8 +++ > strace.c | 43 ++++++++++++ > syscall.c | 9 +++ > unwind.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 8 files changed, 395 insertions(+), 1 deletion(-) > create mode 100644 unwind.c > > diff --git a/Makefile.am b/Makefile.am > index 9d611f3..c8ad026 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -12,7 +12,7 @@ ARCH = @arch@ > > ACLOCAL_AMFLAGS = -I m4 > AM_CFLAGS = $(WARN_CFLAGS) > -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) > +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) > > strace_SOURCES = \ > bjm.c \ > @@ -43,6 +43,14 @@ strace_SOURCES = \ > util.c \ > vsprintf.c > > + > +if LIB_UNWIND > +strace_SOURCES += unwind.c > +strace_LDADD = $(libunwind_LIBS) > +AM_LDFLAGS = $(libunwind_LDFLAGS) > +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 03e49fe..526fbda 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> > # include <asm/sigcontext.h> > #endif]) > > + > +dnl stack trace with libunwind > +AC_ARG_WITH([libunwind], > + [AS_HELP_STRING([--with-libunwind], > + [libunwind is used to display system call stacktrace])], > + [case "${withval}" in > + (yes|no) enable_libunwind=$withval;; > + (*) enable_libunwind=yes > + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" > + libunwind_LDFLAGS="-L${withval}/lib" ;; > + esac], > + [enable_libunwind=maybe]) > + > +AS_IF([test "x$enable_libunwind" != xno], > + [saved_CPPFLAGS="${CPPFLAGS}" > + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" > + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) > + CPPFLAGS="${saved_CPPFLAGS}" ]) > + > +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && > + test "x$ac_cv_header_libunwind_h" = "xyes"; then > + > + dnl code is taken from ltrace > + case "${host_cpu}" in > + arm*|sa110) UNWIND_ARCH="arm" ;; > + i?86) UNWIND_ARCH="x86" ;; > + powerpc) UNWIND_ARCH="ppc32" ;; > + powerpc64) UNWIND_ARCH="ppc64" ;; > + mips*) UNWIND_ARCH="mips" ;; > + *) UNWIND_ARCH="${host_cpu}" ;; > + esac > + > + saved_LDFLAGS="${LDFLAGS}" > + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" > + AC_CHECK_LIB([unwind], [backtrace], > + [libunwind_LIBS="-lunwind"], > + [AC_MSG_FAILURE([Unable to find libunwind])]) > + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], > + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-generic])], > + [$libunwind_LIBS]) > + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], > + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], > + [$libunwind_LIBS]) > + LDFLAGS="${saved_LDFLAGS}" > + > + dnl we have all the dependencies we need we can activate stack tracing > + AC_SUBST(libunwind_LIBS) > + AC_SUBST(libunwind_LDFLAGS) > + AC_SUBST(libunwind_CPPFLAGS) > + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) > + AC_MSG_NOTICE([Enabled stack tracing]) > +fi > +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) > + > + > AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) > > AC_CHECK_DECLS([sys_errlist]) > diff --git a/defs.h b/defs.h > index 64cfc8d..b314622 100644 > --- a/defs.h > +++ b/defs.h > @@ -405,6 +405,33 @@ typedef struct ioctlent { > unsigned long code; > } struct_ioctlent; > > + > +#ifdef LIB_UNWIND > + > +/* keep 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; > +}; > + > + > +/* if this is true do the stack trace for every system call */ > +extern bool use_libunwind; > +#endif > + > /* Trace Control Block */ > struct tcb { > int flags; /* See below for TCB_ values */ > @@ -430,6 +457,12 @@ struct tcb { > struct timeval etime; /* Syscall entry time */ > /* Support for tracing forked processes: */ > long inst[2]; /* Saved clone args (badly named) */ > + > +#ifdef LIB_UNWIND > + struct mmap_cache_t* mmap_cache; > + int mmap_cache_size; > + struct UPT_info* libunwind_ui; > +#endif > }; > > /* TCB flags */ > @@ -721,6 +754,18 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); > extern void tv_mul(struct timeval *, struct timeval *, int); > extern void tv_div(struct timeval *, struct timeval *, int); > > +#ifdef LIB_UNWIND > +/** > + * print stack (-k flag) memory allocation and deallocation > + */ > +extern void alloc_mmap_cache(struct tcb* tcp); > +extern void delete_mmap_cache(struct tcb* tcp); > +extern void print_stacktrace(struct tcb* tcp); > +#else > +# define alloc_mmap_cache(tcp) ((void)0) > +# define delete_mmap_cache(tcp) ((void)0) > +#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 ef273c7..a82b747 100644 > --- a/mem.c > +++ b/mem.c > @@ -175,6 +175,9 @@ static int > print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) > { > if (entering(tcp)) { > + if (use_libunwind) > + delete_mmap_cache(tcp); > + > /* addr */ > if (!u_arg[0]) > tprints("NULL, "); > @@ -304,6 +307,8 @@ sys_munmap(struct tcb *tcp) > tprintf("%#lx, %lu", > tcp->u_arg[0], tcp->u_arg[1]); > } > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > return 0; > } > > @@ -315,6 +320,8 @@ sys_mprotect(struct tcb *tcp) > tcp->u_arg[0], tcp->u_arg[1]); > printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); > } > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > return 0; > } > > diff --git a/process.c b/process.c > index e2fa25b..306aa23 100644 > --- a/process.c > +++ b/process.c > @@ -992,6 +992,10 @@ sys_execve(struct tcb *tcp) > tprints("]"); > } > } > + > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > + > return 0; > } > > @@ -1209,6 +1213,10 @@ sys_waitid(struct tcb *tcp) > printrusage(tcp, tcp->u_arg[4]); > } > } > + > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > + > return 0; > } > > diff --git a/strace.c b/strace.c > index a489db4..5956da6 100644 > --- a/strace.c > +++ b/strace.c > @@ -50,6 +50,13 @@ extern char **environ; > extern int optind; > extern char *optarg; > > +#ifdef LIB_UNWIND > +# include <libunwind-ptrace.h> > + > +/* if this is true do the stack trace for every system call */ > +bool use_libunwind = false; > +unw_addr_space_t libunwind_as; > +#endif > > #if defined __NR_tkill > # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) > @@ -231,6 +238,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 LIB_UNWIND > +"-k obtain stack trace between each syscall\n\ > +" > +#endif > /* ancient, no one should use it > -F -- attempt to follow vforks (deprecated, use -f)\n\ > */ > @@ -685,6 +696,15 @@ alloctcb(int pid) > #if SUPPORTED_PERSONALITIES > 1 > tcp->currpers = current_personality; > #endif > + > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + tcp->libunwind_ui = _UPT_create(tcp->pid); > + if (!tcp->libunwind_ui) > + die_out_of_memory(); > + } > +#endif > + > nprocs++; > if (debug_flag) > fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); > @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) > if (printing_tcp == tcp) > printing_tcp = NULL; > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + delete_mmap_cache(tcp); > + _UPT_destroy(tcp->libunwind_ui); > + } > +#endif > memset(tcp, 0, sizeof(*tcp)); > } > > @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) > qualify("signal=all"); > while ((c = getopt(argc, argv, > "+b:cCdfFhiqrtTvVxyz" > +#ifdef LIB_UNWIND > + "k" > +#endif > "D" > "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { > switch (c) { > @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) > case 'u': > username = strdup(optarg); > break; > +#ifdef LIB_UNWIND > + case 'k': > + use_libunwind = true; > + break; > +#endif > case 'E': > if (putenv(optarg) < 0) > die_out_of_memory(); > @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) > error_msg_and_die("-D and -p are mutually exclusive"); > } > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); > + if (!libunwind_as) { > + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); > + } > + } > +#endif > + > if (!followfork) > followfork = optF; > > diff --git a/syscall.c b/syscall.c > index f524b13..dee4c61 100644 > --- a/syscall.c > +++ b/syscall.c > @@ -2684,6 +2684,15 @@ trace_syscall_exiting(struct tcb *tcp) > dumpio(tcp); > line_ended(); > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + if (!tcp->mmap_cache) { > + alloc_mmap_cache(tcp); > + } > + print_stacktrace(tcp); > + } > +#endif > + > ret: > tcp->flags &= ~TCB_INSYSCALL; > return 0; > diff --git a/unwind.c b/unwind.c > new file mode 100644 > index 0000000..b7bd8ba > --- /dev/null > +++ b/unwind.c > @@ -0,0 +1,217 @@ > +/* > + * Copyright (c) 2013 Luca Clementi <luc...@gm...> > + * > + * 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 <libunwind.h> > + > + > +extern unw_addr_space_t libunwind_as; > +/* > + * 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) > +{ > + /* start with a small dynamically-allocated array and then expand it */ > + int cur_array_size = 10; > + char filename[sizeof ("/proc/0123456789/maps")]; > + struct mmap_cache_t* cache_head = malloc(cur_array_size * sizeof(*cache_head)); > + if (!cache_head) > + die_out_of_memory(); > + > + sprintf(filename, "/proc/%d/maps", tcp->pid); > + > + FILE* f = fopen(filename, "r"); > + if (!f) > + perror_msg_and_die("Can't open %s", filename); > + char s[300]; > + while (fgets(s, sizeof(s), f) != NULL) { > + unsigned long start_addr, end_addr, mmap_offset; > + char binary_path[512]; > + binary_path[0] = '\0'; // 'reset' it just to be paranoid > + > + sscanf(s, "%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("Unrecognized maps file format %s", filename); > + > + struct mmap_cache_t* 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) { > + struct mmap_cache_t* 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(f); > + tcp->mmap_cache = cache_head; > +} > + > +/* deleting the cache */ > +void > +delete_mmap_cache(struct tcb* tcp) > +{ > + int i; > + for (i = 0; i < tcp->mmap_cache_size; i++) { > + free(tcp->mmap_cache[i].binary_filename); > + } > + free(tcp->mmap_cache); > + tcp->mmap_cache = NULL; > + tcp->mmap_cache_size = 0; > +} > + > + > + > +/* > + * use libunwind to unwind the stack and print a backtrace > + * > + * Pre-condition: tcp->mmap_cache is already initialized > + */ > +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 */ > + int lower, upper, mid; > + int symbol_name_size = 40; > + char * symbol_name; > + struct mmap_cache_t* cur_mmap_cache; > + unsigned long true_offset; > + > + symbol_name = malloc(symbol_name_size); > + if (!symbol_name) > + die_out_of_memory(); Looks like you leak symbol_name. > + 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_and_die("Can't walk the stack of process %d", > + tcp->pid); This can happen if process died while we play with it. Shouldn't be fatal (so, _and_die is wrong). > + > + lower = 0; > + upper = tcp->mmap_cache_size - 1; > + > + > + while (lower <= upper) { > + /* find the mmap_cache and print the stack frame */ > + mid = (int)((upper + lower) / 2); Believe it or not, "int_var / 2" uses divide insn (!) if compiled by gcc -Os on x86: int main(int argc, char **argv) { return argc/2; } compiles to movl %edi, %eax movl $2, %ecx cltd idivl %ecx ret Use unsigned for lower, upper, and mid... And maybe make some noise about it here: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30354 since the fix for it is trivial and it's been 5 years since I posted it to gcc BZ. > + cur_mmap_cache = &tcp->mmap_cache[mid]; > + > + if (ip >= cur_mmap_cache->start_addr && > + ip < cur_mmap_cache->end_addr) { > + > + do { > + 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(); Please fix the if () style. > + } while (1); do {} while (1)? Use "while (1) {}" or "for (;;) {}" (some people like the latter, they read it as "forever") > + 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); > + line_ended(); > + } > + else{ > + tprintf(" > %s() [0x%lx]\n", cur_mmap_cache->binary_filename, true_offset); > + line_ended(); > + } > + > + break; /* stack frame printed */ > + } > + else if (ip < cur_mmap_cache->start_addr) > + upper = mid - 1; > + > + else > + lower = mid + 1; > + > + } > + if (lower > upper){ style > + tprintf(" > Unmapped_memory_area:0x%lx\n", ip); > + line_ended(); > + } > + > + ret_val = unw_step(&cursor); > + > + if (++stack_depth > 255) { > + /* guard against bad unwind info in old libraries... */ > + perror_msg("libunwind warning: too deeply nested---assuming bogus unwind\n"); Very deep callchains aren't hard to come by (recursion). So it shouldn't be a "big" warning which goes to stderr, emit it using tprintf(). > + break; > + } > + } while (ret_val > 0); > +} > |
From: Masatake Y. <ya...@re...> - 2013-07-08 12:02:40
|
I've tested on Fedora 19. It works fine. BTW, I found that libunwind doesn't refer deubginfo file. So the patched strace cannot resolve symbols well. Porting debuginfo resolver from gdb to libunwind will be nice. Masatake YAMATO > This patch prints the stack trace of the traced process after > each system call when using -k flag. It uses libunwind to > unwind the stack and to obtain the function name pointed by > the IP. > > Tested on Ubuntu 12.04 64 with the distribution version of > libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind > form the source code. On Ubuntu you need the libunwind and > libunwind-dev package to compile the stack trace feature in > the code. > > Some of this code was originally taken from strace-plus of > Philip J. Guo. > > v2: > fixed several identation issues > removed a strlen in alloc_mmap_cache (Denys) > reload mmap_cache after execv > fixed a bug in libunwind_backtrace binary search (Masatake) > check return value of _UPT_create > use die_out_of_memory after Xalloc > use exiting instead of !entering > created a new separate unwind.c file > removed include from defs.h > added --with-libunwind to autoconf script > cleaned up defs.h from several inclusions > --- > Makefile.am | 10 ++- > configure.ac | 57 +++++++++++++++ > defs.h | 45 ++++++++++++ > mem.c | 7 ++ > process.c | 8 +++ > strace.c | 43 ++++++++++++ > syscall.c | 9 +++ > unwind.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 8 files changed, 395 insertions(+), 1 deletion(-) > create mode 100644 unwind.c > > diff --git a/Makefile.am b/Makefile.am > index 9d611f3..c8ad026 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -12,7 +12,7 @@ ARCH = @arch@ > > ACLOCAL_AMFLAGS = -I m4 > AM_CFLAGS = $(WARN_CFLAGS) > -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) > +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) > > strace_SOURCES = \ > bjm.c \ > @@ -43,6 +43,14 @@ strace_SOURCES = \ > util.c \ > vsprintf.c > > + > +if LIB_UNWIND > +strace_SOURCES += unwind.c > +strace_LDADD = $(libunwind_LIBS) > +AM_LDFLAGS = $(libunwind_LDFLAGS) > +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 03e49fe..526fbda 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> > # include <asm/sigcontext.h> > #endif]) > > + > +dnl stack trace with libunwind > +AC_ARG_WITH([libunwind], > + [AS_HELP_STRING([--with-libunwind], > + [libunwind is used to display system call stacktrace])], > + [case "${withval}" in > + (yes|no) enable_libunwind=$withval;; > + (*) enable_libunwind=yes > + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" > + libunwind_LDFLAGS="-L${withval}/lib" ;; > + esac], > + [enable_libunwind=maybe]) > + > +AS_IF([test "x$enable_libunwind" != xno], > + [saved_CPPFLAGS="${CPPFLAGS}" > + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" > + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) > + CPPFLAGS="${saved_CPPFLAGS}" ]) > + > +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && > + test "x$ac_cv_header_libunwind_h" = "xyes"; then > + > + dnl code is taken from ltrace > + case "${host_cpu}" in > + arm*|sa110) UNWIND_ARCH="arm" ;; > + i?86) UNWIND_ARCH="x86" ;; > + powerpc) UNWIND_ARCH="ppc32" ;; > + powerpc64) UNWIND_ARCH="ppc64" ;; > + mips*) UNWIND_ARCH="mips" ;; > + *) UNWIND_ARCH="${host_cpu}" ;; > + esac > + > + saved_LDFLAGS="${LDFLAGS}" > + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" > + AC_CHECK_LIB([unwind], [backtrace], > + [libunwind_LIBS="-lunwind"], > + [AC_MSG_FAILURE([Unable to find libunwind])]) > + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], > + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-generic])], > + [$libunwind_LIBS]) > + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], > + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], > + [$libunwind_LIBS]) > + LDFLAGS="${saved_LDFLAGS}" > + > + dnl we have all the dependencies we need we can activate stack tracing > + AC_SUBST(libunwind_LIBS) > + AC_SUBST(libunwind_LDFLAGS) > + AC_SUBST(libunwind_CPPFLAGS) > + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) > + AC_MSG_NOTICE([Enabled stack tracing]) > +fi > +AM_CONDITIONAL([LIB_UNWIND], [test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes"]) > + > + > AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) > > AC_CHECK_DECLS([sys_errlist]) > diff --git a/defs.h b/defs.h > index 64cfc8d..b314622 100644 > --- a/defs.h > +++ b/defs.h > @@ -405,6 +405,33 @@ typedef struct ioctlent { > unsigned long code; > } struct_ioctlent; > > + > +#ifdef LIB_UNWIND > + > +/* keep 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; > +}; > + > + > +/* if this is true do the stack trace for every system call */ > +extern bool use_libunwind; > +#endif > + > /* Trace Control Block */ > struct tcb { > int flags; /* See below for TCB_ values */ > @@ -430,6 +457,12 @@ struct tcb { > struct timeval etime; /* Syscall entry time */ > /* Support for tracing forked processes: */ > long inst[2]; /* Saved clone args (badly named) */ > + > +#ifdef LIB_UNWIND > + struct mmap_cache_t* mmap_cache; > + int mmap_cache_size; > + struct UPT_info* libunwind_ui; > +#endif > }; > > /* TCB flags */ > @@ -721,6 +754,18 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); > extern void tv_mul(struct timeval *, struct timeval *, int); > extern void tv_div(struct timeval *, struct timeval *, int); > > +#ifdef LIB_UNWIND > +/** > + * print stack (-k flag) memory allocation and deallocation > + */ > +extern void alloc_mmap_cache(struct tcb* tcp); > +extern void delete_mmap_cache(struct tcb* tcp); > +extern void print_stacktrace(struct tcb* tcp); > +#else > +# define alloc_mmap_cache(tcp) ((void)0) > +# define delete_mmap_cache(tcp) ((void)0) > +#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 ef273c7..a82b747 100644 > --- a/mem.c > +++ b/mem.c > @@ -175,6 +175,9 @@ static int > print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) > { > if (entering(tcp)) { > + if (use_libunwind) > + delete_mmap_cache(tcp); > + > /* addr */ > if (!u_arg[0]) > tprints("NULL, "); > @@ -304,6 +307,8 @@ sys_munmap(struct tcb *tcp) > tprintf("%#lx, %lu", > tcp->u_arg[0], tcp->u_arg[1]); > } > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > return 0; > } > > @@ -315,6 +320,8 @@ sys_mprotect(struct tcb *tcp) > tcp->u_arg[0], tcp->u_arg[1]); > printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); > } > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > return 0; > } > > diff --git a/process.c b/process.c > index e2fa25b..306aa23 100644 > --- a/process.c > +++ b/process.c > @@ -992,6 +992,10 @@ sys_execve(struct tcb *tcp) > tprints("]"); > } > } > + > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > + > return 0; > } > > @@ -1209,6 +1213,10 @@ sys_waitid(struct tcb *tcp) > printrusage(tcp, tcp->u_arg[4]); > } > } > + > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > + > return 0; > } > > diff --git a/strace.c b/strace.c > index a489db4..5956da6 100644 > --- a/strace.c > +++ b/strace.c > @@ -50,6 +50,13 @@ extern char **environ; > extern int optind; > extern char *optarg; > > +#ifdef LIB_UNWIND > +# include <libunwind-ptrace.h> > + > +/* if this is true do the stack trace for every system call */ > +bool use_libunwind = false; > +unw_addr_space_t libunwind_as; > +#endif > > #if defined __NR_tkill > # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) > @@ -231,6 +238,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 LIB_UNWIND > +"-k obtain stack trace between each syscall\n\ > +" > +#endif > /* ancient, no one should use it > -F -- attempt to follow vforks (deprecated, use -f)\n\ > */ > @@ -685,6 +696,15 @@ alloctcb(int pid) > #if SUPPORTED_PERSONALITIES > 1 > tcp->currpers = current_personality; > #endif > + > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + tcp->libunwind_ui = _UPT_create(tcp->pid); > + if (!tcp->libunwind_ui) > + die_out_of_memory(); > + } > +#endif > + > nprocs++; > if (debug_flag) > fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); > @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) > if (printing_tcp == tcp) > printing_tcp = NULL; > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + delete_mmap_cache(tcp); > + _UPT_destroy(tcp->libunwind_ui); > + } > +#endif > memset(tcp, 0, sizeof(*tcp)); > } > > @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) > qualify("signal=all"); > while ((c = getopt(argc, argv, > "+b:cCdfFhiqrtTvVxyz" > +#ifdef LIB_UNWIND > + "k" > +#endif > "D" > "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { > switch (c) { > @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) > case 'u': > username = strdup(optarg); > break; > +#ifdef LIB_UNWIND > + case 'k': > + use_libunwind = true; > + break; > +#endif > case 'E': > if (putenv(optarg) < 0) > die_out_of_memory(); > @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) > error_msg_and_die("-D and -p are mutually exclusive"); > } > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); > + if (!libunwind_as) { > + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); > + } > + } > +#endif > + > if (!followfork) > followfork = optF; > > diff --git a/syscall.c b/syscall.c > index f524b13..dee4c61 100644 > --- a/syscall.c > +++ b/syscall.c > @@ -2684,6 +2684,15 @@ trace_syscall_exiting(struct tcb *tcp) > dumpio(tcp); > line_ended(); > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + if (!tcp->mmap_cache) { > + alloc_mmap_cache(tcp); > + } > + print_stacktrace(tcp); > + } > +#endif > + > ret: > tcp->flags &= ~TCB_INSYSCALL; > return 0; > diff --git a/unwind.c b/unwind.c > new file mode 100644 > index 0000000..b7bd8ba > --- /dev/null > +++ b/unwind.c > @@ -0,0 +1,217 @@ > +/* > + * Copyright (c) 2013 Luca Clementi <luc...@gm...> > + * > + * 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 <libunwind.h> > + > + > +extern unw_addr_space_t libunwind_as; > +/* > + * 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) > +{ > + /* start with a small dynamically-allocated array and then expand it */ > + int cur_array_size = 10; > + char filename[sizeof ("/proc/0123456789/maps")]; > + struct mmap_cache_t* cache_head = malloc(cur_array_size * sizeof(*cache_head)); > + if (!cache_head) > + die_out_of_memory(); > + > + sprintf(filename, "/proc/%d/maps", tcp->pid); > + > + FILE* f = fopen(filename, "r"); > + if (!f) > + perror_msg_and_die("Can't open %s", filename); > + char s[300]; > + while (fgets(s, sizeof(s), f) != NULL) { > + unsigned long start_addr, end_addr, mmap_offset; > + char binary_path[512]; > + binary_path[0] = '\0'; // 'reset' it just to be paranoid > + > + sscanf(s, "%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("Unrecognized maps file format %s", filename); > + > + struct mmap_cache_t* 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) { > + struct mmap_cache_t* 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(f); > + tcp->mmap_cache = cache_head; > +} > + > +/* deleting the cache */ > +void > +delete_mmap_cache(struct tcb* tcp) > +{ > + int i; > + for (i = 0; i < tcp->mmap_cache_size; i++) { > + free(tcp->mmap_cache[i].binary_filename); > + } > + free(tcp->mmap_cache); > + tcp->mmap_cache = NULL; > + tcp->mmap_cache_size = 0; > +} > + > + > + > +/* > + * use libunwind to unwind the stack and print a backtrace > + * > + * Pre-condition: tcp->mmap_cache is already initialized > + */ > +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 */ > + int lower, upper, mid; > + int symbol_name_size = 40; > + char * symbol_name; > + struct mmap_cache_t* cur_mmap_cache; > + unsigned long true_offset; > + > + 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_and_die("Can't walk the stack of process %d", > + tcp->pid); > + > + lower = 0; > + upper = tcp->mmap_cache_size - 1; > + > + > + while (lower <= upper) { > + /* find the mmap_cache and print the stack frame */ > + mid = (int)((upper + lower) / 2); > + cur_mmap_cache = &tcp->mmap_cache[mid]; > + > + if (ip >= cur_mmap_cache->start_addr && > + ip < cur_mmap_cache->end_addr) { > + > + do { > + 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(); > + } while (1); > + > + 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); > + line_ended(); > + } > + else{ > + tprintf(" > %s() [0x%lx]\n", cur_mmap_cache->binary_filename, true_offset); > + line_ended(); > + } > + > + break; /* stack frame printed */ > + } > + else if (ip < cur_mmap_cache->start_addr) > + upper = mid - 1; > + > + else > + lower = mid + 1; > + > + } > + if (lower > upper){ > + tprintf(" > Unmapped_memory_area:0x%lx\n", ip); > + line_ended(); > + } > + > + ret_val = unw_step(&cursor); > + > + if (++stack_depth > 255) { > + /* guard against bad unwind info in old libraries... */ > + perror_msg("libunwind warning: too deeply nested---assuming bogus unwind\n"); > + break; > + } > + } while (ret_val > 0); > +} > -- > 1.7.9.5 > |
From: Dmitry V. L. <ld...@al...> - 2013-07-08 22:08:32
|
On Mon, Jul 08, 2013 at 12:24:14AM -0700, Luca Clementi wrote: [...] > --- a/configure.ac > +++ b/configure.ac > @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> > # include <asm/sigcontext.h> > #endif]) > > + > +dnl stack trace with libunwind > +AC_ARG_WITH([libunwind], > + [AS_HELP_STRING([--with-libunwind], > + [libunwind is used to display system call stacktrace])], > + [case "${withval}" in > + (yes|no) enable_libunwind=$withval;; > + (*) enable_libunwind=yes > + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" > + libunwind_LDFLAGS="-L${withval}/lib" ;; > + esac], > + [enable_libunwind=maybe]) If the option is called --with-libunwind, then the options's argument variable should be called with_libunwind. BTW, do you really need this libunwind_CPPFLAGS/libunwind_LDFLAGS stuff? Wouldn't a simple tristate (yes|no|check) be enough? > +AS_IF([test "x$enable_libunwind" != xno], > + [saved_CPPFLAGS="${CPPFLAGS}" > + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" > + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) > + CPPFLAGS="${saved_CPPFLAGS}" ]) > + > +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && > + test "x$ac_cv_header_libunwind_h" = "xyes"; then If --with-libunwind was given and one of these header files is not available, configure should fail. > + dnl code is taken from ltrace > + case "${host_cpu}" in > + arm*|sa110) UNWIND_ARCH="arm" ;; > + i?86) UNWIND_ARCH="x86" ;; > + powerpc) UNWIND_ARCH="ppc32" ;; > + powerpc64) UNWIND_ARCH="ppc64" ;; > + mips*) UNWIND_ARCH="mips" ;; > + *) UNWIND_ARCH="${host_cpu}" ;; > + esac > + > + saved_LDFLAGS="${LDFLAGS}" > + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" > + AC_CHECK_LIB([unwind], [backtrace], > + [libunwind_LIBS="-lunwind"], > + [AC_MSG_FAILURE([Unable to find libunwind])]) > + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], > + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-generic])], > + [$libunwind_LIBS]) > + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], > + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], > + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], > + [$libunwind_LIBS]) > + LDFLAGS="${saved_LDFLAGS}" If --with-libunwind was not given and one of these symbols is not available, configure should not fail. -- ldv |
From: Luca C. <luc...@gm...> - 2013-07-09 05:28:43
|
On Mon, Jul 8, 2013 at 3:08 PM, Dmitry V. Levin <ld...@al...> wrote: > On Mon, Jul 08, 2013 at 12:24:14AM -0700, Luca Clementi wrote: > [...] >> --- a/configure.ac >> +++ b/configure.ac >> @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> >> # include <asm/sigcontext.h> >> #endif]) >> >> + >> +dnl stack trace with libunwind >> +AC_ARG_WITH([libunwind], >> + [AS_HELP_STRING([--with-libunwind], >> + [libunwind is used to display system call stacktrace])], >> + [case "${withval}" in >> + (yes|no) enable_libunwind=$withval;; >> + (*) enable_libunwind=yes >> + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" >> + libunwind_LDFLAGS="-L${withval}/lib" ;; >> + esac], >> + [enable_libunwind=maybe]) > > If the option is called --with-libunwind, then the options's argument > variable should be called with_libunwind. Sorry that's a leftover from previous tests. > BTW, do you really need this > libunwind_CPPFLAGS/libunwind_LDFLAGS stuff? Wouldn't a simple tristate > (yes|no|check) be enough? I did it because I thought it was the "standard" (if there is such a thing) way of using the --with flag. In this way if you have libunwind compiled libunwind in your own path you can use only ./configure --with-libunwind=/some/odd/path instead of having to set both the CPPFLAGS and the LDFLAGS. I did some testing on Centos6 (where there is no libunwind). >> +AS_IF([test "x$enable_libunwind" != xno], >> + [saved_CPPFLAGS="${CPPFLAGS}" >> + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" >> + AC_CHECK_HEADERS([libunwind-ptrace.h libunwind.h]) >> + CPPFLAGS="${saved_CPPFLAGS}" ]) >> + >> +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && >> + test "x$ac_cv_header_libunwind_h" = "xyes"; then > > If --with-libunwind was given and one of these header files is not > available, configure should fail. Good catch, I'll get it fixed. That's why I have the maybe option above. >> + dnl code is taken from ltrace >> + case "${host_cpu}" in >> + arm*|sa110) UNWIND_ARCH="arm" ;; >> + i?86) UNWIND_ARCH="x86" ;; >> + powerpc) UNWIND_ARCH="ppc32" ;; >> + powerpc64) UNWIND_ARCH="ppc64" ;; >> + mips*) UNWIND_ARCH="mips" ;; >> + *) UNWIND_ARCH="${host_cpu}" ;; >> + esac >> + >> + saved_LDFLAGS="${LDFLAGS}" >> + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" >> + AC_CHECK_LIB([unwind], [backtrace], >> + [libunwind_LIBS="-lunwind"], >> + [AC_MSG_FAILURE([Unable to find libunwind])]) >> + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], >> + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], >> + [AC_MSG_FAILURE([Unable to find libunwind-generic])], >> + [$libunwind_LIBS]) >> + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], >> + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], >> + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], >> + [$libunwind_LIBS]) >> + LDFLAGS="${saved_LDFLAGS}" > > If --with-libunwind was not given and one of these symbols is not > available, configure should not fail. But I enter this "if" block only if I find the headers. My default is: if no option is given and the headers are found it's like if you use --with-libunwind (then you can use --with-libunwind=no to disable that). Is that wrong? Luca |
From: Dmitry V. L. <ld...@al...> - 2013-07-09 12:47:44
|
On Mon, Jul 08, 2013 at 10:28:33PM -0700, Luca Clementi wrote: > On Mon, Jul 8, 2013 at 3:08 PM, Dmitry V. Levin <ld...@al...> wrote: > > On Mon, Jul 08, 2013 at 12:24:14AM -0700, Luca Clementi wrote: > > [...] > >> --- a/configure.ac > >> +++ b/configure.ac > >> @@ -261,6 +261,63 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> > >> # include <asm/sigcontext.h> > >> #endif]) > >> > >> + > >> +dnl stack trace with libunwind > >> +AC_ARG_WITH([libunwind], > >> + [AS_HELP_STRING([--with-libunwind], > >> + [libunwind is used to display system call stacktrace])], > >> + [case "${withval}" in > >> + (yes|no) enable_libunwind=$withval;; > >> + (*) enable_libunwind=yes > >> + libunwind_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" > >> + libunwind_LDFLAGS="-L${withval}/lib" ;; > >> + esac], > >> + [enable_libunwind=maybe]) > > > > If the option is called --with-libunwind, then the options's argument > > variable should be called with_libunwind. > > Sorry that's a leftover from previous tests. Also, - there should be no need to use ${AM_CPPFLAGS} in configure.ac; - autoconf documentation suggest using "check" instead of "maybe"; - with_libunwind=$withval is a no-op. > >> + saved_LDFLAGS="${LDFLAGS}" > >> + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" > >> + AC_CHECK_LIB([unwind], [backtrace], > >> + [libunwind_LIBS="-lunwind"], > >> + [AC_MSG_FAILURE([Unable to find libunwind])]) > >> + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], > >> + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], > >> + [AC_MSG_FAILURE([Unable to find libunwind-generic])], > >> + [$libunwind_LIBS]) > >> + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], > >> + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], > >> + [AC_MSG_FAILURE([Unable to find libunwind-ptrace])], > >> + [$libunwind_LIBS]) > >> + LDFLAGS="${saved_LDFLAGS}" > > > > If --with-libunwind was not given and one of these symbols is not > > available, configure should not fail. > > But I enter this "if" block only if I find the headers. > My default is: if no option is given and the headers are found it's > like if you use --with-libunwind (then you can use --with-libunwind=no > to disable that). > Is that wrong? It's not obvious for me why presence of these two header files means --with-libunwind. I thought the default behaviour (for the case when neither --with-libunwind nor --without-libunwind is given) would be --without-libunwind if no suitable libunwind found, and --with-libunwind otherwise. -- ldv |
From: Luca C. <luc...@gm...> - 2013-07-20 05:49:33
|
On Mon, Jul 8, 2013 at 5:02 AM, Masatake YAMATO <ya...@re...> wrote: > I've tested on Fedora 19. It works fine. > > BTW, I found that libunwind doesn't refer deubginfo file. > So the patched strace cannot resolve symbols well. > Porting debuginfo resolver from gdb to libunwind will be nice. > > Masatake YAMATO > Hey Masatake, did you use the --enable-debug-frame flag to compile it? I think that it is disable by default. Luca |
From: Masatake Y. <ya...@re...> - 2013-07-22 02:56:08
|
Hi, > On Mon, Jul 8, 2013 at 5:02 AM, Masatake YAMATO <ya...@re...> wrote: >> I've tested on Fedora 19. It works fine. >> >> BTW, I found that libunwind doesn't refer deubginfo file. >> So the patched strace cannot resolve symbols well. >> Porting debuginfo resolver from gdb to libunwind will be nice. >> >> Masatake YAMATO >> > > Hey Masatake, > did you use the --enable-debug-frame flag to compile it? > I think that it is disable by default. (I'm using /usr/bin/ls command for testing. Dwarf data is in separated file /usr/lib/debug/usr/bin/ls.debug.) As you wrote it was disabled. I didn't try the option yet. However, as far as seeing the output of objdump running against ls and ls.debug, there is no .debug_frame section. So I guess the function to read the separate debuginfo file is needed in libunwind. As far as reading configure.ac in libunwind, the option is for ARM architecture. Anyway I will try the option. Thanks. I'm working on modifying libunwind to read the separate debuginfo file. In my small test, my patch works: static functions defined in ls are shown in stacktrace of strace+your patch. > Luca Masatake YAMATO |
From: Luca C. <luc...@gm...> - 2013-07-19 06:40:56
|
This patch prints the stack trace of the traced process after each system call when using -k flag. It uses libunwind to unwind the stack and to obtain the function name pointed by the IP. Tested on Ubuntu 12.04 64 with the distribution version of libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind form the source code. This code was originally taken from strace-plus of Philip J. Guo. v3: adjusted several datatype to use unsigned (Denys) fix sybol_name leak (Denys) workaround for libunwind bug with libpthread (unw_step does not return 1 when at the end of the stack) fix several problem in in autoconf script (Dmitry) use PATH_MAX for DSO file path buffer v2: fixed several identation issues removed a strlen in alloc_mmap_cache (Denys) reload mmap_cache after execv fixed a bug in libunwind_backtrace binary search (Masatake) check return value of _UPT_create use die_out_of_memory after Xalloc use exiting instead of !entering created a new separate unwind.c file removed include from defs.h added --with-libunwind to autoconf script cleaned up defs.h from several inclusions --- Makefile.am | 10 ++- configure.ac | 79 ++++++++++++++++++++ defs.h | 42 +++++++++++ mem.c | 15 ++++ process.c | 14 ++++ strace.c | 43 +++++++++++ syscall.c | 9 +++ unwind.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 unwind.c diff --git a/Makefile.am b/Makefile.am index 9d611f3..c8ad026 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ ARCH = @arch@ ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = $(WARN_CFLAGS) -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) strace_SOURCES = \ bjm.c \ @@ -43,6 +43,14 @@ strace_SOURCES = \ util.c \ vsprintf.c + +if LIB_UNWIND +strace_SOURCES += unwind.c +strace_LDADD = $(libunwind_LIBS) +AM_LDFLAGS = $(libunwind_LDFLAGS) +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 b3b62e8..0f64a12 100644 --- a/configure.ac +++ b/configure.ac @@ -265,6 +265,85 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> # include <asm/sigcontext.h> #endif]) + +dnl stack trace with libunwind +AC_ARG_WITH([libunwind], + [AS_HELP_STRING([--with-libunwind], + [libunwind is used to display system call stacktrace])], + [case "${withval}" in + (yes|no) with_libunwind=$withval;; + (*) with_libunwind=yes + libunwind_CPPFLAGS="-I${withval}/include" + libunwind_LDFLAGS="-L${withval}/lib" ;; + esac], + [with_libunwind=check]) + +AS_IF([test "x$with_libunwind" != "xno"], + [saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" + AC_CHECK_HEADERS([libunwind-ptrace.h],[], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind-ptrace.h header]) + fi]) + AC_CHECK_HEADERS([libunwind.h],[], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind.h header]) + fi]) + CPPFLAGS="${saved_CPPFLAGS}" ]) + + + +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && + test "x$ac_cv_header_libunwind_h" = "xyes"; then + + dnl code is taken from ltrace + case "${host_cpu}" in + arm*|sa110) UNWIND_ARCH="arm" ;; + i?86) UNWIND_ARCH="x86" ;; + powerpc) UNWIND_ARCH="ppc32" ;; + powerpc64) UNWIND_ARCH="ppc64" ;; + mips*) UNWIND_ARCH="mips" ;; + *) UNWIND_ARCH="${host_cpu}" ;; + esac + + saved_LDFLAGS="${LDFLAGS}" + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" + AC_CHECK_LIB([unwind], [backtrace], + [libunwind_LIBS="-lunwind"], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind]) + fi]) + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind-generic]) + fi], + [$libunwind_LIBS]) + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind-ptrace]) + fi], + [$libunwind_LIBS]) + LDFLAGS="${saved_LDFLAGS}" + + dnl we have all the dependencies we need we can activate stack tracing + AC_SUBST(libunwind_LIBS) + AC_SUBST(libunwind_LDFLAGS) + AC_SUBST(libunwind_CPPFLAGS) +fi + +dnl enable libunwind +if test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes" && + eval test \"x\$ac_cv_lib_unwind_generic__U"${UNWIND_ARCH}"_create_addr_space\" == "xyes" && + test "x$ac_cv_lib_unwind_backtrace" == "xyes"; then + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) + AC_MSG_NOTICE([Enabled stack tracing]) + with_libunwind="yes" +fi +AM_CONDITIONAL([LIB_UNWIND], [test "x$with_libunwind" == "xyes"]) + + AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) AC_CHECK_DECLS([sys_errlist]) diff --git a/defs.h b/defs.h index 964636b..1b1fa8d 100644 --- a/defs.h +++ b/defs.h @@ -414,6 +414,33 @@ typedef struct ioctlent { unsigned long code; } struct_ioctlent; + +#ifdef LIB_UNWIND + +/* keep 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; +}; + + +/* if this is true do the stack trace for every system call */ +extern bool use_libunwind; +#endif + /* Trace Control Block */ struct tcb { int flags; /* See below for TCB_ values */ @@ -439,6 +466,12 @@ struct tcb { struct timeval etime; /* Syscall entry time */ /* Support for tracing forked processes: */ long inst[2]; /* Saved clone args (badly named) */ + +#ifdef LIB_UNWIND + struct mmap_cache_t* mmap_cache; + unsigned int mmap_cache_size; + struct UPT_info* libunwind_ui; +#endif }; /* TCB flags */ @@ -724,6 +757,15 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); extern void tv_mul(struct timeval *, struct timeval *, int); extern void tv_div(struct timeval *, struct timeval *, int); +#ifdef LIB_UNWIND +/** + * print stack (-k flag) memory allocation and deallocation + */ +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 ef273c7..b3c6abe 100644 --- a/mem.c +++ b/mem.c @@ -175,6 +175,11 @@ static int print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) { if (entering(tcp)) { +#ifdef LIB_UNWIND + if (use_libunwind) + delete_mmap_cache(tcp); +#endif + /* addr */ if (!u_arg[0]) tprints("NULL, "); @@ -304,6 +309,11 @@ sys_munmap(struct tcb *tcp) tprintf("%#lx, %lu", tcp->u_arg[0], tcp->u_arg[1]); } + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif return 0; } @@ -315,6 +325,11 @@ sys_mprotect(struct tcb *tcp) tcp->u_arg[0], tcp->u_arg[1]); printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); } + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif return 0; } diff --git a/process.c b/process.c index e2fa25b..e058bbb 100644 --- a/process.c +++ b/process.c @@ -992,6 +992,13 @@ sys_execve(struct tcb *tcp) tprints("]"); } } + + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif + return 0; } @@ -1209,6 +1216,13 @@ sys_waitid(struct tcb *tcp) printrusage(tcp, tcp->u_arg[4]); } } + + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif + return 0; } diff --git a/strace.c b/strace.c index 32a3f5e..5c46d7f 100644 --- a/strace.c +++ b/strace.c @@ -50,6 +50,13 @@ extern char **environ; extern int optind; extern char *optarg; +#ifdef LIB_UNWIND +# include <libunwind-ptrace.h> + +/* if this is true do the stack trace for every system call */ +bool use_libunwind = false; +unw_addr_space_t libunwind_as; +#endif #if defined __NR_tkill # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) @@ -231,6 +238,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 LIB_UNWIND +"-k obtain stack trace between each syscall\n\ +" +#endif /* ancient, no one should use it -F -- attempt to follow vforks (deprecated, use -f)\n\ */ @@ -685,6 +696,15 @@ alloctcb(int pid) #if SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif + +#ifdef LIB_UNWIND + if (use_libunwind) { + tcp->libunwind_ui = _UPT_create(tcp->pid); + if (!tcp->libunwind_ui) + die_out_of_memory(); + } +#endif + nprocs++; if (debug_flag) fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) if (printing_tcp == tcp) printing_tcp = NULL; +#ifdef LIB_UNWIND + if (use_libunwind) { + delete_mmap_cache(tcp); + _UPT_destroy(tcp->libunwind_ui); + } +#endif memset(tcp, 0, sizeof(*tcp)); } @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) qualify("signal=all"); while ((c = getopt(argc, argv, "+b:cCdfFhiqrtTvVxyz" +#ifdef LIB_UNWIND + "k" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) case 'u': username = strdup(optarg); break; +#ifdef LIB_UNWIND + case 'k': + use_libunwind = true; + break; +#endif case 'E': if (putenv(optarg) < 0) die_out_of_memory(); @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) error_msg_and_die("-D and -p are mutually exclusive"); } +#ifdef LIB_UNWIND + if (use_libunwind) { + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); + if (!libunwind_as) { + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); + } + } +#endif + if (!followfork) followfork = optF; diff --git a/syscall.c b/syscall.c index 1b49bb3..0c23e40 100644 --- a/syscall.c +++ b/syscall.c @@ -2683,6 +2683,15 @@ trace_syscall_exiting(struct tcb *tcp) dumpio(tcp); line_ended(); +#ifdef LIB_UNWIND + if (use_libunwind) { + if (!tcp->mmap_cache) { + alloc_mmap_cache(tcp); + } + print_stacktrace(tcp); + } +#endif + ret: tcp->flags &= ~TCB_INSYSCALL; return 0; diff --git a/unwind.c b/unwind.c new file mode 100644 index 0000000..5dc0175 --- /dev/null +++ b/unwind.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2013 Luca Clementi <luc...@gm...> + * + * 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.h> + + +extern unw_addr_space_t libunwind_as; +/* + * 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 = malloc(cur_array_size * sizeof(*cache_head)); + if (!cache_head) + die_out_of_memory(); + + sprintf(filename, "/proc/%d/maps", tcp->pid); + + FILE* f = fopen(filename, "r"); + if (!f) + perror_msg_and_die("Can't open %s", filename); + while (fgets(buffer, sizeof(buffer), f) != 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("Unrecognized maps file format %s", 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(f); + 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); + } + free(tcp->mmap_cache); + tcp->mmap_cache = NULL; + tcp->mmap_cache_size = 0; +} + + + +/* + * use libunwind to unwind the stack and print a backtrace + * + * Pre-condition: tcp->mmap_cache is already initialized + */ +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; + + 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); + + lower = 0; + upper = tcp->mmap_cache_size - 1; + + while (lower <= upper) { + /* find the mmap_cache and print the stack frame */ + mid = (unsigned int)((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); + line_ended(); + } 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 + */ + 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("libunwind warning: stack frame greater than 255 aborting backtracing\n"); + line_ended(); + break; + } + } while (ret_val > 0); +ret: + free(symbol_name); +} -- 1.7.9.5 |
From: Luca C. <luc...@gm...> - 2013-07-23 07:12:10
|
This patch prints the stack trace of the traced process after each system call when using -k flag. It uses libunwind to unwind the stack and to obtain the function name pointed by the IP. Tested on Ubuntu 12.04 64 with the distribution version of libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind form the source code. This code was originally taken from strace-plus of Philip J. Guo. v4: updated man page v3: adjusted several datatype to use unsigned (Denys) fix sybol_name leak (Denys) workaround for libunwind bug with libpthread (unw_step does not return 1 when at the end of the stack) fix several problem in in autoconf script (Dmitry) use PATH_MAX for DSO file path buffer v2: fixed several identation issues removed a strlen in alloc_mmap_cache (Denys) reload mmap_cache after execv fixed a bug in libunwind_backtrace binary search (Masatake) check return value of _UPT_create use die_out_of_memory after Xalloc use exiting instead of !entering created a new separate unwind.c file removed include from defs.h added --with-libunwind to autoconf script cleaned up defs.h from several inclusions --- Makefile.am | 10 ++- configure.ac | 79 ++++++++++++++++++++ defs.h | 42 +++++++++++ mem.c | 15 ++++ process.c | 14 ++++ strace.1 | 5 +- strace.c | 43 +++++++++++ syscall.c | 9 +++ unwind.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 unwind.c diff --git a/Makefile.am b/Makefile.am index 9d611f3..c8ad026 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ ARCH = @arch@ ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS = $(WARN_CFLAGS) -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) strace_SOURCES = \ bjm.c \ @@ -43,6 +43,14 @@ strace_SOURCES = \ util.c \ vsprintf.c + +if LIB_UNWIND +strace_SOURCES += unwind.c +strace_LDADD = $(libunwind_LIBS) +AM_LDFLAGS = $(libunwind_LDFLAGS) +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 b3b62e8..0f64a12 100644 --- a/configure.ac +++ b/configure.ac @@ -265,6 +265,85 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> # include <asm/sigcontext.h> #endif]) + +dnl stack trace with libunwind +AC_ARG_WITH([libunwind], + [AS_HELP_STRING([--with-libunwind], + [libunwind is used to display system call stacktrace])], + [case "${withval}" in + (yes|no) with_libunwind=$withval;; + (*) with_libunwind=yes + libunwind_CPPFLAGS="-I${withval}/include" + libunwind_LDFLAGS="-L${withval}/lib" ;; + esac], + [with_libunwind=check]) + +AS_IF([test "x$with_libunwind" != "xno"], + [saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" + AC_CHECK_HEADERS([libunwind-ptrace.h],[], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind-ptrace.h header]) + fi]) + AC_CHECK_HEADERS([libunwind.h],[], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind.h header]) + fi]) + CPPFLAGS="${saved_CPPFLAGS}" ]) + + + +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && + test "x$ac_cv_header_libunwind_h" = "xyes"; then + + dnl code is taken from ltrace + case "${host_cpu}" in + arm*|sa110) UNWIND_ARCH="arm" ;; + i?86) UNWIND_ARCH="x86" ;; + powerpc) UNWIND_ARCH="ppc32" ;; + powerpc64) UNWIND_ARCH="ppc64" ;; + mips*) UNWIND_ARCH="mips" ;; + *) UNWIND_ARCH="${host_cpu}" ;; + esac + + saved_LDFLAGS="${LDFLAGS}" + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" + AC_CHECK_LIB([unwind], [backtrace], + [libunwind_LIBS="-lunwind"], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind]) + fi]) + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind-generic]) + fi], + [$libunwind_LIBS]) + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], + [if test "x$with_libunwind" != "xcheck" ; then + AC_MSG_FAILURE([Unable to find libunwind-ptrace]) + fi], + [$libunwind_LIBS]) + LDFLAGS="${saved_LDFLAGS}" + + dnl we have all the dependencies we need we can activate stack tracing + AC_SUBST(libunwind_LIBS) + AC_SUBST(libunwind_LDFLAGS) + AC_SUBST(libunwind_CPPFLAGS) +fi + +dnl enable libunwind +if test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes" && + eval test \"x\$ac_cv_lib_unwind_generic__U"${UNWIND_ARCH}"_create_addr_space\" == "xyes" && + test "x$ac_cv_lib_unwind_backtrace" == "xyes"; then + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) + AC_MSG_NOTICE([Enabled stack tracing]) + with_libunwind="yes" +fi +AM_CONDITIONAL([LIB_UNWIND], [test "x$with_libunwind" == "xyes"]) + + AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) AC_CHECK_DECLS([sys_errlist]) diff --git a/defs.h b/defs.h index 964636b..1b1fa8d 100644 --- a/defs.h +++ b/defs.h @@ -414,6 +414,33 @@ typedef struct ioctlent { unsigned long code; } struct_ioctlent; + +#ifdef LIB_UNWIND + +/* keep 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; +}; + + +/* if this is true do the stack trace for every system call */ +extern bool use_libunwind; +#endif + /* Trace Control Block */ struct tcb { int flags; /* See below for TCB_ values */ @@ -439,6 +466,12 @@ struct tcb { struct timeval etime; /* Syscall entry time */ /* Support for tracing forked processes: */ long inst[2]; /* Saved clone args (badly named) */ + +#ifdef LIB_UNWIND + struct mmap_cache_t* mmap_cache; + unsigned int mmap_cache_size; + struct UPT_info* libunwind_ui; +#endif }; /* TCB flags */ @@ -724,6 +757,15 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); extern void tv_mul(struct timeval *, struct timeval *, int); extern void tv_div(struct timeval *, struct timeval *, int); +#ifdef LIB_UNWIND +/** + * print stack (-k flag) memory allocation and deallocation + */ +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 ef273c7..b3c6abe 100644 --- a/mem.c +++ b/mem.c @@ -175,6 +175,11 @@ static int print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) { if (entering(tcp)) { +#ifdef LIB_UNWIND + if (use_libunwind) + delete_mmap_cache(tcp); +#endif + /* addr */ if (!u_arg[0]) tprints("NULL, "); @@ -304,6 +309,11 @@ sys_munmap(struct tcb *tcp) tprintf("%#lx, %lu", tcp->u_arg[0], tcp->u_arg[1]); } + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif return 0; } @@ -315,6 +325,11 @@ sys_mprotect(struct tcb *tcp) tcp->u_arg[0], tcp->u_arg[1]); printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); } + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif return 0; } diff --git a/process.c b/process.c index e2fa25b..e058bbb 100644 --- a/process.c +++ b/process.c @@ -992,6 +992,13 @@ sys_execve(struct tcb *tcp) tprints("]"); } } + + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif + return 0; } @@ -1209,6 +1216,13 @@ sys_waitid(struct tcb *tcp) printrusage(tcp, tcp->u_arg[4]); } } + + +#ifdef LIB_UNWIND + if (exiting(tcp) && use_libunwind) + delete_mmap_cache(tcp); +#endif + return 0; } diff --git a/strace.1 b/strace.1 index 6ca4bda..e6373c0 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]... @@ -306,6 +306,9 @@ Print all non-ASCII strings in hexadecimal string format. .B \-xx Print all strings in hexadecimal string format. .TP +.B \-k +Print the execution stack trace of the traced processes after each system call. +.TP .B \-y Print paths associated with file descriptor arguments. .TP diff --git a/strace.c b/strace.c index 32a3f5e..5c46d7f 100644 --- a/strace.c +++ b/strace.c @@ -50,6 +50,13 @@ extern char **environ; extern int optind; extern char *optarg; +#ifdef LIB_UNWIND +# include <libunwind-ptrace.h> + +/* if this is true do the stack trace for every system call */ +bool use_libunwind = false; +unw_addr_space_t libunwind_as; +#endif #if defined __NR_tkill # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) @@ -231,6 +238,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 LIB_UNWIND +"-k obtain stack trace between each syscall\n\ +" +#endif /* ancient, no one should use it -F -- attempt to follow vforks (deprecated, use -f)\n\ */ @@ -685,6 +696,15 @@ alloctcb(int pid) #if SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif + +#ifdef LIB_UNWIND + if (use_libunwind) { + tcp->libunwind_ui = _UPT_create(tcp->pid); + if (!tcp->libunwind_ui) + die_out_of_memory(); + } +#endif + nprocs++; if (debug_flag) fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) if (printing_tcp == tcp) printing_tcp = NULL; +#ifdef LIB_UNWIND + if (use_libunwind) { + delete_mmap_cache(tcp); + _UPT_destroy(tcp->libunwind_ui); + } +#endif memset(tcp, 0, sizeof(*tcp)); } @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) qualify("signal=all"); while ((c = getopt(argc, argv, "+b:cCdfFhiqrtTvVxyz" +#ifdef LIB_UNWIND + "k" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) case 'u': username = strdup(optarg); break; +#ifdef LIB_UNWIND + case 'k': + use_libunwind = true; + break; +#endif case 'E': if (putenv(optarg) < 0) die_out_of_memory(); @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) error_msg_and_die("-D and -p are mutually exclusive"); } +#ifdef LIB_UNWIND + if (use_libunwind) { + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); + if (!libunwind_as) { + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); + } + } +#endif + if (!followfork) followfork = optF; diff --git a/syscall.c b/syscall.c index 1b49bb3..0c23e40 100644 --- a/syscall.c +++ b/syscall.c @@ -2683,6 +2683,15 @@ trace_syscall_exiting(struct tcb *tcp) dumpio(tcp); line_ended(); +#ifdef LIB_UNWIND + if (use_libunwind) { + if (!tcp->mmap_cache) { + alloc_mmap_cache(tcp); + } + print_stacktrace(tcp); + } +#endif + ret: tcp->flags &= ~TCB_INSYSCALL; return 0; diff --git a/unwind.c b/unwind.c new file mode 100644 index 0000000..5dc0175 --- /dev/null +++ b/unwind.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2013 Luca Clementi <luc...@gm...> + * + * 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.h> + + +extern unw_addr_space_t libunwind_as; +/* + * 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 = malloc(cur_array_size * sizeof(*cache_head)); + if (!cache_head) + die_out_of_memory(); + + sprintf(filename, "/proc/%d/maps", tcp->pid); + + FILE* f = fopen(filename, "r"); + if (!f) + perror_msg_and_die("Can't open %s", filename); + while (fgets(buffer, sizeof(buffer), f) != 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("Unrecognized maps file format %s", 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(f); + 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); + } + free(tcp->mmap_cache); + tcp->mmap_cache = NULL; + tcp->mmap_cache_size = 0; +} + + + +/* + * use libunwind to unwind the stack and print a backtrace + * + * Pre-condition: tcp->mmap_cache is already initialized + */ +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; + + 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); + + lower = 0; + upper = tcp->mmap_cache_size - 1; + + while (lower <= upper) { + /* find the mmap_cache and print the stack frame */ + mid = (unsigned int)((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); + line_ended(); + } 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 + */ + 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("libunwind warning: stack frame greater than 255 aborting backtracing\n"); + line_ended(); + break; + } + } while (ret_val > 0); +ret: + free(symbol_name); +} -- 1.7.9.5 |
From: Masatake Y. <ya...@re...> - 2013-08-16 05:20:21
|
After rebuilding libunwind with enabling minidebuginfo feature[1], your patch works fine on my Fedora 19. strace maintainer(s), is there any issue for merging the patch to the official source tree? [1] https://bugzilla.redhat.com/show_bug.cgi?id=972737 Masatake YAMATO > This patch prints the stack trace of the traced process after > each system call when using -k flag. It uses libunwind to > unwind the stack and to obtain the function name pointed by > the IP. > > Tested on Ubuntu 12.04 64 with the distribution version of > libunwind (0.99-0.3ubuntu1) and on CentOS 6.3 with libunwind > form the source code. > > This code was originally taken from strace-plus of Philip J. Guo. > > v4: > updated man page > > v3: > adjusted several datatype to use unsigned (Denys) > fix sybol_name leak (Denys) > workaround for libunwind bug with libpthread (unw_step does > not return 1 when at the end of the stack) > fix several problem in in autoconf script (Dmitry) > use PATH_MAX for DSO file path buffer > > v2: > fixed several identation issues > removed a strlen in alloc_mmap_cache (Denys) > reload mmap_cache after execv > fixed a bug in libunwind_backtrace binary search (Masatake) > check return value of _UPT_create > use die_out_of_memory after Xalloc > use exiting instead of !entering > created a new separate unwind.c file > removed include from defs.h > added --with-libunwind to autoconf script > cleaned up defs.h from several inclusions > --- > Makefile.am | 10 ++- > configure.ac | 79 ++++++++++++++++++++ > defs.h | 42 +++++++++++ > mem.c | 15 ++++ > process.c | 14 ++++ > strace.1 | 5 +- > strace.c | 43 +++++++++++ > syscall.c | 9 +++ > unwind.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 9 files changed, 445 insertions(+), 2 deletions(-) > create mode 100644 unwind.c > > diff --git a/Makefile.am b/Makefile.am > index 9d611f3..c8ad026 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -12,7 +12,7 @@ ARCH = @arch@ > > ACLOCAL_AMFLAGS = -I m4 > AM_CFLAGS = $(WARN_CFLAGS) > -AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) > +AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) $(libunwind_CPPFLAGS) > > strace_SOURCES = \ > bjm.c \ > @@ -43,6 +43,14 @@ strace_SOURCES = \ > util.c \ > vsprintf.c > > + > +if LIB_UNWIND > +strace_SOURCES += unwind.c > +strace_LDADD = $(libunwind_LIBS) > +AM_LDFLAGS = $(libunwind_LDFLAGS) > +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 b3b62e8..0f64a12 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -265,6 +265,85 @@ AC_CHECK_MEMBERS([struct sigcontext.sc_hi2],,, [#include <signal.h> > # include <asm/sigcontext.h> > #endif]) > > + > +dnl stack trace with libunwind > +AC_ARG_WITH([libunwind], > + [AS_HELP_STRING([--with-libunwind], > + [libunwind is used to display system call stacktrace])], > + [case "${withval}" in > + (yes|no) with_libunwind=$withval;; > + (*) with_libunwind=yes > + libunwind_CPPFLAGS="-I${withval}/include" > + libunwind_LDFLAGS="-L${withval}/lib" ;; > + esac], > + [with_libunwind=check]) > + > +AS_IF([test "x$with_libunwind" != "xno"], > + [saved_CPPFLAGS="${CPPFLAGS}" > + CPPFLAGS="${CPPFLAGS} ${libunwind_CPPFLAGS}" > + AC_CHECK_HEADERS([libunwind-ptrace.h],[], > + [if test "x$with_libunwind" != "xcheck" ; then > + AC_MSG_FAILURE([Unable to find libunwind-ptrace.h header]) > + fi]) > + AC_CHECK_HEADERS([libunwind.h],[], > + [if test "x$with_libunwind" != "xcheck" ; then > + AC_MSG_FAILURE([Unable to find libunwind.h header]) > + fi]) > + CPPFLAGS="${saved_CPPFLAGS}" ]) > + > + > + > +if test "x$ac_cv_header_libunwind_ptrace_h" = "xyes" && > + test "x$ac_cv_header_libunwind_h" = "xyes"; then > + > + dnl code is taken from ltrace > + case "${host_cpu}" in > + arm*|sa110) UNWIND_ARCH="arm" ;; > + i?86) UNWIND_ARCH="x86" ;; > + powerpc) UNWIND_ARCH="ppc32" ;; > + powerpc64) UNWIND_ARCH="ppc64" ;; > + mips*) UNWIND_ARCH="mips" ;; > + *) UNWIND_ARCH="${host_cpu}" ;; > + esac > + > + saved_LDFLAGS="${LDFLAGS}" > + LDFLAGS="${LDFLAGS} ${libunwind_LDFLAGS}" > + AC_CHECK_LIB([unwind], [backtrace], > + [libunwind_LIBS="-lunwind"], > + [if test "x$with_libunwind" != "xcheck" ; then > + AC_MSG_FAILURE([Unable to find libunwind]) > + fi]) > + AC_CHECK_LIB([unwind-generic], [_U${UNWIND_ARCH}_create_addr_space], > + [libunwind_LIBS="-lunwind-generic $libunwind_LIBS"], > + [if test "x$with_libunwind" != "xcheck" ; then > + AC_MSG_FAILURE([Unable to find libunwind-generic]) > + fi], > + [$libunwind_LIBS]) > + AC_CHECK_LIB([unwind-ptrace], [_UPT_create], > + [libunwind_LIBS="-lunwind-ptrace $libunwind_LIBS"], > + [if test "x$with_libunwind" != "xcheck" ; then > + AC_MSG_FAILURE([Unable to find libunwind-ptrace]) > + fi], > + [$libunwind_LIBS]) > + LDFLAGS="${saved_LDFLAGS}" > + > + dnl we have all the dependencies we need we can activate stack tracing > + AC_SUBST(libunwind_LIBS) > + AC_SUBST(libunwind_LDFLAGS) > + AC_SUBST(libunwind_CPPFLAGS) > +fi > + > +dnl enable libunwind > +if test "x$ac_cv_lib_unwind_ptrace__UPT_create" == "xyes" && > + eval test \"x\$ac_cv_lib_unwind_generic__U"${UNWIND_ARCH}"_create_addr_space\" == "xyes" && > + test "x$ac_cv_lib_unwind_backtrace" == "xyes"; then > + AC_DEFINE([LIB_UNWIND], 1, [Compile stack tracing functionality]) > + AC_MSG_NOTICE([Enabled stack tracing]) > + with_libunwind="yes" > +fi > +AM_CONDITIONAL([LIB_UNWIND], [test "x$with_libunwind" == "xyes"]) > + > + > AC_CHECK_MEMBERS([struct utsname.domainname],,, [#include <sys/utsname.h>]) > > AC_CHECK_DECLS([sys_errlist]) > diff --git a/defs.h b/defs.h > index 964636b..1b1fa8d 100644 > --- a/defs.h > +++ b/defs.h > @@ -414,6 +414,33 @@ typedef struct ioctlent { > unsigned long code; > } struct_ioctlent; > > + > +#ifdef LIB_UNWIND > + > +/* keep 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; > +}; > + > + > +/* if this is true do the stack trace for every system call */ > +extern bool use_libunwind; > +#endif > + > /* Trace Control Block */ > struct tcb { > int flags; /* See below for TCB_ values */ > @@ -439,6 +466,12 @@ struct tcb { > struct timeval etime; /* Syscall entry time */ > /* Support for tracing forked processes: */ > long inst[2]; /* Saved clone args (badly named) */ > + > +#ifdef LIB_UNWIND > + struct mmap_cache_t* mmap_cache; > + unsigned int mmap_cache_size; > + struct UPT_info* libunwind_ui; > +#endif > }; > > /* TCB flags */ > @@ -724,6 +757,15 @@ extern void tv_sub(struct timeval *, struct timeval *, struct timeval *); > extern void tv_mul(struct timeval *, struct timeval *, int); > extern void tv_div(struct timeval *, struct timeval *, int); > > +#ifdef LIB_UNWIND > +/** > + * print stack (-k flag) memory allocation and deallocation > + */ > +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 ef273c7..b3c6abe 100644 > --- a/mem.c > +++ b/mem.c > @@ -175,6 +175,11 @@ static int > print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset) > { > if (entering(tcp)) { > +#ifdef LIB_UNWIND > + if (use_libunwind) > + delete_mmap_cache(tcp); > +#endif > + > /* addr */ > if (!u_arg[0]) > tprints("NULL, "); > @@ -304,6 +309,11 @@ sys_munmap(struct tcb *tcp) > tprintf("%#lx, %lu", > tcp->u_arg[0], tcp->u_arg[1]); > } > + > +#ifdef LIB_UNWIND > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > +#endif > return 0; > } > > @@ -315,6 +325,11 @@ sys_mprotect(struct tcb *tcp) > tcp->u_arg[0], tcp->u_arg[1]); > printflags(mmap_prot, tcp->u_arg[2], "PROT_???"); > } > + > +#ifdef LIB_UNWIND > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > +#endif > return 0; > } > > diff --git a/process.c b/process.c > index e2fa25b..e058bbb 100644 > --- a/process.c > +++ b/process.c > @@ -992,6 +992,13 @@ sys_execve(struct tcb *tcp) > tprints("]"); > } > } > + > + > +#ifdef LIB_UNWIND > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > +#endif > + > return 0; > } > > @@ -1209,6 +1216,13 @@ sys_waitid(struct tcb *tcp) > printrusage(tcp, tcp->u_arg[4]); > } > } > + > + > +#ifdef LIB_UNWIND > + if (exiting(tcp) && use_libunwind) > + delete_mmap_cache(tcp); > +#endif > + > return 0; > } > > diff --git a/strace.1 b/strace.1 > index 6ca4bda..e6373c0 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]... > @@ -306,6 +306,9 @@ Print all non-ASCII strings in hexadecimal string format. > .B \-xx > Print all strings in hexadecimal string format. > .TP > +.B \-k > +Print the execution stack trace of the traced processes after each system call. > +.TP > .B \-y > Print paths associated with file descriptor arguments. > .TP > diff --git a/strace.c b/strace.c > index 32a3f5e..5c46d7f 100644 > --- a/strace.c > +++ b/strace.c > @@ -50,6 +50,13 @@ extern char **environ; > extern int optind; > extern char *optarg; > > +#ifdef LIB_UNWIND > +# include <libunwind-ptrace.h> > + > +/* if this is true do the stack trace for every system call */ > +bool use_libunwind = false; > +unw_addr_space_t libunwind_as; > +#endif > > #if defined __NR_tkill > # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) > @@ -231,6 +238,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 LIB_UNWIND > +"-k obtain stack trace between each syscall\n\ > +" > +#endif > /* ancient, no one should use it > -F -- attempt to follow vforks (deprecated, use -f)\n\ > */ > @@ -685,6 +696,15 @@ alloctcb(int pid) > #if SUPPORTED_PERSONALITIES > 1 > tcp->currpers = current_personality; > #endif > + > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + tcp->libunwind_ui = _UPT_create(tcp->pid); > + if (!tcp->libunwind_ui) > + die_out_of_memory(); > + } > +#endif > + > nprocs++; > if (debug_flag) > fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); > @@ -721,6 +741,12 @@ droptcb(struct tcb *tcp) > if (printing_tcp == tcp) > printing_tcp = NULL; > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + delete_mmap_cache(tcp); > + _UPT_destroy(tcp->libunwind_ui); > + } > +#endif > memset(tcp, 0, sizeof(*tcp)); > } > > @@ -1642,6 +1668,9 @@ init(int argc, char *argv[]) > qualify("signal=all"); > while ((c = getopt(argc, argv, > "+b:cCdfFhiqrtTvVxyz" > +#ifdef LIB_UNWIND > + "k" > +#endif > "D" > "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { > switch (c) { > @@ -1744,6 +1773,11 @@ init(int argc, char *argv[]) > case 'u': > username = strdup(optarg); > break; > +#ifdef LIB_UNWIND > + case 'k': > + use_libunwind = true; > + break; > +#endif > case 'E': > if (putenv(optarg) < 0) > die_out_of_memory(); > @@ -1775,6 +1809,15 @@ init(int argc, char *argv[]) > error_msg_and_die("-D and -p are mutually exclusive"); > } > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + libunwind_as = unw_create_addr_space(&_UPT_accessors, 0); > + if (!libunwind_as) { > + error_msg_and_die("Fatal error: unable to create address space for stack tracing\n"); > + } > + } > +#endif > + > if (!followfork) > followfork = optF; > > diff --git a/syscall.c b/syscall.c > index 1b49bb3..0c23e40 100644 > --- a/syscall.c > +++ b/syscall.c > @@ -2683,6 +2683,15 @@ trace_syscall_exiting(struct tcb *tcp) > dumpio(tcp); > line_ended(); > > +#ifdef LIB_UNWIND > + if (use_libunwind) { > + if (!tcp->mmap_cache) { > + alloc_mmap_cache(tcp); > + } > + print_stacktrace(tcp); > + } > +#endif > + > ret: > tcp->flags &= ~TCB_INSYSCALL; > return 0; > diff --git a/unwind.c b/unwind.c > new file mode 100644 > index 0000000..5dc0175 > --- /dev/null > +++ b/unwind.c > @@ -0,0 +1,230 @@ > +/* > + * Copyright (c) 2013 Luca Clementi <luc...@gm...> > + * > + * 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.h> > + > + > +extern unw_addr_space_t libunwind_as; > +/* > + * 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 = malloc(cur_array_size * sizeof(*cache_head)); > + if (!cache_head) > + die_out_of_memory(); > + > + sprintf(filename, "/proc/%d/maps", tcp->pid); > + > + FILE* f = fopen(filename, "r"); > + if (!f) > + perror_msg_and_die("Can't open %s", filename); > + while (fgets(buffer, sizeof(buffer), f) != 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("Unrecognized maps file format %s", 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(f); > + 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); > + } > + free(tcp->mmap_cache); > + tcp->mmap_cache = NULL; > + tcp->mmap_cache_size = 0; > +} > + > + > + > +/* > + * use libunwind to unwind the stack and print a backtrace > + * > + * Pre-condition: tcp->mmap_cache is already initialized > + */ > +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; > + > + 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); > + > + lower = 0; > + upper = tcp->mmap_cache_size - 1; > + > + while (lower <= upper) { > + /* find the mmap_cache and print the stack frame */ > + mid = (unsigned int)((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); > + line_ended(); > + } 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 > + */ > + 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("libunwind warning: stack frame greater than 255 aborting backtracing\n"); > + line_ended(); > + break; > + } > + } while (ret_val > 0); > +ret: > + free(symbol_name); > +} > -- > 1.7.9.5 > > > ------------------------------------------------------------------------------ > See everything from the browser to the database with AppDynamics > Get end-to-end visibility with application monitoring from AppDynamics > Isolate bottlenecks and diagnose root cause in seconds. > Start your free trial of AppDynamics Pro today! > http://pubads.g.doubleclick.net/gampad/clk?id=48808831&iu=/4140/ostg.clktrk > _______________________________________________ > Strace-devel mailing list > Str...@li... > https://lists.sourceforge.net/lists/listinfo/strace-devel |
From: Masatake Y. <ya...@re...> - 2013-09-17 10:53:09
|
In the patch `delete_mmap_cache' is used as a trigger for updating proc/maps info cache. It is called as expected when mmap and friends are traced. However, if the tracing is disabled with -e option, strae misses the chance to updating the info cache. Here is the example: $ LD_LIBRARY_PATH=/usr/local/lib ./strace -e read,mmap -k ps > /dev/null ... read(6, "Name:\tps\nState:\tR (running)\nTgid"..., 1023) = 888 > /usr/lib64/libc-2.16.so(__read_nocancel+0x7) [0xe5790] > /usr/lib64/libprocps.so.0.0.1(file2str.constprop.2+0x63) [0x7f73] > /usr/lib64/libprocps.so.0.0.1(simple_readproc+0x316) [0x8786] > /usr/lib64/libprocps.so.0.0.1(readproc+0xea) [0x8a0a] > /usr/bin/ps(main+0x7b9) [0x2aa9] > /usr/lib64/libc-2.16.so(__libc_start_main+0xf5) [0x21a05] > /usr/bin/ps(deregister_tm_clones+0x1d) [0x2bed] $ LD_LIBRARY_PATH=/usr/local/lib ./strace -e read -k ps > /dev/null ... read(6, "Name:\tps\nState:\tR (running)\nTgid"..., 1023) = 888 > backtracing_error If -k option is given, mmap and friends must be traced to update the cache info. ...Sorry but a patch for fixing it is not included here. Masatake YAMATO |