[ltrace] Support vfork and fix a bunch of errors

Petr Machata pmachata at fedoraproject.org
Fri Nov 25 14:18:10 UTC 2011


commit d2c71fc092486d674244df46e38f42416c6c58b9
Author: Petr Machata <pmachata at redhat.com>
Date:   Fri Nov 25 15:16:39 2011 +0100

    Support vfork and fix a bunch of errors

 ltrace-0.6.0-ppc-args.patch     |   30 +
 ltrace-0.6.0-ppc-shift.patch    |   21 +
 ltrace-0.6.0-thread-races.patch |  406 ++++++++++++++
 ltrace-0.6.0-vfork.patch        | 1130 +++++++++++++++++++++++++++++++++++++++
 ltrace.spec                     |   16 +
 5 files changed, 1603 insertions(+), 0 deletions(-)
---
diff --git a/ltrace-0.6.0-ppc-args.patch b/ltrace-0.6.0-ppc-args.patch
new file mode 100644
index 0000000..43bfa2e
--- /dev/null
+++ b/ltrace-0.6.0-ppc-args.patch
@@ -0,0 +1,30 @@
+diff --git a/sysdeps/linux-gnu/ppc/trace.c b/sysdeps/linux-gnu/ppc/trace.c
+index 20b3f5d..321e6ec 100644
+--- a/sysdeps/linux-gnu/ppc/trace.c
++++ b/sysdeps/linux-gnu/ppc/trace.c
+@@ -87,10 +87,22 @@ gimme_arg_regset(enum tof type, Process *proc, int arg_num, arg_type_info *info,
+ 	}
+ 	else if (greg <= 10)
+ 		return (*regs)[greg++];
+-	else
++	else {
++#ifdef __powerpc64__
++		if (proc->mask_32bit)
++			return ptrace (PTRACE_PEEKDATA, proc->pid,
++				       proc->stack_pointer + 8 +
++				       sizeof (int) * (arg_num - 8), 0) >> 32;
++		else
++			return ptrace (PTRACE_PEEKDATA, proc->pid,
++				       proc->stack_pointer + 112 +
++				       sizeof (long) * (arg_num - 8), 0);
++#else
+ 		return ptrace (PTRACE_PEEKDATA, proc->pid,
+-				proc->stack_pointer + sizeof (long) *
+-				(arg_num - 8), 0);
++			       proc->stack_pointer + 8 +
++			       sizeof (long) * (arg_num - 8), 0);
++#endif
++	}
+ 
+ 	return 0;
+ }
diff --git a/ltrace-0.6.0-ppc-shift.patch b/ltrace-0.6.0-ppc-shift.patch
new file mode 100644
index 0000000..27d8b95
--- /dev/null
+++ b/ltrace-0.6.0-ppc-shift.patch
@@ -0,0 +1,21 @@
+diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c
+index 980d028..668f63d 100644
+--- a/sysdeps/linux-gnu/ppc/plt.c
++++ b/sysdeps/linux-gnu/ppc/plt.c
+@@ -44,12 +44,16 @@ sym2addr(Process *proc, struct library_symbol *sym) {
+ 
+ 	pt_ret = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0);
+ 
++#if SIZEOF_LONG == 8
+ 	if (proc->mask_32bit) {
+ 		// Assume big-endian.
+ 		addr = (void *)((pt_ret >> 32) & 0xffffffff);
+ 	} else {
+ 		addr = (void *)pt_ret;
+ 	}
++#else
++	addr = (void *)pt_ret;
++#endif
+ 
+ 	return addr;
+ }
diff --git a/ltrace-0.6.0-thread-races.patch b/ltrace-0.6.0-thread-races.patch
new file mode 100644
index 0000000..8250aae
--- /dev/null
+++ b/ltrace-0.6.0-thread-races.patch
@@ -0,0 +1,406 @@
+diff --git a/common.h b/common.h
+index 2fff8dd..715898d 100644
+--- a/common.h
++++ b/common.h
+@@ -343,6 +343,7 @@ extern void disable_breakpoint(Process * proc, Breakpoint * sbp);
+ extern int syscall_p(Process * proc, int status, int * sysnum);
+ extern void continue_process(pid_t pid);
+ extern void continue_after_signal(pid_t pid, int signum);
++extern void continue_after_syscall(Process *proc, int sysnum, int ret_p);
+ extern void continue_after_breakpoint(Process * proc, Breakpoint * sbp);
+ extern void continue_after_vfork(Process * proc);
+ extern void ltrace_exiting(void);
+diff --git a/handle_event.c b/handle_event.c
+index f56c537..203459c 100644
+--- a/handle_event.c
++++ b/handle_event.c
+@@ -70,7 +70,9 @@ handle_event(Event *event) {
+ 
+ 		/* Note: the previous handler has a chance to alter
+ 		 * the event.  */
+-		if (event->proc->leader != NULL) {
++		if (event->proc != NULL
++		    && event->proc->leader != NULL
++		    && event->proc != event->proc->leader) {
+ 			event = call_handler(event->proc->leader, event);
+ 			if (event == NULL)
+ 				return;
+@@ -454,7 +456,7 @@ handle_syscall(Event *event) {
+ 			enable_all_breakpoints(event->proc);
+ 		}
+ 	}
+-	continue_process(event->proc->pid);
++	continue_after_syscall(event->proc, event->e_un.sysnum, 0);
+ }
+ 
+ static void
+@@ -533,9 +535,12 @@ handle_sysret(Event *event) {
+ 			output_right(LT_TOF_SYSCALLR, event->proc,
+ 					sysname(event->proc, event->e_un.sysnum));
+ 		}
++		assert(event->proc->callstack_depth > 0);
++		unsigned d = event->proc->callstack_depth - 1;
++		assert(event->proc->callstack[d].is_syscall);
+ 		callstack_pop(event->proc);
+ 	}
+-	continue_process(event->proc->pid);
++	continue_after_syscall(event->proc, event->e_un.sysnum, 1);
+ }
+ 
+ static void
+@@ -639,7 +644,7 @@ handle_breakpoint(Event *event) {
+ 			struct library_symbol *sym= event->proc->callstack[i].c_un.libfunc;
+ 			struct library_symbol *new_sym;
+ 			assert(sym);
+-			addr = sym2addr(leader, sym);
++			addr = sym2addr(event->proc, sym);
+ 			sbp = dict_find_entry(leader->breakpoints, addr);
+ 			if (sbp) {
+ 				if (addr != sbp->addr) {
+diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
+index 0685342..021192f 100644
+--- a/sysdeps/linux-gnu/events.c
++++ b/sysdeps/linux-gnu/events.c
+@@ -9,6 +9,7 @@
+ #include <string.h>
+ #include <sys/ptrace.h>
+ #include <assert.h>
++#include <unistd.h>
+ 
+ #include "common.h"
+ 
+@@ -138,6 +139,26 @@ next_event(void)
+ 	}
+ 	event.proc = pid2proc(pid);
+ 	if (!event.proc || event.proc->state == STATE_BEING_CREATED) {
++		/* Work around (presumably) a bug on some kernels,
++		 * where we are seeing a waitpid event even though the
++		 * process is still reported to be running.  Wait for
++		 * the tracing stop to propagate.  But don't get stuck
++		 * here forever.
++		 *
++		 * We need the process in T, because there's a lot of
++		 * ptracing going on all over the place, and these
++		 * calls fail when the process is not in T.
++		 *
++		 * N.B. This was observed on RHEL 5 Itanium, but I'm
++		 * turning this on globally, to save some poor soul
++		 * down the road (which could well be me a year from
++		 * now) the pain of figuring this out all over again.
++		 * Petr Machata 2011-11-22.  */
++		int i = 0;
++		for (; i < 100 && process_status(pid) != ps_tracing_stop; ++i) {
++			debug(2, "waiting for %d to stop", pid);
++			usleep(10000);
++		}
+ 		event.type = EVENT_NEW;
+ 		event.e_un.newpid = pid;
+ 		debug(DEBUG_EVENT, "event: NEW: pid=%d", pid);
+diff --git a/sysdeps/linux-gnu/ia64/regs.c b/sysdeps/linux-gnu/ia64/regs.c
+index 00df572..3f5d951 100644
+--- a/sysdeps/linux-gnu/ia64/regs.c
++++ b/sysdeps/linux-gnu/ia64/regs.c
+@@ -2,6 +2,7 @@
+ 
+ #include <sys/types.h>
+ #include <sys/ptrace.h>
++#include <errno.h>
+ 
+ #include <asm/ptrace_offsets.h>
+ #include <asm/rse.h>
+@@ -36,12 +37,18 @@ set_instruction_pointer(Process *proc, void *addr) {
+ 
+ void *
+ get_stack_pointer(Process *proc) {
+-	return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0);
++	long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0);
++	if (l == -1 && errno)
++		return NULL;
++	return (void *)l;
+ }
+ 
+ void *
+ get_return_addr(Process *proc, void *stack_pointer) {
+-	return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, PT_B0, 0);
++	long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_B0, 0);
++	if (l == -1 && errno)
++		return NULL;
++	return (void *)l;
+ }
+ 
+ void
+diff --git a/sysdeps/linux-gnu/ia64/trace.c b/sysdeps/linux-gnu/ia64/trace.c
+index 799e0ff..079ed55 100644
+--- a/sysdeps/linux-gnu/ia64/trace.c
++++ b/sysdeps/linux-gnu/ia64/trace.c
+@@ -9,6 +9,7 @@
+ #include <string.h>
+ #include <asm/ptrace_offsets.h>
+ #include <asm/rse.h>
++#include <errno.h>
+ 
+ #include "common.h"
+ 
+@@ -48,9 +49,10 @@ int
+ syscall_p(Process *proc, int status, int *sysnum) {
+ 	if (WIFSTOPPED(status)
+ 	    && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) {
+-		unsigned long slot =
+-		    (ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0) >> 41) &
+-		    0x3;
++		long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0);
++		if (l == -1 && errno)
++			return -1;
++		unsigned long slot = ((unsigned long)l >> 41) & 0x3;
+ 		unsigned long ip =
+ 		    ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IIP, 0);
+ 
+diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
+index ba3806d..db18df0 100644
+--- a/sysdeps/linux-gnu/trace.c
++++ b/sysdeps/linux-gnu/trace.c
+@@ -146,8 +146,8 @@ continue_process(pid_t pid)
+ 	debug(DEBUG_PROCESS, "continue_process: pid=%d", pid);
+ 
+ 	/* Only really continue the process if there are no events in
+-	   the queue for this process.  Otherwise just for the other
+-	   events to arrive.  */
++	   the queue for this process.  Otherwise just wait for the
++	   other events to arrive.  */
+ 	if (!have_events_for(pid))
+ 		/* We always trace syscalls to control fork(),
+ 		 * clone(), execve()... */
+@@ -168,6 +168,7 @@ struct pid_task {
+ 	int got_event : 1;
+ 	int delivered : 1;
+ 	int vforked : 1;
++	int sysret : 1;
+ } * pids;
+ 
+ struct pid_set {
+@@ -259,10 +260,14 @@ task_stopped(Process * task, void * data)
+ 	case ps_invalid:
+ 	case ps_tracing_stop:
+ 	case ps_zombie:
++	case ps_sleeping:
+ 		return pcb_cont;
+-	default:
++	case ps_stop:
++	case ps_other:
+ 		return pcb_stop;
+ 	}
++
++	abort ();
+ }
+ 
+ /* Task is blocked if it's stopped, or if it's a vfork parent.  */
+@@ -376,7 +381,8 @@ process_stopping_done(struct process_stopping_handler * self, Process * leader)
+ 	if (!self->exiting) {
+ 		for (i = 0; i < self->pids.count; ++i)
+ 			if (self->pids.tasks[i].pid != 0
+-			    && self->pids.tasks[i].delivered)
++			    && (self->pids.tasks[i].delivered
++				|| self->pids.tasks[i].sysret))
+ 				continue_process(self->pids.tasks[i].pid);
+ 		continue_process(self->task_enabling_breakpoint->pid);
+ 		destroy_event_handler(leader);
+@@ -469,7 +475,10 @@ handle_stopping_event(struct pid_task * task_info, Event ** eventp)
+ /* Some SIGSTOPs may have not been delivered to their respective tasks
+  * yet.  They are still in the queue.  If we have seen an event for
+  * that process, continue it, so that the SIGSTOP can be delivered and
+- * caught by ltrace.  */
++ * caught by ltrace.  We don't mind that the process is after
++ * breakpoint (and therefore potentially doesn't have aligned IP),
++ * because the signal will be delivered without the process actually
++ * starting.  */
+ static void
+ continue_for_sigstop_delivery(struct pid_set * pids)
+ {
+@@ -549,6 +558,14 @@ all_stops_accountable(struct pid_set * pids)
+ 	return 1;
+ }
+ 
++static void
++singlestep(Process * proc)
++{
++	debug(1, "PTRACE_SINGLESTEP");
++	if (ptrace(PTRACE_SINGLESTEP, proc->pid, 0, 0))
++		perror("PTRACE_SINGLESTEP");
++}
++
+ /* This event handler is installed when we are in the process of
+  * stopping the whole thread group to do the pointer re-enablement for
+  * one of the threads.  We pump all events to the queue for later
+@@ -580,6 +597,17 @@ process_stopping_on_event(Event_Handler * super, Event * event)
+ 	if (event_exit_p(event) && task_info != NULL)
+ 		task_info->pid = 0;
+ 
++	/* Always handle sysrets.  Whether sysret occurred and what
++	 * sys it rets from may need to be determined based on process
++	 * stack, so we need to keep that in sync with reality.  Note
++	 * that we don't continue the process after the sysret is
++	 * handled.  See continue_after_syscall.  */
++	if (event != NULL && event->type == EVENT_SYSRET) {
++		debug(1, "%d LT_EV_SYSRET", event->proc->pid);
++		event_to_queue = 0;
++		task_info->sysret = 1;
++	}
++
+ 	switch (state) {
+ 	case psh_stopping:
+ 		/* If everyone is stopped, singlestep.  */
+@@ -588,16 +616,22 @@ process_stopping_on_event(Event_Handler * super, Event * event)
+ 			      teb->pid);
+ 			if (sbp->enabled)
+ 				disable_breakpoint(teb, sbp);
+-			if (ptrace(PTRACE_SINGLESTEP, teb->pid, 0, 0))
+-				perror("PTRACE_SINGLESTEP");
++			singlestep(teb);
+ 			self->state = state = psh_singlestep;
+ 		}
+ 		break;
+ 
+-	case psh_singlestep: {
++	case psh_singlestep:
+ 		/* In singlestep state, breakpoint signifies that we
+ 		 * have now stepped, and can re-enable the breakpoint.  */
+ 		if (event != NULL && task == teb) {
++
++			/* This is not the singlestep that we are waiting for.  */
++			if (event->type == EVENT_SIGNAL) {
++				singlestep(task);
++				break;
++			}
++
+ 			/* Essentially we don't care what event caused
+ 			 * the thread to stop.  We can do the
+ 			 * re-enablement now.  */
+@@ -613,7 +647,6 @@ process_stopping_on_event(Event_Handler * super, Event * event)
+ 				event = NULL; // handled
+ 		} else
+ 			break;
+-	}
+ 
+ 		/* fall-through */
+ 
+@@ -806,9 +839,6 @@ ltrace_exiting_install_handler(Process * proc)
+  * 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
+@@ -840,9 +870,9 @@ process_vfork_on_event(Event_Handler * super, Event * event)
+ 			sbp = dict_find_entry(event->proc->leader->breakpoints,
+ 					      self->bp_addr);
+ 			if (sbp != NULL)
+-				insert_breakpoint(event->proc->leader,
+-						  self->bp_addr, sbp->libsym,
+-						  1);
++				insert_breakpoint(event->proc->parent,
++						  self->bp_addr,
++						  sbp->libsym, 1);
+ 		}
+ 
+ 		continue_process(event->proc->parent->pid);
+@@ -852,11 +882,6 @@ process_vfork_on_event(Event_Handler * super, Event * event)
+ 		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:
+ 		;
+ 	}
+@@ -893,6 +918,27 @@ continue_after_vfork(Process * proc)
+ 	change_process_leader(proc, proc->parent->leader);
+ }
+ 
++static int
++is_mid_stopping(Process *proc)
++{
++	return proc != NULL
++		&& proc->event_handler != NULL
++		&& proc->event_handler->on_event == &process_stopping_on_event;
++}
++
++void
++continue_after_syscall(Process * proc, int sysnum, int ret_p)
++{
++	/* Don't continue if we are mid-stopping.  */
++	if (ret_p && (is_mid_stopping(proc) || is_mid_stopping(proc->leader))) {
++		debug(DEBUG_PROCESS,
++		      "continue_after_syscall: don't continue %d",
++		      proc->pid);
++		return;
++	}
++	continue_process(proc->pid);
++}
++
+ /* 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/hello-vfork.c b/testsuite/ltrace.main/hello-vfork.c
+new file mode 100644
+index 0000000..228c052
+--- /dev/null
++++ b/testsuite/ltrace.main/hello-vfork.c
+@@ -0,0 +1,11 @@
++/* Copyright (C) 2008, Red Hat, Inc.
++ * Written by Denys Vlasenko */
++#include <stdio.h>
++#include <unistd.h>
++
++int main() {
++        int r = vfork();
++        fprintf(stderr, "vfork():%d\n", r);
++        _exit(0);
++}
++
+diff --git a/testsuite/ltrace.main/hello-vfork.exp b/testsuite/ltrace.main/hello-vfork.exp
+new file mode 100644
+index 0000000..12c9ca3
+--- /dev/null
++++ b/testsuite/ltrace.main/hello-vfork.exp
+@@ -0,0 +1,35 @@
++# This file was written by Yao Qi <qiyao at cn.ibm.com>.
++
++set testfile "hello-vfork"
++set srcfile ${testfile}.c
++set binfile ${testfile}
++
++
++if [get_compiler_info $binfile] {
++  return -1
++}
++
++verbose "compiling source file now....."
++if { [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executable debug ] != ""} {
++  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 "_exit" 2
++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2
diff --git a/ltrace-0.6.0-vfork.patch b/ltrace-0.6.0-vfork.patch
new file mode 100644
index 0000000..8f56414
--- /dev/null
+++ b/ltrace-0.6.0-vfork.patch
@@ -0,0 +1,1130 @@
+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/common.h
++++ b/common.h
+@@ -252,6 +252,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.  */
+ };
+@@ -265,6 +266,7 @@ enum pcb_status {
+ extern Process * pid2proc(pid_t pid);
+ extern void add_process(Process * proc);
+ extern void remove_process(Process * proc);
++extern void change_process_leader(Process * proc, Process * leader);
+ extern Process *each_process(Process * start,
+ 			     enum pcb_status (* cb)(Process * proc, void * data),
+ 			     void * data);
+@@ -313,6 +315,10 @@ extern void add_library_symbol(GElf_Addr addr, const char *name,
+ 		struct library_symbol **library_symbolspp,
+ 		enum toplt type_of_plt, int is_weak);
+ 
++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);
+@@ -335,6 +341,7 @@ extern int syscall_p(Process * proc, int status, int * sysnum);
+ extern void continue_process(pid_t pid);
+ extern void continue_after_signal(pid_t pid, int signum);
+ extern void continue_after_breakpoint(Process * proc, Breakpoint * sbp);
++extern void continue_after_vfork(Process * proc);
+ extern void ltrace_exiting(void);
+ extern long gimme_arg(enum tof type, Process * proc, int arg_num, arg_type_info * info);
+ extern void save_register_args(enum tof type, Process * proc);
+diff --git a/dict.c b/dict.c
+index 486a461..ba318cd 100644
+--- a/dict.c
++++ b/dict.c
+@@ -180,7 +180,9 @@ dict_key_cmp_int(void *key1, void *key2) {
+ }
+ 
+ Dict *
+-dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)) {
++dict_clone2(Dict * old, void * (*key_clone)(void *, void *),
++	    void * (*value_clone)(void *, void *), void * data)
++{
+ 	Dict *d;
+ 	int i;
+ 
+@@ -199,17 +201,64 @@ dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*))
+ 		de_old = old->buckets[i];
+ 		de_new = &d->buckets[i];
+ 		while (de_old) {
++			void * nkey, * nval;
+ 			*de_new = malloc(sizeof(struct dict_entry));
+ 			if (!*de_new) {
+ 				perror("malloc()");
+ 				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);
++
++			/* The error detection is rather weak :-/ */
++			nkey = key_clone(de_old->key, data);
++			if (nkey == NULL && de_old->key != NULL) {
++				perror("key_clone");
++			err:
++				/* XXX Will this actually work?  We
++				 * simply memcpy the old dictionary
++				 * over up there.  */
++				dict_clear(d);
++				free(de_new);
++				return NULL;
++			}
++
++			nval = value_clone(de_old->value, data);
++			if (nval == NULL && de_old->value != NULL) {
++				perror("value_clone");
++				goto err;
++			}
++
++			(*de_new)->key = nkey;
++			(*de_new)->value = nval;
+ 			de_new = &(*de_new)->next;
+ 			de_old = de_old->next;
+ 		}
+ 	}
+ 	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);
++}
++
++Dict *
++dict_clone(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,7 @@ extern int dict_key_cmp_string(void *key1, void *key2);
+ extern unsigned int dict_key2hash_int(void *key);
+ extern int dict_key_cmp_int(void *key1, void *key2);
+ extern Dict * dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*));
++extern Dict * dict_clone2(Dict * old,
++			  void * (* key_clone)(void * key, void * data),
++			  void * (* value_clone)(void * value, void * data),
++			  void * data);
+diff --git a/handle_event.c b/handle_event.c
+index 0aa40f7..f56c537 100644
+--- a/handle_event.c
++++ b/handle_event.c
+@@ -7,6 +7,7 @@
+ #include <signal.h>
+ #include <assert.h>
+ #include <sys/time.h>
++#include <errno.h>
+ 
+ #include "common.h"
+ 
+@@ -35,6 +36,18 @@ static char * shortsignal(Process *proc, int signum);
+ static char * sysname(Process *proc, int sysnum);
+ static char * arch_sysname(Process *proc, int sysnum);
+ 
++static Event *
++call_handler(Process * proc, Event * event)
++{
++	assert(proc != NULL);
++
++	Event_Handler * handler = proc->event_handler;
++	if (handler == NULL)
++		return event;
++
++	return (*handler->on_event) (handler, event);
++}
++
+ void
+ handle_event(Event *event) {
+ 	if (exiting == 1) {
+@@ -44,15 +57,22 @@ handle_event(Event *event) {
+ 	}
+ 	debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)",
+ 	      event->proc ? event->proc->pid : -1, event->type);
+-	/* 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;
+ 		}
+ 	}
+@@ -102,6 +122,7 @@ handle_event(Event *event) {
+ 		handle_arch_sysret(event);
+ 		return;
+ 	case EVENT_CLONE:
++	case EVENT_VFORK:
+ 		debug(1, "event: clone (%u)", event->e_un.newpid);
+ 		handle_clone(event);
+ 		return;
+@@ -125,14 +146,17 @@ handle_event(Event *event) {
+ 
+ /* TODO */
+ static void *
+-address_clone(void * addr) {
++address_clone(void * addr, void * data)
++{
+ 	debug(DEBUG_FUNCTION, "address_clone(%p)", addr);
+ 	return addr;
+ }
+ 
+ static void *
+-breakpoint_clone(void * bp) {
++breakpoint_clone(void * bp, void * data)
++{
+ 	Breakpoint * b;
++	Dict * map = data;
+ 	debug(DEBUG_FUNCTION, "breakpoint_clone(%p)", bp);
+ 	b = malloc(sizeof(Breakpoint));
+ 	if (!b) {
+@@ -140,6 +164,15 @@ breakpoint_clone(void * bp) {
+ 		exit(1);
+ 	}
+ 	memcpy(b, bp, sizeof(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;
+ }
+ 
+@@ -204,6 +237,40 @@ pending_new_remove(pid_t pid) {
+ 	}
+ }
+ 
++static int
++clone_breakpoints(Process * proc, Process * orig_proc)
++{
++	/* When copying breakpoints, we also have to copy the
++	 * referenced symbols, and link them properly.  */
++	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
+ handle_clone(Event * event) {
+ 	Process *p;
+@@ -216,7 +283,6 @@ handle_clone(Event * event) {
+ 		exit(1);
+ 	}
+ 	memcpy(p, event->proc, sizeof(Process));
+-	p->breakpoints = dict_clone(event->proc->breakpoints, address_clone, breakpoint_clone);
+ 	p->pid = event->e_un.newpid;
+ 	p->parent = event->proc;
+ 
+@@ -239,7 +305,17 @@ handle_clone(Event * event) {
+ 		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->type == EVENT_VFORK)
++		continue_after_vfork(p);
++	else
++		continue_process(event->proc->pid);
+ }
+ 
+ static void
+@@ -253,8 +329,6 @@ handle_new(Event * event) {
+ 		pending_new_insert(event->e_un.newpid);
+ 	} else {
+ 		assert(proc->state == STATE_BEING_CREATED);
+-		if (proc->event_handler != NULL)
+-			destroy_event_handler(proc);
+ 		if (options.follow) {
+ 			proc->state = STATE_ATTACHED;
+ 		} else {
+diff --git a/libltrace.c b/libltrace.c
+index e731fe1..19bfafd 100644
+--- a/libltrace.c
++++ b/libltrace.c
+@@ -107,6 +107,11 @@ ltrace_init(int argc, char **argv) {
+ 		}
+ 	}
+ 	if (command) {
++		/* Check that the binary ABI is supported before
++		 * calling execute_program.  */
++		struct ltelf lte = {};
++		open_elf(&lte, command);
++
+ 		open_program(command, execute_program(command, argv), 0);
+ 	}
+ 	opt_p_tmp = opt_p;
+diff --git a/ltrace-elf.c b/ltrace-elf.c
+index d88d5a6..9aea4a9 100644
+--- a/ltrace-elf.c
++++ b/ltrace-elf.c
+@@ -136,18 +136,14 @@ static GElf_Addr get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot,
+ }
+ 
+ int
+-do_init_elf(struct ltelf *lte, const char *filename) {
+-	int i;
+-	GElf_Addr relplt_addr = 0;
+-	size_t relplt_size = 0;
+-
+-	debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename);
+-	debug(1, "Reading ELF from %s...", filename);
+-
++open_elf(struct ltelf *lte, const char *filename)
++{
+ 	lte->fd = open(filename, O_RDONLY);
+ 	if (lte->fd == -1)
+ 		return 1;
+ 
++	elf_version(EV_CURRENT);
++
+ #ifdef HAVE_ELF_C_READ_MMAP
+ 	lte->elf = elf_begin(lte->fd, ELF_C_READ_MMAP, NULL);
+ #else
+@@ -180,6 +176,21 @@ do_init_elf(struct ltelf *lte, const char *filename) {
+ 		error(EXIT_FAILURE, 0,
+ 		      "\"%s\" is ELF from incompatible architecture", filename);
+ 
++	return 0;
++}
++
++int
++do_init_elf(struct ltelf *lte, const char *filename) {
++	int i;
++	GElf_Addr relplt_addr = 0;
++	size_t relplt_size = 0;
++
++	debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename);
++	debug(1, "Reading ELF from %s...", filename);
++
++	if (open_elf(lte, filename) < 0)
++		return -1;
++
+ 	Elf_Data *plt_data = NULL;
+ 	GElf_Addr ppcgot = 0;
+ 
+@@ -465,30 +476,76 @@ do_close_elf(struct ltelf *lte) {
+ 	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;
++}
++
+ void
+ add_library_symbol(GElf_Addr addr, const char *name,
+ 		   struct library_symbol **library_symbolspp,
+-		   enum toplt type_of_plt, int is_weak) {
++		   enum toplt type_of_plt, int is_weak)
++{
+ 	struct library_symbol *s;
+ 
+ 	debug(DEBUG_FUNCTION, "add_library_symbol()");
+ 
+-	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");
+ 
+ 	s->needs_init = 1;
+ 	s->is_weak = is_weak;
+ 	s->plt_type = type_of_plt;
++
+ 	s->next = *library_symbolspp;
+-	s->enter_addr = (void *)(uintptr_t) addr;
+-	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;
++
++	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;
++	}
++}
++
+ /* stolen from elfutils-0.123 */
+ static unsigned long
+ private_elf_gnu_hash(const char *name) {
+@@ -620,8 +677,6 @@ read_elf(Process *proc) {
+ 	library_num = 0;
+ 	proc->libdl_hooked = 0;
+ 
+-	elf_version(EV_CURRENT);
+-
+ 	if (do_init_elf(lte, proc->filename))
+ 		return NULL;
+ 
+@@ -637,7 +692,7 @@ read_elf(Process *proc) {
+ 	for (i = 0; i < library_num; ++i) {
+ 		if (do_init_elf(&lte[i + 1], library[i]))
+ 			error(EXIT_FAILURE, errno, "Can't open \"%s\"",
+-			      proc->filename);
++			      library[i]);
+ 	}
+ 
+ 	if (!options.no_plt) {
+diff --git a/ltrace-elf.h b/ltrace-elf.h
+index a29fe2c..3b675c5 100644
+--- a/ltrace-elf.h
++++ b/ltrace-elf.h
+@@ -44,6 +44,7 @@ struct ltelf {
+ extern size_t library_num;
+ extern char *library[MAX_LIBRARIES];
+ 
++extern int open_elf(struct ltelf *lte, const char *filename);
+ extern struct library_symbol *read_elf(Process *);
+ 
+ extern GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *);
+diff --git a/ltrace.h b/ltrace.h
+index 0ff4572..194704d 100644
+--- a/ltrace.h
++++ b/ltrace.h
+@@ -9,6 +9,7 @@ enum Event_type {
+ 	EVENT_ARCH_SYSCALL,
+ 	EVENT_ARCH_SYSRET,
+ 	EVENT_CLONE,
++	EVENT_VFORK,
+ 	EVENT_EXEC,
+ 	EVENT_BREAKPOINT,
+ 	EVENT_LIBCALL,
+diff --git a/proc.c b/proc.c
+index 0425e09..f4d3396 100644
+--- a/proc.c
++++ b/proc.c
+@@ -154,9 +154,30 @@ pid2proc(pid_t pid) {
+ 	return each_process(NULL, &find_proc, (void *)(uintptr_t)pid);
+ }
+ 
+-
+ static Process * list_of_processes = NULL;
+ 
++static void
++unlist_process(Process * proc)
++{
++	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;
++		}
++	}
++}
++
+ Process *
+ each_process(Process * proc,
+ 	     enum pcb_status (* cb)(Process * proc, void * data),
+@@ -213,6 +234,23 @@ add_process(Process * proc)
+ 	*leaderp = proc;
+ }
+ 
++void
++change_process_leader(Process * proc, Process * leader)
++{
++	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(Process * proc, void * data)
+ {
+@@ -242,31 +280,14 @@ delete_events_for(Process * proc)
+ void
+ remove_process(Process *proc)
+ {
+-	Process *tmp, *tmp2;
+-
+ 	debug(DEBUG_FUNCTION, "remove_proc(pid=%d)", proc->pid);
+ 
+ 	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,7 +304,8 @@ destroy_event_handler(Process * proc)
+ 	Event_Handler * handler = proc->event_handler;
+ 	debug(DEBUG_FUNCTION, "destroy_event_handler(pid=%d, %p)", proc->pid, 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/sysdeps/linux-gnu/events.c
++++ b/sysdeps/linux-gnu/events.c
+@@ -240,13 +240,20 @@ next_event(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.type = EVENT_CLONE;
+-		event.e_un.newpid = data;
+-		debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d", pid, (int)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.type = what == PTRACE_EVENT_VFORK
++				? EVENT_VFORK : EVENT_CLONE;
++			ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
++			event.e_un.newpid = data;
++			debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d",
++			      pid, (int)data);
++			return &event;
++		}
+ 	}
+ 	if (WIFSTOPPED(status) && (status>>16 == PTRACE_EVENT_EXEC)) {
+ 		event.type = 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(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(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(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 Event * process_vfork_on_event(Event_Handler * super, Event * event);
++
++static enum pcb_status
++task_vforked(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(Process * task)
++{
++	return each_task(task->leader, &task_vforked, NULL) != NULL;
++}
++
++static enum pcb_status
+ send_sigstop(Process * task, void * data)
+ {
+ 	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;
+@@ -536,7 +583,7 @@ process_stopping_on_event(Event_Handler * super, Event * event)
+ 	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) {
+ 			debug(DEBUG_PROCESS, "all stopped, now SINGLESTEP %d",
+ 			      teb->pid);
+ 			if (sbp->enabled)
+@@ -742,6 +789,110 @@ 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 Event *
++process_vfork_on_event(Event_Handler * super, Event * event)
++{
++	struct process_vfork_handler * self = (void *)super;
++	Breakpoint * sbp;
++	assert(self != NULL);
++
++	switch (event->type) {
++	case EVENT_BREAKPOINT:
++		/* Remember the vfork return breakpoint.  */
++		if (self->bp_addr == NULL)
++			self->bp_addr = event->e_un.brk_addr;
++		break;
++
++	case EVENT_EXIT:
++	case EVENT_EXIT_SIGNAL:
++	case EVENT_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->leader,
++						  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(Process * proc)
++{
++	debug(DEBUG_PROCESS, "continue_after_vfork: pid=%d", proc->pid);
++	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-threaded.exp b/testsuite/ltrace.main/main-threaded.exp
+index 0157797..5539805 100644
+--- a/testsuite/ltrace.main/main-threaded.exp
++++ b/testsuite/ltrace.main/main-threaded.exp
+@@ -19,7 +19,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != ""
+ }
+ 
+ # set options for ltrace.
+-ltrace_options "-l" "$objdir/$subdir/libmain.so" "-f"
++ltrace_options "-l" "$lib_sl" "-f"
+ 
+ # Run PUT for ltarce.
+ set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
+@@ -35,5 +35,5 @@ if [regexp {ELF from incompatible architecture} $exec_output] {
+ }
+ 
+ # Verify the output by checking numbers of print in main-threaded.ltrace.
+-set pattern "print("
++set pattern "print"
+ ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 30
+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 at 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 "-l" "$lib_sl" "-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 at 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
diff --git a/ltrace.spec b/ltrace.spec
index cf57e98..bf21297 100644
--- a/ltrace.spec
+++ b/ltrace.spec
@@ -23,6 +23,10 @@ Patch5: ltrace-0.6.0-return-string-n.patch
 Patch6: ltrace-0.6.0-threads.patch
 Patch7: ltrace-0.6.0-endian.patch
 Patch8: ltrace-0.6.0-clone-test.patch
+Patch9: ltrace-0.6.0-ppc-args.patch
+Patch10: ltrace-0.6.0-ppc-shift.patch
+Patch11: ltrace-0.6.0-vfork.patch
+Patch12: ltrace-0.6.0-thread-races.patch
 
 %description
 Ltrace is a debugging program which runs a specified command until the
@@ -44,6 +48,10 @@ execution of processes.
 %patch6 -p1
 %patch7 -p1
 %patch8 -p1
+%patch9 -p1
+%patch10 -p1
+%patch11 -p1
+%patch12 -p1
 sed -i -e 's/-o root -g root//' Makefile.in
 
 %build
@@ -76,6 +84,14 @@ rm -rf $RPM_BUILD_ROOT
 %config(noreplace) %{_sysconfdir}/ltrace.conf
 
 %changelog
+* Fri Nov 25 2011 Petr Machata <pmachata at redhat.com> - 0.6.0-4
+- Add several upstream patches that fix various races in tracing
+  multi-threaded processes
+- Add upstream patches for support of tracing across vfork
+- Add upstream patches for ppc: excessive shift, and fetching
+  function arguments
+- Bump up revision to preserve upgrade path
+
 * Fri Sep  2 2011 Petr Machata <pmachata at redhat.com> - 0.6.0-2
 - Add upstream patches for tracing multi-threaded processes, endian
   fixes, and a test suite fixlet


More information about the scm-commits mailing list