]> granicus.if.org Git - strace/commitdiff
Add -k option to print stack trace after each syscall
authorLuca Clementi <luca.clementi@gmail.com>
Tue, 23 Jul 2013 07:11:35 +0000 (00:11 -0700)
committerDmitry V. Levin <ldv@altlinux.org>
Fri, 30 May 2014 22:24:31 +0000 (22:24 +0000)
Print the stack trace of the traced process after each system call when
-k option is specified.  It is implemented using libunwind to unwind the
stack and to obtain the function name pointed by the IP.

Based on the code that was originally taken from strace-plus
of Philip J. Guo.

* configure.ac: Add --with-libunwind option.  Check libunwind support.
* Makefile.am: Add libunwind support.
* defs.h (struct tcb) [USE_LIBUNWIND]: Append libunwind specific fields.
[USE_LIBUNWIND] (stack_trace_enabled, alloc_mmap_cache,
delete_mmap_cache, print_stacktrace): New prototypes.
* mem.c (print_mmap, sys_munmap, sys_mprotect): Add libunwind support.
* process.c (sys_execve): Likewise.
* strace.c (usage, alloctcb, droptcb, init): Likewise.
* syscall.c (trace_syscall_exiting): Likewise.
* unwind.c: New file.
* strace.1: Document -k option.

Makefile.am
configure.ac
defs.h
mem.c
process.c
strace.1
strace.c
syscall.c
unwind.c [new file with mode: 0644]

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