The first crude version of tracing across libraries

- the patch will be sliced later
diff --git a/proc.c b/proc.c
index 5360b0b..1984d12 100644
--- a/proc.c
+++ b/proc.c
@@ -17,40 +17,189 @@
 #include "breakpoint.h"
 #include "proc.h"
 
-Process *
-open_program(char *filename, pid_t pid, int enable) {
-	Process *proc;
-	assert(pid != 0);
-	proc = calloc(sizeof(Process), 1);
-	if (!proc) {
-		perror("malloc");
-		exit(1);
-	}
+static int
+process_bare_init(struct Process *proc, const char *filename, pid_t pid)
+{
+	fprintf(stderr, "process_bare_init %s %d\n", filename, pid);
+	memset(proc, 0, sizeof(*proc));
 
 	proc->filename = strdup(filename);
+	if (proc->filename == NULL) {
+	fail:
+		free(proc->filename);
+		if (proc->breakpoints != NULL)
+			dict_clear(proc->breakpoints);
+		return -1;
+	}
+
+	/* Add process so that we know who the leader is.  */
 	proc->pid = pid;
+	add_process(proc);
+	if (proc->leader == NULL)
+		goto fail;
+
+	if (proc->leader == proc) {
+		proc->breakpoints = dict_init(dict_key2hash_int,
+					      dict_key_cmp_int);
+		if (proc->breakpoints == NULL)
+			goto fail;
+	} else {
+		proc->breakpoints = NULL;
+	}
+
 #if defined(HAVE_LIBUNWIND)
 	proc->unwind_priv = _UPT_create(pid);
 	proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0);
 #endif /* defined(HAVE_LIBUNWIND) */
 
-	add_process(proc);
-	if (proc->leader == NULL) {
+	return 0;
+}
+
+static void
+process_bare_destroy(struct Process *proc)
+{
+	free(proc->filename);
+	dict_clear(proc->breakpoints);
+	remove_process(proc);
+}
+
+int
+process_init(struct Process *proc, const char *filename, pid_t pid, int enable)
+{
+	fprintf(stderr, "process_init %s %d enable=%d\n", filename, pid, enable);
+	if (process_bare_init(proc, filename, pid) < 0) {
+		error(0, errno, "init process %d", pid);
+		return -1;
+	}
+
+	if (proc->leader == proc && breakpoints_init(proc, enable) < 0) {
+		fprintf(stderr, "failed to init breakpoints %d\n",
+			proc->pid);
+		process_bare_destroy(proc);
+		return -1;
+	}
+
+	return 0;
+}
+
+struct Process *
+open_program(const char *filename, pid_t pid, int enable)
+{
+	fprintf(stderr, "open_program %s %d enable=%d\n",
+		filename, pid, enable);
+	assert(pid != 0);
+	struct Process *proc = malloc(sizeof(*proc));
+	if (proc == NULL
+	    || process_init(proc, filename, pid, enable) < 0) {
 		free(proc);
 		return NULL;
 	}
+	return proc;
+}
 
-	if (proc->leader == proc) {
-		trace_set_options(proc, proc->pid);
-		if (breakpoints_init(proc, enable)) {
-			fprintf(stderr, "failed to init breakpoints %d\n",
-				proc->pid);
-			remove_process(proc);
-			return NULL;
-		}
+struct clone_single_bp_data {
+	struct Process *old_proc;
+	struct Process *new_proc;
+	int error;
+};
+
+struct find_symbol_data {
+	struct library_symbol *old_libsym;
+	struct library_symbol *found_libsym;
+};
+
+static enum callback_status
+find_sym_in_lib(struct Process *proc, struct library *lib, void *u)
+{
+	struct find_symbol_data *fs = u;
+	fs->found_libsym
+		= library_each_symbol(lib, NULL, library_symbol_equal_cb,
+				      fs->old_libsym);
+	return fs->found_libsym != NULL ? CBS_STOP : CBS_CONT;
+}
+
+static void
+clone_single_bp(void *key, void *value, void *u)
+{
+	target_address_t addr = (target_address_t)key;
+	struct breakpoint *bp = value;
+	struct clone_single_bp_data *data = u;
+
+	/* Find library and symbol that this symbol was linked to.  */
+	struct library_symbol *libsym = bp->libsym;
+	struct library *lib = NULL;
+	if (libsym != NULL) {
+		struct find_symbol_data f_data = {
+			.old_libsym = libsym,
+		};
+		lib = proc_each_library(data->old_proc, NULL,
+					find_sym_in_lib, &f_data);
+		assert(lib != NULL);
+		libsym = f_data.found_libsym;
 	}
 
-	return proc;
+	/* LIB and LIBSYM now hold the new library and symbol that
+	 * correspond to the original breakpoint.  Now we can do the
+	 * clone itself.  */
+	struct breakpoint *clone = malloc(sizeof(*clone));
+	if (clone == NULL
+	    || breakpoint_init(clone, data->new_proc, addr,
+			       libsym, bp->cbs) < 0) {
+		data->error = -1;
+		return;
+	}
+}
+
+int
+process_clone(struct Process *retp, struct Process *proc, pid_t pid)
+{
+	if (process_bare_init(retp, proc->filename, pid) < 0) {
+	fail:
+		error(0, errno, "clone process %d->%d", proc->pid, pid);
+		return -1;
+	}
+
+	/* For non-leader processes, that's all we need to do.  */
+	if (proc->leader != proc)
+		return 0;
+
+	/* Clone symbols first so that we can clone and relink
+	 * breakpoints.  */
+	struct library *lib;
+	struct library **nlibp = &retp->libraries;
+	for (lib = proc->libraries; lib != NULL; lib = lib->next) {
+		*nlibp = malloc(sizeof(**nlibp));
+		if (*nlibp == NULL
+		    || library_clone(*nlibp, lib) < 0) {
+		fail2:
+			process_bare_destroy(retp);
+
+			/* Error when cloning.  Unroll what was done.  */
+			for (lib = retp->libraries; lib != NULL; ) {
+				struct library *next = lib->next;
+				library_destroy(lib);
+				free(lib);
+				lib = next;
+			}
+			goto fail;
+		}
+
+		nlibp = &(*nlibp)->next;
+	}
+
+	/* Now clone breakpoints.  Symbol relinking is done in
+	 * clone_single_bp.  */
+	struct clone_single_bp_data data = {
+		.old_proc = proc,
+		.new_proc = retp,
+		.error = 0,
+	};
+	dict_apply_to_all(proc->breakpoints, &clone_single_bp, &data);
+
+	if (data.error < 0)
+		goto fail2;
+
+	return 0;
 }
 
 static int
@@ -76,11 +225,11 @@
 	return 0;
 }
 
-static enum pcb_status
+static enum callback_status
 start_one_pid(Process * proc, void * data)
 {
 	continue_process(proc->pid);
-	return pcb_cont;
+	return CBS_CONT;
 }
 
 void
@@ -144,11 +293,11 @@
 	each_task(pid2proc(pid)->leader, start_one_pid, NULL);
 }
 
-static enum pcb_status
+static enum callback_status
 find_proc(Process * proc, void * data)
 {
 	pid_t pid = (pid_t)(uintptr_t)data;
-	return proc->pid == pid ? pcb_stop : pcb_cont;
+	return proc->pid == pid ? CBS_STOP : CBS_CONT;
 }
 
 Process *
@@ -180,33 +329,43 @@
 	}
 }
 
-Process *
-each_process(Process * proc,
-	     enum pcb_status (* cb)(Process * proc, void * data),
-	     void * data)
+struct Process *
+each_process(struct Process *it,
+	     enum callback_status(*cb)(struct Process *proc, void *data),
+	     void *data)
 {
-	Process * it = proc ?: list_of_processes;
+	if (it == NULL)
+		it = list_of_processes;
 	for (; it != NULL; ) {
 		/* Callback might call remove_process.  */
 		Process * next = it->next;
-		if ((*cb) (it, data) == pcb_stop)
+		switch ((*cb)(it, data)) {
+		case CBS_STOP:
 			return it;
+		case CBS_CONT:
+			break;
+		}
 		it = next;
 	}
 	return NULL;
 }
 
 Process *
-each_task(Process * it, enum pcb_status (* cb)(Process * proc, void * data),
-	  void * data)
+each_task(struct Process *it,
+	  enum callback_status(*cb)(struct Process *proc, void *data),
+	  void *data)
 {
 	if (it != NULL) {
 		Process * leader = it->leader;
 		for (; it != NULL && it->leader == leader; ) {
 			/* Callback might call remove_process.  */
 			Process * next = it->next;
-			if ((*cb) (it, data) == pcb_stop)
+			switch ((*cb)(it, data)) {
+			case CBS_STOP:
 				return it;
+			case CBS_CONT:
+				break;
+			}
 			it = next;
 		}
 	}
@@ -216,9 +375,11 @@
 void
 add_process(Process * proc)
 {
+	fprintf(stderr, "add_process %d\n", proc->pid);
 	Process ** leaderp = &list_of_processes;
 	if (proc->pid) {
 		pid_t tgid = process_leader(proc->pid);
+		fprintf(stderr, " + leader is %d\n", tgid);
 		if (tgid == 0)
 			/* Must have been terminated before we managed
 			 * to fully attach.  */
@@ -253,13 +414,13 @@
 	*leaderp = proc;
 }
 
-static enum pcb_status
-clear_leader(Process * proc, void * data)
+static enum callback_status
+clear_leader(struct Process *proc, void *data)
 {
 	debug(DEBUG_FUNCTION, "detach_task %d from leader %d",
 	      proc->pid, proc->leader->pid);
 	proc->leader = NULL;
-	return pcb_cont;
+	return CBS_CONT;
 }
 
 static enum ecb_status
@@ -311,3 +472,69 @@
 	free(handler);
 	proc->event_handler = NULL;
 }
+
+static enum callback_status
+breakpoint_for_symbol(struct library_symbol *libsym, void *data)
+{
+	struct Process *proc = data;
+	fprintf(stderr, "  %s@%p\n", libsym->name, libsym->enter_addr);
+
+	if (insert_breakpoint(proc, libsym->enter_addr, libsym, 1) == NULL)
+		return CBS_STOP;
+
+	return CBS_CONT;
+}
+
+void
+proc_add_library(struct Process *proc, struct library *lib)
+{
+	assert(lib->next == NULL);
+	lib->next = proc->libraries;
+	proc->libraries = lib;
+	fprintf(stderr, "=== Added library %s@%p to %d:\n",
+		lib->name, lib->base, proc->pid);
+
+	struct library_symbol *libsym = NULL;
+	while ((libsym = library_each_symbol(lib, libsym, breakpoint_for_symbol,
+					     proc)) != NULL) {
+		error(0, errno, "insert breakpoint for %s", libsym->name);
+		libsym = libsym->next;
+	}
+}
+
+int
+proc_remove_library(struct Process *proc, struct library *lib)
+{
+	struct library **libp;
+	for (libp = &proc->libraries; *libp != NULL; libp = &(*libp)->next)
+		if (*libp == lib) {
+			*libp = lib->next;
+			return 0;
+		}
+	return -1;
+}
+
+struct library *
+proc_each_library(struct Process *proc, struct library *it,
+		  enum callback_status (*cb)(struct Process *proc,
+					     struct library *lib, void *data),
+		  void *data)
+{
+	if (it == NULL)
+		it = proc->libraries;
+
+	while (it != NULL) {
+		struct library *next = it->next;
+
+		switch (cb(proc, it, data)) {
+		case CBS_STOP:
+			return it;
+		case CBS_CONT:
+			break;
+		}
+
+		it = next;
+	}
+
+	return NULL;
+}