The first crude version of tracing across libraries

- the patch will be sliced later
diff --git a/Makefile.am b/Makefile.am
index 6c299d8..7c9faf6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,7 +24,8 @@
 	output.c \
 	proc.c \
 	read_config_file.c  \
-	summary.c
+	summary.c \
+	library.c
 
 libltrace_la_LIBADD = \
 	$(libelf_LIBS) \
@@ -56,7 +57,8 @@
 	ltrace.h \
 	options.h \
 	output.h \
-	read_config_file.h
+	read_config_file.h \
+	library.h
 
 dist_man1_MANS = \
 	ltrace.1
diff --git a/breakpoint.h b/breakpoint.h
index ce6f501..53cdbf2 100644
--- a/breakpoint.h
+++ b/breakpoint.h
@@ -57,6 +57,7 @@
  */
 
 #include "arch.h"
+#include "library.h"
 
 struct Process;
 struct breakpoint;
@@ -66,15 +67,18 @@
 	void (*on_destroy) (struct breakpoint *bp);
 };
 
+#ifndef ARCH_HAVE_BREAKPOINT_DATA
+struct arch_breakpoint_data {
+};
+#endif
+
 struct breakpoint {
 	struct bp_callbacks *cbs;
 	void *addr;
 	unsigned char orig_value[BREAKPOINT_LENGTH];
 	int enabled;
 	struct library_symbol *libsym;
-#ifdef __arm__
-	int thumb_mode;
-#endif
+	struct arch_breakpoint_data arch;
 };
 
 /* Call on-hit handler of BP, if any is set.  */
@@ -83,6 +87,14 @@
 /* Call on-destroy handler of BP, if any is set.  */
 void breakpoint_on_destroy(struct breakpoint *bp);
 
+/* Initialize a breakpoint structure.  That doesn't actually realize
+ * the breakpoint.  The breakpoint is initially assumed to be
+ * disabled.  orig_value has to be set separately.  CBS may be
+ * NULL.  */
+int breakpoint_init(struct breakpoint *bp, struct Process *proc,
+		    target_address_t addr, struct library_symbol *libsym,
+		    struct bp_callbacks *cbs);
+
 /* This is actually three functions rolled in one:
  *  - breakpoint_init
  *  - proc_insert_breakpoint
@@ -100,7 +112,4 @@
 void disable_all_breakpoints(struct Process *proc);
 int breakpoints_init(struct Process *proc, int enable);
 
-void reinitialize_breakpoints(struct Process *proc);
-
-
 #endif /* BREAKPOINT_H */
diff --git a/breakpoints.c b/breakpoints.c
index 5a473a9..f0f8db4 100644
--- a/breakpoints.c
+++ b/breakpoints.c
@@ -3,6 +3,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <error.h>
+#include <errno.h>
 
 #ifdef __powerpc__
 #include <sys/ptrace.h>
@@ -11,6 +13,7 @@
 #include "breakpoint.h"
 #include "common.h"
 #include "proc.h"
+#include "library.h"
 
 void
 breakpoint_on_hit(struct breakpoint *bp, struct Process *proc)
@@ -40,12 +43,33 @@
 	return dict_find_entry(proc->breakpoints, addr);
 }
 
+#ifdef ARCH_HAVE_BREAKPOINT_DATA
+int arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp);
+#else
+int
+arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp)
+{
+	return 0;
+}
+#endif
+
+int
+breakpoint_init(struct breakpoint *bp, struct Process *proc,
+		target_address_t addr, struct library_symbol *libsym,
+		struct bp_callbacks *cbs)
+{
+	bp->cbs = cbs;
+	bp->addr = addr;
+	memset(bp->orig_value, 0, sizeof(bp->orig_value));
+	bp->enabled = 0;
+	bp->libsym = libsym;
+	return arch_breakpoint_init(proc, bp);
+}
+
 struct breakpoint *
 insert_breakpoint(Process *proc, void *addr,
 		  struct library_symbol *libsym, int enable)
 {
-	struct breakpoint *sbp;
-
 	Process * leader = proc->leader;
 
 	/* Only the group leader should be getting the breakpoints and
@@ -53,12 +77,6 @@
 	assert(leader != NULL);
 	assert(leader->breakpoints != NULL);
 
-#ifdef __arm__
-	int thumb_mode = (int)addr & 1;
-	if (thumb_mode)
-		addr = (void *)((int)addr & ~1);
-#endif
-
 	debug(DEBUG_FUNCTION, "insert_breakpoint(pid=%d, addr=%p, symbol=%s)", proc->pid, addr, libsym ? libsym->name : "NULL");
 	debug(1, "symbol=%s, addr=%p", libsym?libsym->name:"(nil)", addr);
 
@@ -68,20 +86,17 @@
 	if (libsym)
 		libsym->needs_init = 0;
 
-	sbp = dict_find_entry(leader->breakpoints, addr);
+	struct breakpoint *sbp = dict_find_entry(leader->breakpoints, addr);
 	if (sbp == NULL) {
-		sbp = calloc(1, sizeof(*sbp));
-		if (sbp == NULL) {
-			return NULL;	/* TODO FIXME XXX: error_mem */
+		sbp = malloc(sizeof(*sbp));
+		if (sbp == NULL
+		    || breakpoint_init(sbp, proc, addr, libsym, NULL) < 0
+		    || dict_enter(leader->breakpoints, addr, sbp) < 0) {
+			free(sbp);
+			return NULL;
 		}
-		dict_enter(leader->breakpoints, addr, sbp);
-		sbp->addr = addr;
-		sbp->libsym = libsym;
 	}
-#ifdef __arm__
-	sbp->thumb_mode = thumb_mode | proc->thumb_mode;
-	proc->thumb_mode = 0;
-#endif
+
 	sbp->enabled++;
 	if (sbp->enabled == 1 && enable) {
 		assert(proc->pid != 0);
@@ -177,108 +192,80 @@
 	dict_apply_to_all(proc->breakpoints, disable_bp_cb, proc);
 }
 
-static void
-free_bp_cb(void *addr, void *sbp, void *data) {
-	debug(DEBUG_FUNCTION, "free_bp_cb(sbp=%p)", sbp);
-	assert(sbp);
-	free(sbp);
+static enum callback_status
+reinitialize_breakpoints(struct Process *proc, struct library *library,
+			 void *data)
+{
+	debug(DEBUG_FUNCTION, "reinitialize_breakpoints_in(pid=%d, %s)",
+	      proc->pid, library->name);
+
+	struct library_symbol *sym;
+	for (sym = library->symbols; sym != NULL; sym = sym->next)
+		if (sym->needs_init) {
+			target_address_t addr = sym2addr(proc, sym);
+			if (insert_breakpoint(proc, addr, sym, 1) == NULL
+			    || (sym->needs_init && !sym->is_weak))
+				fprintf(stderr,
+					"could not re-initialize breakpoint "
+					"for \"%s\" in file \"%s\"\n",
+					sym->name, proc->filename);
+		}
+
+	return CBS_CONT;
 }
 
 static void
 entry_callback_hit(struct breakpoint *bp, struct Process *proc)
 {
+	fprintf(stderr, "entry_callback_hit\n");
 	if (proc == NULL || proc->leader == NULL)
 		return;
 	delete_breakpoint(proc, bp->addr); // xxx
-	reinitialize_breakpoints(proc->leader);
+
+	linkmap_init(proc);
+	proc_each_library(proc->leader, NULL, reinitialize_breakpoints, NULL);
 }
 
 int
 breakpoints_init(Process *proc, int enable)
 {
+	fprintf(stderr, "breakpoints_init %d enable=%d\n", proc->pid, enable);
 	debug(DEBUG_FUNCTION, "breakpoints_init(pid=%d)", proc->pid);
-	if (proc->breakpoints) {	/* let's remove that struct */
-		dict_apply_to_all(proc->breakpoints, free_bp_cb, NULL);
-		dict_clear(proc->breakpoints);
-		proc->breakpoints = NULL;
-	}
 
-	/* Only the thread group leader should hold the breakpoints.
-	 * (N.B. PID may be set to 0 temporarily when called by
-	 * handle_exec).  */
+	/* XXX breakpoint dictionary should be initialized
+	 * outside.  Here we just put in breakpoints.  */
+	assert(proc->breakpoints != NULL);
+
+	/* Only the thread group leader should hold the breakpoints.  */
 	assert(proc->leader == proc);
 
-	proc->breakpoints = dict_init(dict_key2hash_int,
-				      dict_key_cmp_int);
-
-	destroy_library_symbol_chain(proc->list_of_symbols);
-	proc->list_of_symbols = NULL;
-
-	GElf_Addr entry;
 	if (options.libcalls && proc->filename) {
-		proc->list_of_symbols = read_elf(proc, &entry);
-		if (proc->list_of_symbols == NULL) {
+		struct library *lib = ltelf_read_main_binary(proc, proc->filename);
+		switch (lib != NULL) {
 		fail:
-			/* XXX leak breakpoints */
+			proc_remove_library(proc, lib);
+			library_destroy(lib);
+		case 0:
 			return -1;
 		}
+		proc_add_library(proc, lib);
+		fprintf(stderr, "note: symbols in %s were not filtered.\n",
+			lib->name);
 
-		if (opt_e) {
-			struct library_symbol **tmp1 = &proc->list_of_symbols;
-			while (*tmp1) {
-				struct opt_e_t *tmp2 = opt_e;
-				int keep = !opt_e_enable;
-
-				while (tmp2) {
-					if (!strcmp((*tmp1)->name,
-						    tmp2->name)) {
-						keep = opt_e_enable;
-					}
-					tmp2 = tmp2->next;
-				}
-				if (!keep) {
-					*tmp1 = (*tmp1)->next;
-				} else {
-					tmp1 = &((*tmp1)->next);
-				}
-			}
+		struct breakpoint *entry_bp
+			= insert_breakpoint(proc, lib->entry, NULL, 1);
+		if (entry_bp == NULL) {
+			error(0, errno, "couldn't insert entry breakpoint");
+			goto fail;
 		}
-	}
 
-	struct breakpoint *entry_bp
-		= insert_breakpoint(proc, (void *)(uintptr_t)entry, NULL, 1);
-	if (entry_bp == NULL) {
-		fprintf(stderr, "fail!\n");
-		goto fail;
+		fprintf(stderr, "setting entry_callbacks by hand, fix it\n");
+		static struct bp_callbacks entry_callbacks = {
+			.on_hit = entry_callback_hit,
+		};
+		entry_bp->cbs = &entry_callbacks;
 	}
 
-	static struct bp_callbacks entry_callbacks = {
-		.on_hit = entry_callback_hit,
-	};
-	entry_bp->cbs = &entry_callbacks;
-
 	proc->callstack_depth = 0;
 	return 0;
 }
-
-void
-reinitialize_breakpoints(Process *proc) {
-	struct library_symbol *sym;
-
-	debug(DEBUG_FUNCTION, "reinitialize_breakpoints(pid=%d)", proc->pid);
-
-	sym = proc->list_of_symbols;
-
-	while (sym) {
-		if (sym->needs_init) {
-			insert_breakpoint(proc, sym2addr(proc, sym), sym, 1);
-			if (sym->needs_init && !sym->is_weak) {
-				fprintf(stderr,
-					"could not re-initialize breakpoint for \"%s\" in file \"%s\"\n",
-					sym->name, proc->filename);
-				exit(1);
-			}
-		}
-		sym = sym->next;
-	}
-}
diff --git a/common.h b/common.h
index 0d56a2b..087b2bc 100644
--- a/common.h
+++ b/common.h
@@ -14,6 +14,7 @@
 #include "debug.h"
 #include "ltrace-elf.h"
 #include "read_config_file.h"
+#include "proc.h"
 
 #if defined HAVE_LIBIBERTY || defined HAVE_LIBSUPC__
 # define USE_DEMANGLE
@@ -113,24 +114,9 @@
 	Function * next;
 };
 
-enum toplt {
-	LS_TOPLT_NONE = 0,	/* PLT not used for this symbol. */
-	LS_TOPLT_EXEC,		/* PLT for this symbol is executable. */
-	LS_TOPLT_POINT		/* PLT for this symbol is a non-executable. */
-};
-
 extern Function * list_of_functions;
 extern char *PLTs_initialized_by_here;
 
-struct library_symbol {
-	const char *name;
-	void * enter_addr;
-	char needs_init;
-	enum toplt plt_type;
-	char is_weak;
-	struct library_symbol * next;
-};
-
 struct opt_c_struct {
 	int count;
 	struct timeval tv;
@@ -174,22 +160,8 @@
 extern void show_summary(void);
 extern arg_type_info * lookup_prototype(enum arg_type at);
 
-extern int do_init_elf(struct ltelf *lte, const char *filename);
-extern void do_close_elf(struct ltelf *lte);
-extern int in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym);
-extern struct library_symbol *library_symbols;
-extern void library_symbol_init(struct library_symbol *libsym,
-				GElf_Addr addr, const char *name,
-				enum toplt type_of_plt, int is_weak);
-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);
-
 struct breakpoint;
+struct library_symbol;
 
 /* Arch-dependent stuff: */
 extern char * pid2name(pid_t pid);
@@ -223,7 +195,7 @@
 extern size_t umovebytes (Process *proc, void * addr, void * laddr, size_t count);
 extern int ffcheck(void * maddr);
 extern void * sym2addr(Process *, struct library_symbol *);
-extern int linkmap_init(Process *, struct ltelf *);
+extern int linkmap_init(struct Process *);
 extern void arch_check_dbg(Process *proc);
 extern int task_kill (pid_t pid, int sig);
 
diff --git a/defs.h b/defs.h
index dea000b..1eadb47 100644
--- a/defs.h
+++ b/defs.h
@@ -14,5 +14,3 @@
 #ifndef DEFAULT_ARRAYLEN
 #define DEFAULT_ARRAYLEN  4	/* default maximum # array elements */
 #endif				/* (-A switch) */
-
-#define MAX_LIBRARIES 200
diff --git a/handle_event.c b/handle_event.c
index afabd96..bdd9af9 100644
--- a/handle_event.c
+++ b/handle_event.c
@@ -16,6 +16,7 @@
 #include "common.h"
 #include "breakpoint.h"
 #include "proc.h"
+#include "library.h"
 
 static void handle_signal(Event *event);
 static void handle_exit(Event *event);
@@ -149,37 +150,6 @@
 	}
 }
 
-/* TODO */
-static void *
-address_clone(void * addr, void * data)
-{
-	debug(DEBUG_FUNCTION, "address_clone(%p)", addr);
-	return addr;
-}
-
-static void *
-breakpoint_clone(void *bp, void *data)
-{
-	Dict *map = data;
-	debug(DEBUG_FUNCTION, "breakpoint_clone(%p)", bp);
-	struct breakpoint *b = malloc(sizeof(*b));
-	if (!b) {
-		perror("malloc()");
-		exit(1);
-	}
-	memcpy(b, bp, sizeof(*b));
-	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;
-}
-
 typedef struct Pending_New Pending_New;
 struct Pending_New {
 	pid_t pid;
@@ -241,6 +211,7 @@
 	}
 }
 
+#if 0
 static int
 clone_breakpoints(Process * proc, Process * orig_proc)
 {
@@ -274,50 +245,48 @@
 	dict_clear(map);
 	return 0;
 }
+#endif
 
 static void
-handle_clone(Event * event) {
-	Process *p;
-
+handle_clone(Event *event)
+{
 	debug(DEBUG_FUNCTION, "handle_clone(pid=%d)", event->proc->pid);
 
-	p = malloc(sizeof(Process));
-	if (!p) {
+	struct Process *proc = malloc(sizeof(*proc));
+	if (proc == NULL) {
+	fail:
+		free(proc);
+		/* XXX proper error handling here, please.  */
 		perror("malloc()");
 		exit(1);
 	}
-	memcpy(p, event->proc, sizeof(Process));
-	p->pid = event->e_un.newpid;
-	p->parent = event->proc;
+
+	if (process_clone(proc, event->proc, event->e_un.newpid) < 0)
+		goto fail;
+	proc->parent = event->proc;
 
 	/* We save register values to the arch pointer, and these need
 	   to be per-thread.  */
-	p->arch_ptr = NULL;
+	proc->arch_ptr = NULL;
 
-	if (pending_new(p->pid)) {
-		pending_new_remove(p->pid);
-		if (p->event_handler != NULL)
-			destroy_event_handler(p);
-		if (event->proc->state == STATE_ATTACHED && options.follow) {
-			p->state = STATE_ATTACHED;
-		} else {
-			p->state = STATE_IGNORED;
-		}
-		continue_process(p->pid);
-		add_process(p);
+	if (pending_new(proc->pid)) {
+		pending_new_remove(proc->pid);
+		/* XXX this used to be destroy_event_handler call, but
+		 * I don't think we want to call that on a shared
+		 * state.  */
+		proc->event_handler = NULL;
+		if (event->proc->state == STATE_ATTACHED && options.follow)
+			proc->state = STATE_ATTACHED;
+		else
+			proc->state = STATE_IGNORED;
+		continue_process(proc->pid);
 	} else {
-		p->state = STATE_BEING_CREATED;
-		add_process(p);
+		proc->state = STATE_BEING_CREATED;
 	}
-
-	if (p->leader == p)
-		clone_breakpoints(p, event->proc->leader);
-	else
-		/* Thread groups share breakpoints.  */
-		p->breakpoints = NULL;
+	add_process(proc);
 
 	if (event->type == EVENT_VFORK)
-		continue_after_vfork(p);
+		continue_after_vfork(proc);
 	else
 		continue_process(event->proc->pid);
 }
@@ -676,17 +645,8 @@
 	if ((sbp = address2bpstruct(leader, brk_addr))) {
 		breakpoint_on_hit(sbp, event->proc);
 
-		if (sbp->libsym == NULL) {
-			continue_after_breakpoint(event->proc, sbp);
-			return;
-		}
-
-		if (strcmp(sbp->libsym->name, "") == 0) {
-			debug(DEBUG_PROCESS, "Hit _dl_debug_state breakpoint!\n");
-			arch_check_dbg(leader);
-		}
-
-		if (event->proc->state != STATE_IGNORED) {
+		if (event->proc->state != STATE_IGNORED
+		    && sbp->libsym != NULL) {
 			event->proc->stack_pointer = get_stack_pointer(event->proc);
 			event->proc->return_addr =
 				get_return_addr(event->proc, event->proc->stack_pointer);
diff --git a/libltrace.c b/libltrace.c
index dcd5537..2295901 100644
--- a/libltrace.c
+++ b/libltrace.c
@@ -16,8 +16,8 @@
 
 int exiting = 0;		/* =1 if a SIGINT or SIGTERM has been received */
 
-static enum pcb_status
-stop_non_p_processes (Process * proc, void * data)
+static enum callback_status
+stop_non_p_processes(Process *proc, void *data)
 {
 	int stop = 1;
 
@@ -39,7 +39,7 @@
 		kill(proc->pid, SIGSTOP);
 	}
 
-	return pcb_cont;
+	return CBS_CONT;
 }
 
 static void
diff --git a/library.c b/library.c
new file mode 100644
index 0000000..56a47c4
--- /dev/null
+++ b/library.c
@@ -0,0 +1,207 @@
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2001,2009 Juan Cespedes
+ * Copyright (C) 2006 Ian Wienand
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "library.h"
+#include "proc.h" // for enum callback_status
+#include "debug.h"
+
+/* If the other symbol owns the name, we need to make the copy, so
+ * that the life-times of the two symbols are not dependent on each
+ * other.  */
+static int
+strdup_if_owned(const char **retp, const char *str, int owned)
+{
+	if (!owned || str == NULL) {
+		*retp = str;
+		return 0;
+	} else {
+		*retp = strdup(str);
+		return *retp != NULL ? 0 : -1;
+	}
+}
+
+void
+library_symbol_init(struct library_symbol *libsym,
+		    GElf_Addr addr, const char *name, int own_name,
+		    enum toplt type_of_plt, int is_weak)
+{
+	libsym->needs_init = 0;
+	libsym->is_weak = is_weak;
+	libsym->plt_type = type_of_plt;
+	libsym->name = name;
+	libsym->own_name = own_name;
+	libsym->enter_addr = (void *)(uintptr_t)addr;
+	libsym->next = NULL;
+}
+
+void
+library_symbol_destroy(struct library_symbol *libsym)
+{
+	if (libsym != NULL && libsym->own_name)
+		free((char *)libsym->name);
+}
+
+int
+library_symbol_clone(struct library_symbol *retp, struct library_symbol *libsym)
+{
+	const char *name;
+	if (strdup_if_owned(&name, libsym->name, libsym->own_name) < 0)
+		return -1;
+
+	library_symbol_init(retp, (GElf_Addr)libsym->enter_addr,
+			    name, libsym->own_name, libsym->plt_type,
+			    libsym->is_weak);
+	retp->needs_init = libsym->needs_init;
+	return 0;
+}
+
+int
+library_symbol_cmp(struct library_symbol *a, struct library_symbol *b)
+{
+	if (a->enter_addr < b->enter_addr)
+		return -1;
+	if (a->enter_addr > b->enter_addr)
+		return 1;
+	if (a->name != NULL && b->name != NULL)
+		return strcmp(a->name, b->name);
+	if (a->name == NULL) {
+		if (b->name == NULL)
+			return 0;
+		return -1;
+	}
+	return 1;
+}
+
+enum callback_status
+library_symbol_equal_cb(struct library_symbol *libsym, void *u)
+{
+	struct library_symbol *standard = u;
+	return library_symbol_cmp(libsym, standard) == 0 ? CBS_STOP : CBS_CONT;
+}
+
+void
+library_init(struct library *lib, const char *name, int own_name)
+{
+	lib->next = NULL;
+	lib->name = name;
+	lib->own_name = own_name;
+	lib->symbols = NULL;
+}
+
+int
+library_clone(struct library *retp, struct library *lib)
+{
+	const char *name;
+	if (strdup_if_owned(&name, lib->name, lib->own_name) < 0)
+		return -1;
+
+	library_init(retp, lib->name, lib->own_name);
+
+	struct library_symbol *it;
+	struct library_symbol **nsymp = &retp->symbols;
+	for (it = lib->symbols; it != NULL; it = it->next) {
+		*nsymp = malloc(sizeof(**nsymp));
+		if (*nsymp == NULL
+		    || library_symbol_clone(*nsymp, it) < 0) {
+			/* Release what we managed to allocate.  */
+			library_destroy(retp);
+			return -1;
+		}
+
+		nsymp = &(*nsymp)->next;
+	}
+	return 0;
+}
+
+void
+library_destroy(struct library *lib)
+{
+	if (lib == NULL)
+		return;
+	library_set_name(lib, NULL, 0);
+
+	struct library_symbol *sym;
+	for (sym = lib->symbols; sym != NULL; ) {
+		struct library_symbol *next = sym->next;
+		library_symbol_destroy(sym);
+		free(sym);
+		sym = next;
+	}
+}
+
+void
+library_set_name(struct library *lib, const char *new_name, int own_name)
+{
+	if (lib->own_name)
+		free((char *)lib->name);
+	lib->name = new_name;
+	lib->own_name = own_name;
+}
+
+struct library_symbol *
+library_each_symbol(struct library *lib, struct library_symbol *it,
+		    enum callback_status (*cb)(struct library_symbol *, void *),
+		    void *data)
+{
+	if (it == NULL)
+		it = lib->symbols;
+
+	while (it != NULL) {
+		struct library_symbol *next = it->next;
+
+		switch ((*cb)(it, data)) {
+		case CBS_STOP:
+			return it;
+		case CBS_CONT:
+			break;
+		}
+
+		it = next;
+	}
+
+	return NULL;
+}
+
+void
+library_add_symbol(struct library *lib, struct library_symbol *sym)
+{
+	sym->next = lib->symbols;
+	lib->symbols = sym;
+}
+
+enum callback_status
+library_named_cb(struct Process *proc, struct library *lib, void *name)
+{
+	if (name == lib->name
+	    || strcmp(lib->name, (char *)name) == 0)
+		return CBS_STOP;
+	else
+		return CBS_CONT;
+}
+
+enum callback_status
+library_with_base_cb(struct Process *proc, struct library *lib, void *basep)
+{
+	return lib->base == *(target_address_t *)basep ? CBS_STOP : CBS_CONT;
+}
diff --git a/library.h b/library.h
new file mode 100644
index 0000000..88da6c5
--- /dev/null
+++ b/library.h
@@ -0,0 +1,149 @@
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2012 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2006 Paul Gilliam
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef _LIBRARY_H_
+#define _LIBRARY_H_
+
+#include <stdint.h>
+#include <gelf.h> // XXX 
+
+struct Process;
+struct library;
+
+enum toplt {
+	LS_TOPLT_NONE = 0,	/* PLT not used for this symbol. */
+	LS_TOPLT_EXEC,		/* PLT for this symbol is executable. */
+	LS_TOPLT_POINT		/* PLT for this symbol is a non-executable. */
+};
+
+/* We should in general be able to trace 64-bit processes with 32-bit
+ * ltrace.  (At least PPC has several PTRACE requests related to
+ * tracing 64-on-32, so presumably it should be possible.)  But ltrace
+ * is currently hopelessly infested with using void* for host address.
+ * So keep with it, for now.  */
+typedef void *target_address_t;
+
+struct library_symbol {
+	struct library_symbol *next;
+	const char *name;
+	target_address_t enter_addr;
+	enum toplt plt_type;
+	char needs_init;
+	char is_weak;
+	char own_name;
+};
+
+/* Init LIBSYM.  NAME will be freed when LIBSYM is destroyed if
+ * OWN_NAME.  */
+/* XXX note that we shouldn't use GElf_Addr for ADDR either.  The fact
+ * that Elf is used is a back-end detail.  At least ltrace pretends
+ * that it would like to be cross-platform like that one day.  */
+void library_symbol_init(struct library_symbol *libsym,
+			 GElf_Addr addr, const char *name, int own_name,
+			 enum toplt type_of_plt, int is_weak);
+
+/* Copy library symbol SYM into the area pointed-to by RETP.  Return 0
+ * on success or a negative value on failure.  */
+int library_symbol_clone(struct library_symbol *retp,
+			 struct library_symbol *sym);
+
+/* Destroy library symbol.  This essentially just frees name if it's
+ * owned.  It doesn't free the memory associated with SYM pointer
+ * itself.  Returns 0 on success or a negative value in case of an
+ * error (which would be an out of memory condition).  */
+void library_symbol_destroy(struct library_symbol *sym);
+
+/* Compare two library symbols.  Returns a negative value, 0, or a
+ * positive value, much like strcmp.  The function compares symbol
+ * addresses, and if those are equal, it compares symbol names.  If
+ * those are equal, too, the symbols are considered equal.  */
+int library_symbol_cmp(struct library_symbol *a, struct library_symbol *b);
+
+/* A function that can be used as library_each_symbol callback.  Looks
+ * for a symbol SYM for which library_symbol_cmp(SYM, STANDARD)
+ * returns 0.  */
+enum callback_status library_symbol_equal_cb(struct library_symbol *libsym,
+					     void *standard);
+
+/* XXX we might consider sharing libraries across processes.  Things
+ * like libc will be opened by every single process, no point cloning
+ * these everywhere.  But for now, keep the ownership structure
+ * simple.  */
+struct library {
+	struct library *next;
+
+	/* Address where the library is mapped.  Two library objects
+	 * are considered equal, if they have the same base.  */
+	target_address_t base;
+
+	/* Absolute address of the entry point.  Useful for main
+	 * binary, though I the value might be useful for the dynamic
+	 * linker, too (in case we ever want to do early process
+	 * tracing).  */
+	target_address_t entry;
+
+	/* Symbols associated with the library.  */
+	struct library_symbol *symbols;
+	const char *name;
+	char own_name;
+};
+
+/* Init LIB.  NAME will be freed when LIB is destroyed if
+ * OWN_NAME.  */
+void library_init(struct library *lib, const char *name, int own_name);
+
+/* Initialize RETP to a library identical to LIB.  Symbols are not
+ * shared, but copied over.  Returns 0 on success and a negative value
+ * in case of failure.  */
+int library_clone(struct library *retp, struct library *lib);
+
+/* Destroy library.  Doesn't free LIB itself.  */
+void library_destroy(struct library *lib);
+
+/* Set library name.  Frees the old name if necessary.  */
+void library_set_name(struct library *lib, const char *new_name, int own_name);
+
+/* Iterate through list of symbols of library LIB.  Restarts are
+ * supported via START (see each_process for details of iteration
+ * interface).  */
+struct library_symbol *library_each_symbol
+	(struct library *lib, struct library_symbol *start,
+	 enum callback_status (*cb)(struct library_symbol *, void *),
+	 void *data);
+
+/* Add a new symbol SYM to LIB.  SYM is assumed owned, we need to
+ * overwrite SYM->next.  */
+void library_add_symbol(struct library *lib, struct library_symbol *sym);
+
+/* A function that can be used as proc_each_library callback.  Looks
+ * for a library with the name passed in DATA.  PROC is ignored.  */
+enum callback_status library_named_cb(struct Process *proc,
+				      struct library *lib, void *name);
+
+/* A function that can be used as proc_each_library callback.  Looks
+ * for a library with given base.
+ *
+ * NOTE: The base is passed as a POINTER to target_address_t (that
+ * because in general, target_address_t doesn't fit in void*). */
+enum callback_status library_with_base_cb(struct Process *proc,
+					  struct library *lib, void *basep);
+
+#endif /* _LIBRARY_H_ */
diff --git a/ltrace-elf.c b/ltrace-elf.c
index d3d67d5..7d2ba5b 100644
--- a/ltrace-elf.c
+++ b/ltrace-elf.c
@@ -14,16 +14,7 @@
 
 #include "common.h"
 #include "proc.h"
-
-void do_close_elf(struct ltelf *lte);
-void add_library_symbol(GElf_Addr addr, const char *name,
-		struct library_symbol **library_symbolspp,
-		enum toplt type_of_plt, int is_weak);
-int in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym);
-static GElf_Addr opd2addr(struct ltelf *ltc, GElf_Addr addr);
-
-struct library_symbol *library_symbols = NULL;
-struct ltelf main_lte;
+#include "library.h"
 
 #ifdef PLT_REINITALISATION_BP
 extern char *PLTs_initialized_by_here;
@@ -54,7 +45,7 @@
 			      Elf_Scn *in_sec, GElf_Shdr *in_shdr,
 			      Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr)
 {
-	if (inside (addr, in_shdr)) {
+	if (inside(addr, in_shdr)) {
 		*tgt_sec = in_sec;
 		*tgt_shdr = *in_shdr;
 		return 1;
@@ -180,10 +171,13 @@
 	return 0;
 }
 
+/* XXX temporarily non-static */
 int
-do_init_elf(struct ltelf *lte, const char *filename) {
+do_init_elf(struct ltelf *lte, const char *filename)
+{
 	int i;
 	GElf_Addr relplt_addr = 0;
+	GElf_Addr soname_offset = 0;
 	size_t relplt_size = 0;
 
 	debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename);
@@ -195,6 +189,21 @@
 	Elf_Data *plt_data = NULL;
 	GElf_Addr ppcgot = 0;
 
+	/* For non-DSO objects, find out the base address.  */
+	if (lte->ehdr.e_type == ET_EXEC) {
+		GElf_Phdr phdr;
+		for (i = 0; gelf_getphdr (lte->elf, i, &phdr) != NULL; ++i) {
+			if (phdr.p_type == PT_LOAD) {
+				fprintf(stderr, " + loadable segment at %#lx\n",
+					phdr.p_vaddr);
+				lte->base_addr = phdr.p_vaddr;
+				break;
+			}
+		}
+	}
+
+	lte->entry_addr = lte->ehdr.e_entry;
+
 	for (i = 1; i < lte->ehdr.e_shnum; ++i) {
 		Elf_Scn *scn;
 		GElf_Shdr shdr;
@@ -268,6 +277,9 @@
 			size_t j;
 
 			lte->dyn_addr = shdr.sh_addr;
+			fprintf(stderr, "dyn_addr = %#lx\n", lte->dyn_addr);
+			extern void *dyn_addr;
+			dyn_addr = (void *)lte->dyn_addr;
 			lte->dyn_sz = shdr.sh_size;
 
 			data = elf_getdata(scn, NULL);
@@ -317,75 +329,10 @@
 				else if (dyn.d_tag == DT_PPC_GOT) {
 					ppcgot = dyn.d_un.d_val;
 					debug(1, "ppcgot %#" PRIx64, ppcgot);
+				} else if (dyn.d_tag == DT_SONAME) {
+					soname_offset = dyn.d_un.d_val;
 				}
 			}
-		} else if (shdr.sh_type == SHT_HASH) {
-			Elf_Data *data;
-			size_t j;
-
-			lte->hash_type = SHT_HASH;
-
-			data = elf_getdata(scn, NULL);
-			if (data == NULL || elf_getdata(scn, data) != NULL
-			    || data->d_off || data->d_size != shdr.sh_size)
-				error(EXIT_FAILURE, 0,
-				      "Couldn't get .hash data from \"%s\"",
-				      filename);
-
-			if (shdr.sh_entsize == 4) {
-				/* Standard conforming ELF.  */
-				if (data->d_type != ELF_T_WORD)
-					error(EXIT_FAILURE, 0,
-					      "Couldn't get .hash data from \"%s\"",
-					      filename);
-				lte->hash = (Elf32_Word *) data->d_buf;
-			} else if (shdr.sh_entsize == 8) {
-				/* Alpha or s390x.  */
-				Elf32_Word *dst, *src;
-				size_t hash_count = data->d_size / 8;
-
-				lte->hash = (Elf32_Word *)
-				    malloc(hash_count * sizeof(Elf32_Word));
-				if (lte->hash == NULL)
-					error(EXIT_FAILURE, 0,
-					      "Couldn't convert .hash section from \"%s\"",
-					      filename);
-				lte->lte_flags |= LTE_HASH_MALLOCED;
-				dst = lte->hash;
-				src = (Elf32_Word *) data->d_buf;
-				if ((data->d_type == ELF_T_WORD
-				     && __BYTE_ORDER == __BIG_ENDIAN)
-				    || (data->d_type == ELF_T_XWORD
-					&& lte->ehdr.e_ident[EI_DATA] ==
-					ELFDATA2MSB))
-					++src;
-				for (j = 0; j < hash_count; ++j, src += 2)
-					*dst++ = *src;
-			} else
-				error(EXIT_FAILURE, 0,
-				      "Unknown .hash sh_entsize in \"%s\"",
-				      filename);
-		} else if (shdr.sh_type == SHT_GNU_HASH
-			   && lte->hash == NULL) {
-			Elf_Data *data;
-
-			lte->hash_type = SHT_GNU_HASH;
-
-			if (shdr.sh_entsize != 0
-			    && shdr.sh_entsize != 4) {
-				error(EXIT_FAILURE, 0,
-				      ".gnu.hash sh_entsize in \"%s\" "
-					"should be 4, but is %#" PRIx64,
-					filename, shdr.sh_entsize);
-			}
-
-			data = loaddata(scn, &shdr);
-			if (data == NULL)
-				error(EXIT_FAILURE, 0,
-				      "Couldn't get .gnu.hash data from \"%s\"",
-				      filename);
-
-			lte->hash = (Elf32_Word *) data->d_buf;
 		} else if (shdr.sh_type == SHT_PROGBITS
 			   || shdr.sh_type == SHT_NOBITS) {
 			if (strcmp(name, ".plt") == 0) {
@@ -465,193 +412,24 @@
 
 		debug(1, "%s %zd PLT relocations", filename, lte->relplt_count);
 	}
+
+	if (soname_offset != 0)
+		lte->soname = lte->dynstr + soname_offset;
+
 	return 0;
 }
 
+/* XXX temporarily non-static */
 void
 do_close_elf(struct ltelf *lte) {
 	debug(DEBUG_FUNCTION, "do_close_elf()");
-	if (lte->lte_flags & LTE_HASH_MALLOCED)
-		free((char *)lte->hash);
 	elf_end(lte->elf);
 	close(lte->fd);
 }
 
-void
-library_symbol_init(struct library_symbol *libsym,
-		    GElf_Addr addr, const char *name,
-		    enum toplt type_of_plt, int is_weak)
-{
-	libsym->needs_init = 0;
-	libsym->is_weak = is_weak;
-	libsym->plt_type = type_of_plt;
-	libsym->name = name;
-	libsym->enter_addr = (void *)(uintptr_t)addr;
-	libsym->next = NULL;
-}
-
-static struct library_symbol *
-create_library_symbol(const char *name, GElf_Addr addr,
-		      enum toplt type_of_plt, int is_weak)
-{
-	size_t namel = strlen(name) + 1;
-	struct library_symbol * libsym = calloc(sizeof(*libsym) + namel, 1);
-	if (libsym == NULL) {
-		perror("create_library_symbol");
-		return NULL;
-	}
-	memcpy(libsym + 1, name, namel);
-	library_symbol_init(libsym, addr,
-			    (char *)(libsym + 1), type_of_plt, is_weak);
-
-	return libsym;
-}
-
-void
-add_library_symbol(GElf_Addr addr, const char *name,
-		   struct library_symbol **library_symbolspp,
-		   enum toplt type_of_plt, int is_weak)
-{
-	debug(DEBUG_FUNCTION, "add_library_symbol()");
-
-	struct library_symbol *s = create_library_symbol(name, addr,
-							 type_of_plt, is_weak);
-	if (s == NULL)
-		error(EXIT_FAILURE, errno, "add_library_symbol failed");
-
-	s->needs_init = 1;
-	s->next = *library_symbolspp;
-	*library_symbolspp = s;
-
-	debug(2, "addr: %p, symbol: \"%s\"", (void *)(uintptr_t) addr, name);
-}
-
-struct library_symbol *
-clone_library_symbol(struct library_symbol *libsym)
-{
-	GElf_Addr addr = (uintptr_t)libsym->enter_addr;
-	struct library_symbol *copy
-		= create_library_symbol(libsym->name, addr,
-					libsym->plt_type, libsym->is_weak);
-	if (copy != NULL)
-		copy->needs_init = libsym->needs_init;
-
-	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) {
-	unsigned long h = 5381;
-	const unsigned char *string = (const unsigned char *)name;
-	unsigned char c;
-	for (c = *string; c; c = *++string)
-		h = h * 33 + c;
-	return h & 0xffffffff;
-}
-
-static int
-symbol_matches(struct ltelf *lte, size_t lte_i, GElf_Sym *sym,
-	       size_t symidx, const char *name)
-{
-	GElf_Sym tmp_sym;
-	GElf_Sym *tmp;
-
-	tmp = (sym) ? (sym) : (&tmp_sym);
-
-	if (gelf_getsym(lte[lte_i].dynsym, symidx, tmp) == NULL)
-		error(EXIT_FAILURE, 0, "Couldn't get symbol from .dynsym");
-	else {
-		tmp->st_value += lte[lte_i].base_addr;
-		debug(2, "symbol found: %s, %zd, %#" PRIx64,
-		      name, lte_i, tmp->st_value);
-	}
-	return tmp->st_value != 0
-		&& tmp->st_shndx != SHN_UNDEF
-		&& strcmp(name, lte[lte_i].dynstr + tmp->st_name) == 0;
-}
-
-int
-in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym) {
-	size_t i;
-	unsigned long hash;
-	unsigned long gnu_hash;
-
-	if (!count)
-		return 1;
-
-#ifdef ELF_HASH_TAKES_CHARP
-	hash = elf_hash(name);
-#else
-	hash = elf_hash((const unsigned char *)name);
-#endif
-	gnu_hash = private_elf_gnu_hash(name);
-
-	for (i = 0; i < count; ++i) {
-		if (lte[i].hash == NULL)
-			continue;
-
-		if (lte[i].hash_type == SHT_GNU_HASH) {
-			Elf32_Word * hashbase = lte[i].hash;
-			Elf32_Word nbuckets = *hashbase++;
-			Elf32_Word symbias = *hashbase++;
-			Elf32_Word bitmask_nwords = *hashbase++;
-			Elf32_Word * buckets;
-			Elf32_Word * chain_zero;
-			Elf32_Word bucket;
-
-			// +1 for skipped `shift'
-			hashbase += lte[i].ehdr.e_ident[EI_CLASS] * bitmask_nwords + 1;
-			buckets = hashbase;
-			hashbase += nbuckets;
-			chain_zero = hashbase - symbias;
-			bucket = buckets[gnu_hash % nbuckets];
-
-			if (bucket != 0) {
-				const Elf32_Word *hasharr = &chain_zero[bucket];
-				do
-					if ((*hasharr & ~1u) == (gnu_hash & ~1u)) {
-						int symidx = hasharr - chain_zero;
-						if (symbol_matches(lte, i,
-								   sym, symidx,
-								   name))
-							return 1;
-					}
-				while ((*hasharr++ & 1u) == 0);
-			}
-		} else {
-			Elf32_Word nbuckets, symndx;
-			Elf32_Word *buckets, *chain;
-			nbuckets = lte[i].hash[0];
-			buckets = &lte[i].hash[2];
-			chain = &lte[i].hash[2 + nbuckets];
-
-			for (symndx = buckets[hash % nbuckets];
-			     symndx != STN_UNDEF; symndx = chain[symndx])
-				if (symbol_matches(lte, i, sym, symndx, name))
-					return 1;
-		}
-	}
-	return 0;
-}
-
-static GElf_Addr
+/* XXX non-static for now, as it's not called anywhere.  But we want
+ * this code around.  */
+GElf_Addr
 opd2addr(struct ltelf *lte, GElf_Addr addr) {
 #ifdef ARCH_SUPPORTS_OPD
 	unsigned long base, offset;
@@ -670,198 +448,101 @@
 #endif
 }
 
-struct library_symbol *
-read_elf(Process *proc, GElf_Addr *entryp)
+struct library *
+ltelf_read_library(const char *filename, GElf_Addr base)
 {
-	struct ltelf lte[MAX_LIBRARIES + 1];
-	size_t i;
-	struct opt_x_t *xptr;
-	struct opt_x_t *opt_x_loc = opt_x;
-	struct library_symbol **lib_tail = NULL;
-	int exit_out = 0;
-	int count = 0;
-
-	debug(DEBUG_FUNCTION, "read_elf(file=%s)", proc->filename);
-
-	memset(lte, 0, sizeof(lte));
-	library_symbols = NULL;
-	library_num = 0;
-	proc->libdl_hooked = 0;
-
-	if (do_init_elf(lte, proc->filename))
+	 // XXX we leak LTE contents
+	struct ltelf lte = {
+		.base_addr = base,
+	};
+	if (do_init_elf(&lte, filename) < 0)
 		return NULL;
 
-	memcpy(&main_lte, lte, sizeof(struct ltelf));
-
-	if (opt_p && opt_p->pid > 0) {
-		linkmap_init(proc, lte);
-		proc->libdl_hooked = 1;
+	struct library *lib = malloc(sizeof(*lib));
+	char *soname = NULL;
+	if (lib == NULL) {
+	fail:
+		free(soname);
+		library_destroy(lib);
+		free(lib);
+		lib = NULL;
+		goto done;
 	}
 
-	proc->e_machine = lte->ehdr.e_machine;
-
-	for (i = 0; i < library_num; ++i) {
-		if (do_init_elf(&lte[i + 1], library[i]))
-			error(EXIT_FAILURE, errno, "Can't open \"%s\"",
-			      library[i]);
+	if (lte.soname != NULL) {
+		soname = strdup(lte.soname);
+		if (soname == NULL)
+			goto fail;
 	}
 
-	if (!options.no_plt) {
-#ifdef __mips__
-		// MIPS doesn't use the PLT and the GOT entries get changed
-		// on startup.
-		for(i=lte->mips_gotsym; i<lte->dynsym_count;i++){
-			GElf_Sym sym;
-			const char *name;
-			GElf_Addr addr = arch_plt_sym_val(lte, i, 0);
-			if (gelf_getsym(lte->dynsym, i, &sym) == NULL){
-				error(EXIT_FAILURE, 0,
-						"Couldn't get relocation from \"%s\"",
-						proc->filename);
-			}
-			name=lte->dynstr+sym.st_name;
-			if(ELF64_ST_TYPE(sym.st_info) != STT_FUNC){
-				debug(2,"sym %s not a function",name);
-				continue;
-			}
-			add_library_symbol(addr, name, &library_symbols, 0,
-					ELF64_ST_BIND(sym.st_info) != 0);
-			if (!lib_tail)
-				lib_tail = &(library_symbols->next);
-		}
-#else
-		for (i = 0; i < lte->relplt_count; ++i) {
-			GElf_Rel rel;
-			GElf_Rela rela;
-			GElf_Sym sym;
-			GElf_Addr addr;
-			void *ret;
-			const char *name;
+	library_init(lib, soname, soname != NULL);
+	lib->entry = (target_address_t)lte.entry_addr;
+	lib->base = (target_address_t)lte.base_addr;
 
-			if (lte->relplt->d_type == ELF_T_REL) {
-				ret = gelf_getrel(lte->relplt, i, &rel);
-				rela.r_offset = rel.r_offset;
-				rela.r_info = rel.r_info;
-				rela.r_addend = 0;
-			} else
-				ret = gelf_getrela(lte->relplt, i, &rela);
-
-			if (ret == NULL
-					|| ELF64_R_SYM(rela.r_info) >= lte->dynsym_count
-					|| gelf_getsym(lte->dynsym, ELF64_R_SYM(rela.r_info),
-						&sym) == NULL)
-				error(EXIT_FAILURE, 0,
-						"Couldn't get relocation from \"%s\"",
-						proc->filename);
-
-			name = lte->dynstr + sym.st_name;
-			count = library_num ? library_num+1 : 0;
-
-			if (in_load_libraries(name, lte, count, NULL)) {
-				enum toplt pltt;
-				if (sym.st_value == 0 && lte->plt_stub_vma != 0) {
-					pltt = LS_TOPLT_EXEC;
-					addr = lte->plt_stub_vma + PPC_PLT_STUB_SIZE * i;
-				}
-				else {
-					pltt = PLTS_ARE_EXECUTABLE(lte)
-						?  LS_TOPLT_EXEC : LS_TOPLT_POINT;
-					addr = arch_plt_sym_val(lte, i, &rela);
-				}
-
-				add_library_symbol(addr, name, &library_symbols, pltt,
-						ELF64_ST_BIND(sym.st_info) == STB_WEAK);
-				if (!lib_tail)
-					lib_tail = &(library_symbols->next);
-			}
-		}
-#endif // !__mips__
-	} else {
-		lib_tail = &library_symbols;
-	}
-
-	for (i = 0; i < lte->symtab_count; ++i) {
+	size_t i;
+	for (i = 0; i < lte.relplt_count; ++i) {
+		GElf_Rel rel;
+		GElf_Rela rela;
 		GElf_Sym sym;
 		GElf_Addr addr;
-		const char *name;
+		void *ret;
 
-		if (gelf_getsym(lte->symtab, i, &sym) == NULL)
+		if (lte.relplt->d_type == ELF_T_REL) {
+			ret = gelf_getrel(lte.relplt, i, &rel);
+			rela.r_offset = rel.r_offset;
+			rela.r_info = rel.r_info;
+			rela.r_addend = 0;
+		} else {
+			ret = gelf_getrela(lte.relplt, i, &rela);
+		}
+
+		if (ret == NULL
+		    || ELF64_R_SYM(rela.r_info) >= lte.dynsym_count
+		    || gelf_getsym(lte.dynsym, ELF64_R_SYM(rela.r_info),
+				   &sym) == NULL)
 			error(EXIT_FAILURE, 0,
-			      "Couldn't get symbol from \"%s\"",
-			      proc->filename);
+			      "Couldn't get relocation from \"%s\"",
+			      filename);
 
-		name = lte->strtab + sym.st_name;
-		addr = sym.st_value;
-		if (!addr)
-			continue;
-
-		for (xptr = opt_x_loc; xptr; xptr = xptr->next)
-			if (xptr->name && strcmp(xptr->name, name) == 0) {
-				/* FIXME: Should be able to use &library_symbols as above.  But
-				   when you do, none of the real library symbols cause breaks. */
-				add_library_symbol(opd2addr(lte, addr),
-						   name, lib_tail, LS_TOPLT_NONE, 0);
-				xptr->found = 1;
-				break;
-			}
-	}
-
-	unsigned found_count = 0;
-
-	for (xptr = opt_x_loc; xptr; xptr = xptr->next) {
-		if (xptr->found)
-			continue;
-
-		GElf_Sym sym;
-		GElf_Addr addr;
-		if (in_load_libraries(xptr->name, lte, library_num+1, &sym)) {
-			debug(2, "found symbol %s @ %#" PRIx64 ", adding it.",
-					xptr->name, sym.st_value);
-			addr = sym.st_value;
-			if (ELF32_ST_TYPE (sym.st_info) == STT_FUNC) {
-				add_library_symbol(addr, xptr->name, lib_tail, LS_TOPLT_NONE, 0);
-				xptr->found = 1;
-				found_count++;
-			}
+		/* We will destroy the ELF object at the end of the
+		 * scope.  We need to copy the name for our purposes.
+		 * XXX consider just keeping the ELF around.  */
+		char *name = strdup(lte.dynstr + sym.st_name);
+		if (name == NULL) {
+		fail2:
+			free(name);
+			goto fail;
 		}
-		if (found_count == opt_x_cnt){
-			debug(2, "done, found everything: %d\n", found_count);
-			break;
+
+		enum toplt pltt;
+		if (sym.st_value == 0 && lte.plt_stub_vma != 0) {
+			pltt = LS_TOPLT_EXEC;
+			addr = lte.plt_stub_vma + PPC_PLT_STUB_SIZE * i;
+		} else {
+			pltt = PLTS_ARE_EXECUTABLE(&lte)
+				?  LS_TOPLT_EXEC : LS_TOPLT_POINT;
+			addr = arch_plt_sym_val(&lte, i, &rela);
 		}
+
+		struct library_symbol *libsym = malloc(sizeof(*libsym));
+		if (libsym == NULL)
+			goto fail2;
+		library_symbol_init(libsym, addr, name, 1, pltt,
+				    ELF64_ST_BIND(sym.st_info) == STB_WEAK);
+		library_add_symbol(lib, libsym);
 	}
 
-	if (lte->ehdr.e_entry != 0) {
-		*entryp = opd2addr(lte, lte->ehdr.e_entry);
-	} else {
-	}
+done:
+	do_close_elf(&lte);
+	return lib;
+}
 
-	for (xptr = opt_x_loc; xptr; xptr = xptr->next)
-		if ( ! xptr->found) {
-			char *badthing = "WARNING";
-#ifdef PLT_REINITALISATION_BP
-			if (strcmp(xptr->name, PLTs_initialized_by_here) == 0) {
-				if (lte->ehdr.e_entry) {
-					fprintf (stderr, "WARNING: Using e_ent"
-						 "ry from elf header (%p) for "
-						 "address of \"%s\"\n", (void*)
-						 (long) lte->ehdr.e_entry,
-						 PLTs_initialized_by_here);
-					continue;
-				}
-				badthing = "ERROR";
-				exit_out = 1;
-			}
-#endif
-			fprintf (stderr,
-				 "%s: Couldn't find symbol \"%s\" in file \"%s\" assuming it will be loaded by libdl!"
-				 "\n", badthing, xptr->name, proc->filename);
-		}
-	if (exit_out) {
-		exit (1);
-	}
-
-	for (i = 0; i < library_num + 1; ++i)
-		do_close_elf(&lte[i]);
-
-	return library_symbols;
+struct library *
+ltelf_read_main_binary(struct Process *proc, const char *path)
+{
+	fprintf(stderr, "ltelf_read_main_binary %d %s\n", proc->pid, path);
+	char *fname = pid2name(proc->pid);
+	struct library *lib = ltelf_read_library(fname, 0);
+	library_set_name(lib, path, 0);
+	return lib;
 }
diff --git a/ltrace-elf.h b/ltrace-elf.h
index 507a466..7616814 100644
--- a/ltrace-elf.h
+++ b/ltrace-elf.h
@@ -3,8 +3,16 @@
 
 #include <gelf.h>
 #include <stdlib.h>
-#include "proc.h"
 
+struct Process;
+struct library;
+
+/* XXX Ok, the original idea was to separate the low-level ELF data
+ * from the abstract "struct library" object, but we use some of the
+ * following extensively in the back end.  Not all though.  So what we
+ * use should be move to struct library, and the rest of this
+ * structure maybe could be safely hidden in .c.  How to integrate the
+ * arch-specific bits into struct library is unclear as of now.  */
 struct ltelf {
 	int fd;
 	Elf *elf;
@@ -18,16 +26,16 @@
 	size_t relplt_count;
 	Elf_Data *symtab;
 	const char *strtab;
+	const char *soname;
 	size_t symtab_count;
 	Elf_Data *opd;
 	GElf_Addr *opd_addr;
 	size_t opd_size;
-	Elf32_Word *hash;
-	int hash_type;
 	int lte_flags;
 	GElf_Addr dyn_addr;
 	size_t dyn_sz;
 	GElf_Addr base_addr;
+	GElf_Addr entry_addr;
 #ifdef __mips__
 	size_t pltgot_addr;
 	size_t mips_local_gotno;
@@ -37,22 +45,22 @@
 };
 
 #define ELF_MAX_SEGMENTS  50
-#define LTE_HASH_MALLOCED 1
 #define LTE_PLT_EXECUTABLE 2
 
-#define PLTS_ARE_EXECUTABLE(lte) ((lte->lte_flags & LTE_PLT_EXECUTABLE) != 0)
+#define PLTS_ARE_EXECUTABLE(lte) (((lte)->lte_flags & LTE_PLT_EXECUTABLE) != 0)
 
-extern size_t library_num;
-extern char *library[MAX_LIBRARIES];
+int open_elf(struct ltelf *lte, const char *filename);
 
-extern int open_elf(struct ltelf *lte, const char *filename);
-extern struct library_symbol *read_elf(Process *proc, GElf_Addr *entryp);
+/* XXX is it possible to put breakpoints in VDSO and VSYSCALL
+ * pseudo-libraries?  For now we assume that all libraries can be
+ * opened via a filesystem.  BASE is ignored for ET_EXEC files.  */
+struct library *ltelf_read_library(const char *filename, GElf_Addr base);
 
-extern GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *);
+/* Create a library object representing the main binary.  The entry
+ * point address is stored to *ENTRYP.  */
+struct library *ltelf_read_main_binary(struct Process *proc, const char *path);
 
-#ifndef SHT_GNU_HASH
-#define SHT_GNU_HASH	0x6ffffff6	/* GNU-style hash table. */
-#endif
+GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *);
 
 #if __WORDSIZE == 32
 #define PRI_ELF_ADDR		PRIx32
diff --git a/options.c b/options.c
index 74c28bd..eae797e 100644
--- a/options.c
+++ b/options.c
@@ -9,6 +9,7 @@
 #include <sys/ioctl.h>
 
 #include <getopt.h>
+#include <assert.h>
 
 #include "common.h"
 
@@ -36,8 +37,6 @@
 	.follow = 0,                  /* trace child processes */
 };
 
-char *library[MAX_LIBRARIES];
-size_t library_num = 0;
 static char *progname;		/* Program name (`ltrace') */
 int opt_i = 0;			/* instruction pointer */
 int opt_r = 0;			/* print relative timestamp */
@@ -316,13 +315,8 @@
 			opt_i++;
 			break;
 		case 'l':
-			if (library_num == MAX_LIBRARIES) {
-				fprintf(stderr,
-					"Too many libraries.  Maximum is %i.\n",
-					MAX_LIBRARIES);
-				exit(1);
-			}
-			library[library_num++] = optarg;
+			assert(!"-l support not yet implemented");
+			abort();
 			break;
 		case 'L':
 			options.libcalls = 0;
diff --git a/output.h b/output.h
index 10a144f..8e4fa46 100644
--- a/output.h
+++ b/output.h
@@ -1,3 +1,6 @@
-void output_line(Process *proc, char *fmt, ...);
-void output_left(enum tof type, Process *proc, const char *function_name);
-void output_right(enum tof type, Process *proc, const char *function_name);
+struct Process;
+void output_line(struct Process *proc, char *fmt, ...);
+void output_left(enum tof type, struct Process *proc,
+		 const char *function_name);
+void output_right(enum tof type, struct Process *proc,
+		  const char *function_name);
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;
+}
diff --git a/proc.h b/proc.h
index 9b80556..0f670e5 100644
--- a/proc.h
+++ b/proc.h
@@ -1,3 +1,25 @@
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2010,2011,2012 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2010 Joe Damato
+ * Copyright (C) 1998,2001,2008,2009 Juan Cespedes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
 #ifndef _PROC_H_
 #define _PROC_H_
 
@@ -8,6 +30,16 @@
 #include "ltrace.h"
 #include "dict.h"
 
+struct library;
+
+/* XXX Move this somewhere where it makes sense.  When the mess in
+ * common.h is disentangled, that would actually be a good place for
+ * this.  */
+enum callback_status {
+	CBS_STOP, /* The iteration should stop.  */
+	CBS_CONT, /* The iteration should continue.  */
+};
+
 struct event_handler {
 	/* Event handler that overrides the default one.  Should
 	 * return NULL if the event was handled, otherwise the
@@ -24,11 +56,6 @@
 	STATE_IGNORED  /* ignore this process (it's a fork and no -f was used) */
 };
 
-enum pcb_status {
-	pcb_stop, /* The iteration should stop.  */
-	pcb_cont, /* The iteration should continue.  */
-};
-
 struct callstack_element {
 	union {
 		int syscall;
@@ -65,9 +92,8 @@
 
 	int callstack_depth;
 	struct callstack_element callstack[MAX_CALLDEPTH];
-	struct library_symbol * list_of_symbols;
+	struct library *libraries;
 
-	int libdl_hooked;
 	/* Arch-dependent: */
 	void * debug;	/* arch-dep process debug struct */
 	long debug_state; /* arch-dep debug state */
@@ -76,7 +102,6 @@
 	void * return_addr;
 	void * arch_ptr;
 	short e_machine;
-	short need_to_reinitialize_breakpoints;
 #ifdef __arm__
 	int thumb_mode;           /* ARM execution mode: 0: ARM, 1: Thumb */
 #endif
@@ -104,19 +129,65 @@
 	Process * leader;
 };
 
-Process * open_program(char *filename, pid_t pid, int init_breakpoints);
+int process_init(struct Process *proc,
+		 const char *filename, pid_t pid, int enable_breakpoints);
+
+Process * open_program(const char *filename, pid_t pid, int enable_breakpoints);
 void open_pid(pid_t pid);
 Process * pid2proc(pid_t pid);
+
+/* Clone the contents of PROC into the memory referenced by RETP.
+ * Returns 0 on success or a negative value on failure.  */
+int process_clone(struct Process *retp, struct Process *proc, pid_t pid);
+
+/* Iterate through the processes that ltrace currently traces.  CB is
+ * called for each process.  Tasks are considered to be processes for
+ * the purpose of this iterator.
+ *
+ * Notes on this iteration interface: DATA is passed verbatim to CB.
+ * If CB returns CBS_STOP, the iteration stops and the current
+ * iterator is returned.  That iterator can then be used to restart
+ * the iteration.  If you don't want CB to see the same process more
+ * than once, restart with IT->next instead of just IT.  NULL is
+ * returned when iteration ends.
+ *
+ * There's no provision for returning error states.  Errors need to be
+ * signaled to the caller via DATA, together with any other data that
+ * the callback needs.  */
 Process *each_process(Process *start,
-		      enum pcb_status (* cb)(Process *proc, void *data),
+		      enum callback_status (*cb)(struct Process *proc,
+						 void *data),
 		      void *data);
+
+/* Iterate through list of tasks of given process START.  Normally you
+ * start the iteration by calling this on PROC->leader, the iterator
+ * doesn't do this for you (so as to support restarts).  See above for
+ * details on the iteration interface.  */
 Process *each_task(Process *start,
-		   enum pcb_status (* cb)(Process *proc, void *data),
+		   enum callback_status (*cb)(struct Process *proc,
+					      void *data),
 		   void *data);
+
 void add_process(Process *proc);
 void change_process_leader(Process *proc, Process *leader);
 void remove_process(Process *proc);
 void install_event_handler(Process *proc, struct event_handler *handler);
 void destroy_event_handler(Process *proc);
 
+/* Add a library LIB to the list of PROC's libraries.  */
+void proc_add_library(struct Process *proc, struct library *lib);
+
+/* Remove LIB from list of PROC's libraries.  Returns 0 if the library
+ * was found and unlinked, otherwise returns a negative value.  */
+int proc_remove_library(struct Process *proc, struct library *lib);
+
+/* Iterate through the libraries of PROC.  See each_process for
+ * detailed description of the iteration interface.  */
+struct library *proc_each_library(struct Process *proc, struct library *start,
+				  enum callback_status (*cb)(struct Process *p,
+							     struct library *l,
+							     void *data),
+				  void *data);
+
+
 #endif /* _PROC_H_ */
diff --git a/sysdeps/linux-gnu/arm/arch.h b/sysdeps/linux-gnu/arm/arch.h
index 8f2dfb3..d50e439 100644
--- a/sysdeps/linux-gnu/arm/arch.h
+++ b/sysdeps/linux-gnu/arm/arch.h
@@ -9,3 +9,8 @@
 
 #define LT_ELFCLASS	ELFCLASS32
 #define LT_ELF_MACHINE	EM_ARM
+
+#define ARCH_HAVE_BREAKPOINT_DATA
+struct arch_breakpoint_data {
+	int thumb_mode;
+};
diff --git a/sysdeps/linux-gnu/arm/breakpoint.c b/sysdeps/linux-gnu/arm/breakpoint.c
index 493f973..b94e471 100644
--- a/sysdeps/linux-gnu/arm/breakpoint.c
+++ b/sysdeps/linux-gnu/arm/breakpoint.c
@@ -82,3 +82,14 @@
 		ptrace(PTRACE_POKETEXT, pid, sbp->addr + i * sizeof(long), current.l);
 	}
 }
+
+int
+arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp)
+{
+	int thumb_mode = (int)addr & 1;
+	if (thumb_mode)
+		addr = (void *)((int)addr & ~1);
+	sbp->arch.thumb_mode = thumb_mode | proc->thumb_mode;
+	proc->thumb_mode = 0;
+	return 0;
+}
diff --git a/sysdeps/linux-gnu/arm/regs.c b/sysdeps/linux-gnu/arm/regs.c
index ea4d3a6..22bc4bf 100644
--- a/sysdeps/linux-gnu/arm/regs.c
+++ b/sysdeps/linux-gnu/arm/regs.c
@@ -40,9 +40,15 @@
 get_return_addr(Process *proc, void *stack_pointer) {
 	long addr = ptrace(PTRACE_PEEKUSER, proc->pid, off_lr, 0);
 
+	/* Remember & unset the thumb mode bit.  XXX This is really a
+	 * bit of a hack, as we assume that the following
+	 * insert_breakpoint call will be related to this address.
+	 * This interface should really be get_return_breakpoint, or
+	 * maybe install_return_breakpoint.  */
 	proc->thumb_mode = addr & 1;
 	if (proc->thumb_mode)
 		addr &= ~1;
+
 	return (void *)addr;
 }
 
diff --git a/sysdeps/linux-gnu/breakpoint.c b/sysdeps/linux-gnu/breakpoint.c
index 58eac8d..1012447 100644
--- a/sysdeps/linux-gnu/breakpoint.c
+++ b/sysdeps/linux-gnu/breakpoint.c
@@ -7,6 +7,7 @@
 #include "arch.h"
 #include "breakpoint.h"
 #include "proc.h"
+#include "library.h"
 
 #ifdef ARCH_HAVE_ENABLE_BREAKPOINT
 extern void arch_enable_breakpoint(pid_t, struct breakpoint *);
diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
index 9c376f3..91d873e 100644
--- a/sysdeps/linux-gnu/events.c
+++ b/sysdeps/linux-gnu/events.c
@@ -22,10 +22,10 @@
 static Event * delayed_events = NULL;
 static Event * end_delayed_events = NULL;
 
-static enum pcb_status
+static enum callback_status
 first (Process * proc, void * data)
 {
-	return pcb_stop;
+	return CBS_STOP;
 }
 
 void
@@ -175,14 +175,6 @@
 	get_arch_dep(event.proc);
 	debug(3, "event from pid %u", pid);
 	Process *leader = event.proc->leader;
-	if (leader == event.proc) {
-		if (!event.proc->libdl_hooked) {
-			/* debug struct may not have been written yet.. */
-			if (linkmap_init(event.proc, &main_lte) == 0) {
-				event.proc->libdl_hooked = 1;
-			}
-		}
-	}
 
 	/* The process should be stopped after the waitpid call.  But
 	 * when the whole thread group is terminated, we see
diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c
index 70bc19b..707d9d9 100644
--- a/sysdeps/linux-gnu/ppc/plt.c
+++ b/sysdeps/linux-gnu/ppc/plt.c
@@ -8,6 +8,7 @@
 	return rela->r_offset;
 }
 
+/* XXX Apparently PPC64 doesn't support PLT breakpoints.  */
 void *
 sym2addr(Process *proc, struct library_symbol *sym) {
 	void *addr = sym->enter_addr;
@@ -53,6 +54,8 @@
 		addr = (void *)pt_ret;
 	}
 #else
+	/* XXX Um, so where exactly are we dealing with the non-secure
+	   PLT thing?  */
 	addr = (void *)pt_ret;
 #endif
 
diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c
index eba030f..4592924 100644
--- a/sysdeps/linux-gnu/proc.c
+++ b/sysdeps/linux-gnu/proc.c
@@ -19,6 +19,7 @@
 #include "common.h"
 #include "breakpoint.h"
 #include "proc.h"
+#include "library.h"
 
 /* /proc/pid doesn't exist just after the fork, and sometimes `ltrace'
  * couldn't open it to find the executable.  So it may be necessary to
@@ -78,27 +79,28 @@
 }
 
 static void
-each_line_starting(FILE * file, const char *prefix,
-		   enum pcb_status (*cb)(const char * line, const char * prefix,
-					 void * data),
-		   void * data)
+each_line_starting(FILE *file, const char *prefix,
+		   enum callback_status (*cb)(const char *line,
+					      const char *prefix,
+					      void *data),
+		   void *data)
 {
 	size_t len = strlen(prefix);
 	char * line;
 	while ((line = find_line_starting(file, prefix, len)) != NULL) {
-		enum pcb_status st = (*cb)(line, prefix, data);
+		enum callback_status st = (*cb)(line, prefix, data);
 		free (line);
-		if (st == pcb_stop)
+		if (st == CBS_STOP)
 			return;
 	}
 }
 
-static enum pcb_status
-process_leader_cb(const char * line, const char * prefix, void * data)
+static enum callback_status
+process_leader_cb(const char *line, const char *prefix, void *data)
 {
 	pid_t * pidp = data;
 	*pidp = atoi(line + strlen(prefix));
-	return pcb_stop;
+	return CBS_STOP;
 }
 
 pid_t
@@ -114,13 +116,13 @@
 	return tgid;
 }
 
-static enum pcb_status
-process_stopped_cb(const char * line, const char * prefix, void * data)
+static enum callback_status
+process_stopped_cb(const char *line, const char *prefix, void *data)
 {
 	char c = line[strlen(prefix)];
 	// t:tracing stop, T:job control stop
 	*(int *)data = (c == 't' || c == 'T');
-	return pcb_stop;
+	return CBS_STOP;
 }
 
 int
@@ -136,15 +138,15 @@
 	return is_stopped;
 }
 
-static enum pcb_status
-process_status_cb(const char * line, const char * prefix, void * data)
+static enum callback_status
+process_status_cb(const char *line, const char *prefix, void *data)
 {
 	const char * status = line + strlen(prefix);
 	const char c = *status;
 
 #define RETURN(C) do {					\
 		*(enum process_status *)data = C;	\
-		return pcb_stop;			\
+		return CBS_STOP;			\
 	} while (0)
 
 	switch (c) {
@@ -245,6 +247,8 @@
 
 static int
 find_dynamic_entry_addr(Process *proc, void *pvAddr, int d_tag, void **addr) {
+	fprintf(stderr, "find_dynamic_entry_addr %d %p %d\n",
+		proc->pid, pvAddr, d_tag);
 	int i = 0, done = 0;
 	ElfW(Dyn) entry;
 
@@ -257,7 +261,9 @@
 	while ((!done) && (i < ELF_MAX_SEGMENTS) &&
 		(sizeof(entry) == umovebytes(proc, pvAddr, &entry, sizeof(entry))) &&
 		(entry.d_tag != DT_NULL)) {
+		fprintf(stderr, " entry %ld %#lx\n", entry.d_tag, entry.d_un.d_val);
 		if (entry.d_tag == d_tag) {
+			fprintf(stderr, "   hit\n");
 			done = 1;
 			*addr = (void *)entry.d_un.d_val;
 		}
@@ -275,15 +281,16 @@
 	}
 }
 
-struct cb_data {
-	const char *lib_name;
-	struct ltelf *lte;
-	ElfW(Addr) addr;
-	Process *proc;
-};
+enum callback_status
+find_library_addr(struct Process *proc, struct library *lib, void *data)
+{
+	target_address_t addr = (target_address_t)*(GElf_Addr *)data;
+	return lib->base == addr ? CBS_STOP : CBS_CONT;
+}
 
 static void
-crawl_linkmap(Process *proc, struct r_debug *dbg, void (*callback)(void *), struct cb_data *data) {
+crawl_linkmap(Process *proc, struct r_debug *dbg)
+{
 	struct link_map rlm;
 	char lib_name[BUFSIZ];
 	struct link_map *lm = NULL;
@@ -311,19 +318,33 @@
 
 		umovebytes(proc, rlm.l_name, lib_name, sizeof(lib_name));
 
-		if (lib_name[0] == '\0') {
-			debug(2, "Library name is an empty string");
+		debug(2, "Dispatching callback for: %s, "
+		      "Loaded at 0x%" PRI_ELF_ADDR "\n",
+		      lib_name, rlm.l_addr);
+		fprintf(stderr, "DSO addr=%#lx, name='%s'\n", rlm.l_addr, lib_name);
+
+		/* Do we have that library already?  */
+		struct library *lib
+			= proc_each_library(proc, NULL, find_library_addr,
+					    &rlm.l_addr);
+		if (lib != NULL)
+			continue;
+
+		if (*lib_name == '\0') {
+			/* VDSO.  No associated file, XXX but we might
+			 * load it from the address space of the
+			 * process.  */
 			continue;
 		}
 
-		if (callback) {
-			debug(2, "Dispatching callback for: %s, "
-					"Loaded at 0x%" PRI_ELF_ADDR "\n",
-					lib_name, rlm.l_addr);
-			data->addr = rlm.l_addr;
-			data->lib_name = lib_name;
-			callback(data);
+		lib = ltelf_read_library(lib_name, rlm.l_addr);
+		if (lib == NULL) {
+			error(0, errno, "Couldn't load ELF object %s\n",
+			      lib_name);
+			continue;
 		}
+
+		proc_add_library(proc, lib);
 	}
 	return;
 }
@@ -349,63 +370,11 @@
 }
 
 static void
-linkmap_add_cb(void *data) { //const char *lib_name, ElfW(Addr) addr) {
-	size_t i = 0;
-	struct cb_data *lm_add = data;
-	struct ltelf lte;
-	struct opt_x_t *xptr;
-
-	debug(DEBUG_FUNCTION, "linkmap_add_cb");
-
-	/*
-		XXX
-		iterate through library[i]'s to see if this lib is in the list.
-		if not, add it
-	 */
-	for(;i < library_num;i++) {
-		if (strcmp(library[i], lm_add->lib_name) == 0) {
-			/* found it, so its not new */
-			return;
-		}
-	}
-
-	/* new library linked! */
-	debug(2, "New libdl loaded library found: %s\n", lm_add->lib_name);
-
-	if (library_num < MAX_LIBRARIES) {
-		library[library_num++] = strdup(lm_add->lib_name);
-		memset(&lte, 0, sizeof(struct ltelf));
-		lte.base_addr = lm_add->addr;
-		do_init_elf(&lte, library[library_num-1]);
-		/* add bps */
-		for (xptr = opt_x; xptr; xptr = xptr->next) {
-			if (xptr->found)
-				continue;
-
-			GElf_Sym sym;
-			GElf_Addr addr;
-
-			if (in_load_libraries(xptr->name, &lte, 1, &sym)) {
-				debug(2, "found symbol %s @ %#" PRIx64
-						", adding it.",
-						xptr->name, sym.st_value);
-				addr = sym.st_value;
-				add_library_symbol(addr, xptr->name, &library_symbols, LS_TOPLT_NONE, 0);
-				xptr->found = 1;
-				insert_breakpoint(lm_add->proc,
-						  sym2addr(lm_add->proc,
-							   library_symbols),
-						  library_symbols, 1);
-			}
-		}
-		do_close_elf(&lte);
-	}
-}
-
-void
-arch_check_dbg(Process *proc) {
+rdebug_callback_hit(struct breakpoint *bp, struct Process *proc)
+{
+	fprintf(stderr, "======= HIT\n");
 	struct r_debug *dbg = NULL;
-	struct cb_data data;
+	//struct cb_data data;
 
 	debug(DEBUG_FUNCTION, "arch_check_dbg");
 
@@ -418,8 +387,9 @@
 		debug(2, "Linkmap is now consistent");
 		if (proc->debug_state == RT_ADD) {
 			debug(2, "Adding DSO to linkmap");
-			data.proc = proc;
-			crawl_linkmap(proc, dbg, linkmap_add_cb, &data);
+			//data.proc = proc;
+			crawl_linkmap(proc, dbg);
+			//&data);
 		} else if (proc->debug_state == RT_DELETE) {
 			debug(2, "Removing DSO from linkmap");
 		} else {
@@ -428,45 +398,19 @@
 	}
 
 	proc->debug_state = dbg->r_state;
-
 	return;
 }
 
-static void
-hook_libdl_cb(void *data) {
-	struct cb_data *hook_data = data;
-	const char *lib_name = NULL;
-	ElfW(Addr) addr;
-	struct ltelf *lte = NULL;
-
-	debug(DEBUG_FUNCTION, "add_library_cb");
-
-	if (!data) {
-		debug(2, "No callback data");
-		return;
-	}
-
-	lib_name = hook_data->lib_name;
-	addr = hook_data->addr;
-	lte = hook_data->lte;
-
-	if (library_num < MAX_LIBRARIES) {
-		lte[library_num].base_addr = addr;
-		library[library_num++] = strdup(lib_name);
-	}
-	else {
-		fprintf (stderr, "MAX LIBS REACHED\n");
-		exit(EXIT_FAILURE);
-	}
-}
-
+void *dyn_addr;
 int
-linkmap_init(Process *proc, struct ltelf *lte) {
-	void *dbg_addr = NULL, *dyn_addr = GELF_ADDR_CAST(lte->dyn_addr);
+linkmap_init(struct Process *proc)
+{
+	void *dbg_addr = NULL;
 	struct r_debug *rdbg = NULL;
-	struct cb_data data;
+	//struct cb_data data;
 
 	debug(DEBUG_FUNCTION, "linkmap_init()");
+	fprintf(stderr, "linkmap_init dyn_addr=%p\n", dyn_addr);
 
 	if (find_dynamic_entry_addr(proc, dyn_addr, DT_DEBUG, &dbg_addr) == -1) {
 		debug(2, "Couldn't find debug structure!");
@@ -480,13 +424,23 @@
 		return -1;
 	}
 
-	data.lte = lte;
+	//data.lte = lte;
 
-	add_library_symbol(rdbg->r_brk, "", &library_symbols, LS_TOPLT_NONE, 0);
-	insert_breakpoint(proc, sym2addr(proc, library_symbols),
-			  library_symbols, 1);
+	void *addr;
+	{
+		struct library_symbol libsym;
+		library_symbol_init(&libsym, rdbg->r_brk, NULL, 0,
+				    LS_TOPLT_NONE, 0);
+		addr = sym2addr(proc, &libsym);
+		library_symbol_destroy(&libsym);
+	}
+	struct breakpoint *rdebug_bp = insert_breakpoint(proc, addr, NULL, 1);
+	static struct bp_callbacks rdebug_callbacks = {
+		.on_hit = rdebug_callback_hit,
+	};
+	rdebug_bp->cbs = &rdebug_callbacks;
 
-	crawl_linkmap(proc, rdbg, hook_libdl_cb, &data);
+	crawl_linkmap(proc, rdbg);
 
 	free(rdbg);
 	return 0;
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index 6c6e814..9613271 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -304,8 +304,8 @@
 	return task_info;
 }
 
-static enum pcb_status
-task_stopped(Process * task, void * data)
+static enum callback_status
+task_stopped(struct Process *task, void *data)
 {
 	enum process_status st = process_status(task->pid);
 	if (data != NULL)
@@ -319,38 +319,38 @@
 	case ps_invalid:
 	case ps_tracing_stop:
 	case ps_zombie:
-		return pcb_cont;
+		return CBS_CONT;
 	case ps_sleeping:
 	case ps_stop:
 	case ps_other:
-		return pcb_stop;
+		return CBS_STOP;
 	}
 
 	abort ();
 }
 
 /* Task is blocked if it's stopped, or if it's a vfork parent.  */
-static enum pcb_status
-task_blocked(Process * task, void * data)
+static enum callback_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 CBS_CONT;
 
 	return task_stopped(task, NULL);
 }
 
 static Event *process_vfork_on_event(struct event_handler *super, Event *event);
 
-static enum pcb_status
-task_vforked(Process * task, void * data)
+static enum callback_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;
+		return CBS_STOP;
+	return CBS_CONT;
 }
 
 static int
@@ -359,8 +359,8 @@
 	return each_task(task->leader, &task_vforked, NULL) != NULL;
 }
 
-static enum pcb_status
-send_sigstop(Process * task, void * data)
+static enum callback_status
+send_sigstop(struct Process *task, void *data)
 {
 	Process * leader = task->leader;
 	struct pid_set * pids = data;
@@ -373,24 +373,24 @@
 		perror("send_sigstop: add_task_info");
 		destroy_event_handler(leader);
 		/* Signal failure upwards.  */
-		return pcb_stop;
+		return CBS_STOP;
 	}
 
 	/* This task still has not been attached to.  It should be
 	   stopped by the kernel.  */
 	if (task->state == STATE_BEING_CREATED)
-		return pcb_cont;
+		return CBS_CONT;
 
 	/* Don't bother sending SIGSTOP if we are already stopped, or
 	 * 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_stopped(task, &st) == CBS_CONT)
+		return CBS_CONT;
 	if (task_info->sigstopped) {
 		if (!task_info->delivered)
-			return pcb_cont;
+			return CBS_CONT;
 		task_info->delivered = 0;
 	}
 
@@ -401,7 +401,7 @@
 	if (st == ps_sleeping
 	    && is_vfork_parent (task)) {
 		task_info->vforked = 1;
-		return pcb_cont;
+		return CBS_CONT;
 	}
 
 	if (task_kill(task->pid, SIGSTOP) >= 0) {
@@ -411,7 +411,7 @@
 		fprintf(stderr,
 			"Warning: couldn't send SIGSTOP to %d\n", task->pid);
 
-	return pcb_cont;
+	return CBS_CONT;
 }
 
 /* On certain kernels, detaching right after a singlestep causes the
@@ -465,21 +465,21 @@
 	return ecb_cont;
 }
 
-static enum pcb_status
-untrace_task(Process * task, void * data)
+static enum callback_status
+untrace_task(struct Process *task, void *data)
 {
 	if (task != data)
 		untrace_pid(task->pid);
-	return pcb_cont;
+	return CBS_CONT;
 }
 
-static enum pcb_status
-remove_task(Process * task, void * data)
+static enum callback_status
+remove_task(struct Process *task, void *data)
 {
 	/* Don't untrace leader just yet.  */
 	if (task != data)
 		remove_process(task);
-	return pcb_cont;
+	return CBS_CONT;
 }
 
 static void
diff --git a/sysdeps/linux-gnu/x86_64/plt.c b/sysdeps/linux-gnu/x86_64/plt.c
index 8b0fc46..bb1b2b1 100644
--- a/sysdeps/linux-gnu/x86_64/plt.c
+++ b/sysdeps/linux-gnu/x86_64/plt.c
@@ -1,6 +1,7 @@
 #include <gelf.h>
 #include "proc.h"
 #include "common.h"
+#include "library.h"
 
 GElf_Addr
 arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) {