[PATCH 01/25] ldv/unwind: improve stacktrace feature(v2)

Masatake YAMATO yamato at redhat.com
Sun Nov 3 22:01:42 UTC 2013


Based on the discussion on strace-devel, I improved following points:

* use unwind_ as prefix for functions exported from unwind.c

* stacktrace for execve

    About the most of system calls, stacktrace is captured and printed at
    the same time, the system call exiting.  However, about execve, which
    is marked with STACKTRACE_CAPTURE_IN_ENTERING(CE), stacktrace is
    captured at the system call entering, and is printed at the exiting.
    By capturing in the system call entering, user can know the process
    context of execve is called.

    struct queue_t and some manipulators are introduced for
    capturing-at-entering and printing-at-exiting.

* control the maps cache with STACKTRACE_MAKE_CACHE_INVALID(CI) marker

    Currently stacktrace feature of strace needs the information of the
    memory mapping. Some of system calls like mmap change the memory
    mapping of the target process. Therefore unwind.c must track the
    modification of memory mapping.

    In older version the modification is tracked in print functions of
    each system call. In this patch STACKTRACE_MAKE_CACHE_INVALID(CI)
    marker is introduced. In stead of handling in print functions, the
    modification is tracked in system call entering only if current
    system call is marked with CI.

    If new system call modifying the memory mapping is added, just
    mark it with CI.

* mark CI on brk, mremap, shmat, shmdt, and remap_file_pages

* separate arch dependent CI/CE marking code to different patches(v2)

TODO:

* move the memory mapping cache to libuwind and use API for it

    See http://lists.nongnu.org/archive/html/libunwind-devel/2013-10/msg00001.html

* handle multi threads

    If target threads are share the same address space, cache invalidation
    on a thread must be propagated to the other threads.

* static link target

  Target compiled with gcc -static, stacktrace is not
  shown well through gdb can show it. Maybe libunwind
  side issue.

* test cases

Signed-off-by: Masatake YAMATO <yamato at redhat.com>
---
 defs.h    |  15 +--
 mem.c     |  17 ----
 process.c |  10 --
 strace.c  |  10 +-
 syscall.c |  27 ++++-
 unwind.c  | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 6 files changed, 342 insertions(+), 71 deletions(-)

diff --git a/defs.h b/defs.h
index 7f76008..af3bcdf 100644
--- a/defs.h
+++ b/defs.h
@@ -445,6 +445,7 @@ struct tcb {
 	struct UPT_info* libunwind_ui;
 	struct mmap_cache_t* mmap_cache;
 	unsigned int mmap_cache_size;
+        struct queue_t* queue;
 #endif
 };
 
@@ -557,6 +558,8 @@ extern const struct xlat whence_codes[];
 #define TRACE_DESC	040	/* Trace file descriptor-related syscalls. */
 #define TRACE_MEMORY	0100	/* Trace memory mapping-related syscalls. */
 #define SYSCALL_NEVER_FAILS	0200	/* Syscall is always successful. */
+#define STACKTRACE_MAKE_CACHE_INVALID   0400	/* Trigger proc/maps cache updating */
+#define STACKTRACE_CAPTURE_IN_ENTERING  01000	/* Capture stacktrace in "entering" stage */
 
 typedef enum {
 	CFLAG_NONE = 0,
@@ -737,12 +740,12 @@ extern void tv_mul(struct timeval *, struct timeval *, int);
 extern void tv_div(struct timeval *, 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);
+extern void unwind_init(void);
+extern void unwind_tcb_init(struct tcb *);
+extern void unwind_tcb_fin(struct tcb *);
+extern void unwind_cache_invalidate(struct tcb * );
+extern void unwind_stacktrace_capture(struct tcb *);
+extern void unwind_stacktrace_print(struct tcb *);
 #endif
 
 /* Strace log generation machinery.
diff --git a/mem.c b/mem.c
index ca6caa1..ef273c7 100644
--- a/mem.c
+++ b/mem.c
@@ -175,11 +175,6 @@ 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, ");
@@ -309,12 +304,6 @@ 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;
 }
 
@@ -326,12 +315,6 @@ 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;
 }
 
diff --git a/process.c b/process.c
index 22bb5b6..ab3511c 100644
--- a/process.c
+++ b/process.c
@@ -992,12 +992,6 @@ sys_execve(struct tcb *tcp)
 			tprints("]");
 		}
 	}
-#ifdef USE_LIBUNWIND
-	else {
-		if (stack_trace_enabled)
-			delete_mmap_cache(tcp);
-	}
-#endif
 
 	return 0;
 }
@@ -1200,10 +1194,6 @@ sys_waitid(struct tcb *tcp)
 		tprintf(", %ld, ", tcp->u_arg[1]);
 	}
 	else {
-#ifdef USE_LIBUNWIND
-		if (stack_trace_enabled)
-			delete_mmap_cache(tcp);
-#endif
 		/* siginfo */
 		printsiginfo_at(tcp, tcp->u_arg[2]);
 		/* options */
diff --git a/strace.c b/strace.c
index 7775b42..5875cf5 100644
--- a/strace.c
+++ b/strace.c
@@ -696,7 +696,7 @@ alloctcb(int pid)
 
 #ifdef USE_LIBUNWIND
 			if (stack_trace_enabled)
-				init_libunwind_ui(tcp);
+			        unwind_tcb_init(tcp);
 #endif
 
 			nprocs++;
@@ -736,10 +736,8 @@ droptcb(struct tcb *tcp)
 		printing_tcp = NULL;
 
 #ifdef USE_LIBUNWIND
-	if (stack_trace_enabled) {
-		delete_mmap_cache(tcp);
-		free_libunwind_ui(tcp);
-	}
+	if (stack_trace_enabled)
+	        unwind_tcb_fin(tcp);
 #endif
 	memset(tcp, 0, sizeof(*tcp));
 }
@@ -1805,7 +1803,7 @@ init(int argc, char *argv[])
 
 #ifdef USE_LIBUNWIND
 	if (stack_trace_enabled)
-		init_unwind_addr_space();
+		unwind_init();
 #endif
 
 	if (!followfork)
diff --git a/syscall.c b/syscall.c
index b01c023..8c4c738 100644
--- a/syscall.c
+++ b/syscall.c
@@ -97,6 +97,8 @@
 #define TM TRACE_MEMORY
 #define NF SYSCALL_NEVER_FAILS
 #define MA MAX_ARGS
+#define CI STACKTRACE_MAKE_CACHE_INVALID
+#define CE STACKTRACE_CAPTURE_IN_ENTERING
 
 const struct_sysent sysent0[] = {
 #include "syscallent.h"
@@ -124,6 +126,8 @@ static const struct_sysent sysent2[] = {
 #undef TM
 #undef NF
 #undef MA
+#undef CI
+#undef CE
 
 /*
  * `ioctlent.h' may be generated from `ioctlent.raw' by the auxiliary
@@ -2025,12 +2029,31 @@ trace_syscall_entering(struct tcb *tcp)
 	 || (tracing_paths && !pathtrace_match(tcp))
 	) {
 		tcp->flags |= TCB_INSYSCALL | TCB_FILTERED;
+#ifdef USE_LIBUNWIND
+		if (stack_trace_enabled)
+			if (tcp->s_ent->sys_flags & STACKTRACE_MAKE_CACHE_INVALID)
+				unwind_cache_invalidate(tcp);
+#endif
 		return 0;
 	}
 
 	tcp->flags &= ~TCB_FILTERED;
 
-	if (cflag == CFLAG_ONLY_STATS || hide_log_until_execve) {
+	if (cflag == CFLAG_ONLY_STATS) {
+		res = 0;
+		goto ret;
+	}
+
+#ifdef USE_LIBUNWIND
+	if (stack_trace_enabled) {
+		if (tcp->s_ent->sys_flags & STACKTRACE_CAPTURE_IN_ENTERING)
+			unwind_stacktrace_capture(tcp);
+		if (tcp->s_ent->sys_flags & STACKTRACE_MAKE_CACHE_INVALID)
+			unwind_cache_invalidate(tcp);
+	}
+#endif
+
+	if (hide_log_until_execve) {
 		res = 0;
 		goto ret;
 	}
@@ -2708,7 +2731,7 @@ trace_syscall_exiting(struct tcb *tcp)
 
 #ifdef USE_LIBUNWIND
 	if (stack_trace_enabled)
-		print_stacktrace(tcp);
+		unwind_stacktrace_print(tcp);
 #endif
 
  ret:
diff --git a/unwind.c b/unwind.c
index 0b8f0b0..5adf7ec 100644
--- a/unwind.c
+++ b/unwind.c
@@ -28,6 +28,15 @@
 #include <limits.h>
 #include <libunwind-ptrace.h>
 
+/* #define UNWINDE_DEBUG */
+
+#ifdef UNWINDE_DEBUG
+#include <stdio.h>
+#define DPRINTF(F, ...) fprintf(stderr, "--- <unwind> " F " ---\n", __VA_ARGS__);
+#else
+#define DPRINTF(...)
+#endif
+
 /*
  * Кeep a sorted array of cache entries,
  * so that we can binary search through it.
@@ -48,9 +57,54 @@ struct mmap_cache_t {
 	char* binary_filename;
 };
 
+/*
+ * Keep a captured stracktrace.
+ */
+struct call_t {
+	struct call_t* next;
+	char* binary_filename;
+	char* symbol_name;
+	unw_word_t function_off_set;
+	unsigned long true_offset;
+	const char* error;
+};
+
+struct queue_t {
+	struct call_t *tail;
+	struct call_t *head;
+};
+typedef void (* call_action_fn)(void* data,
+				char* binary_filename,
+				char* symbol_name,
+				unw_word_t function_off_set,
+				unsigned long true_offset);
+typedef void (* error_action_fn)(void* data,
+				 const char* error,
+				 unsigned long true_offset);
+
+static void queue_free(struct queue_t *queue);
+static void queue_print(struct queue_t *queue);
+static void queue_walk(struct queue_t *queue,
+		       call_action_fn call_action,
+		       error_action_fn error_action,
+		       void* data);
+static void queue_put_call(void *queue,
+			   char* binary_filename,
+			   char* symbol_name,
+			   unw_word_t function_off_set,
+			   unsigned long true_offset);
+static void queue_put_error(void *queue, const char* error,
+			    unw_word_t ip);
+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);
+
 static unw_addr_space_t libunwind_as;
 
-void
+static void
 init_unwind_addr_space(void)
 {
 	libunwind_as = unw_create_addr_space(&_UPT_accessors, 0);
@@ -58,7 +112,7 @@ init_unwind_addr_space(void)
 		error_msg_and_die("failed to create address space for stack tracing");
 }
 
-void
+static void
 init_libunwind_ui(struct tcb *tcp)
 {
 	tcp->libunwind_ui = _UPT_create(tcp->pid);
@@ -66,7 +120,7 @@ init_libunwind_ui(struct tcb *tcp)
 		die_out_of_memory();
 }
 
-void
+static void
 free_libunwind_ui(struct tcb *tcp)
 {
 	_UPT_destroy(tcp->libunwind_ui);
@@ -78,8 +132,8 @@ free_libunwind_ui(struct tcb *tcp)
  *
  * The cache must be refreshed after some syscall: mmap, mprotect, munmap, execve
  */
-void
-alloc_mmap_cache(struct tcb* tcp)
+static void
+build_cache(struct tcb* tcp)
 {
 	unsigned long start_addr, end_addr, mmap_offset;
 	char filename[sizeof ("/proc/0123456789/maps")];
@@ -91,6 +145,8 @@ alloc_mmap_cache(struct tcb* tcp)
 	struct mmap_cache_t *cache_head;
 	FILE *fp;
 
+	DPRINTF("BUILDCACHE: %p", tcp->queue->head);
+
 	sprintf(filename, "/proc/%d/maps", tcp->pid);
 	fp = fopen(filename, "r");
 	if (!fp) {
@@ -155,7 +211,7 @@ alloc_mmap_cache(struct tcb* tcp)
 }
 
 /* deleting the cache */
-void
+static void
 delete_mmap_cache(struct tcb* tcp)
 {
 	unsigned int i;
@@ -168,9 +224,158 @@ delete_mmap_cache(struct tcb* tcp)
 	tcp->mmap_cache_size = 0;
 }
 
-/* use libunwind to unwind the stack and print a backtrace */
-void
-print_stacktrace(struct tcb* tcp)
+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->binary_filename = binary_filename;
+	call->symbol_name = symbol_name;
+	call->function_off_set = function_off_set;
+	call->true_offset = true_offset;
+	call->error = 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)
+{
+
+	char* binary_filename0 = strdup(binary_filename);
+	char* symbol_name0 = strdup(symbol_name);
+	if (!binary_filename0)
+		die_out_of_memory();
+	if (!symbol_name0)
+		die_out_of_memory();
+
+	queue_put(queue,
+		binary_filename0,
+		symbol_name0,
+		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_walk(struct queue_t *queue,
+	   call_action_fn call_action,
+	   error_action_fn error_action,
+	   void* data)
+{
+	struct call_t* call;
+
+
+	call = queue->head;
+	while(call) {
+		if (call->error)
+			error_action(data,
+				     call->error,
+				     call->true_offset);
+		else
+			call_action(data,
+				    call->binary_filename,
+				    call->symbol_name,
+				    call->function_off_set,
+				    call->true_offset);
+		call = call->next;
+	}
+}
+
+static void
+queue_free(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;
+		free(tmp->binary_filename);
+		tmp->binary_filename = NULL;
+		free(tmp->symbol_name);
+		tmp->symbol_name = NULL;
+		tmp->error = NULL;
+		tmp->next = NULL;
+		free(tmp);
+	}
+}
+
+static void
+print_call(void* dummy,
+	   char* binary_filename,
+	   char* symbol_name,
+	   unw_word_t function_off_set,
+	   unsigned long true_offset)
+{
+	if (symbol_name) {
+		tprintf(" > %s(%s+0x%lx) [0x%lx]\n",
+			binary_filename,
+			symbol_name, function_off_set, true_offset);
+	}
+	else if (binary_filename)
+		tprintf(" > %s() [0x%lx]\n",
+			binary_filename, true_offset);
+	else
+		tprintf(" > BUG IN print_call\n");
+
+	line_ended();
+}
+
+static void
+print_error(void* dummy,
+	    const char* error,
+	    unsigned long true_offset)
+{
+	if (true_offset)
+		tprintf(" > %s [0x%lx]\n", error, true_offset);
+	else
+		tprintf(" > %s\n", error);
+	line_ended();
+}
+
+static void
+queue_print(struct queue_t *queue)
+{
+	DPRINTF("QUEUEPRINT: %p", queue->head);
+	queue_walk(queue, print_call, print_error, NULL);
+}
+
+static void
+stacktrace_walk(struct tcb* tcp,
+		call_action_fn call_action,
+		error_action_fn error_action,
+		void *data)
 {
 	unw_word_t ip;
 	unw_cursor_t cursor;
@@ -183,10 +388,6 @@ print_stacktrace(struct tcb* tcp)
 	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)
@@ -236,25 +437,24 @@ print_stacktrace(struct tcb* tcp)
 					 * /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);
+					call_action(data,
+						    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);
+					call_action(data,
+						    cur_mmap_cache->binary_filename,
+						    symbol_name,
+						    0,
+						    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();
+					error_action(data,
+						     "backtracing_error", 0);
 				goto ret;
 			}
 			else if (ip < cur_mmap_cache->start_addr)
@@ -264,19 +464,93 @@ print_stacktrace(struct tcb* tcp)
 
 		}
 		if (lower > upper) {
-			tprintf(" > backtracing_error [0x%lx]\n", ip);
-			line_ended();
+			error_action(data,
+				     "backtracing_error", ip);
 			goto ret;
 		}
 
 		ret_val = unw_step(&cursor);
 
 		if (++stack_depth > 255) {
-			tprintf("> too many stack frames\n");
-			line_ended();
+			error_action(data,
+				     "too many stack frames", 0);
 			break;
 		}
 	} while (ret_val > 0);
 ret:
 	free(symbol_name);
 }
+
+static void
+stacktrace_print(struct tcb* tcp)
+{
+	DPRINTF("STACKPRINT: %p", tcp->queue->head);
+	stacktrace_walk(tcp, print_call, print_error, NULL);
+}
+
+/* use libunwind to unwind the stack and print a backtrace */
+void
+unwind_init(void)
+{
+	init_unwind_addr_space();
+}
+
+void
+unwind_tcb_init(struct tcb *tcp)
+{
+	init_libunwind_ui(tcp);
+	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)
+{
+	delete_mmap_cache(tcp);
+	free_libunwind_ui(tcp);
+	queue_free(tcp->queue);
+	free(tcp->queue);
+	tcp->queue = NULL;
+}
+
+void
+unwind_cache_invalidate(struct tcb* tcp)
+{
+	delete_mmap_cache(tcp);
+	DPRINTF("INVALIDATED: %p", tcp->queue->head);
+}
+
+
+void
+unwind_stacktrace_capture(struct tcb* tcp)
+{
+	queue_free(tcp->queue);
+
+	if (!tcp->mmap_cache)
+		build_cache(tcp);
+	if (!tcp->mmap_cache || !tcp->mmap_cache_size)
+		return;
+
+	stacktrace_walk(tcp, queue_put_call, queue_put_error,
+			tcp->queue);
+	DPRINTF("CAPTURED: %p", tcp->queue->head);
+}
+
+void
+unwind_stacktrace_print(struct tcb* tcp)
+{
+	if (tcp->s_ent->sys_flags & STACKTRACE_CAPTURE_IN_ENTERING) {
+		queue_print(tcp->queue);
+		queue_free(tcp->queue);
+	}
+	else {
+		if (!tcp->mmap_cache)
+			build_cache(tcp);
+		if (!tcp->mmap_cache || !tcp->mmap_cache_size)
+			return;
+		stacktrace_print(tcp);
+	}
+}
-- 
1.8.3.1





More information about the Strace-devel mailing list