diff --git a/elf.h b/elf.h
index 426f7b8..18f0260 100644
--- a/elf.h
+++ b/elf.h
@@ -24,6 +24,10 @@ struct ltelf {
 	Elf32_Word *hash;
 	int hash_type;
 	int lte_flags;
+	GElf_Addr dyn_addr;
+	size_t dyn_sz;
+	size_t debug_offset;
+	GElf_Addr base_addr;
 #ifdef __mips__
 	size_t pltgot_addr;
 	size_t mips_local_gotno;
@@ -31,6 +35,8 @@ struct ltelf {
 #endif // __mips__
 };
 
+#define ELF_MAX_SEGMENTS  50
+#define ELF_SYMTAB_MAX    5000
 #define LTE_HASH_MALLOCED 1
 #define LTE_PLT_EXECUTABLE 2
 
diff --git a/execute_program.c b/execute_program.c
index 5fd6379..35886d1 100644
--- a/execute_program.c
+++ b/execute_program.c
@@ -1,5 +1,6 @@
 #include "config.h"
 
+#include <libunwind-ptrace.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
@@ -86,6 +87,7 @@ execute_program(Process *sp, char **argv) {
 	debug(1, "PID=%d", pid);
 
 	sp->pid = pid;
+	sp->unwind_priv = _UPT_create(pid);
 
 	return;
 }
diff --git a/handle_event.c b/handle_event.c
index 1dfb82b..175d76c 100644
--- a/handle_event.c
+++ b/handle_event.c
@@ -330,7 +330,7 @@ handle_signal(Event *event) {
 		remove_proc(event->proc);
 		return;
 	}
-	if (event->proc->state != STATE_IGNORED) {
+	if (event->proc->state != STATE_IGNORED && !options.no_signals) {
 		output_line(event->proc, "--- %s (%s) ---",
 				shortsignal(event->proc, event->e_un.signum),
 				strsignal(event->e_un.signum));
@@ -607,7 +607,10 @@ handle_breakpoint(Event *event) {
 	}
 
 	if ((sbp = address2bpstruct(event->proc, event->e_un.brk_addr))) {
-		if (event->proc->state != STATE_IGNORED) {
+		if (strcmp(sbp->libsym->name, "") == 0) {
+			debug(2, "Hit _dl_debug_state breakpoint!\n");
+			arch_check_dbg(event->proc);
+		} else if (event->proc->state != STATE_IGNORED) {
 			event->proc->stack_pointer = get_stack_pointer(event->proc);
 			event->proc->return_addr =
 				get_return_addr(event->proc, event->proc->stack_pointer);
@@ -625,7 +628,8 @@ handle_breakpoint(Event *event) {
 		return;
 	}
 
-	if (event->proc->state != STATE_IGNORED) {
+	/* XXX this check is probably wrong, but I'll debug more when time allows */
+	if (event->proc->state != STATE_IGNORED && !options.no_plt) {
 		output_line(event->proc, "unexpected breakpoint at %p",
 				(void *)event->e_un.brk_addr);
 	}
diff --git a/ltrace.1 b/ltrace.1
index 358d6aa..0434600 100644
--- a/ltrace.1
+++ b/ltrace.1
@@ -6,7 +6,7 @@ ltrace \- A library call tracer
 
 .SH SYNOPSIS
 .B ltrace
-.I "[-CfhiLrStttV] [-a column] [-A maxelts] [-D level] [-e expr] [-l filename] [-n nr] [-o filename] [-p pid] ... [-s strsize] [-u username] [-X extern] [-x extern] ... [--align=column] [--debug=level] [--demangle] [--help] [--indent=nr] [--library=filename] [--output=filename] [--version] [command [arg ...]]"
+.I "[-bCfghiLrStttV] [-a column] [-A maxelts] [-D level] [-e expr] [-l filename] [-n nr] [-o filename] [-p pid] ... [-s strsize] [-u username] [-w count] [-X extern] [-x extern] ... [--align=column] [--debug=level] [--demangle] [--help] [--indent=nr] [--library=filename] [--no-signals] [--output=filename] [--version] [command [arg ...]]"
 
 .SH DESCRIPTION
 .B ltrace
@@ -23,11 +23,16 @@ Its use is very similar to
 .SH OPTIONS
 .TP
 .I \-a, \-\-align column
-Align return values in a specific column (default column is 5/8 of screen width).
+Align return values in a specific
+.IR column
+(default column is 5/8 of screen width).
 .TP
 .I \-A maxelts
 Maximum number of array elements to print before suppressing the rest with an ellipsis ("...")
 .TP
+.I \-b, \-\-no-signals
+Disable printing of signals recieved by the traced process.
+.TP
 .I \-c
 Count time and calls for each library call and report a summary on program exit.
 .TP
@@ -82,6 +87,11 @@ currently traced processes as a result of the fork(2)
 or clone(2) system calls.
 The new process is attached immediately.
 .TP
+.I \-g
+Do not place breakpoints on PLT entries. This option reduces
+the output of ltrace. This is commonly used to avoid tracing
+libc functions.
+.TP
 .I \-F
 Load an alternate config file. Normally, /etc/ltrace.conf and
 ~/.ltrace.conf will be read (the latter only if it exists).
@@ -153,6 +163,9 @@ Run command with the userid, groupid and supplementary groups of
 This option is only useful when running as root and enables the
 correct execution of setuid and/or setgid binaries.
 .TP
+.I \-w, --where NR
+Show backtrace of NR stack frames for each traced function.
+.TP
 .I \-X extern
 Some architectures need to know where to set a breakpoint that will be hit
 after the dynamic linker has run.  If this flag is used, then the breakpoint
@@ -164,6 +177,8 @@ NOTE: this flag is only available on the architectures that need it.
 .I \-x extern
 Trace the external function
 .IR extern .
+This option will search the symbol table and lib-dl loaded libraries when
+attempting to match the given symbol name.
 This option may be repeated.
 .TP
 .I \-V, \-\-version
@@ -180,8 +195,6 @@ Option -f sometimes fails to trace some children.
 It only works on Linux and in a small subset of architectures.
 .LP
 Only ELF32 binaries are supported.
-.LP
-Calls to dlopen()ed libraries will not be traced.
 .PP
 If you would like to report a bug, send a message to the mailing list
 (ltrace-devel@lists.alioth.debian.org), or use the
diff --git a/options.c b/options.c
index aef73b1..329c982 100644
--- a/options.c
+++ b/options.c
@@ -53,6 +53,7 @@ int opt_e_enable = 1;
 
 /* List of global function names given to -x: */
 struct opt_x_t *opt_x = NULL;
+unsigned int opt_x_cnt = 0;
 
 /* List of filenames give to option -F: */
 struct opt_F_t *opt_F = NULL;	/* alternate configuration file(s) */
@@ -75,6 +76,7 @@ usage(void) {
 		"Trace library calls of a given program.\n\n"
 		"  -a, --align=COLUMN  align return values in a secific column.\n"
 		"  -A ARRAYLEN         maximum number of array elements to print.\n"
+		"  -b, --no-signals    don't print signals.\n"
 		"  -c                  count time and calls, and report a summary on exit.\n"
 # ifdef USE_DEMANGLE
 		"  -C, --demangle      decode low-level symbol names into user-level names.\n"
@@ -84,6 +86,7 @@ usage(void) {
 		"  -e expr             modify which events to trace.\n"
 		"  -f                  trace children (fork() and clone()).\n"
 		"  -F, --config=FILE   load alternate configuration file (may be repeated).\n"
+		"  -g, --no-plt        disable breakpoints on PLT entries.\n"
 		"  -h, --help          display this help and exit.\n"
 		"  -i                  print instruction pointer at time of library call.\n"
 		"  -l, --library=FILE  print library calls from this library only.\n"
@@ -98,6 +101,7 @@ usage(void) {
 		"  -T                  show the time spent inside each call.\n"
 		"  -u USERNAME         run command with the userid, groupid of username.\n"
 		"  -V, --version       output version information and exit.\n"
+		"  -w=NR, --where=NR   print backtrace showing NR stack frames at most.\n"
 		"  -x NAME             treat the global NAME like a library subroutine.\n"
 #ifdef PLT_REINITALISATION_BP
 		"  -X NAME             same as -x; and PLT's will be initialized by here.\n"
@@ -179,6 +183,9 @@ char **
 process_options(int argc, char **argv) {
 	progname = argv[0];
 	options.output = stderr;
+	options.no_plt = 0;
+	options.no_signals = 0;
+	options.bt_depth = -1;
 
 	guess_cols();
 
@@ -198,13 +205,16 @@ process_options(int argc, char **argv) {
 			{"library", 1, 0, 'l'},
 			{"output", 1, 0, 'o'},
 			{"version", 0, 0, 'V'},
+			{"no-plt", 0, 0, 'g'},
+			{"no-signals", 0, 0, 'b'},
+			{"where", 1, 0, 'w'},
 			{0, 0, 0, 0}
 		};
-		c = getopt_long(argc, argv, "+cfhiLrStTV"
+		c = getopt_long(argc, argv, "+cfhiLrStTVgb"
 # ifdef USE_DEMANGLE
 				"C"
 # endif
-				"a:A:D:e:F:l:n:o:p:s:u:x:X:", long_options,
+				"a:A:D:e:F:l:n:o:p:s:u:x:X:w:", long_options,
 				&option_index);
 		if (c == -1) {
 			break;
@@ -216,6 +226,9 @@ process_options(int argc, char **argv) {
 		case 'A':
 			options.arraylen = atoi(optarg);
 			break;
+		case 'b':
+			options.no_signals = 1;
+			break;
 		case 'c':
 			options.summary++;
 			break;
@@ -283,6 +296,9 @@ process_options(int argc, char **argv) {
 				opt_F = tmp;
 				break;
 			}
+		case 'g':
+			options.no_plt = 1;
+			break;
 		case 'h':
 			usage();
 			exit(0);
@@ -351,6 +367,9 @@ process_options(int argc, char **argv) {
 					"This is free software; see the GNU General Public Licence\n"
 					"version 2 or later for copying conditions.  There is NO warranty.\n");
 			exit(0);
+		case 'w':
+			options.bt_depth = atoi(optarg);
+			break;
 		case 'X':
 #ifdef PLT_REINITALISATION_BP
 			PLTs_initialized_by_here = optarg;
@@ -378,9 +397,11 @@ process_options(int argc, char **argv) {
 					perror("ltrace: malloc");
 					exit(1);
 				}
+				opt_x_cnt++;
 				p->name = optarg;
 				p->found = 0;
 				p->next = opt_x;
+				p->hash = ~(0UL);
 				opt_x = p;
 				break;
 			}
diff --git a/options.h b/options.h
index db253c5..d429af0 100644
--- a/options.h
+++ b/options.h
@@ -2,18 +2,21 @@
 #include <sys/types.h>
 
 struct options_t {
-	int align;    /* -a: default alignment column for results */
-	char * user;  /* -u: username to run command as */
-	int syscalls; /* -S: display system calls */
-	int libcalls; /* -L: display library calls */
-	int demangle; /* -C: demangle low-level names into user-level names */
-	int indent;   /* -n: indent trace output according to program flow */
-	FILE *output; /* output to a specific file */
-	int summary;  /* count time, calls, and report a summary on program exit */
-	int debug;    /* debug */
-	int arraylen; /* default maximum # of array elements printed */
-	int strlen;   /* default maximum # of bytes printed in strings */
-	int follow;   /* trace child processes */
+	int align;      /* -a: default alignment column for results */
+	char * user;    /* -u: username to run command as */
+	int syscalls;   /* -S: display system calls */
+	int libcalls;   /* -L: display library calls */
+	int demangle;   /* -C: demangle low-level names into user-level names */
+	int indent;     /* -n: indent trace output according to program flow */
+	FILE *output;   /* output to a specific file */
+	int summary;    /* count time, calls, and report a summary on program exit */
+	int debug;      /* debug */
+	int arraylen;   /* default maximum # of array elements printed */
+	int strlen;     /* default maximum # of bytes printed in strings */
+	int follow;     /* trace child processes */
+	int no_plt;     /* set bps on PLT entries */
+	int no_signals; /* don't print signals */
+	int bt_depth;	 /* how may levels of stack frames to show */
 };
 extern struct options_t options;
 
@@ -40,6 +43,7 @@ struct opt_F_t {
 struct opt_x_t {
 	char *name;
 	int found;
+	unsigned long hash;
 	struct opt_x_t *next;
 };
 
@@ -51,5 +55,6 @@ extern int opt_e_enable;	/* 0 if '!' is used, 1 otherwise */
 extern struct opt_F_t *opt_F;	/* alternate configuration file(s) */
 
 extern struct opt_x_t *opt_x;	/* list of functions to break at */
+extern unsigned int opt_x_cnt;
 
 extern char **process_options(int argc, char **argv);
diff --git a/output.c b/output.c
index bb07ab1..45db900 100644
--- a/output.c
+++ b/output.c
@@ -298,6 +298,26 @@ output_right(enum tof type, Process *proc, char *function_name) {
 			(int)current_time_spent.tv_usec);
 	}
 	fprintf(options.output, "\n");
+
+	if (options.bt_depth > 0) {
+		unw_cursor_t cursor;
+		unw_word_t ip, sp;
+		int unwind_depth = options.bt_depth;
+		char fn_name[100];
+
+		unw_init_remote(&cursor, proc->unwind_as, proc->unwind_priv);
+		while (unwind_depth) {
+			unw_get_reg(&cursor, UNW_REG_IP, &ip);
+			unw_get_reg(&cursor, UNW_REG_SP, &sp);
+			unw_get_proc_name(&cursor, fn_name, 100, NULL);
+			fprintf(options.output, "\t\t\t%s (ip = 0x%lx)\n", fn_name, (long) ip);
+			if (unw_step(&cursor) <= 0)
+				break;
+			unwind_depth--;
+		}
+		fprintf(options.output, "\n");
+	}
+
 	current_proc = 0;
 	current_column = 0;
 }
diff --git a/proc.c b/proc.c
index bfc6e41..237ff4a 100644
--- a/proc.c
+++ b/proc.c
@@ -1,3 +1,5 @@
+#include <libunwind.h>
+#include <libunwind-ptrace.h>
 #include <sys/types.h>
 #include <string.h>
 #include <stdio.h>
@@ -18,11 +20,17 @@ open_program(char *filename, pid_t pid) {
 	proc->breakpoints_enabled = -1;
 	if (pid) {
 		proc->pid = pid;
+		proc->unwind_priv = _UPT_create(pid);
+	} else {
+		proc->unwind_priv = NULL;
 	}
+
 	breakpoints_init(proc);
 
 	proc->next = list_of_processes;
 	list_of_processes = proc;
+
+	proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0);
 	return proc;
 }
 
diff --git a/sysdeps/README b/sysdeps/README
index ce033ef..0a37909 100644
--- a/sysdeps/README
+++ b/sysdeps/README
@@ -30,3 +30,4 @@ char * pid2name(pid_t pid);
 void trace_me(void);
 int trace_pid(pid_t pid);
 void untrace_pid(pid_t pid);
+void linkmap_init(Process *, struct ltelf *);
diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
index a1e2a14..5b636f5 100644
--- a/sysdeps/linux-gnu/events.c
+++ b/sysdeps/linux-gnu/events.c
@@ -49,13 +49,19 @@ next_event(void) {
 	event.proc->instruction_pointer = NULL;
 	debug(3, "event from pid %u", pid);
 	if (event.proc->breakpoints_enabled == -1) {
-		enable_all_breakpoints(event.proc);
 		event.type = EVENT_NONE;
 		trace_set_options(event.proc, event.proc->pid);
+		enable_all_breakpoints(event.proc);
 		continue_process(event.proc->pid);
 		debug(DEBUG_EVENT, "event: NONE: pid=%d (enabling breakpoints)", pid);
 		return &event;
+	} else if (!libdl_hooked) {
+		/* debug struct may not have been written yet.. */
+		if (linkmap_init(event.proc, &main_lte) == 0) {
+			libdl_hooked = 1;
+		}
 	}
+
 	if (opt_i) {
 		event.proc->instruction_pointer =
 			get_instruction_pointer(event.proc);
diff --git a/sysdeps/linux-gnu/i386/trace.c b/sysdeps/linux-gnu/i386/trace.c
index 76f1105..634b5d3 100644
--- a/sysdeps/linux-gnu/i386/trace.c
+++ b/sysdeps/linux-gnu/i386/trace.c
@@ -1,5 +1,6 @@
 #include "config.h"
 
+#include <gelf.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/wait.h>
diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c
index 4251c1d..fa64db7 100644
--- a/sysdeps/linux-gnu/proc.c
+++ b/sysdeps/linux-gnu/proc.c
@@ -1,6 +1,8 @@
 #include "config.h"
+#include "common.h"
 
 #include <sys/types.h>
+#include <link.h>
 #include <stdio.h>
 #include <string.h>
 #include <signal.h>
@@ -34,3 +36,282 @@ pid2name(pid_t pid) {
 	}
 	return NULL;
 }
+
+static int
+find_dynamic_entry_addr(Process *proc, void *pvAddr, int d_tag, void **addr) {
+	int i = 0, done = 0;
+	ElfW(Dyn) entry;
+
+	debug(DEBUG_FUNCTION, "find_dynamic_entry()");
+
+	if (addr ==	NULL || pvAddr == NULL || d_tag < 0 || d_tag > DT_NUM) {
+		return -1;
+	}
+
+	while ((!done) && (i < ELF_MAX_SEGMENTS) &&
+		(sizeof(entry) == umovebytes(proc, pvAddr, &entry, sizeof(entry))) &&
+		(entry.d_tag != DT_NULL)) {
+		if (entry.d_tag == d_tag) {
+			done = 1;
+			*addr = (void *)entry.d_un.d_val;
+		}
+		pvAddr += sizeof(entry);
+		i++;
+	}
+
+	if (done) {
+		debug(2, "found address: 0x%p in dtag %d\n", *addr, d_tag);
+		return 0;
+	}
+	else {
+		debug(2, "Couldn't address for dtag!\n");
+		return -1;
+	}
+}
+
+struct cb_data {
+	const char *lib_name;
+	struct ltelf *lte;
+	ElfW(Addr) addr;
+	Process *proc;
+};
+
+static void
+crawl_linkmap(Process *proc, struct r_debug *dbg, void (*callback)(void *), struct cb_data *data) {
+	struct link_map rlm;
+	char lib_name[BUFSIZ];
+	struct link_map *lm = NULL;
+
+	debug (DEBUG_FUNCTION, "crawl_linkmap()");
+
+	if (!dbg || !dbg->r_map) {
+		debug(2, "Debug structure or it's linkmap are NULL!");
+		return;
+	}
+
+	lm = dbg->r_map;
+
+	while (lm) {
+		if (umovebytes(proc, lm, &rlm, sizeof(rlm)) != sizeof(rlm)) {
+			debug(2, "Unable to read link map\n");
+			return;
+		}
+
+		lm = rlm.l_next;
+		if (rlm.l_name == NULL) {
+			debug(2, "Invalid library name referenced in dynamic linker map\n");
+			return;
+		}
+
+		umovebytes(proc, rlm.l_name, lib_name, sizeof(lib_name));
+
+		if (lib_name[0] == '\0') {
+			debug(2, "Library name is an empty string");
+			continue;
+		}
+
+		if (callback) {
+			debug(2, "Dispatching callback for: %s, Loaded at 0x%x\n", lib_name, rlm.l_addr);
+			data->addr = rlm.l_addr;
+			data->lib_name = lib_name;
+			callback(data);
+		}
+	}
+	return;
+}
+
+static struct r_debug *
+load_debug_struct(Process *proc) {
+	struct r_debug *rdbg = NULL;
+
+	debug(DEBUG_FUNCTION, "load_debug_struct");
+
+	rdbg = malloc(sizeof(*rdbg));
+	if (!rdbg) {
+		return NULL;
+	}
+
+	if (umovebytes(proc, proc->debug, rdbg, sizeof(*rdbg)) != sizeof(*rdbg)) {
+		debug(2, "This process does not have a debug structure!\n");
+		free(rdbg);
+		return NULL;
+	}
+
+	return rdbg;
+}
+
+static void
+linkmap_add_cb(void *data) { //const char *lib_name, ElfW(Addr) addr) {
+	int 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 @ %lx, 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);
+			}
+		}
+		do_close_elf(&lte);
+	}
+}
+
+void
+arch_check_dbg(Process *proc) {
+	struct r_debug *dbg = NULL;
+	struct cb_data data;
+
+	debug(DEBUG_FUNCTION, "arch_check_dbg");
+
+	if (!(dbg = load_debug_struct(proc))) {
+		debug(2, "Unable to load debug structure!");
+		return;
+	}
+
+	if (dbg->r_state == RT_CONSISTENT) {
+		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);
+		} else if (proc->debug_state == RT_DELETE) {
+			debug(2, "Removing DSO from linkmap");
+		} else {
+			debug(2, "Unexpected debug state!");
+		}
+	}
+
+	proc->debug_state = dbg->r_state;
+
+	return;
+}
+
+#if 0
+
+/*(XXX TODO)
+
+	 support removal of libraries loaded with dlsym:
+		- should be fairly easy, but some refactoring needed
+		- create a mapping between libraries and their breakpoints
+		- once a library "disappears" form the linkmap, remove each breakpoint
+*/
+
+static void
+check_still_there(void *data) {
+	debug(DEBUG_FUNCTION, "check_still_there");
+
+	/* check if lib found in link map matches the library[i] we are searching
+		 for */
+}
+
+static void
+linkmap_remove() {
+	int i = 0;
+	debug(DEBUG_FUNCTION, "linkmap_remove");
+	/*
+		 XXX
+		 for each library[i], see if its still in the linkmap. 
+	 */
+
+	data.lib_name2find = lib_name;
+
+	/* store some search state in data, too */
+
+	for(;i < library_num;i++) {
+		crawl_linkmap(proc, lte, debug struct, check_still_there, data); 
+	}
+}
+
+#endif
+
+static void
+hook_libdl_cb(void *data) { //const char *lib_name, ElfW(Addr) addr) {
+	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) {
+		library[library_num++] = strdup(lib_name);
+		lte[library_num].base_addr = addr;
+	}
+	else {
+		fprintf (stderr, "MAX LIBS REACHED\n");
+		exit(EXIT_FAILURE);
+	}
+}
+
+int
+linkmap_init(Process *proc, struct ltelf *lte) {
+	void *dbg_addr = NULL;
+	struct r_debug *rdbg = NULL;
+	struct cb_data data;
+
+	debug(DEBUG_FUNCTION, "linkmap_init()");
+
+	if (find_dynamic_entry_addr(proc, (void *)lte->dyn_addr, DT_DEBUG, &dbg_addr) == -1) {
+		debug(2, "Couldn't find debug structure!");
+		return -1;
+	}
+
+	proc->debug = dbg_addr;
+
+	if (!(rdbg = load_debug_struct(proc))) {
+		debug(2, "No debug structure or no memory to allocate one!");
+		return -1;
+	}
+
+	data.lte = lte;
+
+	add_library_symbol(rdbg->r_brk, "", &library_symbols, LS_TOPLT_NONE, 0);
+	insert_breakpoint(proc, sym2addr(proc, library_symbols), library_symbols);
+
+	crawl_linkmap(proc, rdbg, hook_libdl_cb, &data);
+
+	free(rdbg);
+	return 0;
+}
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index df5b090..f7df7f1 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -163,6 +163,30 @@ continue_after_breakpoint(Process *proc, Breakpoint *sbp) {
 	}
 }
 
+size_t
+umovebytes(Process *proc, void *src, void *dest, size_t count) {
+	size_t bytes_read = 0;
+	long word = 0;
+	void *src_iter = src, *dest_iter = dest;
+	int min = 0, max = 0;
+
+	while (bytes_read < count) {
+		word = ptrace(PTRACE_PEEKDATA, proc->pid, src_iter, NULL);
+		if (word == -1) {
+			return bytes_read;
+		}
+
+		min = ((src_iter < src) ? (src - src_iter) : 0);
+		max = ((count - bytes_read) > sizeof(word) ? sizeof(word) : (count - bytes_read));
+		memcpy(dest_iter, (void *) ((unsigned long) &word + min), max - min);
+		bytes_read += max - min;
+		src_iter += sizeof(word);
+		dest_iter += max - min;
+
+	}
+	return bytes_read;
+}
+
 /* Read a series of bytes starting at the process's memory address
    'addr' and continuing until a NUL ('\0') is seen or 'len' bytes
    have been read.
