]> granicus.if.org Git - strace/commitdiff
unwind: introduce queue_t for capturing stacktrace
authorMasatake YAMATO <yamato@redhat.com>
Wed, 16 Apr 2014 06:33:06 +0000 (15:33 +0900)
committerDmitry V. Levin <ldv@altlinux.org>
Fri, 30 May 2014 22:55:08 +0000 (22:55 +0000)
This is the second step for splitting capturing from printing.

New `queue' field is added to tcb.  Captured stacktrace is stored here.
The field is initialized/finalized at unwind_tcb_init/unwind_tcb_fin.

New API function unwind_capture_stacktrace is added.  This function
captures the currest stack using stracktrace_walker and records it in
tcb.  It's printing is delayed to the next call of
unwind_print_stacktrace.

unwind_print_stacktrace is extended.  Now it checks queue field of
the given tcb at the start of function.  If the function finds a
captured stack trace, the latter is printed using stracktrace_walker.

Currently unwind_capture_stacktrace invocations are added directly to
handlers of mmap, munmap, mprotect, and execve.

Here is the difference of output with/without patch:

(without patch)
  execve("./test-fork", ["./test-fork"], [/* 56 vars */]) = 0
   > /usr/lib64/ld-2.18.so(check_one_fd.part.0+0x82) [0x11f0]

(with patch)
  execve("./test-fork", ["./test-fork"], [/* 54 vars */]) = 0
   > /usr/lib64/libc-2.18.so(execve+0x7) [0xbcd27]
   > /home/yamato/var/strace/strace(exec_or_die+0x10c) [0x26ac]
   > /home/yamato/var/strace/strace(startup_child+0x346) [0x134f6]
   > /home/yamato/var/strace/strace(init+0x89f) [0x13dff]
   > /home/yamato/var/strace/strace(main+0xa) [0x26ca]
   > /usr/lib64/libc-2.18.so(__libc_start_main+0xf5) [0x21d65]
   > /home/yamato/var/strace/strace(_start+0x29) [0x2799]

In older version output lines of captured elements were built when
printing.  In this version they are built when capturing the stack.
As result, unneeded dynamic memory allocations are avoided.
Suggested by Luca Clementi.

In older version the combination of snprintf and realloc were used.
In this version they are replaced with asprintf.
Suggested by Dmitry Levin.

Signed-off-by: Masatake YAMATO <yamato@redhat.com>
defs.h
mem.c
process.c
unwind.c

diff --git a/defs.h b/defs.h
index f642d6548dead448c32302f92c26ff9730da37ea..70b42a88fccd5c56320f120c02156e8d6966ddf3 100644 (file)
--- a/defs.h
+++ b/defs.h
@@ -430,6 +430,7 @@ struct tcb {
        struct UPT_info* libunwind_ui;
        struct mmap_cache_t* mmap_cache;
        unsigned int mmap_cache_size;
+       struct queue_t* queue;
 #endif
 };
 
@@ -737,6 +738,7 @@ extern void unwind_tcb_init(struct tcb *tcp);
 extern void unwind_tcb_fin(struct tcb *tcp);
 extern void unwind_cache_invalidate(struct tcb* tcp);
 extern void unwind_print_stacktrace(struct tcb* tcp);
+extern void unwind_capture_stacktrace(struct tcb* tcp);
 #endif
 
 /* Strace log generation machinery.
diff --git a/mem.c b/mem.c
index ed853d9bfeac060d98844aa632a70ab51e1392f8..180e37006035c52e07b29503dfe6c42500b5cca3 100644 (file)
--- a/mem.c
+++ b/mem.c
@@ -61,8 +61,10 @@ print_mmap(struct tcb *tcp, long *u_arg, unsigned long long offset)
 {
        if (entering(tcp)) {
 #ifdef USE_LIBUNWIND
-               if (stack_trace_enabled)
+               if (stack_trace_enabled) {
+                       unwind_capture_stacktrace(tcp);
                        unwind_cache_invalidate(tcp);
+               }
 #endif
 
                /* addr */
@@ -193,6 +195,10 @@ sys_munmap(struct tcb *tcp)
        if (entering(tcp)) {
                tprintf("%#lx, %lu",
                        tcp->u_arg[0], tcp->u_arg[1]);
+#ifdef USE_LIBUNWIND
+               if (stack_trace_enabled)
+                       unwind_capture_stacktrace(tcp);
+#endif
        }
 #ifdef USE_LIBUNWIND
        else {
@@ -210,6 +216,10 @@ sys_mprotect(struct tcb *tcp)
                tprintf("%#lx, %lu, ",
                        tcp->u_arg[0], tcp->u_arg[1]);
                printflags(mmap_prot, tcp->u_arg[2], "PROT_???");
+#ifdef USE_LIBUNWIND
+               if (stack_trace_enabled)
+                       unwind_capture_stacktrace(tcp);
+#endif
        }
 #ifdef USE_LIBUNWIND
        else {
index a152ad70bf21a74a94ef78eada33b5a779381281..ec9e1b4ed43ab1a773e14e495d0685d43c86a72f 100644 (file)
--- a/process.c
+++ b/process.c
@@ -798,11 +798,17 @@ sys_execve(struct tcb *tcp)
                        printargv(tcp, tcp->u_arg[2]);
                        tprints("]");
                }
+#ifdef USE_LIBUNWIND
+               if (stack_trace_enabled) {
+                       unwind_capture_stacktrace(tcp);
+               }
+#endif
        }
 #ifdef USE_LIBUNWIND
        else {
-               if (stack_trace_enabled)
+               if (stack_trace_enabled) {
                        unwind_cache_invalidate(tcp);
+               }
        }
 #endif
 
index e4beb2854781c6e3a06cac899e0ce7283059fdaf..5794801aa0a518f8760364a35cfd7eabf1258b23 100644 (file)
--- a/unwind.c
+++ b/unwind.c
@@ -62,6 +62,19 @@ typedef void (*error_action_fn)(void *data,
                                const char *error,
                                unsigned long true_offset);
 
+/*
+ * Type used in stacktrace capturing
+ */
+struct call_t {
+       struct call_t* next;
+       char *output_line;
+};
+
+struct queue_t {
+       struct call_t *tail;
+       struct call_t *head;
+};
+static void queue_print(struct queue_t *queue);
 
 static unw_addr_space_t libunwind_as;
 
@@ -79,12 +92,23 @@ unwind_tcb_init(struct tcb *tcp)
        tcp->libunwind_ui = _UPT_create(tcp->pid);
        if (!tcp->libunwind_ui)
                die_out_of_memory();
+
+       tcp->queue = malloc(sizeof(*tcp->queue));
+       if (!tcp->queue)
+               die_out_of_memory();
+       tcp->queue->head = NULL;
+       tcp->queue->tail = NULL;
 }
 
 void
 unwind_tcb_fin(struct tcb *tcp)
 {
+       queue_print(tcp->queue);
+       free(tcp->queue);
+       tcp->queue = NULL;
+
        unwind_cache_invalidate(tcp);
+
        _UPT_destroy(tcp->libunwind_ui);
        tcp->libunwind_ui = NULL;
 }
@@ -298,7 +322,7 @@ ret:
 }
 
 /*
- * printing an entry in stack
+ * printing an entry in stack to stream or buffer
  */
 /*
  * we want to keep the format used by backtrace_symbols from the glibc
@@ -355,11 +379,137 @@ print_error_cb(void *dummy,
        line_ended();
 }
 
+static char *
+sprint_call_or_error(char *binary_filename,
+                    char *symbol_name,
+                    unw_word_t function_off_set,
+                    unsigned long true_offset,
+                    const char *error)
+{
+       char *output_line = NULL;
+       int n;
+
+       if (symbol_name)
+               n = asprintf(&output_line, STACK_ENTRY_SYMBOL_FMT);
+       else if (binary_filename)
+               n = asprintf(&output_line, STACK_ENTRY_NOSYMBOL_FMT);
+       else if (error)
+               n = true_offset
+                       ? asprintf(&output_line, STACK_ENTRY_ERROR_WITH_OFFSET_FMT)
+                       : asprintf(&output_line, STACK_ENTRY_ERROR_FMT);
+       else
+               n = asprintf(&output_line, STACK_ENTRY_BUG_FMT, __FUNCTION__);
+
+       if (n < 0)
+               error_msg_and_die("error in asprintf");
+
+       return output_line;
+}
+
+/*
+ * queue manipulators
+ */
+static void
+queue_put(struct queue_t *queue,
+         char *binary_filename,
+         char *symbol_name,
+         unw_word_t function_off_set,
+         unsigned long true_offset,
+         const char *error)
+{
+       struct call_t *call;
+
+       call = malloc(sizeof(*call));
+       if (!call)
+               die_out_of_memory();
+
+       call->output_line = sprint_call_or_error(binary_filename,
+                                                symbol_name,
+                                                function_off_set,
+                                                true_offset,
+                                                error);
+       call->next = NULL;
+
+       if (!queue->head) {
+               queue->head = call;
+               queue->tail = call;
+       } else {
+               queue->tail->next = call;
+               queue->tail = call;
+       }
+}
+
+static void
+queue_put_call(void *queue,
+              char *binary_filename,
+              char *symbol_name,
+              unw_word_t function_off_set,
+              unsigned long true_offset)
+{
+       queue_put(queue,
+                 binary_filename,
+                 symbol_name,
+                 function_off_set,
+                 true_offset,
+                 NULL);
+}
+
+static void
+queue_put_error(void *queue,
+               const char *error,
+               unw_word_t ip)
+{
+       queue_put(queue, NULL, NULL, 0, ip, error);
+}
+
+static void
+queue_print(struct queue_t *queue)
+{
+       struct call_t *call, *tmp;
+
+       queue->tail = NULL;
+       call = queue->head;
+       queue->head = NULL;
+       while (call) {
+               tmp = call;
+               call = call->next;
+
+               tprints(tmp->output_line);
+               line_ended();
+
+               free(tmp->output_line);
+               tmp->output_line = NULL;
+               tmp->next = NULL;
+               free(tmp);
+       }
+}
+
 /*
  * printing stack
  */
 void
 unwind_print_stacktrace(struct tcb* tcp)
 {
-       stacktrace_walk(tcp, print_call_cb, print_error_cb, NULL);
+       if (tcp->queue->head) {
+              DPRINTF("tcp=%p, queue=%p", "queueprint", tcp, tcp->queue->head);
+              queue_print(tcp->queue);
+       }
+       else {
+               DPRINTF("tcp=%p, queue=%p", "stackprint", tcp, tcp->queue->head);
+               stacktrace_walk(tcp, print_call_cb, print_error_cb, NULL);
+       }
+}
+
+/*
+ * capturing stack
+ */
+void
+unwind_capture_stacktrace(struct tcb *tcp)
+{
+       if (tcp->queue->head)
+               error_msg_and_die("bug: unprinted entries in queue");
+
+       stacktrace_walk(tcp, queue_put_call, queue_put_error,
+                       tcp->queue);
+       DPRINTF("tcp=%p, queue=%p", "captured", tcp, tcp->queue->head);
 }