Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > media > main-src > by-pkgid > b3bd92884018251b87f9099340c300c3 > files > 50

ltrace-0.5-13.45svn.el5_7.12.src.rpm

diff --git a/breakpoints.c b/breakpoints.c
index 1eff8b0..387b2a5 100644
--- a/breakpoints.c
+++ b/breakpoints.c
@@ -203,14 +203,7 @@ breakpoints_init(Process *proc, int enable)
 	proc->breakpoints = dict_init(dict_key2hash_int,
 				      dict_key_cmp_int);
 
-	if (proc->list_of_symbols != NULL) {
-		struct library_symbol * sym = proc->list_of_symbols;
-		while (sym != NULL) {
-			struct library_symbol * next = sym->next;
-			free(sym);
-			sym = next;
-		}
-	}
+	destroy_library_symbol_chain(proc->list_of_symbols);
 	proc->list_of_symbols = NULL;
 
 	if (options.libcalls && proc->filename) {
diff --git a/common.h b/common.h
index 49861cf..c0b24e5 100644
--- a/ltrace.h
+++ b/ltrace.h
@@ -200,6 +200,7 @@ enum process_status {
 	ps_invalid,	/* Failure.  */
 	ps_stop,	/* Job-control stop.  */
 	ps_tracing_stop,
+	ps_sleeping,
 	ps_zombie,
 	ps_other,	/* Necessary other states can be added as needed.  */
 };
@@ -255,6 +255,10 @@ extern void open_pid(pid_t pid, int verb
 extern void open_forked_pid(pid_t pid, int early);
 extern void show_summary(void);
 
+extern struct library_symbol * clone_library_symbol(struct library_symbol * s);
+extern void destroy_library_symbol(struct library_symbol * s);
+extern void destroy_library_symbol_chain(struct library_symbol * chain);
+
 /* Arch-dependent stuff: */
 extern char *pid2name(pid_t pid);
 extern pid_t process_leader(pid_t pid);
@@ -280,6 +280,7 @@ extern int syscall_p(struct process *pro
 extern void continue_process(pid_t pid);
 extern void continue_after_signal(pid_t pid, int signum);
 extern void continue_after_breakpoint(struct process *proc, struct breakpoint *sbp);
+extern void continue_after_vfork(struct process * proc);
 extern void ltrace_exiting(void);
 extern void continue_enabling_breakpoint(struct process *proc,
 					 struct breakpoint *sbp);
@@ -292,6 +293,7 @@ extern int task_kill (pid_t pid, int sig
 extern struct process *pid2proc(int pid);
 extern void add_process(struct process * proc);
 extern void remove_process(struct process * proc);
+extern void change_process_leader(struct process * proc, struct process * leader);
 extern struct process *each_process(struct process * start,
 				    enum pcb_status (* cb)(struct process *,
 							   void * data),
diff --git a/dict.c b/dict.c
index 486a461..ba318cd 100644
--- a/dict.c
+++ b/dict.c
@@ -167,7 +167,9 @@ int dict_key_cmp_int(void *key1, void *k
 	return key1 - key2;
 }
 
-struct dict *dict_clone(struct dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)) {
+struct dict *dict_clone2(struct dict *old, void * (*key_clone)(void*, void*),
+			 void * (*value_clone)(void*, void*), void *data)
+{
 	struct dict *d;
 	int i;
 
@@ -190,8 +192,8 @@ struct dict *dict_clone(struct dict *old
 				exit(1);
 			}
 			memcpy(*de_new, de_old, sizeof(struct dict_entry));
-			(*de_new)->key = key_clone(de_old->key);
-			(*de_new)->value = value_clone(de_old->value);
+			(*de_new)->key = key_clone(de_old->key, data);
+			(*de_new)->value = value_clone(de_old->value, data);
 			de_new = &(*de_new)->next;
 			de_old = de_old->next;
 		}
@@ -199,3 +201,28 @@ struct dict *dict_clone(struct dict *old
 	return d;
 }
 
+struct wrap_clone_cb
+{
+	void * (*key_clone)(void *);
+	void * (*value_clone)(void *);
+};
+
+static void *
+value_clone_1(void * arg, void * data)
+{
+	return ((struct wrap_clone_cb *)data)->value_clone(arg);
+}
+
+static void *
+key_clone_1(void * arg, void * data)
+{
+	return ((struct wrap_clone_cb *)data)->key_clone(arg);
+}
+
+struct dict *
+dict_clone(struct dict * old, void * (*key_clone)(void *),
+	   void * (*value_clone)(void *))
+{
+	struct wrap_clone_cb cb = { key_clone, value_clone };
+	return dict_clone2(old, &key_clone_1, &value_clone_1, &cb);
+}
diff --git a/dict.h b/dict.h
index a70c3d5..27dc7bf 100644
--- a/dict.h
+++ b/dict.h
@@ -18,3 +18,5 @@ extern int dict_key_cmp_string(void *key
 extern unsigned int dict_key2hash_int(void *key);
 extern int dict_key_cmp_int(void *key1, void *key2);
 extern struct dict * dict_clone(struct dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*));
+extern struct dict * dict_clone2(struct dict *old, void * (*key_clone)(void*, void*),
+				 void * (*value_clone)(void*, void*), void *data);
--- a/process_event.c	2011-09-27 21:58:40.972954149 +0200
+++ b/process_event.c	2011-09-27 21:58:25.349953795 +0200
@@ -9,6 +9,7 @@
 #include <signal.h>
 #include <assert.h>
 #include <sys/time.h>
+#include <errno.h>
 
 #include "ltrace.h"
 #include "output.h"
@@ -35,18 +36,29 @@
 static void callstack_pop(struct process *proc);
 
 /* TODO */
+static int clone_breakpoints(struct process * proc, struct process * orig_proc);
-void * address_clone(void * addr) {
+void * address_clone(void * addr, void * data) {
 	return addr;
 }
 
-void * breakpoint_clone(void * bp) {
+void * breakpoint_clone(void * bp, void * data) {
 	struct breakpoint * b;
+	struct dict * map = data;
 	b = malloc(sizeof(struct breakpoint));
 	if (!b) {
 		perror("malloc()");
 		exit(1);
 	}
 	memcpy(b, bp, sizeof(struct breakpoint));
+	if (b->libsym != NULL) {
+		struct library_symbol * sym = dict_find_entry(map, b->libsym);
+		if (b->libsym == NULL) {
+			fprintf(stderr, "Can't find cloned symbol %s.\n",
+				b->libsym->name);
+			return NULL;
+		}
+		b->libsym = sym;
+	}
 	return b;
 }
 
@@ -108,7 +119,6 @@
 		exit(1);
 	}
 	memcpy(p, event->proc, sizeof(struct process));
-	p->breakpoints = dict_clone(event->proc->breakpoints, address_clone, breakpoint_clone);
 	p->pid = event->e_un.newpid;
 	p->parent = event->proc;
 
@@ -131,7 +141,17 @@
 		p->state = STATE_BEING_CREATED;
 		add_process(p);
 	}
-	continue_process(event->proc->pid);
+
+	if (p->leader == p)
+		clone_breakpoints(p, event->proc->leader);
+	else
+		/* Thread groups share breakpoints.  */
+		p->breakpoints = NULL;
+
+	if (event->thing == LT_EV_VFORK)
+		continue_after_vfork(p);
+	else
+		continue_process(event->proc->pid);
 }
 
 static void process_new(struct event * event) {
@@ -140,8 +160,6 @@
 		pending_new_insert(event->e_un.newpid);
 	} else {
 		assert(proc->state == STATE_BEING_CREATED);
-		if (proc->event_handler != NULL)
-			destroy_event_handler(proc);
 		if (opt_f) {
 			proc->state = STATE_ATTACHED;
 		} else {
@@ -199,6 +217,18 @@
 	}
 }
 
+static struct event *
+call_handler(struct process * proc, struct event * event)
+{
+	assert(proc != NULL);
+
+	Event_Handler * handler = proc->event_handler;
+	if (handler == NULL)
+		return event;
+
+	return (*handler->on_event) (handler, event);
+}
+
 void process_event(struct event *event)
 {
 	if (exiting == 1) {
@@ -207,13 +237,20 @@
 		ltrace_exiting();
 	}
 
-	/* If the thread group defines an overriding event handler,
-	   give it a chance to kick in.  */
-	if (event->proc != NULL
-	    && event->proc->leader != NULL) {
-		Event_Handler * handler = event->proc->leader->event_handler;
-		if (handler != NULL) {
-			event = (*handler->on_event) (handler, event);
+	/* If the thread group or an individual task define an
+	   overriding event handler, give them a chance to kick in.
+	   We will end up calling both handlers, if the first one
+	   doesn't sink the event.  */
+	if (event->proc != NULL) {
+		event = call_handler(event->proc, event);
+		if (event == NULL)
+			/* It was handled.  */
+			return;
+
+		/* Note: the previous handler has a chance to alter
+		 * the event.  */
+		if (event->proc->leader != NULL) {
+			event = call_handler(event->proc->leader, event);
 			if (event == NULL)
 				/* It was handled.  */
 				return;
@@ -253,6 +290,7 @@
 		process_sysret(event);
 		return;
 	case LT_EV_CLONE:
+	case LT_EV_VFORK:
 		debug(1, "event: clone (%u)", event->e_un.newpid);
 		process_clone(event);
 		return;
@@ -501,6 +539,40 @@
 	}
 }
 
+static int
+clone_breakpoints(struct process * proc, struct process * orig_proc)
+{
+	/* When copying breakpoints, we also have to copy the
+	 * referenced symbols, and link them properly.  */
+	struct dict * map = dict_init(&dict_key2hash_int, &dict_key_cmp_int);
+	struct library_symbol * it = proc->list_of_symbols;
+	proc->list_of_symbols = NULL;
+	for (; it != NULL; it = it->next) {
+		struct library_symbol * libsym = clone_library_symbol(it);
+		if (libsym == NULL) {
+			int save_errno;
+		err:
+			save_errno = errno;
+			destroy_library_symbol_chain(proc->list_of_symbols);
+			dict_clear(map);
+			errno = save_errno;
+			return -1;
+		}
+		libsym->next = proc->list_of_symbols;
+		proc->list_of_symbols = libsym;
+		if (dict_enter(map, it, libsym) != 0)
+			goto err;
+	}
+
+	proc->breakpoints = dict_clone2(orig_proc->breakpoints,
+					address_clone, breakpoint_clone, map);
+	if (proc->breakpoints == NULL)
+		goto err;
+
+	dict_clear(map);
+	return 0;
+}
+
 static void
 callstack_push_symfunc(struct process *proc, struct library_symbol *sym)
 {
diff --git a/ltrace-elf.c b/ltrace-elf.c
index 60fe2e9..9aea4a9 100644
--- a/elf.c
+++ b/elf.c
@@ -309,13 +309,28 @@
 	close(lte->fd);
 }
 
+static struct library_symbol *
+create_library_symbol(const char * name, GElf_Addr addr)
+{
+	size_t namel = strlen(name) + 1;
+	struct library_symbol * sym = calloc(sizeof(*sym) + namel, 1);
+	if (sym == NULL) {
+		perror("create_library_symbol");
+		return NULL;
+	}
+	sym->name = (char *)(sym + 1);
+	memcpy(sym->name, name, namel);
+	sym->enter_addr = (void *)(uintptr_t) addr;
+	return sym;
+}
+
 static void
 add_library_symbol(GElf_Addr addr, const char *name,
 		   struct library_symbol **library_symbolspp,
 		   enum toplt type_of_plt, int is_weak)
 {
 	struct library_symbol *s;
-	s = malloc(sizeof(struct library_symbol) + strlen(name) + 1);
+	s = create_library_symbol(name, addr);
 	if (s == NULL)
 		error(EXIT_FAILURE, errno, "add_library_symbol failed");
 
@@ -323,15 +338,45 @@
 	s->is_weak = is_weak;
 	s->plt_type = type_of_plt;
 	s->next = *library_symbolspp;
-	s->enter_addr = (void *)(uintptr_t) addr;
 	s->brkpnt = NULL;
-	s->name = (char *)(s + 1);
-	strcpy(s->name, name);
 	*library_symbolspp = s;
 
 	debug(2, "addr: %p, symbol: \"%s\"", (void *)(uintptr_t) addr, name);
 }
 
+struct library_symbol *
+clone_library_symbol(struct library_symbol * sym)
+{
+	struct library_symbol * copy
+		= create_library_symbol(sym->name,
+					(GElf_Addr)(uintptr_t)sym->enter_addr);
+	if (copy == NULL)
+		return NULL;
+
+	copy->needs_init = sym->needs_init;
+	copy->is_weak = sym->is_weak;
+	copy->plt_type = sym->plt_type;
+	copy->brkpnt = sym->brkpnt;
+
+	return copy;
+}
+
+void
+destroy_library_symbol(struct library_symbol * sym)
+{
+	free(sym);
+}
+
+void
+destroy_library_symbol_chain(struct library_symbol * sym)
+{
+	while (sym != NULL) {
+		struct library_symbol * next = sym->next;
+		destroy_library_symbol(sym);
+		sym = next;
+	}
+}
+
 static int in_load_libraries(const char *name, struct ltelf *lte)
 {
 	size_t i;
diff --git a/ltrace.h b/ltrace.h
index 0ff4572..194704d 100644
--- a/ltrace.h
+++ b/ltrace.h
@@ -184,6 +184,7 @@ struct event {
 		LT_EV_SYSCALL,
 		LT_EV_SYSRET,
 		LT_EV_CLONE,
+		LT_EV_VFORK,
 		LT_EV_EXEC,
 		LT_EV_BREAKPOINT,
 		LT_EV_NEW
diff --git a/proc.c b/proc.c
index 0425e09..f4d3396 100644
--- a/proc.c
+++ b/proc.c
@@ -150,6 +150,28 @@ void open_forked_pid(pid_t pid, int earl
 
 static struct process * list_of_processes = NULL;
 
+static void
+unlist_process(struct process * proc)
+{
+	struct process *tmp;
+
+	if (list_of_processes == proc) {
+		list_of_processes = list_of_processes->next;
+		return;
+	}
+
+	for (tmp = list_of_processes; ; tmp = tmp->next) {
+		/* If the following assert fails, the process wasn't
+		 * in the list.  */
+		assert(tmp->next != NULL);
+
+		if (tmp->next == proc) {
+			tmp->next = tmp->next->next;
+			return;
+		}
+	}
+}
+
 struct process *
 each_process(struct process * proc,
 	     enum pcb_status (* cb)(struct process * proc, void * data),
@@ -213,6 +234,23 @@ add_process(struct process * proc)
 	*leaderp = proc;
 }
 
+void
+change_process_leader(struct process * proc, struct process * leader)
+{
+	struct process ** leaderp = &list_of_processes;
+	if (proc->leader == leader)
+		return;
+
+	assert(leader != NULL);
+	unlist_process(proc);
+	if (proc != leader)
+		leaderp = &leader->next;
+
+	proc->leader = leader;
+	proc->next = *leaderp;
+	*leaderp = proc;
+}
+
 static enum pcb_status
 clear_leader(struct process * proc, void * data)
 {
@@ -242,29 +280,12 @@ delete_events_for(struct process * proc)
 void
 remove_process(struct process *proc)
 {
-	struct process *tmp, *tmp2;
-
 	if (proc->leader == proc)
 		each_task(proc, &clear_leader, NULL);
 
-	if (list_of_processes == proc) {
-		tmp = list_of_processes;
-		list_of_processes = list_of_processes->next;
-		delete_events_for(tmp);
-		free(tmp);
-		return;
-	}
-	tmp = list_of_processes;
-	while (tmp->next) {
-		if (tmp->next == proc) {
-			tmp2 = tmp->next;
-			tmp->next = tmp->next->next;
-			delete_events_for(tmp2);
-			free(tmp2);
-			return;
-		}
-		tmp = tmp->next;
-	}
+	unlist_process(proc);
+	delete_events_for(proc);
+	free(proc);
 }
 
 void
@@ -283,6 +304,7 @@ destroy_event_handler(struct process * proc)
 	Event_Handler * handler = proc->event_handler;
 	assert(handler != NULL);
-	handler->destroy(handler);
+	if (handler->destroy != NULL)
+		handler->destroy(handler);
 	free(handler);
 	proc->event_handler = NULL;
 }
diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
index 8a79583..0685342 100644
--- a/wait_for_something.c
+++ b/wait_for_something.c
@@ -270,12 +270,18 @@ struct event *wait_for_something(void)
 		if (errno != 0)
 			perror("syscall_p");
 	}
-	if (WIFSTOPPED(status) && ((status>>16 == PTRACE_EVENT_FORK) || (status>>16 == PTRACE_EVENT_VFORK) || (status>>16 == PTRACE_EVENT_CLONE))) {
-		unsigned long data;
-		ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
-		event.thing = LT_EV_CLONE;
-		event.e_un.newpid = data;
-		return &event;
+	if (WIFSTOPPED(status)) {
+		int what = status >> 16;
+		if (what == PTRACE_EVENT_VFORK
+		    || what == PTRACE_EVENT_FORK
+		    || what == PTRACE_EVENT_CLONE) {
+			unsigned long data;
+			event.thing = what == PTRACE_EVENT_VFORK
+				? LT_EV_VFORK : LT_EV_CLONE;
+			ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
+			event.e_un.newpid = data;
+			return &event;
+		}
 	}
 	/* TODO: check for EVENT_CLONE */
 	if (WIFSTOPPED(status) && (status>>16 == PTRACE_EVENT_EXEC)) {
diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c
index e3b71e5..a99593c 100644
--- a/sysdeps/linux-gnu/proc.c
+++ b/sysdeps/linux-gnu/proc.c
@@ -148,7 +148,7 @@ process_status_cb(const char * line, const char * prefix, void * data)
 	switch (c) {
 	case 'Z': RETURN(ps_zombie);
 	case 't': RETURN(ps_tracing_stop);
-	case 'T': {
+	case 'T':
 		/* This can be either "T (stopped)" or, for older
 		 * kernels, "T (tracing stop)".  */
 		if (!strcmp(status, "T (stopped)\n"))
@@ -161,7 +161,8 @@ process_status_cb(const char * line, const char * prefix, void * data)
 			RETURN(ps_stop); /* Some sort of stop
 					  * anyway.  */
 		}
-	}
+	case 'D':
+	case 'S': RETURN(ps_sleeping);
 	}
 
 	RETURN(ps_other);
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index f8a1779..ba3806d 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -164,9 +164,10 @@ continue_process(pid_t pid)
 struct pid_task {
 	pid_t pid;	/* This may be 0 for tasks that exited
 			 * mid-handling.  */
-	int sigstopped;
-	int got_event;
-	int delivered;
+	int sigstopped : 1;
+	int got_event : 1;
+	int delivered : 1;
+	int vforked : 1;
 } * pids;
 
 struct pid_set {
@@ -213,23 +214,6 @@ struct process_stopping_handler
 	struct pid_set pids;
 };
 
-static enum pcb_status
-task_stopped(struct process * task, void * data)
-{
-	/* If the task is already stopped, don't worry about it.
-	 * Likewise if it managed to become a zombie or terminate in
-	 * the meantime.  This can happen when the whole thread group
-	 * is terminating.  */
-	switch (process_status(task->pid)) {
-	case ps_invalid:
-	case ps_tracing_stop:
-	case ps_zombie:
-		return pcb_cont;
-	default:
-		return pcb_stop;
-	}
-}
-
 static struct pid_task *
 get_task_info(struct pid_set * pids, pid_t pid)
 {
@@ -261,6 +245,57 @@ add_task_info(struct pid_set * pids, pid_t pid)
 }
 
 static enum pcb_status
+task_stopped(struct process * task, void * data)
+{
+	enum process_status st = process_status(task->pid);
+	if (data != NULL)
+		*(enum process_status *)data = st;
+
+	/* If the task is already stopped, don't worry about it.
+	 * Likewise if it managed to become a zombie or terminate in
+	 * the meantime.  This can happen when the whole thread group
+	 * is terminating.  */
+	switch (st) {
+	case ps_invalid:
+	case ps_tracing_stop:
+	case ps_zombie:
+		return pcb_cont;
+	default:
+		return pcb_stop;
+	}
+}
+
+/* Task is blocked if it's stopped, or if it's a vfork parent.  */
+static enum pcb_status
+task_blocked(struct process * task, void * data)
+{
+	struct pid_set * pids = data;
+	struct pid_task * task_info = get_task_info(pids, task->pid);
+	if (task_info != NULL
+	    && task_info->vforked)
+		return pcb_cont;
+
+	return task_stopped(task, NULL);
+}
+
+static struct event * process_vfork_on_event(Event_Handler * super, struct event * event);
+
+static enum pcb_status
+task_vforked(struct process * task, void * data)
+{
+	if (task->event_handler != NULL
+	    && task->event_handler->on_event == &process_vfork_on_event)
+		return pcb_stop;
+	return pcb_cont;
+}
+
+static int
+is_vfork_parent(struct process * task)
+{
+	return each_task(task->leader, &task_vforked, NULL) != NULL;
+}
+
+static enum pcb_status
 send_sigstop(struct process * task, void * data)
 {
 	struct process * leader = task->leader;
@@ -283,9 +318,11 @@ send_sigstop(Process * task, void * data)
 		return pcb_cont;
 
 	/* Don't bother sending SIGSTOP if we are already stopped, or
-	 * if we sent the SIGSTOP already, which happens when we
-	 * inherit the handler from breakpoint re-enablement.  */
-	if (task_stopped(task, NULL) == pcb_cont)
+	 * if we sent the SIGSTOP already, which happens when we are
+	 * handling "onexit" and inherited the handler from breakpoint
+	 * re-enablement.  */
+	enum process_status st;
+	if (task_stopped(task, &st) == pcb_cont)
 		return pcb_cont;
 	if (task_info->sigstopped) {
 		if (!task_info->delivered)
@@ -293,6 +330,16 @@ send_sigstop(Process * task, void * data)
 		task_info->delivered = 0;
 	}
 
+	/* Also don't attempt to stop the process if it's a parent of
+	 * vforked process.  We set up event handler specially to hint
+	 * us.  In that case parent is in D state, which we use to
+	 * weed out unnecessary looping.  */
+	if (st == ps_sleeping
+	    && is_vfork_parent (task)) {
+		task_info->vforked = 1;
+		return pcb_cont;
+	}
+
 	if (task_kill(task->pid, SIGSTOP) >= 0) {
 		debug(DEBUG_PROCESS, "send SIGSTOP to %d", task->pid);
 		task_info->sigstopped = 1;
@@ -519,7 +519,7 @@ process_stopping_on_event(Event_Handler 
 	switch (state) {
 	case psh_stopping:
 		/* If everyone is stopped, singlestep.  */
-		if (each_task(leader, &task_stopped, NULL) == NULL) {
+		if (each_task(leader, &task_blocked, &self->pids) == NULL) {
 			if (sbp->enabled)
 				disable_breakpoint(teb, sbp);
 			if (ptrace(PTRACE_SINGLESTEP, teb->pid, 0, 0))
@@ -742,6 +789,109 @@ ltrace_exiting_install_handler(Process * proc)
 	return 0;
 }
 
+/*
+ * When the traced process vforks, it's suspended until the child
+ * process calls _exit or exec*.  In the meantime, the two share the
+ * address space.
+ *
+ * The child process should only ever call _exit or exec*, but we
+ * can't count on that (it's not the role of ltrace to policy, but to
+ * observe).  In any case, we will _at least_ have to deal with
+ * removal of vfork return breakpoint (which we have to smuggle back
+ * in, so that the parent can see it, too), and introduction of exec*
+ * return breakpoint.  Since we already have both breakpoint actions
+ * to deal with, we might as well support it all.
+ *
+ * The gist is that we pretend that the child is in a thread group
+ * with its parent, and handle it as a multi-threaded case, with the
+ * exception that we know that the parent is blocked, and don't
+ * attempt to stop it.  When the child execs, we undo the setup.
+ *
+ * XXX The parent process could be un-suspended before ltrace gets
+ * child exec/exit event.  Make sure this is taken care of.
+ */
+
+struct process_vfork_handler
+{
+	Event_Handler super;
+	void * bp_addr;
+};
+
+static struct event *
+process_vfork_on_event(Event_Handler * super, struct event * event)
+{
+	struct process_vfork_handler * self = (void *)super;
+	struct breakpoint * sbp;
+	assert(self != NULL);
+
+	switch (event->thing) {
+	case LT_EV_BREAKPOINT:
+		/* Remember the vfork return breakpoint.  */
+		if (self->bp_addr == NULL)
+			self->bp_addr = event->e_un.brk_addr;
+		break;
+
+	case LT_EV_EXIT:
+	case LT_EV_EXIT_SIGNAL:
+	case LT_EV_EXEC:
+		/* Smuggle back in the vfork return breakpoint, so
+		 * that our parent can trip over it once again.  */
+		if (self->bp_addr != NULL) {
+			sbp = dict_find_entry(event->proc->leader->breakpoints,
+					      self->bp_addr);
+			if (sbp != NULL)
+				insert_breakpoint(event->proc->parent,
+						  self->bp_addr, sbp->libsym,
+						  1);
+		}
+
+		continue_process(event->proc->parent->pid);
+
+		/* Remove the leader that we artificially set up
+		 * earlier.  */
+		change_process_leader(event->proc, event->proc);
+		destroy_event_handler(event->proc);
+
+		/* XXXXX this could happen in the middle of handling
+		 * multi-threaded breakpoint.  We must be careful to
+		 * undo the effects that we introduced above (vforked
+		 * = 1 et.al.).  */
+
+	default:
+		;
+	}
+
+	return event;
+}
+
+void
+continue_after_vfork(struct process * proc)
+{
+	struct process_vfork_handler * handler = calloc(sizeof(*handler), 1);
+	if (handler == NULL) {
+		perror("malloc vfork handler");
+		/* Carry on not bothering to treat the process as
+		 * necessary.  */
+		continue_process(proc->parent->pid);
+		return;
+	}
+
+	/* We must set up custom event handler, so that we see
+	 * exec/exit events for the task itself.  */
+	handler->super.on_event = process_vfork_on_event;
+	install_event_handler(proc, &handler->super);
+
+	/* Make sure that the child is sole thread.  */
+	assert(proc->leader == proc);
+	assert(proc->next == NULL || proc->next->leader != proc);
+
+	/* Make sure that the child's parent is properly set up.  */
+	assert(proc->parent != NULL);
+	assert(proc->parent->leader != NULL);
+
+	change_process_leader(proc, proc->parent->leader);
+}
+
 /* If ltrace gets SIGINT, the processes directly or indirectly run by
  * ltrace get it too.  We just have to wait long enough for the signal
  * to be delivered and the process terminated, which we notice and
diff --git a/testsuite/ltrace.main/main-vfork.c b/testsuite/ltrace.main/main-vfork.c
new file mode 100644
index 0000000..a5f6c40
--- /dev/null
+++ b/testsuite/ltrace.main/main-vfork.c
@@ -0,0 +1,28 @@
+#include <unistd.h>
+
+extern void print (char *);
+
+#define	PRINT_LOOP	10
+
+void
+th_main (char * arg)
+{
+  int i;
+  for (i=0; i<PRINT_LOOP; i++)
+    print (arg);
+}
+
+int main (int argc, char ** argv)
+{
+  if (argc != 1)
+    {
+      th_main ("aaa");
+      return 0;
+    }
+
+  if (!vfork ())
+    execlp (argv[0], argv[0], "", NULL);
+  th_main ("bbb");
+
+  return 0;
+}
diff --git a/testsuite/ltrace.main/main-vfork.exp b/testsuite/ltrace.main/main-vfork.exp
new file mode 100644
index 0000000..299c5e0
--- /dev/null
+++ b/testsuite/ltrace.main/main-vfork.exp
@@ -0,0 +1,39 @@
+# This file was written by Yao Qi <qiyao@cn.ibm.com>.
+
+set testfile "main-vfork"
+set srcfile ${testfile}.c
+set binfile ${testfile}
+set libfile "main-lib"
+set libsrc $srcdir/$subdir/$libfile.c
+set lib_sl $objdir/$subdir/lib$testfile.so
+
+
+if [get_compiler_info $binfile] {
+  return -1
+}
+
+verbose "compiling source file now....."
+if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" 
+  || [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executable [list debug shlib=$lib_sl] ] != ""} {
+  send_user "Testcase compile failed, so all tests in this file will automatically fail.\n"
+}
+
+# set options for ltrace.
+ltrace_options "-f"
+
+# Run PUT for ltarce.
+set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
+
+# Check the output of this program.
+verbose "ltrace runtest output: $exec_output\n"
+if [regexp {ELF from incompatible architecture} $exec_output] {
+	fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!"
+	return 
+} elseif [ regexp {Couldn't get .hash data} $exec_output ] {
+	fail "Couldn't get .hash data!"
+	return
+}
+
+# Verify the output by checking numbers of print in main-vfork.ltrace.
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "print" 20
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "'vfork resumed'" 2
diff --git a/testsuite/ltrace.torture/vfork-thread.c b/testsuite/ltrace.torture/vfork-thread.c
new file mode 100644
index 0000000..f909bd3
--- /dev/null
+++ b/testsuite/ltrace.torture/vfork-thread.c
@@ -0,0 +1,50 @@
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+
+void *
+routine (void *data)
+{
+  int i;
+  for (i = 0; i < 6; ++i)
+    {
+      puts ("bleble");
+      sleep (1);
+    }
+}
+
+
+void *
+routine2 (void *data)
+{
+  pid_t child = vfork ();
+  if (child == 0)
+    {
+      int i, j;
+      puts ("vforked");
+      for (i = 0; i < 100000; ++i)
+	for (j = 0; j < 10000; ++j)
+	  ;
+      puts ("vforked child exiting");
+      _exit (0);
+    }
+  puts ("parent continuing");
+  return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+  pthread_t thread;
+  pthread_create (&thread, NULL, &routine, NULL);
+
+  sleep (1);
+
+  pthread_t thread2;
+  pthread_create (&thread2, NULL, &routine2, NULL);
+  pthread_join (thread2, NULL);
+  pthread_join (thread, NULL);
+  return 0;
+}
diff --git a/testsuite/ltrace.torture/vfork-thread.exp b/testsuite/ltrace.torture/vfork-thread.exp
new file mode 100644
index 0000000..bd01319
--- /dev/null
+++ b/testsuite/ltrace.torture/vfork-thread.exp
@@ -0,0 +1,32 @@
+# This file was written by Yao Qi <qiyao@cn.ibm.com>.
+
+set testfile "vfork-thread"
+set srcfile ${testfile}.c
+set binfile ${testfile}
+
+
+verbose "compiling source file now....."
+# Build the shared libraries this test case needs.
+if  { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/${binfile}" executable [list debug ldflags=-pthread] ] != "" } {
+     send_user "Testcase compile failed, so all tests in this file will automatically fail\n."
+}
+
+ltrace_options "-f"
+
+# Run PUT for ltarce.
+set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
+
+# Check the output of this program.
+verbose "ltrace runtest output: $exec_output\n"
+if [regexp {ELF from incompatible architecture} $exec_output] {
+	fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!"
+	return
+} elseif [ regexp {Couldn't get .hash data} $exec_output ] {
+	fail "Couldn't get .hash data!"
+	return
+}
+
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "puts" 9
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "sleep" 7
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "_exit" 1