[PATCH] Add gdb remote protocol handling to strace

Stan Cox scox at redhat.com
Fri Nov 10 04:43:52 UTC 2017


The last posted patch lost a printsiginfo patch that used macro tricks 
to avoid that signal.h problem but I changed to use an opaque pointer 
for next_event/dispatch_event to avoid the problem.   It is assumed that 
a gdbserver is running and the strace gdbserver backend communicates 
with that, typically using tcp.  This is analogous to gdb/gdbserver 
behavior.


diff --git a/Makefile.am b/Makefile.am
index 4aa9846..1f216b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -47,13 +47,22 @@ ARCH		= @arch@

  ACLOCAL_AMFLAGS = -I m4
  AM_CFLAGS = $(WARN_CFLAGS)
-AM_CPPFLAGS = -I$(builddir)/$(OS)/$(ARCH) \
+OS_CPPFLAGS = -I$(builddir)/$(OS)/$(ARCH) \
  	      -I$(srcdir)/$(OS)/$(ARCH) \
  	      -I$(builddir)/$(OS) \
  	      -I$(srcdir)/$(OS) \
  	      -I$(builddir) \
  	      -I$(srcdir)

+if ENABLE_GDBSERVER
+GDBSERVER_CPPFLAGS = -I$(builddir)/gdbserver/$(ARCH) \
+	      -I$(srcdir)/gdbserver/$(ARCH) \
+	      -I$(builddir)/gdbserver \
+	      -I$(srcdir)/gdbserver $(OS_CPPFLAGS)
+endif
+
+AM_CPPFLAGS = $(OS_CPPFLAGS) $(GDBSERVER_CPPFLAGS)
+
  AM_CFLAGS_FOR_BUILD = $(WARN_CFLAGS_FOR_BUILD)
  AM_CPPFLAGS_FOR_BUILD = $(AM_CPPFLAGS)

@@ -325,6 +334,18 @@ strace_SOURCES =	\
  	xmalloc.h	\
  	# end of strace_SOURCES

+if ENABLE_GDBSERVER
+# Note that (some of) the files inside this directory are GPLv3-licensed,
+# so the package which includes them will also be tainted by GPLv3 license.
+strace_SOURCES +=		\
+	gdbserver/gdbserver.c	\
+	gdbserver/protocol.c	\
+	gdbserver/protocol.h	\
+	gdbserver/signals.def	\
+	gdbserver/signals.h	\
+	# end of gdbserver strace_SOURCES
+endif
+
  if USE_LIBUNWIND
  strace_SOURCES += unwind.c
  strace_CPPFLAGS += $(libunwind_CPPFLAGS)
@@ -873,6 +894,13 @@ EXTRA_DIST =				\
  	xlat/gen.sh			\
  	xlate.el

+if ENABLE_GDBSERVER
+EXTRA_DIST +=					\
+	gdbserver/x86_64/gdb_arch_defs.h	\
+	gdbserver/x86_64/gdb_get_regs.c		\
+	# End of gdbserver EXTRA_DIS
+endif
+
  .PHONY: check-valgrind-local
  check-valgrind-local:

diff --git a/README b/README
index bc70155..278c80a 100644
--- a/README
+++ b/README
@@ -3,6 +3,10 @@ This is strace, a system call tracer for Linux.
  strace is released under a Berkeley-style license at the request
  of Paul Kranenburg.

+The gdbserver feature of strace is released under the same license,
+except for a few sources copied from gdb which are required to use
+GPL version 3 or later.  See COPYING3 for details.
+
  See the file CREDITS for a list of authors and other contributors.
  See the file INSTALL for compilation and installation instructions.
  See the file NEWS for information on what has changed in recent versions.
diff --git a/configure.ac b/configure.ac
index 29285db..770c543 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,7 +42,7 @@ AC_COPYRIGHT([Copyright (c) 1999-]copyright_year[ The 
strace developers.])
  AC_CONFIG_SRCDIR([strace.c])
  AC_CONFIG_AUX_DIR([.])
  AC_CONFIG_HEADERS([config.h])
-AM_INIT_AUTOMAKE([foreign nostdinc dist-xz silent-rules parallel-tests 
1.13])
+AM_INIT_AUTOMAKE([foreign nostdinc dist-xz silent-rules parallel-tests 
subdir-objects 1.13])
  AM_MAINTAINER_MODE
  AC_CANONICAL_HOST

@@ -868,6 +868,30 @@ fi
  AM_CONDITIONAL([USE_LIBUNWIND], [test "x$use_libunwind" = xyes])
  AC_MSG_RESULT([$use_libunwind])

+
+dnl gdbserver
+AC_ARG_ENABLE([gdbserver],
+	      [AS_HELP_STRING([--enable-gdbserver],
+			      [enable gdbserver backend (includes GPLv3 code)])],
+	      [], [enable_gdbserver=check])
+
+use_gdbserver=no
+AS_IF([test "x$enable_gdbserver" != xno],
+	[AC_CHECK_FILE([$srcdir/gdbserver/$arch/gdb_arch_defs.h],
+		[use_gdbserver=yes],
+		[if test "x$enable_gdbserver" != xcheck; then
+                  AC_MSG_FAILURE([failed to find arch-specific 
gdbserver definitions $srcdir/gdbserver/$arch/gdb_arch_defs.h])
+                fi])]
+)
+
+AC_MSG_CHECKING([whether to enable gdbserver backend support])
+if test "x$use_gdbserver" = xyes; then
+	AC_DEFINE([ENABLE_GDBSERVER], 1, [Enable gdbserver backend support])
+fi
+AM_CONDITIONAL([ENABLE_GDBSERVER], [test "x$use_gdbserver" = xyes])
+AC_MSG_RESULT([$use_gdbserver])
+
+
  if test "$arch" = mips && test "$no_create" != yes; then
  	mkdir -p linux/mips
  	if $srcdir/linux/mips/genstub.sh linux/mips; then
diff --git a/defs.h b/defs.h
index 06a4baf..3607140 100644
--- a/defs.h
+++ b/defs.h
@@ -253,6 +253,7 @@ struct tcb {
  #define TCB_TAMPERED	0x40	/* A syscall has been tampered with */
  #define TCB_HIDE_LOG	0x80	/* We should hide everything (until execve) */
  #define TCB_SKIP_DETACH_ON_FIRST_EXEC	0x100	/* -b execve should skip 
detach on first execve */
+#define TCB_GDB_CONT_PID_TID 0x200 /* Use vCont;c:pPID.TID for for gdb 
backend */

  /* qualifier flags */
  #define QUAL_TRACE	0x001	/* this system call should be traced */
@@ -274,6 +275,98 @@ struct tcb {
  #define filtered(tcp)	((tcp)->flags & TCB_FILTERED)
  #define hide_log(tcp)	((tcp)->flags & TCB_HIDE_LOG)

+enum trace_event {
+	/* Break the main loop. */
+	TE_BREAK,
+
+	/* Call next_event() again. */
+	TE_NEXT,
+
+	/* Restart the tracee with signal 0 and call next_event() again. */
+	TE_RESTART,
+
+	/*
+	 * For all the events below, current_tcp is set to current tracee's
+	 * tcb.  All the suggested actions imply that you want to continue
+	 * tracing of the current tracee; alternatively, you can detach it.
+	 */
+
+	/*
+	 * Syscall entry or exit.
+	 * Restart the tracee with signal 0, or with an injected signal number.
+	 */
+	TE_SYSCALL_STOP,
+
+	/*
+	 * Tracee received signal with number WSTOPSIG(*pstatus); signal info
+	 * is written to *si.  Restart the tracee (with that signal number
+	 * if you want to deliver it).
+	 */
+	TE_SIGNAL_DELIVERY_STOP,
+
+	/*
+	 * Tracee was killed by a signal with number WTERMSIG(*pstatus).
+	 */
+	TE_SIGNALLED,
+
+	/*
+	 * Tracee was stopped by a signal with number WSTOPSIG(*pstatus).
+	 * Restart the tracee with that signal number.
+	 */
+	TE_GROUP_STOP,
+
+	/*
+	 * Tracee exited with status WEXITSTATUS(*pstatus).
+	 */
+	TE_EXITED,
+
+	/*
+	 * Tracee is going to perform execve().
+	 * Restart the tracee with signal 0.
+	 */
+	TE_STOP_BEFORE_EXECVE,
+
+	/*
+	 * Tracee is going to terminate.
+	 * Restart the tracee with signal 0.
+	 */
+	TE_STOP_BEFORE_EXIT,
+};
+
+/* Backend Dispatch Table */
+struct backend {
+	void (*attach_tcb) (struct tcb *tcp);
+	void (*cleanup) (void);
+	void (*detach) (struct tcb *tcp);
+	bool (*dispatch_event) (enum trace_event, int *pstatus, void *si);
+	void (*end_init) (void);
+	long (*get_regs) (pid_t pid, void *io);
+	int (*get_scno) (struct tcb *tcp);
+	int (*getfdpath) (struct tcb *tcp, int fd, char *buf, unsigned bufsize);
+	bool (*handle_arg) (char arg, char *optarg);
+	enum trace_event (*next_event) (int *pstatus, void *si);
+        bool (*prog_pid_check) (char *exec_name, int nprocs);
+	bool (*start_init) (void);
+	void (*startup_child) (char **argv);
+	long (*set_regs) (pid_t pid, void *io);
+	bool (*trace) (void);
+	int (*umoven) (struct tcb *const tcp, kernel_ulong_t addr, unsigned 
int len,
+	       void *const our_addr);
+	int (*umovestr) (struct tcb *const tcp, kernel_ulong_t addr, unsigned 
int len, char *laddr);
+	int (*upeek_) (int pid, unsigned long off, kernel_ulong_t *res);
+	int (*upoke_) (int pid, unsigned long off, kernel_ulong_t val);
+	bool (*verify_args) (const char *username, bool daemon, unsigned int 
*follow_fork);
+	void *data;
+} backend;
+
+
+extern int upeek(int pid, unsigned long, kernel_ulong_t *);
+extern int upoke(int pid, unsigned long, kernel_ulong_t);
+extern bool gdb_handle_arg (char arg, char *optarg);
+
+#define upeek(pid,off,res) backend.upeek_(pid, off, res)
+#define upoke(pid,off,val) backend.upoke_(pid, off, val)
+
  #include "xlat.h"

  extern const struct xlat addrfams[];
@@ -394,6 +487,8 @@ extern int read_int_from_file(const char *, int *);
  extern void set_sortby(const char *);
  extern void set_overhead(int);
  extern void print_pc(struct tcb *);
+extern int trace_syscall(struct tcb *, unsigned int *);
+extern void update_personality(struct tcb *tcp, unsigned int personality);

  extern int syscall_entering_decode(struct tcb *);
  extern int syscall_entering_trace(struct tcb *, unsigned int *);
@@ -428,6 +523,7 @@ extern void *get_tcb_priv_data(const struct tcb *);
  extern int set_tcb_priv_data(struct tcb *, void *priv_data,
  			     void (*free_priv_data)(void *));
  extern void free_tcb_priv_data(struct tcb *);
+extern struct tcb *pid2tcb(int pid);

  static inline unsigned long get_tcb_priv_ulong(const struct tcb *tcp)
  {
@@ -442,7 +538,7 @@ static inline int set_tcb_priv_ulong(struct tcb 
*tcp, unsigned long val)
  extern int
  umoven(struct tcb *, kernel_ulong_t addr, unsigned int len, void *laddr);
  #define umove(pid, addr, objp)	\
-	umoven((pid), (addr), sizeof(*(objp)), (void *) (objp))
+	backend.umoven((pid), (addr), sizeof(*(objp)), (void *) (objp))

  extern int
  umoven_or_printaddr(struct tcb *, kernel_ulong_t addr,
@@ -457,9 +553,6 @@ umoven_or_printaddr_ignore_syserror(struct tcb *, 
kernel_ulong_t addr,
  extern int
  umovestr(struct tcb *, kernel_ulong_t addr, unsigned int len, char 
*laddr);

-extern int upeek(int pid, unsigned long, kernel_ulong_t *);
-extern int upoke(int pid, unsigned long, kernel_ulong_t);
-
  extern bool
  print_array(struct tcb *,
  	    kernel_ulong_t start_addr,
diff --git a/desc.c b/desc.c
index 52e58c8..6472a94 100644
--- a/desc.c
+++ b/desc.c
@@ -148,7 +148,7 @@ decode_select(struct tcb *const tcp, const 
kernel_ulong_t *const args,
  			int first = 1;

  			addr = args[i+1];
-			if (!addr || !fds || umoven(tcp, addr, fdsize, fds) < 0)
+			if (!addr || !fds || backend.umoven(tcp, addr, fdsize, fds) < 0)
  				continue;
  			for (j = 0;; j++) {
  				j = next_set_bit(fds, j, nfds);
diff --git a/dirent.c b/dirent.c
index 2ab626b..52be6be 100644
--- a/dirent.c
+++ b/dirent.c
@@ -101,7 +101,7 @@ SYS_FUNC(getdents)

  	if (len) {
  		buf = malloc(len);
-		if (!buf || umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
+		if (!buf || backend.umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
  			tprints(", ");
  			printaddr(tcp->u_arg[1]);
  			tprintf(", %u", count);
diff --git a/dirent64.c b/dirent64.c
index 4172d64..8927b9f 100644
--- a/dirent64.c
+++ b/dirent64.c
@@ -69,7 +69,7 @@ SYS_FUNC(getdents64)

  	if (len) {
  		buf = malloc(len);
-		if (!buf || umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
+		if (!buf || backend.umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
  			tprints(", ");
  			printaddr(tcp->u_arg[1]);
  			tprintf(", %u", count);
diff --git a/dm.c b/dm.c
index 28863a8..1e9bd53 100644
--- a/dm.c
+++ b/dm.c
@@ -455,7 +455,7 @@ dm_known_ioctl(struct tcb *const tcp, const unsigned 
int code,
  		ioc = alloca(sizeof(*ioc));
  	}

-	if ((umoven(tcp, arg, offsetof(struct dm_ioctl, data), ioc) < 0) ||
+	if ((backend.umoven(tcp, arg, offsetof(struct dm_ioctl, data), ioc) < 
0) ||
  	    (ioc->data_size < offsetof(struct dm_ioctl, data_size))) {
  		if (entering(tcp))
  			free(ioc);
diff --git a/execve.c b/execve.c
index 5455b15..f40e142 100644
--- a/execve.c
+++ b/execve.c
@@ -54,7 +54,7 @@ printargv(struct tcb *const tcp, kernel_ulong_t addr)
  			char data[sizeof(kernel_ulong_t)];
  		} cp;

-		if (umoven(tcp, addr, wordsize, cp.data)) {
+		if (backend.umoven(tcp, addr, wordsize, cp.data)) {
  			if (sep == start_sep)
  				printaddr(addr);
  			else
@@ -89,7 +89,7 @@ printargc(struct tcb *const tcp, kernel_ulong_t addr)
  	char *cp = NULL;

  	for (; addr; addr += current_wordsize, ++count) {
-		if (umoven(tcp, addr, current_wordsize, &cp)) {
+		if (backend.umoven(tcp, addr, current_wordsize, &cp)) {
  			if (!count)
  				return;

diff --git a/file_handle.c b/file_handle.c
index 46a61c5..e1b21ac 100644
--- a/file_handle.c
+++ b/file_handle.c
@@ -84,7 +84,7 @@ SYS_FUNC(name_to_handle_at)
  				tprintf(", handle_type=%d", h.handle_type);
  				if (h.handle_bytes > MAX_HANDLE_SZ)
  					h.handle_bytes = MAX_HANDLE_SZ;
-				if (!umoven(tcp, addr + sizeof(h), h.handle_bytes,
+				if (!backend.umoven(tcp, addr + sizeof(h), h.handle_bytes,
  					    f_handle)) {
  					tprints(", f_handle=0x");
  					for (i = 0; i < h.handle_bytes; ++i)
@@ -121,7 +121,7 @@ SYS_FUNC(open_by_handle_at)
  			h.handle_bytes, h.handle_type);
  		if (h.handle_bytes > MAX_HANDLE_SZ)
  			h.handle_bytes = MAX_HANDLE_SZ;
-		if (!umoven(tcp, addr + sizeof(h), h.handle_bytes, &f_handle)) {
+		if (!backend.umoven(tcp, addr + sizeof(h), h.handle_bytes, &f_handle)) {
  			unsigned int i;

  			tprints(", f_handle=0x");
diff --git a/gdbserver/gdbserver.c b/gdbserver/gdbserver.c
new file mode 100644
index 0000000..68b8266
--- /dev/null
+++ b/gdbserver/gdbserver.c
@@ -0,0 +1,1223 @@
+ /* Implementation of strace features over the GDB remote protocol.
+ *
+ * Copyright (c) 2015, 2016 Red Hat Inc.
+ * Copyright (c) 2015 Josh Stone <cuviper at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 
USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "defs.h"
+
+#define _GNU_SOURCE 1
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "gdb_arch_defs.h"
+#include "protocol.h"
+#include "scno.h"
+#include "signals.h"
+
+/* FIXME jistone: export hacks */
+struct tcb *pid2tcb(int pid);
+struct tcb *alloctcb(int pid);
+void droptcb(struct tcb *tcp);
+void newoutf(struct tcb *tcp);
+void print_signalled(struct tcb *tcp, const int pid, int status);
+void print_exited(struct tcb *tcp, const int pid, int status);
+void print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned 
int sig);
+void set_sigaction(int signo, void (*sighandler)(int), struct sigaction 
*oldact);
+
+struct tcb *current_tcp;
+int strace_child;
+int detach_on_execve;
+static volatile int interrupted;
+char *gdbserver = NULL;
+static int general_pid; /* process id that gdbserver is focused on */
+static int general_tid; /* thread id that gdbserver is focused on */
+static struct gdb_stop_reply stop;
+static struct gdb_conn* gdb = NULL;
+static bool gdb_extended = false;
+static bool gdb_multiprocess = false;
+static bool gdb_vcont = false;
+static bool gdb_nonstop = false;
+
+static const char * const gdb_signal_names[] = {
+#define SET(symbol, constant, name, string) \
+	[constant] = name,
+#include "signals.def"
+#undef SET
+};
+
+static int gdb_signal_map[SUPPORTED_PERSONALITIES][GDB_SIGNAL_LAST];
+
+enum gdb_stop {
+	gdb_stop_unknown, /* O or F or anything else */
+	gdb_stop_error, /* E */
+	gdb_stop_signal, /* S or T */
+	gdb_stop_exited, /* W */
+	gdb_stop_terminated, /* X */
+
+	/* specific variants of gdb_stop_signal 05 */
+	gdb_stop_trap, /* missing or unrecognized stop reason */
+	gdb_stop_syscall_entry,
+	gdb_stop_syscall_return,
+};
+
+
+struct gdb_stop_reply {
+	char *reply;
+	size_t size;
+
+	enum gdb_stop type;
+	int code; /* error, signal, exit status, scno */
+	int pid; /* process id, aka kernel tgid */
+	int tid; /* thread id, aka kernel tid */
+};
+
+static int
+gdb_map_signal(unsigned int gdb_sig) {
+	/* strace "SIG_0" vs. gdb "0" -- it's all zero */
+	if (gdb_sig == GDB_SIGNAL_0)
+		return 0;
+
+	/* real-time signals are "special", not even fully contiguous */
+	if (gdb_sig == GDB_SIGNAL_REALTIME_32)
+		return 32;
+	if (GDB_SIGNAL_REALTIME_33 <= gdb_sig &&
+			gdb_sig <= GDB_SIGNAL_REALTIME_63)
+		return gdb_sig - GDB_SIGNAL_REALTIME_33 + 33;
+	if (GDB_SIGNAL_REALTIME_64 <= gdb_sig &&
+			gdb_sig <= GDB_SIGNAL_REALTIME_127)
+		return gdb_sig - GDB_SIGNAL_REALTIME_64 + 64;
+
+	const char *gdb_signame = gdb_signal_names[gdb_sig];
+	if (!gdb_signame)
+		return -1;
+
+	/* many of the other signals line up, but not all. */
+	if (gdb_sig < nsignals && !strcmp(gdb_signame, signame(gdb_sig)))
+		return gdb_sig;
+
+	/* scan the rest for a match */
+	unsigned int sig;
+	for (sig = 1; sig < nsignals; ++sig) {
+		if (sig == gdb_sig)
+			continue;
+		if (!strcmp(gdb_signame, signame(sig)))
+			return sig;
+	}
+
+	return -1;
+}
+
+static void
+gdb_signal_map_init(void)
+{
+	unsigned int pers, old_pers = current_personality;
+
+	for (pers = 0; pers < SUPPORTED_PERSONALITIES; ++pers) {
+		if (current_personality != pers)
+			set_personality(pers);
+
+		unsigned int gdb_sig;
+		int *map = gdb_signal_map[pers];
+		for (gdb_sig = 0; gdb_sig < GDB_SIGNAL_LAST; ++gdb_sig)
+			map[gdb_sig] = gdb_map_signal(gdb_sig);
+	}
+
+	if (old_pers != current_personality)
+		set_personality(old_pers);
+}
+
+static int
+gdb_signal_to_target(struct tcb *tcp, unsigned int signal)
+{
+	unsigned int pers = tcp->currpers;
+	if (pers < SUPPORTED_PERSONALITIES && signal < GDB_SIGNAL_LAST)
+		return gdb_signal_map[pers][signal];
+	return -1;
+}
+
+static void
+gdb_parse_thread(const char *id, int *pid, int *tid)
+{
+	if (*id == 'p') {
+		/* pPID or pPID.TID */
+		++id;
+		*pid = gdb_decode_hex_str(id);
+
+		/* stop messages should always have the TID, */
+		/* but if not, just use the PID. */
+		char *dot = strchr(id, '.');
+		if (!dot) {
+			*tid = *pid;
+		} else {
+			*tid = gdb_decode_hex_str(dot + 1);
+		}
+	} else {
+		/* just TID, assume same PID */
+		*tid = gdb_decode_hex_str(id);
+		*pid = *tid;
+	}
+}
+
+static void
+gdb_recv_signal(struct gdb_stop_reply *stop)
+{
+	char *reply = stop->reply;
+
+	stop->code = gdb_decode_hex_n(&reply[1], 2);
+	stop->type = (stop->code == GDB_SIGNAL_TRAP ||
+			stop->code == GDB_SIGNAL_0)
+		? gdb_stop_trap : gdb_stop_signal;
+
+	/* tokenize the n:r pairs */
+	char *info = strdupa(reply + 3);
+	char *savetok = NULL, *nr;
+	for (nr = strtok_r(info, ";", &savetok); nr;
+			nr = strtok_r(NULL, ";", &savetok)) {
+		char *n = strtok(nr, ":");
+		char *r = strtok(NULL, "");
+		if (!n || !r)
+			continue;
+
+		if (!strcmp(n, "thread")) {
+			if (stop->pid == -1) {
+				gdb_parse_thread(r, &stop->pid, &stop->tid);
+				general_pid = stop->pid;
+				general_tid = stop->tid;
+			}
+			else
+				/* an optional 2nd thread component is the */
+				/* thread that gdbserver is focused on */
+				gdb_parse_thread(r, &general_pid, &general_tid);
+		}
+		else if (!strcmp(n, "syscall_entry")) {
+			if (stop->type == gdb_stop_trap) {
+				stop->type = gdb_stop_syscall_entry;
+				stop->code = gdb_decode_hex_str(r);
+			}
+		}
+		else if (!strcmp(n, "syscall_return")) {
+			if (stop->type == gdb_stop_trap) {
+				stop->type = gdb_stop_syscall_return;
+				stop->code = gdb_decode_hex_str(r);
+			}
+		}
+	}
+
+	/* TODO guess architecture by the size of reported registers? */
+}
+
+static void
+gdb_recv_exit(struct gdb_stop_reply *stop)
+{
+	char *reply = stop->reply;
+
+	stop->type = reply[0] == 'W' ?
+		gdb_stop_exited : gdb_stop_terminated;
+	stop->code = gdb_decode_hex_str(&reply[1]);
+
+	const char *process = strstr(reply, ";process:");
+	if (process) {
+		stop->pid = gdb_decode_hex_str(process + 9);
+
+		/* we don't really know the tid, so just use PID for now */
+		/* XXX should exits enumerate all threads we know of a process? */
+		stop->tid = stop->pid;
+	}
+}
+
+static struct gdb_stop_reply
+gdb_recv_stop(struct gdb_stop_reply *stop_reply)
+{
+	struct gdb_stop_reply stop = {
+		.reply = NULL,
+		.size = 0,
+
+		.type = gdb_stop_unknown,
+		.code = -1,
+		.pid = -1,
+		.tid = -1,
+	};
+	char *reply = NULL;
+	size_t stop_size;
+
+
+	if (stop_reply)
+		/* pop_notification gave us a cached notification */
+	    stop = *stop_reply;
+	else
+	    stop.reply = gdb_recv(gdb, &stop.size, true);
+
+	if (debug_flag)
+	{
+		error_msg("%s\n", stop.reply);
+		fflush(stdout);
+	}
+
+	if (gdb_has_non_stop(gdb) && !stop_reply) {
+	    /* non-stop packet order:
+	       client sends: $vCont;c
+	       server sends: OK
+	       server sends: %Stop:T05syscall_entry (possibly out of order)
+	       client sends: $vStopped
+	       server possibly sends 0 or more: T05syscall_entry
+	       client sends to each: $vStopped
+	       server sends: OK
+	    */
+	     /* Do we have an out of order notification?  (see gdb_recv) */
+	     reply = pop_notification(&stop_size);
+	     if (reply) {
+		  if (debug_flag)
+		       error_msg("popped %s\n", reply);
+		  stop.reply = reply;
+		  reply = gdb_recv(gdb, &stop_size, false); /* vContc OK */
+	     }
+	     else {
+		     while (stop.reply[0] != 'T' && stop.reply[0] != 'W')
+			     stop.reply = gdb_recv(gdb, &stop.size, true);
+	     }
+	}
+	if (gdb_has_non_stop(gdb) && (stop.reply[0] == 'T')) {
+		do {
+			size_t this_size;
+			gdb_send(gdb,"vStopped",8);
+			reply = gdb_recv(gdb, &this_size, true);
+			if (strcmp(reply, "OK") == 0)
+				break;
+			push_notification(reply, this_size);
+		} while (true);
+	}
+
+	/* all good packets are at least 3 bytes */
+	switch (stop.size >= 3 ? stop.reply[0] : 0) {
+	case 'E':
+		stop.type = gdb_stop_error;
+		stop.code = gdb_decode_hex_n(stop.reply + 1, 2);
+		break;
+	case 'S':
+	case 'T':
+		gdb_recv_signal(&stop);
+		break;
+	case 'W':
+	case 'X':
+		gdb_recv_exit(&stop);
+		break;
+	default:
+		stop.type = gdb_stop_unknown;
+		break;
+	}
+
+	return stop;
+}
+
+static bool
+gdb_ok(void)
+{
+	size_t size;
+	char *reply = gdb_recv(gdb, &size, false);
+	bool ok = size == 2 && !strcmp(reply, "OK");
+	free(reply);
+	return ok;
+}
+
+
+bool
+gdb_prog_pid_check (char *exec_name, int nprocs)
+{
+	/* under gdbserver, we can reasonably allow having neither to use 
existing targets.  */
+	if (!exec_name && !nprocs && !gdbserver)
+		return false;
+	return true;
+}
+
+
+bool
+gdb_start_init(void)
+{
+# if ! defined X86_64
+	error_msg("-G is not supported on this target.");
+	return false;		/* Only supported on x86_64 */
+# endif
+
+	gdb_signal_map_init();
+
+	if (gdbserver[0] == '|')
+		gdb = gdb_begin_command(gdbserver + 1);
+	else if (strchr(gdbserver, ':') && !strchr(gdbserver, '/')) {
+		if (strchr(gdbserver, ';')) {
+			const char *stop_option;
+			gdbserver = strtok(gdbserver, ";");
+			stop_option = strtok(NULL, "");
+			stop_option += strspn(" ", stop_option);
+			if (!strcmp(stop_option, "non-stop"))
+				gdb_nonstop = true;
+		}
+		const char *node = strtok(gdbserver, ":");
+		const char *service = strtok(NULL, "");
+		gdb = gdb_begin_tcp(node, service);
+	} else
+		gdb = gdb_begin_path(gdbserver);
+
+	if (!gdb_start_noack(gdb))
+		error_msg("couldn't enable GDB server noack mode");
+
+	char multi_cmd[] = "qSupported:multiprocess+;QThreadEvents+"
+		";fork-events+;vfork-events+;exec-events+";
+
+	sprintf(multi_cmd, "qSupported:multiprocess+;QThreadEvents+;%s%s",
+			followfork ? ";fork-events+;vfork-events+" : "",
+			detach_on_execve ? ";exec-events" : "");
+
+	gdb_send(gdb, multi_cmd, sizeof(multi_cmd) - 1);
+
+	size_t size;
+	bool gdb_fork;
+	char *reply = gdb_recv(gdb, &size, false);
+	gdb_multiprocess = strstr(reply, "multiprocess+") != NULL;
+	if (!gdb_multiprocess)
+		error_msg("couldn't enable GDB server multiprocess mode");
+	if (followfork) {
+		gdb_fork = strstr(reply, "fork-events+") != NULL;
+		if (!gdb_fork)
+			error_msg("couldn't enable GDB server fork events handling");
+		gdb_fork = strstr(reply, "vfork-events+") != NULL;
+		if (!gdb_fork)
+			error_msg("couldn't enable GDB server vfork events handling");
+	}
+	if (!detach_on_execve) {
+		if (!strstr(reply, "exec-events+"))
+			error_msg("couldn't enable GDB server exec events handling");
+	}
+	free(reply);
+
+	static const char extended_cmd[] = "!";
+	gdb_send(gdb, extended_cmd, sizeof(extended_cmd) - 1);
+	gdb_extended = gdb_ok();
+	if (!gdb_extended)
+		error_msg("couldn't enable GDB server extended mode");
+
+	static const char pass_signals[] = 
"QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97;";
+	gdb_send(gdb, pass_signals, sizeof(pass_signals) - 1);
+	if (!gdb_ok())
+		error_msg("couldn't enable GDB server signal passing");
+
+	static const char program_signals[] = 
"QProgramSignals:0;1;3;4;6;7;8;9;a;b;c;d;e;f;10;11;12;13;14;15;16;17;18;19;1a;1b;1c;1d;1e;1f;20;21;22;23;24;25;26;27;28;29;2a;2b;2c;2d;2e;2f;30;31;32;33;34;35;36;37;38;39;3a;3b;3c;3d;3e;3f;40;41;42;43;44;45;46;47;48;49;4a;4b;4c;4d;4e;4f;50;51;52;53;54;55;56;57;58;59;5a;5b;5c;5d;5e;5f;60;61;62;63;64;65;66;67;68;69;6a;6b;6c;6d;6e;6f;70;71;72;73;74;75;76;77;78;79;7a;7b;7c;7d;7e;7f;80;81;82;83;84;85;86;87;88;89;8a;8b;8c;8d;8e;8f;90;91;92;93;94;95;96;97;";
+	gdb_send(gdb, program_signals, sizeof(program_signals) - 1);
+	if (!gdb_ok())
+		error_msg("couldn't enable GDB server signal passing");
+
+	static const char vcont_cmd[] = "vCont?";
+	gdb_send(gdb, vcont_cmd, sizeof(vcont_cmd) - 1);
+	reply = gdb_recv(gdb, &size, false);
+	gdb_vcont = strncmp(reply, "vCont", 5) == 0;
+	if (!gdb_vcont)
+		error_msg("GDB server doesn't support vCont");
+	free(reply);
+	return true;
+}
+
+
+static void
+gdb_init_syscalls(void)
+{
+	static const char syscall_cmd[] = "QCatchSyscalls:1";
+	const char *syscall_set = "";
+	bool want_syscall_set = false;
+	unsigned sci;
+
+	/* Only send syscall list if a filtered list was given with -e */
+	for (sci = 0; sci < nsyscalls; sci++)
+		if (! (qual_flags(sci) & QUAL_TRACE)) {
+			want_syscall_set = true;
+			break;
+		}
+
+	for (sci = 0; want_syscall_set && sci < nsyscalls; sci++)
+		if (qual_flags(sci) & QUAL_TRACE)
+			if (asprintf ((char**)&syscall_set, "%s;%x", syscall_set, sci) < 0)
+				error_msg("couldn't enable GDB server syscall catching");
+
+	if (want_syscall_set)
+		asprintf ((char**)&syscall_set, "%s%s", syscall_cmd, syscall_set);
+	else
+		syscall_set = syscall_cmd;
+	gdb_send(gdb, syscall_set, strlen(syscall_set));
+	if (!gdb_ok())
+		error_msg("couldn't enable GDB server syscall catching");
+}
+
+static struct tcb*
+gdb_find_thread(int tid, bool current)
+{
+	if (tid < 0)
+		return NULL;
+
+	/* Look up 'tid' in our table. */
+	struct tcb *tcp = pid2tcb(tid);
+	if (!tcp) {
+		tcp = alloctcb(tid);
+		tcp->flags |= TCB_GDB_CONT_PID_TID;
+		tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+		newoutf(tcp);
+
+		if (!current) {
+			char cmd[] = "Hgxxxxxxxx";
+			sprintf(cmd, "Hg%x", tid);
+			gdb_send(gdb, cmd, strlen(cmd));
+			current = gdb_ok();
+			if (!current)
+				error_msg("couldn't set GDB server to thread %d", tid);
+		}
+	}
+	return tcp;
+}
+
+static void
+gdb_enumerate_threads(void)
+{
+	/* qfThreadInfo [qsThreadInfo]...
+	 * -> m thread
+	 * -> m thread,thread
+	 * -> l (finished) */
+
+	static const char qfcmd[] = "qfThreadInfo";
+
+	gdb_send(gdb, qfcmd, sizeof(qfcmd) - 1);
+
+	size_t size;
+	char *reply = gdb_recv(gdb, &size, false);
+	while (reply[0] == 'm') {
+		char *thread;
+		for (thread = strtok(reply + 1, ","); thread;
+				thread = strtok(NULL, "")) {
+			int pid, tid;
+			gdb_parse_thread(thread, &pid, &tid);
+
+			struct tcb *tcp = gdb_find_thread(tid, false);
+			if (tcp && !current_tcp)
+				current_tcp = tcp;
+		}
+
+		free(reply);
+
+		static const char qscmd[] = "qsThreadInfo";
+		gdb_send(gdb, qscmd, sizeof(qscmd) - 1);
+		reply = gdb_recv(gdb, &size, false);
+	}
+
+	free(reply);
+}
+
+static void
+interrupt(int sig)
+{
+	interrupted = sig;
+}
+
+void
+gdb_end_init(void)
+{
+	/* TODO interface with -I? */
+	set_sigaction(SIGHUP, interrupt, NULL);
+	set_sigaction(SIGINT, interrupt, NULL);
+	set_sigaction(SIGQUIT, interrupt, NULL);
+	set_sigaction(SIGPIPE, interrupt, NULL);
+	set_sigaction(SIGTERM, interrupt, NULL);
+
+	/* We enumerate all attached threads to be sure, especially
+	 * since we get all threads on vAttach, not just the one
+	 * pid. */
+	gdb_enumerate_threads();
+
+	/* Everything was stopped from startup_child/startup_attach,
+	 * now continue them all so the next reply will be a stop
+	 * packet */
+	if (gdb_vcont) {
+		static const char cmd[] = "vCont;c";
+		gdb_send(gdb, cmd, sizeof(cmd) - 1);
+	} else {
+		static const char cmd[] = "c";
+		gdb_send(gdb, cmd, sizeof(cmd) - 1);
+	}
+}
+
+void
+gdb_cleanup(void)
+{
+	if (gdb)
+		gdb_end(gdb);
+	gdb = NULL;
+}
+
+void
+gdb_startup_child(char **argv)
+{
+	if (!gdb)
+		error_msg_and_die("GDB server not connected!");
+
+	if (!gdb_extended)
+		error_msg_and_die("GDB server doesn't support starting processes!");
+
+	/* Without knowing gdb's current tid, vCont of the correct thread for
+	   the multithreaded nonstop case is difficult, so default to all-stop */
+
+	size_t i;
+	size_t size = 4; /*vRun */
+	for (i = 0; argv[i]; ++i) {
+		size += 1 + 2 * strlen(argv[i]); /*;hexified-argument */
+	}
+
+	if (gdb_nonstop) {
+		static const char nonstop_cmd[] = "QNonStop:1";
+		gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+		if (!gdb_ok())
+			gdb_nonstop = false;
+	}
+
+	{
+		char cmd[size];
+		char *cmd_ptr = cmd;
+		memcpy(cmd_ptr, "vRun", 4);
+		cmd_ptr += 4;
+		for (i = 0; argv[i]; ++i) {
+			*cmd_ptr++ = ';';
+			const char *arg = argv[i];
+			while (*arg) {
+				gdb_encode_hex(*arg++, cmd_ptr);
+				cmd_ptr += 2;
+			}
+		}
+
+		gdb_send(gdb, cmd, size);
+	}
+
+	struct gdb_stop_reply stop = gdb_recv_stop(NULL);
+	if (stop.size == 0)
+		error_msg_and_die("GDB server doesn't support vRun!");
+	switch (stop.type) {
+	case gdb_stop_error:
+		error_msg_and_die("GDB server failed vRun with %.*s",
+				(int)stop.size, stop.reply);
+	case gdb_stop_trap:
+		break;
+	default:
+		error_msg_and_die("GDB server expected vRun trap, got: %.*s",
+				(int)stop.size, stop.reply);
+	}
+
+	pid_t tid = stop.tid;
+	free(stop.reply);
+
+	strace_child = tid;
+
+	struct tcb *tcp = alloctcb(tid);
+	tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+	newoutf(tcp);
+	gdb_init_syscalls();
+
+	if (gdb_nonstop)
+		gdb_set_non_stop(gdb, true);
+	else
+		gdb_set_non_stop(gdb, false);
+	/* TODO normal strace attaches right before exec, so the first
+	 * syscall seen is the execve with all its arguments.  Need to
+	 * emulate that here? */
+	tcp->flags &= ~TCB_HIDE_LOG;
+}
+
+void
+gdb_attach_tcb(struct tcb *tcp)
+{
+	if (!gdb)
+		error_msg_and_die("GDB server not connected!");
+
+	if (!gdb_extended)
+		error_msg_and_die("GDB server doesn't support attaching "
+				"processes");
+
+	struct gdb_stop_reply stop;
+	static const char nonstop_cmd[] = "QNonStop:1";
+	char vattach_cmd[] = "vAttach;XXXXXXXX";
+
+	gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+	if (gdb_ok())
+	       gdb_set_non_stop(gdb, true);
+
+	sprintf(vattach_cmd, "vAttach;%x", tcp->pid);
+	gdb_send(gdb, vattach_cmd, strlen(vattach_cmd));
+
+	do {
+		/*
+		  non-stop packet order:
+		  client sends: vCont;t
+		  server sends: OK
+		  server sends: Stop:T05swbreak:;
+		  client sends: vStopped
+		  [ server sends: T05swbreak:;
+		    client sends: vStopped ]
+		  server sends: OK
+		*/
+		char h_cmd[] = "Hgxxxxxxxx";
+		char vcont_cmd[] = "vCont;t:pXXXXXXXX";
+		if (!gdb_ok()) {
+		     stop.type = gdb_stop_unknown;
+		     break;
+		}
+		sprintf(h_cmd, "Hg%x.-1", tcp->pid);
+		gdb_send(gdb, h_cmd, strlen(h_cmd));
+		if (!gdb_ok()) {
+		     stop.type = gdb_stop_unknown;
+		     break;
+		}
+		sprintf(vcont_cmd, "vCont;t:p%x.-1", tcp->pid);
+		gdb_send(gdb, vcont_cmd, strlen(vcont_cmd));
+		stop = gdb_recv_stop(NULL);
+	} while (0);
+
+	if (stop.type == gdb_stop_unknown) {
+		static const char nonstop_cmd[] = "QNonStop:0";
+		gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+		if (gdb_ok())
+			gdb_set_non_stop(gdb, false);
+		else
+			error_msg_and_die("Cannot connect to process %d: "
+					"GDB server doesn't support vAttach!",
+					tcp->pid);
+		gdb_send(gdb, vattach_cmd, strlen(vattach_cmd));
+		stop = gdb_recv_stop(NULL);
+		if (stop.size == 0)
+			error_msg_and_die("Cannot connect to process %d: "
+					"GDB server doesn't support vAttach!",
+					tcp->pid);
+		switch (stop.type) {
+		case gdb_stop_error:
+			error_msg_and_die("Cannot connect to process %d: "
+					"GDB server failed vAttach with %.*s",
+					tcp->pid, (int)stop.size, stop.reply);
+		case gdb_stop_trap:
+			break;
+		case gdb_stop_signal:
+			if (stop.code == 0)
+				break;
+			/* fallthrough */
+		default:
+			error_msg_and_die("Cannot connect to process %d: "
+					"GDB server expected vAttach trap, got: %.*s",
+					  tcp->pid, (int)stop.size, stop.reply);
+	    }
+	  }
+
+	pid_t tid = stop.tid;
+	free(stop.reply);
+
+	if (tid != tcp->pid) {
+		droptcb(tcp);
+		tcp = alloctcb(tid);
+	}
+	tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+	newoutf(tcp);
+	gdb_init_syscalls();
+
+	if (!qflag)
+		fprintf(stderr, "Process %u attached in %s mode\n", tcp->pid,
+			gdb_has_non_stop(gdb) ? "non-stop" : "all-stop");
+}
+
+void
+gdb_detach(struct tcb *tcp)
+{
+	static bool already_detaching = false;
+
+	if (! already_detaching)
+		already_detaching = true;
+	if (already_detaching || gdb == NULL)
+		return;
+	if (gdb_multiprocess) {
+		char cmd[] = "D;XXXXXXXX";
+		sprintf(cmd, "D;%x", tcp->pid);
+		gdb_send(gdb, cmd, strlen(cmd));
+	} else {
+		static const char cmd[] = "D";
+		gdb_send(gdb, cmd, sizeof(cmd) - 1);
+	}
+
+	if (!gdb_ok()) {
+		/* is it still alive? */
+		char cmd[] = "T;XXXXXXXX";
+		sprintf(cmd, "T;%x", tcp->pid);
+		gdb_send(gdb, cmd, strlen(cmd));
+		if (gdb_ok())
+			error_msg("GDB server failed to detach %d", tcp->pid);
+		/* otherwise it's dead, or already detached, fine. */
+	}
+
+	if (!qflag && (tcp->flags & TCB_ATTACHED))
+		error_msg("Process %u detached", tcp->pid);
+
+	droptcb(tcp);
+}
+
+
+enum trace_event
+gdb_next_event(int *pstatus, siginfo_t *si)
+{
+	int gdb_sig = 0;
+	pid_t tid;
+	struct tcb *tcp = NULL;
+
+	if (interrupted)
+		return TE_BREAK;
+
+	stop.reply = pop_notification(&stop.size);
+	if (stop.reply)	    /* cached out of order notification? */
+		stop = gdb_recv_stop(&stop);
+	else
+		stop = gdb_recv_stop(NULL);
+	if (stop.size == 0)
+		error_msg_and_die("GDB server gave an empty stop reply!?");
+
+	switch (stop.type) {
+	case gdb_stop_unknown:
+		error_msg_and_die("GDB server stop reply unknown: %.*s",
+				(int)stop.size, stop.reply);
+		break;
+	case gdb_stop_error:
+		/* vCont error -> no more processes */
+		free(stop.reply);
+		return TE_BREAK;
+	default:
+		break;
+	}
+
+
+	tid = -1;
+	tcp = NULL;
+
+	if (gdb_multiprocess) {
+		tid = stop.tid;
+		tcp = gdb_find_thread(tid, true);
+		/* Set current output file */
+		current_tcp = tcp;
+	} else if (current_tcp) {
+		tcp = current_tcp;
+		tid = tcp->pid;
+	}
+	if (tid < 0 || tcp == NULL)
+		error_msg_and_die("couldn't read tid from stop reply: %.*s",
+				(int)stop.size, stop.reply);
+
+	switch (stop.type) {
+	case gdb_stop_exited:
+		*pstatus = W_EXITCODE(stop.code, 0);
+		return TE_EXITED;
+
+	case gdb_stop_terminated:
+		*pstatus = W_EXITCODE(0, gdb_signal_to_target(tcp, stop.code));
+		return TE_SIGNALLED;
+
+	case gdb_stop_unknown:	/* already handled above */
+	case gdb_stop_error:	/* already handled above */
+	case gdb_stop_trap:	/* misc trap */
+		break;
+	case gdb_stop_syscall_entry:
+		/* If we thought we were already in a syscall --
+		 * missed a return? -- skipping this report doesn't do
+		 * much good.  Might as well force it to be a new
+		 * entry regardless to sync up. */
+		tcp->flags &= ~TCB_INSYSCALL;
+		tcp->scno = stop.code;
+		gdb_sig = stop.code;
+		*pstatus = gdb_signal_to_target(tcp, gdb_sig);
+		if (stop.code == __NR_exit_group)
+			return TE_GROUP_STOP;
+		else
+			return TE_SYSCALL_STOP;
+
+	case gdb_stop_syscall_return:
+		/* If we missed the entry, recording a return will
+		 * only confuse things, so let's just report the good
+		 * ones. */
+		if (exiting(tcp)) {
+			tcp->scno = stop.code;
+			gdb_sig = stop.code;
+			*pstatus = gdb_signal_to_target(tcp, gdb_sig);
+			return TE_SYSCALL_STOP;
+		}
+		break;
+
+	case gdb_stop_signal:
+	{
+		size_t siginfo_size;
+		char *siginfo_reply =
+				gdb_xfer_read(gdb, "siginfo", "", &siginfo_size);
+		if (siginfo_reply && siginfo_size == sizeof(siginfo_t))
+			si = (siginfo_t *) siginfo_reply;
+
+		/* XXX gdbserver returns "native" siginfo of 32/64-bit
+		 * target but strace expects its own format as
+		 * PTRACE_GETSIGINFO would have given it.  (i.e. need
+		 * to reverse siginfo_fixup)
+		 * ((i.e. siginfo_from_compat_siginfo)) */
+
+		gdb_sig = stop.code;
+		*pstatus = gdb_signal_to_target(tcp, gdb_sig);
+		free(siginfo_reply);
+		return TE_SIGNAL_DELIVERY_STOP;
+	}
+
+	default:
+		/* TODO Do we need to handle gdb_multiprocess here? */
+		break;
+	}
+
+	return TE_RESTART;
+}
+
+/* Returns true iff the main trace loop has to continue.  The gdb
+ * connection should be ready for a stop reply on entry,p and we'll
+ * leave it the same way if we return true. */
+
+bool
+gdb_dispatch_event(enum trace_event ret, int *pstatus, void *si_p)
+{
+	siginfo_t *si = (siginfo_t*)si_p;
+	int gdb_sig = 0;
+	struct tcb *tcp = current_tcp;
+	pid_t tid;
+	unsigned int sig = 0;
+
+
+	/* Exit if the process has gone away */
+	if (tcp == 0)
+		return false;
+	tid = tcp->pid;
+	if (! (tcp->flags & TCB_GDB_CONT_PID_TID)) {
+		char cmd[] = "Hgxxxxxxxx";
+		sprintf(cmd, "Hg%x.%x", general_pid, general_tid);
+		if (debug_flag)
+			error_msg("%s %s\n", __FUNCTION__, cmd);
+	}
+	/* TODO need code equivalent to PTRACE_EVENT_EXEC? */
+
+	/* Is this the very first time we see this tracee stopped? */
+	if (tcp->flags & TCB_STARTUP) {
+		tcp->flags &= ~TCB_STARTUP;
+		if (get_scno(tcp) == 1)
+			tcp->s_prev_ent = tcp->s_ent;
+	}
+
+	/* TODO cflag means we need to update tcp->dtime/stime usually
+	 * through wait rusage, but how can we do it? */
+
+	free (stop.reply);
+
+	switch (ret) {
+	case TE_BREAK:
+		return false;
+
+	case TE_RESTART:
+		break;
+
+	case TE_SYSCALL_STOP:
+		trace_syscall(tcp, &sig);
+		break;
+
+	case TE_SIGNAL_DELIVERY_STOP:
+		sig = *pstatus;
+		gdb_sig = stop.code;
+		print_stopped(tcp, si, *pstatus);
+		break;
+
+	case TE_SIGNALLED:
+		print_signalled(tcp, tid, *pstatus);
+		droptcb(tcp);
+		return false;
+
+	case TE_EXITED:
+		print_exited(tcp, tid, *pstatus);
+		droptcb(tcp);
+		/* Don't continue if the process exited */
+		if (!gdb_multiprocess || gdb_has_non_stop(gdb))
+			return false;
+		break;
+
+	case TE_STOP_BEFORE_EXECVE:
+	case TE_STOP_BEFORE_EXIT:
+		/* TODO handle this? */
+		return false;
+
+	case TE_GROUP_STOP:
+		trace_syscall(tcp, &sig);
+		sig = *pstatus;
+		return false;
+
+	case TE_NEXT:
+		break;
+	}
+
+	/* We handled quick cases, we are permitted to interrupt now. */
+	if (interrupted)
+		return false;
+
+	/* Don't continue gdbserver until we handle any queued notifications */
+	if (have_notification())
+		return true;
+
+	if (gdb_sig) {
+		if (gdb_vcont) {
+			/* send the signal to this target and continue everyone else */
+			char cmd[] = "vCont;Cxx:xxxxxxxx;c";
+			sprintf(cmd, "vCont;C%02x:%x;c", gdb_sig, tid);
+			gdb_send(gdb, cmd, strlen(cmd));
+		} else {
+			/* just send the signal */
+			char cmd[] = "Cxx";
+			sprintf(cmd, "C%02x", gdb_sig);
+			gdb_send(gdb, cmd, strlen(cmd));
+		}
+	} else {
+		if (gdb_vcont) {
+			/* For non-stop use $vCont;c:pid.tid where
+			 * pid.tid is the thread gdbserver is focused
+			 * on */
+			char cmd[] = "vCont;c:xxxxxxxx.xxxxxxxx";
+
+			struct tcb *general_tcp = gdb_find_thread(general_tid, true);
+			if (gdb_has_non_stop(gdb) && general_pid != general_tid
+					&& general_tcp->flags & TCB_GDB_CONT_PID_TID)
+				sprintf(cmd, "vCont;c:p%x.%x", general_pid, general_tid);
+			else
+				sprintf(cmd, "vCont;c");
+			gdb_send(gdb, cmd, sizeof(cmd) - 1);
+		} else {
+			static const char cmd[] = "c";
+			gdb_send(gdb, cmd, sizeof(cmd) - 1);
+		}
+	}
+
+	return true;
+}
+
+
+char *
+gdb_get_all_regs(pid_t tid, size_t *size)
+{
+	if (!gdb)
+		return NULL;
+
+	/* NB: this assumes gdbserver's current thread is also tid.  If that
+	 * may not be the case, we should send "HgTID" first, and restore.  */
+	gdb_send(gdb, "g", 1);
+	return gdb_recv(gdb, size, false);
+}
+
+
+#ifdef GDBSERVER_ARCH_HAS_GET_REGS
+# include "gdb_get_regs.c"
+#else
+long gdb_get_regs(pid_t pid, void *io) { return -1; }
+#endif
+
+
+#ifdef GDBSERVER_ARCH_HAS_SET_REGS
+# include "gdb_set_regs.c"
+#else
+long gdb_set_regs(pid_t pid, void *io) { return -1; }
+#endif
+
+
+int
+gdb_get_scno(struct tcb *tcp)
+{
+	return 1;
+}
+
+int
+gdb_read_mem(pid_t tid, long addr, unsigned int len, bool check_nil, 
char *out)
+{
+	if (!gdb) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	/* NB: this assumes gdbserver's current thread is also tid.  If that
+	 * may not be the case, we should send "HgTID" first, and restore.  */
+	while (len) {
+		char cmd[] = "mxxxxxxxxxxxxxxxx,xxxx";
+		unsigned int chunk_len = len < 0x1000 ? len : 0x1000;
+		sprintf(cmd, "m%lx,%x", addr, chunk_len);
+		gdb_send(gdb, cmd, strlen(cmd));
+
+		size_t size;
+		char *reply = gdb_recv(gdb, &size, false);
+		if (size < 2 || reply[0] == 'E' || size > len * 2
+		    || gdb_decode_hex_buf(reply, size, out) < 0) {
+			free(reply);
+			errno = EINVAL;
+			return -1;
+		}
+
+		chunk_len = size / 2;
+		if (check_nil && strnlen(out, chunk_len) < chunk_len)
+		{
+			free(reply);
+			return 1;
+		}
+
+		addr += chunk_len;
+		out += chunk_len;
+		len -= chunk_len;
+		free(reply);
+	}
+
+	return 0;
+}
+
+
+int
+gdb_write_mem(pid_t tid, long addr, unsigned int len, char *buffer)
+{
+	unsigned int i, j;
+	const char packet_template[] = "Xxxxxxxxxxxxxxxxx,xxxx:";
+	char cmd[strlen(packet_template) + len];
+
+	if (!gdb) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	/* NB: this assumes gdbserver's current thread is also tid.  If that
+	 * may not be the case, we should send "HgTID" first, and restore.  */
+	sprintf(cmd, "X%lx,%x:", addr, len);
+	j = strlen(cmd);
+	for (i = 0; i < len; i++)
+		cmd[j++] = buffer[i];
+	cmd[j] = '\0';
+	gdb_send(gdb, cmd, strlen(cmd));
+	if (!gdb_ok())
+		error_msg("Failed to poke data to GDB server");
+
+	return 0;
+}
+
+
+int
+gdb_umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len,
+		void *const our_addr)
+{
+	return gdb_read_mem(tcp->pid, addr, len, false, our_addr);
+}
+
+
+int
+gdb_umovestr(struct tcb *const tcp, kernel_ulong_t addr, unsigned int 
len, char *laddr)
+{
+	return gdb_read_mem(tcp->pid, addr, len, true, laddr);
+}
+
+int
+gdb_upeek(int pid, unsigned long off, kernel_ulong_t *res)
+{
+	return gdb_read_mem(pid, off, current_wordsize, false, (char*)res);
+}
+
+
+int
+gdb_upoke(int pid, unsigned long off, kernel_ulong_t res)
+{
+	kernel_ulong_t buffer = res;
+	return gdb_write_mem(pid, off, current_wordsize, (char*)&buffer);
+}
+
+
+int
+gdb_getfdpath(struct tcb *tcp, int fd, char *buf, unsigned bufsize)
+{
+	if (!gdb || fd < 0)
+		return -1;
+
+	/*
+	 * As long as we assume a Linux target, we can peek at their procfs
+	 * just like normal getfdpath does.  Maybe that won't always be true.
+	 */
+	char linkpath[sizeof("/proc/%u/fd/%u") + 2 * sizeof(int)*3];
+	sprintf(linkpath, "/proc/%u/fd/%u", tcp->pid, fd);
+	return gdb_readlink(gdb, linkpath, buf, bufsize);
+}
+
+
+bool
+gdb_verify_args(const char *username, bool daemon, unsigned int 
*follow_fork)
+{
+	if (username) {
+		error_msg_and_die("-u and -G are mutually exclusive");
+	}
+
+	if (daemon) {
+		error_msg_and_die("-D and -G are mutually exclusive");
+	}
+
+	if (!*follow_fork) {
+		error_msg("-G is always multithreaded, implies -f");
+		*follow_fork = 1;
+	}
+
+#ifdef USE_LIBUNWIND
+	if (stack_trace_enabled)
+		error_msg_and_die("Simultaneous usage of gdbserver backend (-G) and "
+				"stack tracing (-k) is not supported");
+#endif
+
+	return true;
+}
+
+
+bool
+gdb_handle_arg(char arg, char *optarg)
+{
+	if (arg != 'G')
+		return false;
+
+	gdbserver = optarg;
+	backend.attach_tcb = gdb_attach_tcb;
+	backend.cleanup = gdb_cleanup;
+	backend.detach = gdb_detach;
+	backend.dispatch_event = gdb_dispatch_event;
+	backend.end_init = gdb_end_init;
+	backend.get_regs = gdb_get_regs;
+	backend.get_scno = gdb_get_scno;
+	backend.getfdpath = gdb_getfdpath;
+	backend.next_event = gdb_next_event;
+	backend.prog_pid_check = gdb_prog_pid_check;
+	backend.start_init = gdb_start_init;
+	backend.startup_child = gdb_startup_child;
+	backend.umoven = gdb_umoven;
+	backend.umovestr = gdb_umovestr;
+	backend.upeek_ = gdb_upeek;
+	backend.upoke_ = gdb_upoke;
+	backend.verify_args = gdb_verify_args;
+	return true;
+}
diff --git a/gdbserver/protocol.c b/gdbserver/protocol.c
new file mode 100644
index 0000000..1ef5660
--- /dev/null
+++ b/gdbserver/protocol.c
@@ -0,0 +1,734 @@
+/* Simple implementation of a GDB remote protocol client.
+ *
+ * Copyright (c) 2015 Red Hat Inc.
+ * Copyright (c) 2015 Josh Stone <cuviper at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 
USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE 1
+#include <err.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+
+#include "protocol.h"
+#include "defs.h"
+
+struct gdb_conn {
+	FILE *in;
+	FILE *out;
+	bool ack;
+	bool non_stop;
+};
+
+/* non-stop notifications (see gdb_recv_stop) */
+struct notifications_s {
+    int size;
+    int start;
+    int count;
+    char **packet;
+} notifications;
+
+
+void
+gdb_encode_hex(uint8_t byte, char* out) {
+	static const char value_hex[16] = {
+		'0', '1', '2', '3', '4', '5', '6', '7',
+		'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+	};
+	*out++ = value_hex[byte >> 4];
+	*out++ = value_hex[byte & 0xf];
+}
+
+char *
+gdb_encode_hex_string(const char *str)
+{
+	char *out = malloc(2 * strlen(str) + 1);
+	if (out) {
+		char *out_ptr = out;
+		while (*str) {
+			gdb_encode_hex(*str++, out_ptr);
+			out_ptr += 2;
+		}
+		*out_ptr = '\0';
+	}
+	return out;
+}
+
+
+static inline uint8_t
+hex_nibble(uint8_t hex)
+{
+	static const uint8_t hex_value[256] = {
+		[0 ... '0'-1] = UINT8_MAX,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+		['9'+1 ... 'A'-1] = UINT8_MAX,
+		10, 11, 12, 13, 14, 15,
+		['F'+1 ... 'a'-1] = UINT8_MAX,
+		10, 11, 12, 13, 14, 15,
+		['f'+1 ... 255] = UINT8_MAX,
+	};
+	return hex_value[hex];
+}
+
+uint16_t gdb_decode_hex(char msb, char lsb)
+{
+	uint8_t high_nibble = hex_nibble(msb);
+	uint8_t low_nibble = hex_nibble(lsb);
+	if (high_nibble >= 16 || low_nibble >= 16)
+		return UINT16_MAX;
+	return 16 * hex_nibble(msb) + hex_nibble(lsb);
+}
+
+uint64_t gdb_decode_hex_n(const char *bytes, size_t n)
+{
+	uint64_t value = 0;
+	while (n--) {
+		uint8_t nibble = hex_nibble(*bytes++);
+		if (nibble >= 16)
+			break;
+		value = 16 * value + nibble;
+	}
+	return value;
+}
+
+uint64_t gdb_decode_hex_str(const char *bytes)
+{
+	uint64_t value = 0;
+	while (*bytes) {
+		uint8_t nibble = hex_nibble(*bytes++);
+		if (nibble >= 16)
+			break;
+		value = 16 * value + nibble;
+	}
+	return value;
+}
+
+int64_t gdb_decode_signed_hex_str(const char *bytes)
+{
+	return (*bytes == '-')
+		? -(int64_t)gdb_decode_hex_str(bytes + 1)
+		: (int64_t)gdb_decode_hex_str(bytes);
+}
+
+int gdb_decode_hex_buf(const char *bytes, size_t n, char *out)
+{
+	if (n & 1)
+		return -1;
+
+	while (n > 1) {
+		uint16_t byte = gdb_decode_hex(bytes[0], bytes[1]);
+		if (byte > UINT8_MAX)
+			return -1;
+
+		*out++ = byte;
+		bytes += 2;
+		n -= 2;
+	}
+	return 0;
+}
+
+
+static struct gdb_conn *
+gdb_begin(int fd)
+{
+	struct gdb_conn *conn = xcalloc(1, sizeof(struct gdb_conn));
+
+	conn->ack = true;
+
+	/* duplicate the handle to separate read/write state */
+	int fd2 = dup(fd);
+	if (fd2 < 0)
+		perror_msg_and_die("dup");
+
+	/* open a FILE* for reading */
+	conn->in = fdopen(fd, "rb");
+	if (conn->in == NULL)
+		perror_msg_and_die("fdopen in");
+
+	/* open a FILE* for writing */
+	conn->out = fdopen(fd2, "wb");
+	if (conn->out == NULL)
+		perror_msg_and_die("fdopen out");
+
+	/* reset line state by acking any earlier input */
+	fputc('+', conn->out);
+	fflush(conn->out);
+
+	return conn;
+}
+
+#define ZERO_OR_DIE(f, ...) \
+	do { \
+		int ret = f(__VA_ARGS__); \
+		if (ret) \
+			perror_msg_and_die(#f); \
+	} while (0)
+
+struct gdb_conn *
+gdb_begin_command(const char *command)
+{
+	int fds[2];
+	pid_t pid;
+	posix_spawn_file_actions_t file_actions;
+	const char* sh = "/bin/sh";
+	const char *const const_argv[] = {"sh", "-c", command, NULL};
+	char *const *argv = (char *const *) const_argv;
+
+	/* Create a bidirectional "pipe", [0] for us and [1] for the command 
stdio. */
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0)
+		perror_msg_and_die("socketpair");
+
+	ZERO_OR_DIE(posix_spawn_file_actions_init, &file_actions);
+
+	/* Close our end in the child. */
+	ZERO_OR_DIE(posix_spawn_file_actions_addclose, &file_actions, fds[0]);
+
+	/* Copy the child's end to its stdout and stdin. */
+	if (fds[1] != STDOUT_FILENO) {
+		ZERO_OR_DIE(posix_spawn_file_actions_adddup2, &file_actions,
+			    fds[1], STDOUT_FILENO);
+		ZERO_OR_DIE(posix_spawn_file_actions_addclose, &file_actions,
+			    fds[1]);
+	}
+	ZERO_OR_DIE(posix_spawn_file_actions_adddup2, &file_actions,
+		    STDOUT_FILENO, STDIN_FILENO);
+
+	/* Spawn the actual command. */
+	ZERO_OR_DIE(posix_spawn, &pid, sh, &file_actions, NULL, argv, environ);
+
+	/* Cleanup. */
+	ZERO_OR_DIE(posix_spawn_file_actions_destroy, &file_actions);
+
+	close(fds[1]);
+
+	/* Avoid SIGPIPE when the command quits. */
+	signal(SIGPIPE, SIG_IGN);
+
+	/* initialize the rest of gdb on this handle */
+	return gdb_begin(fds[0]);
+}
+
+struct gdb_conn *
+gdb_begin_tcp(const char *node, const char *service)
+{
+	/* NB: gdb doesn't support IPv6 - should we? */
+	const struct addrinfo hints = {
+		.ai_family = AF_UNSPEC,
+		.ai_socktype = SOCK_STREAM,
+	};
+
+	struct addrinfo *result = NULL;
+	int s = getaddrinfo(node, service, &hints, &result);
+	if (s)
+		error_msg_and_die("getaddrinfo: %s", gai_strerror(s));
+
+	int fd = -1;
+	struct addrinfo *ai;
+	for (ai = result; ai; ai = ai->ai_next) {
+		/* open the socket and start the tcp connection */
+		fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+		if (fd < 0)
+			continue;
+
+		if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0)
+			break;
+
+		close(fd);
+		fd = -1;
+	}
+
+	freeaddrinfo(result);
+	if (fd < 0)
+		error_msg_and_die("Cannot connect to GDB server");
+
+	/* initialize the rest of gdb on this handle */
+	return gdb_begin(fd);
+}
+
+struct gdb_conn *
+gdb_begin_path(const char *path)
+{
+	int fd = open(path, O_RDWR);
+	if (fd < 0)
+		perror_msg_and_die("open");
+
+	/* initialize the rest of gdb on this handle */
+	return gdb_begin(fd);
+}
+
+
+void
+gdb_end(struct gdb_conn *conn)
+{
+	fclose(conn->in);
+	fclose(conn->out);
+	free(conn);
+}
+
+
+static void
+send_packet(FILE *out, const char *command, size_t size)
+{
+	/* compute the checksum -- simple mod256 addition */
+	size_t i;
+	uint8_t sum = 0;
+	for (i = 0; i < size; ++i)
+		sum += (uint8_t)command[i];
+
+	/* NB: seems neither escaping nor RLE is generally expected by
+	 * gdbserver.  e.g. giving "invalid hex digit" on an RLE'd
+	 * address.  So just write raw here, and maybe let higher levels
+	 * escape/RLE. */
+
+	if (debug_flag) {
+		error_msg("\tSending packet: $%s", command);
+		fflush(stdout);
+	}
+	fputc('$', out); /* packet start */
+	fwrite(command, 1, size, out); /* payload */
+	fprintf(out, "#%02x", sum); /* packet end, checksum */
+	fflush(out);
+
+	if (ferror(out))
+		error_msg("Error sending message \"$%s\" to GDB server",
+			  command);
+	else if (feof(out))
+		error_msg_and_die("Connection to GDB server has been closed");
+}
+
+void
+gdb_send(struct gdb_conn *conn, const char *command, size_t size)
+{
+	bool acked = false;
+	do {
+		send_packet(conn->out, command, size);
+
+		if (!conn->ack)
+			break;
+
+		/* look for '+' ACK or '-' NACK/resend */
+		acked = fgetc_unlocked(conn->in) == '+';
+	} while (!acked);
+}
+
+
+/* push_notification/pop_notification caches notifications which
+ *   arrive via the following dialogue:
+ *   [ server: %Stop:T05syscall_entry...
+ *     client: $vStopped ]*
+ *     server: OK
+ */
+
+void
+push_notification(char *packet, size_t packet_size)
+{
+	int idx;
+
+	if (strncmp(packet+3, "syscall", 7) != 0)
+		return;
+
+	if (notifications.size == 0) {
+		notifications.size = 32;
+		notifications.start = 0;
+		notifications.count = 0;
+		notifications.packet = xmalloc(sizeof(notifications.packet) * 
notifications.size);
+	}
+
+	if (notifications.count == notifications.size) {
+		error_msg("Buffer overflow");
+	} else {
+		idx = notifications.start + notifications.count++;
+		if (idx >= notifications.size) {
+			idx = 0;
+		}
+		notifications.packet[idx] = packet;
+	}
+
+	if (debug_flag)
+		printf /*error_msg*/("Pushed %s (%d items in queue)\n", packet, 
notifications.count);
+}
+
+
+char*
+pop_notification(size_t *size)
+{
+	char *packet;
+	if (notifications.count == 0) {
+		return (char*)NULL;
+	} else {
+		packet = notifications.packet[notifications.start];
+		notifications.start++;
+		notifications.count--;
+		if (notifications.start == notifications.size)
+			notifications.start = 0;
+	}
+
+	if (debug_flag) {
+		error_msg("Popped %s (%d items in queue)", packet, notifications.count);
+	}
+
+	return packet;
+}
+
+
+bool
+have_notification(void)
+{
+	return (notifications.count == 0 ? false : true);
+}
+
+
+void
+dump_notifications(char *packet, int pid, int tid)
+{
+	int idx;
+
+	for (idx = notifications.start; idx < notifications.count; idx++) {
+		if (notifications.packet[idx] != NULL)
+			printf ("Notify Dump: %s\n", notifications.packet[idx]);
+	}
+}
+
+
+static char *
+recv_packet(FILE *in, size_t *ret_size, bool* ret_sum_ok)
+{
+	size_t i = 0;
+	size_t size = 4096;
+	char *reply = xmalloc(size);
+
+	int c;
+	uint8_t sum = 0;
+	bool escape = false;
+
+	/* fast-forward to the first start of packet */
+	while ((c = fgetc_unlocked(in)) != EOF && (c != '$' && c != '%'));
+	if (c == '%')
+		ungetc (c, in);
+
+	while ((c = fgetc_unlocked(in)) != EOF) {
+		sum += (uint8_t)c;
+		switch (c) {
+		case '$': /* new packet?  start over... */
+			i = 0;
+			sum = 0;
+			escape = false;
+			continue;
+		case '%':
+		{
+			char pcr[6];
+
+			int idx = 0;
+
+			i = 0;
+			sum = 0;
+			escape = false;
+			for (idx = 0; idx < 5; idx++)
+			{
+				pcr[idx] = fgetc_unlocked(in);
+				sum += (uint8_t)pcr[idx];
+			}
+			if (strncmp(pcr, "Stop:", 5) == 0)
+				continue;
+			continue;
+		}
+		case '#': /* end of packet */
+			sum -= c; /* not part of the checksum */
+			{
+				uint8_t msb = fgetc_unlocked(in);
+				uint8_t lsb = fgetc_unlocked(in);
+				*ret_sum_ok = sum == gdb_decode_hex(msb, lsb);
+			}
+			*ret_size = i;
+
+			/* terminate it for good measure */
+			if (i == size) {
+				reply = realloc(reply, size + 1);
+				if (reply == NULL)
+					perror_msg_and_die("realloc");
+			}
+			reply[i] = '\0';
+
+			if (debug_flag) {
+				error_msg("\tPacket received: %s", reply);
+				fflush(stdout);
+			}
+			return reply;
+
+		case '}': /* escape: next char is XOR 0x20 */
+			escape = true;
+			continue;
+
+		case '*':
+			/* run-length-encoding The next character tells how
+			 * many times to repeat the last character we saw.
+			 * The count is added to 29, so that the
+			 * minimum-beneficial RLE 3 is the first printable
+			 * character ' '.  The count character can't be >126
+			 * or '$'/'#' packet markers. */
+
+			if (i > 0) { /* need something to repeat! */
+				int c2 = fgetc_unlocked(in);
+				if (c2 < 29 || c2 > 126 || c2 == '$' || c2 == '#') {
+					/* invalid count character! */
+					ungetc(c2, in);
+				} else {
+					int count = c2 - 29;
+
+					/* get a bigger buffer if needed */
+					if (i + count > size) {
+						size *= 2;
+						reply = realloc(reply, size);
+						if (reply == NULL)
+							perror_msg_and_die("realloc");
+					}
+
+					/* fill the repeated character */
+					memset(&reply[i], reply[i - 1], count);
+					i += count;
+					sum += c2;
+					continue;
+				}
+			}
+		}
+
+		/* XOR an escaped character */
+		if (escape) {
+			c ^= 0x20;
+			escape = false;
+		}
+
+		/* get a bigger buffer if needed */
+		if (i == size) {
+			size *= 2;
+			reply = realloc(reply, size);
+			if (reply == NULL)
+				perror_msg_and_die("realloc");
+		}
+
+		/* add one character */
+		reply[i++] = c;
+	}
+
+	if (ferror(in))
+		error_msg_and_die("got stream error while receiving GDB server"
+				   " packet");
+	else if (feof(in)) {
+		error_msg_and_die("connection closed unexpectedly while "
+				"receiving GDB server packet");
+	}
+
+	error_msg_and_die("unknown GDB server connection error");
+}
+
+char *
+gdb_recv(struct gdb_conn *conn, size_t *size, bool want_stop)
+{
+	char *reply;
+	bool acked = false;
+
+	do {
+		reply = recv_packet(conn->in, size, &acked);
+
+		/* (See gdb_recv_stop for non-stop packet order)
+		   If a notification arrived while expecting another packet
+		   type, then cache the notification. */
+		if (! want_stop && strncmp(reply, "T05syscall", 10) == 0) {
+			push_notification(reply, *size);
+			reply = recv_packet(conn->in, size, &acked);
+		}
+
+		if (conn->ack) {
+			/* send +/- depending on checksum result, retry if needed */
+			fputc(acked ? '+' : '-', conn->out);
+			fflush(conn->out);
+			if (!acked)
+				free(reply);
+		}
+	} while (conn->ack && !acked);
+
+	return reply;
+}
+
+bool
+gdb_start_noack(struct gdb_conn *conn)
+{
+	static const char cmd[] = "QStartNoAckMode";
+	gdb_send(conn, cmd, sizeof(cmd) - 1);
+
+	size_t size;
+	char *reply = gdb_recv(conn, &size, false);
+	bool ok = size == 2 && !strcmp(reply, "OK");
+	free(reply);
+
+	if (ok)
+		conn->ack = false;
+	return ok ? "OK" : "";
+}
+
+void
+gdb_set_non_stop(struct gdb_conn *conn, bool val)
+{
+	conn->non_stop = val;
+}
+
+bool
+gdb_has_non_stop(struct gdb_conn *conn)
+{
+	return conn->non_stop;
+}
+
+/* Read complete qXfer data, returned as binary with the size.
+ * On error, returns NULL with size set to the error code.  */
+char *
+gdb_xfer_read(struct gdb_conn *conn,
+	const char *object, const char *annex,
+	/* out */ size_t *ret_size)
+{
+	size_t error = 0;
+	size_t offset = 0;
+	char *data = NULL;
+	do {
+		char *cmd;
+		int cmd_size = asprintf(&cmd, "qXfer:%s:read:%s:%zx,%x",
+					object ?: "", annex ?: "", offset, 0xfff /* XXX PacketSize */);
+		if (cmd_size < 0) {
+			break;
+		}
+
+		gdb_send(conn, cmd, strlen(cmd));
+		free(cmd);
+
+		size_t size;
+		char *reply = gdb_recv(conn, &size, false);
+		char c = reply[0];
+		switch (c) {
+		case 'm':
+		case 'l':
+			data = realloc(data, offset + size - 1);
+			memcpy(data + offset, reply + 1, size - 1);
+			free(reply);
+			offset += size - 1;
+			if (c == 'l') {
+				*ret_size = offset;
+				return data;
+			}
+			continue;
+		case 'E':
+			error = gdb_decode_hex_str(reply + 1);
+			break;
+		}
+		free(reply);
+		break;
+	} while (0);
+
+	free(data);
+	*ret_size = error;
+	return NULL;
+}
+
+
+struct vfile_response {
+	char *reply;
+	int64_t result;
+	int64_t errnum; /* avoid 'errno' macros */
+	size_t attachment_size;
+	const char *attachment;
+};
+
+static struct vfile_response
+gdb_vfile(struct gdb_conn *conn, const char *operation, const char 
*parameters)
+{
+	struct vfile_response res = { NULL, -1, 0, 0, NULL };
+
+	char *cmd;
+	int cmd_size = asprintf(&cmd, "vFile:%s:%s", operation, parameters);
+	if (cmd_size < 0) {
+		return res;
+	}
+
+	gdb_send(conn, cmd, strlen(cmd));
+	free(cmd);
+
+	size_t size;
+	res.reply = gdb_recv(conn, &size, false);
+	if (size > 1 && res.reply[0] == 'F') {
+		/* F result [, errno] [; attachment] */
+		res.result = gdb_decode_signed_hex_str(res.reply + 1);
+
+		const char *attachment = memchr(res.reply, ';', size);
+		if (attachment) {
+			res.attachment = attachment + 1;
+			res.attachment_size = size - (res.attachment - res.reply);
+		}
+
+		const char *errnum = memchr(res.reply, ',', size - res.attachment_size);
+		if (errnum)
+			res.errnum = gdb_decode_signed_hex_str(errnum + 1);
+	}
+	return res;
+}
+
+int
+gdb_readlink(struct gdb_conn *conn, const char *linkpath,
+	char *buf, unsigned bufsize)
+{
+	char *parameters = gdb_encode_hex_string(linkpath);
+	if (!parameters)
+		return -1;
+
+	struct vfile_response res = gdb_vfile(conn, "readlink", parameters);
+	free(parameters);
+
+	int ret = -1;
+	if (res.result >= 0 && res.attachment != NULL
+		&& res.result == (int64_t)res.attachment_size) {
+		size_t data_len = res.attachment_size;
+		if (data_len >= bufsize)
+			data_len = bufsize - 1; /* truncate -- ok? */
+		memcpy(buf, res.attachment, data_len);
+		buf[data_len] = 0;
+		ret = data_len;
+	}
+	free(res.reply);
+	return ret;
+}
diff --git a/gdbserver/protocol.h b/gdbserver/protocol.h
new file mode 100644
index 0000000..7220052
--- /dev/null
+++ b/gdbserver/protocol.h
@@ -0,0 +1,77 @@
+#ifndef STRACE_GDBSERVER_PROTOCOL_H
+#define STRACE_GDBSERVER_PROTOCOL_H
+
+/* Simple interface of a GDB remote protocol client.
+ *
+ * Copyright (c) 2015 Red Hat Inc.
+ * Copyright (c) 2015 Josh Stone <cuviper at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 
USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct gdb_conn;
+
+void gdb_encode_hex(uint8_t byte, char *out);
+uint16_t gdb_decode_hex(char msb, char lsb);
+uint64_t gdb_decode_hex_n(const char *bytes, size_t n);
+uint64_t gdb_decode_hex_str(const char *bytes);
+int gdb_decode_hex_buf(const char *bytes, size_t n, char *out);
+
+struct gdb_conn *gdb_begin_command(const char *command);
+struct gdb_conn *gdb_begin_tcp(const char *node, const char *service);
+struct gdb_conn *gdb_begin_path(const char *path);
+
+void gdb_end(struct gdb_conn *conn);
+
+void gdb_send(struct gdb_conn *conn, const char *command, size_t size);
+
+char *gdb_recv(struct gdb_conn *conn, /* out */ size_t *size, bool 
want_stop);
+
+bool gdb_start_noack(struct gdb_conn *conn);
+
+void gdb_set_non_stop(struct gdb_conn *conn, bool val);
+
+bool gdb_has_non_stop(struct gdb_conn *conn);
+
+char* pop_notification(size_t *size);
+
+void push_notification(char *packet, size_t packet_size);
+
+bool have_notification();
+
+/* Read complete qXfer data, returned as binary with the size.
+ * On error, returns NULL with size set to the error code.
+ */
+char *gdb_xfer_read(struct gdb_conn *conn,
+        const char *object, const char *annex,
+        /* out */ size_t *size);
+
+int gdb_readlink(struct gdb_conn *conn, const char *linkpath,
+        char *buf, unsigned bufsize);
+
+#endif /* !STRACE_GDBSERVER_PROTOCOL_H */
diff --git a/gdbserver/signals.def b/gdbserver/signals.def
new file mode 100644
index 0000000..3f49980
--- /dev/null
+++ b/gdbserver/signals.def
@@ -0,0 +1,200 @@
+/* Target signal numbers for GDB and the GDB remote protocol.
+   Copyright (C) 2010-2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 3 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, see 
<http://www.gnu.org/licenses/>.  */
+
+/* Used some places (e.g. stop_signal) to record the concept that
+   there is no signal.  */
+SET (GDB_SIGNAL_0, 0, "0", "Signal 0")
+#define GDB_SIGNAL_FIRST GDB_SIGNAL_0
+SET (GDB_SIGNAL_HUP, 1, "SIGHUP", "Hangup")
+SET (GDB_SIGNAL_INT, 2, "SIGINT", "Interrupt")
+SET (GDB_SIGNAL_QUIT, 3, "SIGQUIT", "Quit")
+SET (GDB_SIGNAL_ILL, 4, "SIGILL", "Illegal instruction")
+SET (GDB_SIGNAL_TRAP, 5, "SIGTRAP", "Trace/breakpoint trap")
+SET (GDB_SIGNAL_ABRT, 6, "SIGABRT", "Aborted")
+SET (GDB_SIGNAL_EMT, 7, "SIGEMT", "Emulation trap")
+SET (GDB_SIGNAL_FPE, 8, "SIGFPE", "Arithmetic exception")
+SET (GDB_SIGNAL_KILL, 9, "SIGKILL", "Killed")
+SET (GDB_SIGNAL_BUS, 10, "SIGBUS", "Bus error")
+SET (GDB_SIGNAL_SEGV, 11, "SIGSEGV", "Segmentation fault")
+SET (GDB_SIGNAL_SYS, 12, "SIGSYS", "Bad system call")
+SET (GDB_SIGNAL_PIPE, 13, "SIGPIPE", "Broken pipe")
+SET (GDB_SIGNAL_ALRM, 14, "SIGALRM", "Alarm clock")
+SET (GDB_SIGNAL_TERM, 15, "SIGTERM", "Terminated")
+SET (GDB_SIGNAL_URG, 16, "SIGURG", "Urgent I/O condition")
+SET (GDB_SIGNAL_STOP, 17, "SIGSTOP", "Stopped (signal)")
+SET (GDB_SIGNAL_TSTP, 18, "SIGTSTP", "Stopped (user)")
+SET (GDB_SIGNAL_CONT, 19, "SIGCONT", "Continued")
+SET (GDB_SIGNAL_CHLD, 20, "SIGCHLD", "Child status changed")
+SET (GDB_SIGNAL_TTIN, 21, "SIGTTIN", "Stopped (tty input)")
+SET (GDB_SIGNAL_TTOU, 22, "SIGTTOU", "Stopped (tty output)")
+SET (GDB_SIGNAL_IO, 23, "SIGIO", "I/O possible")
+SET (GDB_SIGNAL_XCPU, 24, "SIGXCPU", "CPU time limit exceeded")
+SET (GDB_SIGNAL_XFSZ, 25, "SIGXFSZ", "File size limit exceeded")
+SET (GDB_SIGNAL_VTALRM, 26, "SIGVTALRM", "Virtual timer expired")
+SET (GDB_SIGNAL_PROF, 27, "SIGPROF", "Profiling timer expired")
+SET (GDB_SIGNAL_WINCH, 28, "SIGWINCH", "Window size changed")
+SET (GDB_SIGNAL_LOST, 29, "SIGLOST", "Resource lost")
+SET (GDB_SIGNAL_USR1, 30, "SIGUSR1", "User defined signal 1")
+SET (GDB_SIGNAL_USR2, 31, "SIGUSR2", "User defined signal 2")
+SET (GDB_SIGNAL_PWR, 32, "SIGPWR", "Power fail/restart")
+/* Similar to SIGIO.  Perhaps they should have the same number.  */
+SET (GDB_SIGNAL_POLL, 33, "SIGPOLL", "Pollable event occurred")
+SET (GDB_SIGNAL_WIND, 34, "SIGWIND", "SIGWIND")
+SET (GDB_SIGNAL_PHONE, 35, "SIGPHONE", "SIGPHONE")
+SET (GDB_SIGNAL_WAITING, 36, "SIGWAITING", "Process's LWPs are blocked")
+SET (GDB_SIGNAL_LWP, 37, "SIGLWP", "Signal LWP")
+SET (GDB_SIGNAL_DANGER, 38, "SIGDANGER", "Swap space dangerously low")
+SET (GDB_SIGNAL_GRANT, 39, "SIGGRANT", "Monitor mode granted")
+SET (GDB_SIGNAL_RETRACT, 40, "SIGRETRACT",
+     "Need to relinquish monitor mode")
+SET (GDB_SIGNAL_MSG, 41, "SIGMSG", "Monitor mode data available")
+SET (GDB_SIGNAL_SOUND, 42, "SIGSOUND", "Sound completed")
+SET (GDB_SIGNAL_SAK, 43, "SIGSAK", "Secure attention")
+SET (GDB_SIGNAL_PRIO, 44, "SIGPRIO", "SIGPRIO")
+SET (GDB_SIGNAL_REALTIME_33, 45, "SIG33", "Real-time event 33")
+SET (GDB_SIGNAL_REALTIME_34, 46, "SIG34", "Real-time event 34")
+SET (GDB_SIGNAL_REALTIME_35, 47, "SIG35", "Real-time event 35")
+SET (GDB_SIGNAL_REALTIME_36, 48, "SIG36", "Real-time event 36")
+SET (GDB_SIGNAL_REALTIME_37, 49, "SIG37", "Real-time event 37")
+SET (GDB_SIGNAL_REALTIME_38, 50, "SIG38", "Real-time event 38")
+SET (GDB_SIGNAL_REALTIME_39, 51, "SIG39", "Real-time event 39")
+SET (GDB_SIGNAL_REALTIME_40, 52, "SIG40", "Real-time event 40")
+SET (GDB_SIGNAL_REALTIME_41, 53, "SIG41", "Real-time event 41")
+SET (GDB_SIGNAL_REALTIME_42, 54, "SIG42", "Real-time event 42")
+SET (GDB_SIGNAL_REALTIME_43, 55, "SIG43", "Real-time event 43")
+SET (GDB_SIGNAL_REALTIME_44, 56, "SIG44", "Real-time event 44")
+SET (GDB_SIGNAL_REALTIME_45, 57, "SIG45", "Real-time event 45")
+SET (GDB_SIGNAL_REALTIME_46, 58, "SIG46", "Real-time event 46")
+SET (GDB_SIGNAL_REALTIME_47, 59, "SIG47", "Real-time event 47")
+SET (GDB_SIGNAL_REALTIME_48, 60, "SIG48", "Real-time event 48")
+SET (GDB_SIGNAL_REALTIME_49, 61, "SIG49", "Real-time event 49")
+SET (GDB_SIGNAL_REALTIME_50, 62, "SIG50", "Real-time event 50")
+SET (GDB_SIGNAL_REALTIME_51, 63, "SIG51", "Real-time event 51")
+SET (GDB_SIGNAL_REALTIME_52, 64, "SIG52", "Real-time event 52")
+SET (GDB_SIGNAL_REALTIME_53, 65, "SIG53", "Real-time event 53")
+SET (GDB_SIGNAL_REALTIME_54, 66, "SIG54", "Real-time event 54")
+SET (GDB_SIGNAL_REALTIME_55, 67, "SIG55", "Real-time event 55")
+SET (GDB_SIGNAL_REALTIME_56, 68, "SIG56", "Real-time event 56")
+SET (GDB_SIGNAL_REALTIME_57, 69, "SIG57", "Real-time event 57")
+SET (GDB_SIGNAL_REALTIME_58, 70, "SIG58", "Real-time event 58")
+SET (GDB_SIGNAL_REALTIME_59, 71, "SIG59", "Real-time event 59")
+SET (GDB_SIGNAL_REALTIME_60, 72, "SIG60", "Real-time event 60")
+SET (GDB_SIGNAL_REALTIME_61, 73, "SIG61", "Real-time event 61")
+SET (GDB_SIGNAL_REALTIME_62, 74, "SIG62", "Real-time event 62")
+SET (GDB_SIGNAL_REALTIME_63, 75, "SIG63", "Real-time event 63")
+
+/* Used internally by Solaris threads.  See signal(5) on Solaris.  */
+SET (GDB_SIGNAL_CANCEL, 76, "SIGCANCEL", "LWP internal signal")
+
+/* Yes, this pains me, too.  But LynxOS didn't have SIG32, and now
+   GNU/Linux does, and we can't disturb the numbering, since it's
+   part of the remote protocol.  Note that in some GDB's
+   GDB_SIGNAL_REALTIME_32 is number 76.  */
+SET (GDB_SIGNAL_REALTIME_32, 77, "SIG32", "Real-time event 32")
+/* Yet another pain, IRIX 6 has SIG64. */
+SET (GDB_SIGNAL_REALTIME_64, 78, "SIG64", "Real-time event 64")
+/* Yet another pain, GNU/Linux MIPS might go up to 128. */
+SET (GDB_SIGNAL_REALTIME_65, 79, "SIG65", "Real-time event 65")
+SET (GDB_SIGNAL_REALTIME_66, 80, "SIG66", "Real-time event 66")
+SET (GDB_SIGNAL_REALTIME_67, 81, "SIG67", "Real-time event 67")
+SET (GDB_SIGNAL_REALTIME_68, 82, "SIG68", "Real-time event 68")
+SET (GDB_SIGNAL_REALTIME_69, 83, "SIG69", "Real-time event 69")
+SET (GDB_SIGNAL_REALTIME_70, 84, "SIG70", "Real-time event 70")
+SET (GDB_SIGNAL_REALTIME_71, 85, "SIG71", "Real-time event 71")
+SET (GDB_SIGNAL_REALTIME_72, 86, "SIG72", "Real-time event 72")
+SET (GDB_SIGNAL_REALTIME_73, 87, "SIG73", "Real-time event 73")
+SET (GDB_SIGNAL_REALTIME_74, 88, "SIG74", "Real-time event 74")
+SET (GDB_SIGNAL_REALTIME_75, 89, "SIG75", "Real-time event 75")
+SET (GDB_SIGNAL_REALTIME_76, 90, "SIG76", "Real-time event 76")
+SET (GDB_SIGNAL_REALTIME_77, 91, "SIG77", "Real-time event 77")
+SET (GDB_SIGNAL_REALTIME_78, 92, "SIG78", "Real-time event 78")
+SET (GDB_SIGNAL_REALTIME_79, 93, "SIG79", "Real-time event 79")
+SET (GDB_SIGNAL_REALTIME_80, 94, "SIG80", "Real-time event 80")
+SET (GDB_SIGNAL_REALTIME_81, 95, "SIG81", "Real-time event 81")
+SET (GDB_SIGNAL_REALTIME_82, 96, "SIG82", "Real-time event 82")
+SET (GDB_SIGNAL_REALTIME_83, 97, "SIG83", "Real-time event 83")
+SET (GDB_SIGNAL_REALTIME_84, 98, "SIG84", "Real-time event 84")
+SET (GDB_SIGNAL_REALTIME_85, 99, "SIG85", "Real-time event 85")
+SET (GDB_SIGNAL_REALTIME_86, 100, "SIG86", "Real-time event 86")
+SET (GDB_SIGNAL_REALTIME_87, 101, "SIG87", "Real-time event 87")
+SET (GDB_SIGNAL_REALTIME_88, 102, "SIG88", "Real-time event 88")
+SET (GDB_SIGNAL_REALTIME_89, 103, "SIG89", "Real-time event 89")
+SET (GDB_SIGNAL_REALTIME_90, 104, "SIG90", "Real-time event 90")
+SET (GDB_SIGNAL_REALTIME_91, 105, "SIG91", "Real-time event 91")
+SET (GDB_SIGNAL_REALTIME_92, 106, "SIG92", "Real-time event 92")
+SET (GDB_SIGNAL_REALTIME_93, 107, "SIG93", "Real-time event 93")
+SET (GDB_SIGNAL_REALTIME_94, 108, "SIG94", "Real-time event 94")
+SET (GDB_SIGNAL_REALTIME_95, 109, "SIG95", "Real-time event 95")
+SET (GDB_SIGNAL_REALTIME_96, 110, "SIG96", "Real-time event 96")
+SET (GDB_SIGNAL_REALTIME_97, 111, "SIG97", "Real-time event 97")
+SET (GDB_SIGNAL_REALTIME_98, 112, "SIG98", "Real-time event 98")
+SET (GDB_SIGNAL_REALTIME_99, 113, "SIG99", "Real-time event 99")
+SET (GDB_SIGNAL_REALTIME_100, 114, "SIG100", "Real-time event 100")
+SET (GDB_SIGNAL_REALTIME_101, 115, "SIG101", "Real-time event 101")
+SET (GDB_SIGNAL_REALTIME_102, 116, "SIG102", "Real-time event 102")
+SET (GDB_SIGNAL_REALTIME_103, 117, "SIG103", "Real-time event 103")
+SET (GDB_SIGNAL_REALTIME_104, 118, "SIG104", "Real-time event 104")
+SET (GDB_SIGNAL_REALTIME_105, 119, "SIG105", "Real-time event 105")
+SET (GDB_SIGNAL_REALTIME_106, 120, "SIG106", "Real-time event 106")
+SET (GDB_SIGNAL_REALTIME_107, 121, "SIG107", "Real-time event 107")
+SET (GDB_SIGNAL_REALTIME_108, 122, "SIG108", "Real-time event 108")
+SET (GDB_SIGNAL_REALTIME_109, 123, "SIG109", "Real-time event 109")
+SET (GDB_SIGNAL_REALTIME_110, 124, "SIG110", "Real-time event 110")
+SET (GDB_SIGNAL_REALTIME_111, 125, "SIG111", "Real-time event 111")
+SET (GDB_SIGNAL_REALTIME_112, 126, "SIG112", "Real-time event 112")
+SET (GDB_SIGNAL_REALTIME_113, 127, "SIG113", "Real-time event 113")
+SET (GDB_SIGNAL_REALTIME_114, 128, "SIG114", "Real-time event 114")
+SET (GDB_SIGNAL_REALTIME_115, 129, "SIG115", "Real-time event 115")
+SET (GDB_SIGNAL_REALTIME_116, 130, "SIG116", "Real-time event 116")
+SET (GDB_SIGNAL_REALTIME_117, 131, "SIG117", "Real-time event 117")
+SET (GDB_SIGNAL_REALTIME_118, 132, "SIG118", "Real-time event 118")
+SET (GDB_SIGNAL_REALTIME_119, 133, "SIG119", "Real-time event 119")
+SET (GDB_SIGNAL_REALTIME_120, 134, "SIG120", "Real-time event 120")
+SET (GDB_SIGNAL_REALTIME_121, 135, "SIG121", "Real-time event 121")
+SET (GDB_SIGNAL_REALTIME_122, 136, "SIG122", "Real-time event 122")
+SET (GDB_SIGNAL_REALTIME_123, 137, "SIG123", "Real-time event 123")
+SET (GDB_SIGNAL_REALTIME_124, 138, "SIG124", "Real-time event 124")
+SET (GDB_SIGNAL_REALTIME_125, 139, "SIG125", "Real-time event 125")
+SET (GDB_SIGNAL_REALTIME_126, 140, "SIG126", "Real-time event 126")
+SET (GDB_SIGNAL_REALTIME_127, 141, "SIG127", "Real-time event 127")
+
+SET (GDB_SIGNAL_INFO, 142, "SIGINFO", "Information request")
+
+/* Some signal we don't know about.  */
+SET (GDB_SIGNAL_UNKNOWN, 143, NULL, "Unknown signal")
+
+/* Use whatever signal we use when one is not specifically specified
+   (for passing to proceed and so on).  */
+SET (GDB_SIGNAL_DEFAULT, 144, NULL,
+     "Internal error: printing GDB_SIGNAL_DEFAULT")
+
+/* Mach exceptions.  In versions of GDB before 5.2, these were just before
+   GDB_SIGNAL_INFO if you were compiling on a Mach host (and missing
+   otherwise).  */
+SET (GDB_EXC_BAD_ACCESS, 145, "EXC_BAD_ACCESS", "Could not access memory")
+SET (GDB_EXC_BAD_INSTRUCTION, 146, "EXC_BAD_INSTRUCTION",
+     "Illegal instruction/operand")
+SET (GDB_EXC_ARITHMETIC, 147, "EXC_ARITHMETIC", "Arithmetic exception")
+SET (GDB_EXC_EMULATION, 148, "EXC_EMULATION", "Emulation instruction")
+SET (GDB_EXC_SOFTWARE, 149, "EXC_SOFTWARE", "Software generated exception")
+SET (GDB_EXC_BREAKPOINT, 150, "EXC_BREAKPOINT", "Breakpoint")
+
+/* If you are adding a new signal, add it just above this comment.  */
+
+/* Last and unused enum value, for sizing arrays, etc.  */
+SET (GDB_SIGNAL_LAST, 151, NULL, "GDB_SIGNAL_LAST")
diff --git a/gdbserver/signals.h b/gdbserver/signals.h
new file mode 100644
index 0000000..f7ed973
--- /dev/null
+++ b/gdbserver/signals.h
@@ -0,0 +1,58 @@
+/* Target signal numbers for GDB and the GDB remote protocol.
+   Copyright (C) 1986-2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 3 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, see 
<http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_SIGNALS_H
+#define GDB_SIGNALS_H
+
+/* The numbering of these signals is chosen to match traditional unix
+   signals (insofar as various unices use the same numbers, anyway).
+   It is also the numbering of the GDB remote protocol.  Other remote
+   protocols, if they use a different numbering, should make sure to
+   translate appropriately.
+
+   Since these numbers have actually made it out into other software
+   (stubs, etc.), you mustn't disturb the assigned numbering.  If you
+   need to add new signals here, add them to the end of the explicitly
+   numbered signals, at the comment marker.  Add them unconditionally,
+   not within any #if or #ifdef.
+
+   This is based strongly on Unix/POSIX signals for several reasons:
+   (1) This set of signals represents a widely-accepted attempt to
+   represent events of this sort in a portable fashion, (2) we want a
+   signal to make it from wait to child_wait to the user intact, (3) many
+   remote protocols use a similar encoding.  However, it is
+   recognized that this set of signals has limitations (such as not
+   distinguishing between various kinds of SIGSEGV, or not
+   distinguishing hitting a breakpoint from finishing a single step).
+   So in the future we may get around this either by adding additional
+   signals for breakpoint, single-step, etc., or by adding signal
+   codes; the latter seems more in the spirit of what BSD, System V,
+   etc. are doing to address these issues.  */
+
+/* For an explanation of what each signal means, see
+   gdb_signal_to_string.  */
+
+enum gdb_signal
+  {
+#define SET(symbol, constant, name, string) \
+    symbol = constant,
+#include "signals.def"
+#undef SET
+  };
+
+#endif /* #ifndef GDB_SIGNALS_H */
diff --git a/gdbserver/x86_64/gdb_get_regs.c 
b/gdbserver/x86_64/gdb_get_regs.c
new file mode 100644
index 0000000..ecd103a
--- /dev/null
+++ b/gdbserver/x86_64/gdb_get_regs.c
@@ -0,0 +1,129 @@
+long
+gdb_get_regs (pid_t pid, void *io)
+{
+#include <sys/user.h>
+/* for struct iovec */
+#include <sys/uio.h>
+
+// Begin adapted from linux/x86_64/arch_regs.c
+	struct i386_user_regs_struct {
+		uint32_t ebx;
+		uint32_t ecx;
+		uint32_t edx;
+		uint32_t esi;
+		uint32_t edi;
+		uint32_t ebp;
+		uint32_t eax;
+		uint32_t xds;
+		uint32_t xes;
+		uint32_t xfs;
+		uint32_t xgs;
+		uint32_t orig_eax;
+		uint32_t eip;
+		uint32_t xcs;
+		uint32_t eflags;
+		uint32_t esp;
+		uint32_t xss;
+	};
+	struct iovec *x86_io;
+	static union {
+		struct user_regs_struct      x86_64_r;
+		struct i386_user_regs_struct i386_r;
+	} *x86_regs_union;
+
+	x86_io = (struct iovec*)io;
+	x86_regs_union = x86_io->iov_base;
+
+#define x86_64_regs x86_regs_union->x86_64_r
+#define i386_regs   x86_regs_union->i386_r
+// End adapted from linux/x86_64/arch_regs.c
+
+        int get_regs_error;
+        size_t size;
+        char *regs = gdb_get_all_regs(pid, &size);
+	struct tcb *tcp = pid2tcb(pid);
+        if (regs) {
+                if (size == 0 || regs[0] == 'E') {
+                        get_regs_error = -1;
+                        free(regs);
+                        return get_regs_error;
+                }
+
+		if (size == 624) {
+			get_regs_error = 0;
+			x86_io->iov_len = sizeof(i386_regs);
+
+			/* specified in 32bit-core.xml */
+			i386_regs.eax = be32toh(gdb_decode_hex_n(&regs[0], 8));
+			i386_regs.ecx = be32toh(gdb_decode_hex_n(&regs[8], 8));
+			i386_regs.edx = be32toh(gdb_decode_hex_n(&regs[16], 8));
+			i386_regs.ebx = be32toh(gdb_decode_hex_n(&regs[24], 8));
+			i386_regs.esp = be32toh(gdb_decode_hex_n(&regs[32], 8));
+			i386_regs.ebp = be32toh(gdb_decode_hex_n(&regs[40], 8));
+			i386_regs.esi = be32toh(gdb_decode_hex_n(&regs[48], 8));
+			i386_regs.edi = be32toh(gdb_decode_hex_n(&regs[56], 8));
+			i386_regs.eip = be32toh(gdb_decode_hex_n(&regs[60], 8));
+			i386_regs.eflags = be32toh(gdb_decode_hex_n(&regs[68], 8));
+			i386_regs.xcs = be32toh(gdb_decode_hex_n(&regs[76], 8));
+			i386_regs.xss = be32toh(gdb_decode_hex_n(&regs[84], 8));
+			i386_regs.xds = be32toh(gdb_decode_hex_n(&regs[92], 8));
+			i386_regs.xes = be32toh(gdb_decode_hex_n(&regs[100], 8));
+			i386_regs.xfs = be32toh(gdb_decode_hex_n(&regs[108], 8));
+			i386_regs.xgs = be32toh(gdb_decode_hex_n(&regs[116], 8));
+
+			/* specified in 32bit-linux.xml */
+			i386_regs.orig_eax = be64toh(gdb_decode_hex_n(&regs[616], 8));
+
+			update_personality(tcp, 1);
+			free(regs);
+			return 0;
+		}
+
+		else if (size >= 1088) {
+			get_regs_error = 0;
+			x86_io->iov_len = sizeof(x86_64_regs);
+
+			/* specified in 64bit-core.xml */
+			x86_64_regs.rax = be64toh(gdb_decode_hex_n(&regs[0], 16));
+			x86_64_regs.rbx = be64toh(gdb_decode_hex_n(&regs[16], 16));
+			x86_64_regs.rcx = be64toh(gdb_decode_hex_n(&regs[32], 16));
+			x86_64_regs.rdx = be64toh(gdb_decode_hex_n(&regs[48], 16));
+			x86_64_regs.rsi = be64toh(gdb_decode_hex_n(&regs[64], 16));
+			x86_64_regs.rdi = be64toh(gdb_decode_hex_n(&regs[80], 16));
+			x86_64_regs.rbp = be64toh(gdb_decode_hex_n(&regs[96], 16));
+			x86_64_regs.rsp = be64toh(gdb_decode_hex_n(&regs[112], 16));
+			x86_64_regs.r8  = be64toh(gdb_decode_hex_n(&regs[128], 16));
+			x86_64_regs.r9  = be64toh(gdb_decode_hex_n(&regs[144], 16));
+			x86_64_regs.r10 = be64toh(gdb_decode_hex_n(&regs[160], 16));
+			x86_64_regs.r11 = be64toh(gdb_decode_hex_n(&regs[176], 16));
+			x86_64_regs.r12 = be64toh(gdb_decode_hex_n(&regs[192], 16));
+			x86_64_regs.r13 = be64toh(gdb_decode_hex_n(&regs[208], 16));
+			x86_64_regs.r14 = be64toh(gdb_decode_hex_n(&regs[224], 16));
+			x86_64_regs.r15 = be64toh(gdb_decode_hex_n(&regs[240], 16));
+			x86_64_regs.rip = be64toh(gdb_decode_hex_n(&regs[256], 16));
+			x86_64_regs.eflags = be32toh(gdb_decode_hex_n(&regs[272], 8));
+			x86_64_regs.cs = be32toh(gdb_decode_hex_n(&regs[280], 8));
+			x86_64_regs.ss = be32toh(gdb_decode_hex_n(&regs[288], 8));
+			x86_64_regs.ds = be32toh(gdb_decode_hex_n(&regs[296], 8));
+			x86_64_regs.es = be32toh(gdb_decode_hex_n(&regs[304], 8));
+			x86_64_regs.fs = be32toh(gdb_decode_hex_n(&regs[312], 8));
+			x86_64_regs.gs = be32toh(gdb_decode_hex_n(&regs[320], 8));
+
+			/* specified in 64bit-linux.xml */
+			x86_64_regs.orig_rax = be64toh(gdb_decode_hex_n(&regs[1072], 16));
+
+			update_personality(tcp, 0);
+			free(regs);
+			return get_regs_error;
+		}
+
+                else {
+                        get_regs_error = -1;
+                        free(regs);
+                        return get_regs_error;
+                }
+	}
+        return -1;
+}
+
+
diff --git a/ipc_shm.c b/ipc_shm.c
index 7284b18..9503f81 100644
--- a/ipc_shm.c
+++ b/ipc_shm.c
@@ -78,7 +78,7 @@ SYS_FUNC(shmat)
  				uint64_t r64;
  				uint32_t r32;
  			} u;
-			if (umoven(tcp, tcp->u_arg[2], current_wordsize, &u) < 0)
+			if (backend.umoven(tcp, tcp->u_arg[2], current_wordsize, &u) < 0)
  				return RVAL_NONE;
  			tcp->u_rval = (sizeof(u.r32) == current_wordsize)
  				      ? u.r32 : u.r64;
diff --git a/mem.c b/mem.c
index 00f5610..78b2bd0 100644
--- a/mem.c
+++ b/mem.c
@@ -276,7 +276,7 @@ SYS_FUNC(mincore)
  		len = len / page_size + (len & page_mask ? 1 : 0);
  		if (syserror(tcp) || !verbose(tcp) ||
  		    !tcp->u_arg[2] || !(vec = malloc(len)) ||
-		    umoven(tcp, tcp->u_arg[2], len, vec) < 0)
+		    backend.umoven(tcp, tcp->u_arg[2], len, vec) < 0)
  			printaddr(tcp->u_arg[2]);
  		else {
  			unsigned long i;
diff --git a/msghdr.c b/msghdr.c
index dd9c430..00b60cf 100644
--- a/msghdr.c
+++ b/msghdr.c
@@ -297,7 +297,7 @@ decode_msg_control(struct tcb *const tcp, const 
kernel_ulong_t addr,
  				   ? get_optmem_max() : in_control_len;
  	unsigned int buf_len = control_len;
  	char *buf = buf_len < cmsg_size ? NULL : malloc(buf_len);
-	if (!buf || umoven(tcp, addr, buf_len, buf) < 0) {
+	if (!buf || backend.umoven(tcp, addr, buf_len, buf) < 0) {
  		printaddr(addr);
  		free(buf);
  		return;
diff --git a/pathtrace.c b/pathtrace.c
index 5258238..39f5b17 100644
--- a/pathtrace.c
+++ b/pathtrace.c
@@ -59,7 +59,7 @@ upathmatch(struct tcb *const tcp, const kernel_ulong_t 
upath,
  {
  	char path[PATH_MAX + 1];

-	return umovestr(tcp, upath, sizeof(path), path) > 0 &&
+	return backend.umovestr(tcp, upath, sizeof(path), path) > 0 &&
  		pathmatch(path, set);
  }

@@ -70,7 +70,7 @@ static bool
  fdmatch(struct tcb *tcp, int fd, struct path_set *set)
  {
  	char path[PATH_MAX + 1];
-	int n = getfdpath(tcp, fd, path, sizeof(path));
+	int n = backend.getfdpath(tcp, fd, path, sizeof(path));

  	return n >= 0 && pathmatch(path, set);
  }
@@ -296,7 +296,7 @@ pathtrace_match_set(struct tcb *tcp, struct path_set 
*set)
  		for (i = 1; i <= 3; ++i) {
  			if (args[i] == 0)
  				continue;
-			if (umoven(tcp, args[i], fdsize, fds) < 0) {
+			if (backend.umoven(tcp, args[i], fdsize, fds) < 0) {
  				continue;
  			}
  			for (j = 0;; j++) {
diff --git a/rt_sigreturn.c b/rt_sigreturn.c
index af705c3..38f1e94 100644
--- a/rt_sigreturn.c
+++ b/rt_sigreturn.c
@@ -27,6 +27,29 @@

  #include "defs.h"

+/* Avoids the "#define si_pid _sifields._kill.si_pid" in siginfo.h */
+
+#pragma push_macro ("si_pid")
+#undef si_pid
+#pragma push_macro ("si_overrun")
+#undef si_overrun
+#pragma push_macro ("si_addr")
+#undef si_addr
+#pragma push_macro ("si_band")
+#undef si_band
+#pragma push_macro ("si_uid")
+#undef si_uid
+#pragma push_macro ("si_status")
+#undef si_status
+#pragma push_macro ("si_utime")
+#undef si_utime
+#pragma push_macro ("si_fd")
+#undef si_fd
+#pragma push_macro ("si_addr_lsb")
+#undef si_addr_lsb
+#pragma push_macro ("si_stime")
+#undef si_stime
+
  #include DEF_MPERS_TYPE(struct_rt_sigframe)

  #include "rt_sigframe.h"
@@ -38,6 +61,17 @@
  		offsetof(struct_rt_sigframe, uc.uc_sigmask)
  #endif

+#pragma pop_macro ("si_pid")
+#pragma pop_macro ("si_overrun")
+#pragma pop_macro ("si_addr")
+#pragma pop_macro ("si_band")
+#pragma pop_macro ("si_uid")
+#pragma pop_macro ("si_status")
+#pragma pop_macro ("si_utime")
+#pragma pop_macro ("si_fd")
+#pragma pop_macro ("si_addr_lsb")
+#pragma pop_macro ("si_stime")
+
  SYS_FUNC(rt_sigreturn)
  {
  	const kernel_ulong_t sf_addr = get_rt_sigframe_addr(tcp);
diff --git a/strace.c b/strace.c
index 6ed86a6..bec7e4f 100644
--- a/strace.c
+++ b/strace.c
@@ -129,11 +129,11 @@ bool not_failing_only;
  /* Show path associated with fd arguments */
  unsigned int show_fd_path;

-static bool detach_on_execve;
+bool detach_on_execve;

  static int exit_code;
-static int strace_child;
-static int strace_tracer_pid;
+int strace_child = 0;
+static int strace_tracer_pid = 0;

  static const char *username;
  static uid_t run_uid;
@@ -147,8 +147,8 @@ static const char *outfname;
  /* If -ff, points to stderr. Else, it's our common output log */
  static FILE *shared_log;

-struct tcb *printing_tcp;
-static struct tcb *current_tcp;
+struct tcb *printing_tcp = NULL;
+struct tcb *current_tcp;

  static struct tcb **tcbtab;
  static unsigned int nprocs, tcbtabsize;
@@ -163,6 +163,12 @@ static void detach(struct tcb *tcp);
  static void cleanup(void);
  static void interrupt(int sig);
  static sigset_t start_set, blocked_set;
+static enum trace_event next_event (int *pstatus, void *si);
+static bool dispatch_event (enum trace_event, int *pstatus, void *si);
+
+int    umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len,
+		void *const our_addr);
+int    default_arch_get_scno(struct tcb *tcp);

  #ifdef HAVE_SIG_ATOMIC_T
  static volatile sig_atomic_t interrupted;
@@ -664,7 +670,7 @@ tabto(void)
   * Otherwise, "strace -oFILE -ff -p<nonexistant_pid>"
   * may create bogus empty FILE.<nonexistant_pid>, and then die.
   */
-static void
+void
  newoutf(struct tcb *tcp)
  {
  	tcp->outf = shared_log; /* if not -ff mode, the same file is for all */
@@ -699,7 +705,7 @@ expand_tcbtab(void)
  		tcbtab[tcbtabsize++] = newtcbs++;
  }

-static struct tcb *
+struct tcb *
  alloctcb(int pid)
  {
  	unsigned int i;
@@ -763,7 +769,7 @@ free_tcb_priv_data(struct tcb *tcp)
  	}
  }

-static void
+void
  droptcb(struct tcb *tcp)
  {
  	if (tcp->pid == 0)
@@ -1111,7 +1117,7 @@ startup_attach(void)
  			continue;
  		}

-		attach_tcb(tcp);
+		backend.attach_tcb(tcp);

  		if (interactive) {
  			sigprocmask(SIG_SETMASK, &start_set, NULL);
@@ -1539,7 +1545,22 @@ get_os_release(void)
  	return rel;
  }

-static void
+
+static bool
+prog_pid_check (char *exec_name, int nprocs)
+{
+	return exec_name || nprocs;
+}
+
+
+static bool
+handle_arg (char arg, char *optarg)
+{
+	return false;
+}
+
+
+void
  set_sigaction(int signo, void (*sighandler)(int), struct sigaction 
*oldact)
  {
  	/* if signal handler is a function, add the signal to blocked_set */
@@ -1550,6 +1571,21 @@ set_sigaction(int signo, void (*sighandler)(int), 
struct sigaction *oldact)
  	sigaction(signo, &sa, oldact);
  }

+
+static void
+end_init(void) {}
+
+static void
+final_cleanup(void) {}
+
+static bool
+verify_args(const char *username, bool daemon, unsigned int *follow_fork)
+{return true;}
+
+static bool
+start_init(void)
+{return true;}
+
  /*
   * Initialization part of main() was eating much stack (~0.5k),
   * which was unused after init.
@@ -1564,6 +1600,25 @@ init(int argc, char *argv[])
  	int c, i;
  	int optF = 0;

+	backend.attach_tcb = attach_tcb;
+	backend.cleanup = final_cleanup;
+	backend.detach = detach;
+	backend.dispatch_event = dispatch_event;
+	backend.end_init = end_init;
+	backend.get_regs = NULL;
+	backend.get_scno = default_arch_get_scno;
+	backend.getfdpath = getfdpath;
+	backend.handle_arg = handle_arg;
+	backend.next_event = next_event;
+	backend.prog_pid_check = prog_pid_check;
+	backend.start_init = start_init;
+	backend.startup_child = startup_child;
+	backend.umoven = umoven;
+	backend.umovestr = umovestr;
+	backend.upeek_ = upeek;
+	backend.upoke_ = upoke;
+	backend.verify_args = verify_args;
+
  	if (!program_invocation_name || !*program_invocation_name) {
  		static char name[] = "strace";
  		program_invocation_name =
@@ -1590,6 +1645,7 @@ init(int argc, char *argv[])
  		"k"
  #endif
  		"D"
+		"G:"
  		"a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
  		switch (c) {
  		case 'b':
@@ -1694,6 +1750,11 @@ init(int argc, char *argv[])
  		case 'u':
  			username = optarg;
  			break;
+#ifdef ENABLE_GDBSERVER
+		case 'G':
+			gdb_handle_arg (c, optarg);
+			break;
+#endif
  #ifdef USE_LIBUNWIND
  		case 'k':
  			stack_trace_enabled = true;
@@ -1709,7 +1770,8 @@ init(int argc, char *argv[])
  				error_opt_arg(c, optarg);
  			break;
  		default:
-			error_msg_and_help(NULL);
+			if (! backend.handle_arg(c, optarg))
+				error_msg_and_help(NULL);
  			break;
  		}
  	}
@@ -1717,7 +1779,7 @@ init(int argc, char *argv[])
  	argv += optind;
  	argc -= optind;

-	if (argc < 0 || (!argv[0] && !nprocs)) {
+	if (argc < 0 || ! backend.prog_pid_check(argv[0], nprocs)) {
  		error_msg_and_help("must have PROG [ARGS] or -p PID");
  	}

@@ -1728,6 +1790,8 @@ init(int argc, char *argv[])
  	if (!followfork)
  		followfork = optF;

+	backend.verify_args(username, daemonized_tracer, &followfork);
+
  	if (followfork >= 2 && cflag) {
  		error_msg_and_help("(-c or -C) and -ff are mutually exclusive");
  	}
@@ -1759,6 +1823,9 @@ init(int argc, char *argv[])
  		tflag = 1;
  	}

+	if (backend.start_init && ! backend.start_init())
+			error_msg_and_die("Cannot initialize backend on this target.");
+
  	acolumn_spaces = xmalloc(acolumn + 1);
  	memset(acolumn_spaces, ' ', acolumn);
  	acolumn_spaces[acolumn] = '\0';
@@ -1865,7 +1932,7 @@ init(int argc, char *argv[])
  	 * in the startup_child() mode we kill the spawned process anyway.
  	 */
  	if (argv[0]) {
-		startup_child(argv);
+		backend.startup_child(argv);
  	}

  	set_sigaction(SIGTTOU, SIG_IGN, NULL);
@@ -1889,6 +1956,9 @@ init(int argc, char *argv[])
  	if (nprocs != 0 || daemonized_tracer)
  		startup_attach();

+	if (backend.end_init)
+		backend.end_init();
+
  	/* Do we want pids printed in our -o OUTFILE?
  	 * -ff: no (every pid has its own file); or
  	 * -f: yes (there can be more pids in the future); or
@@ -1897,7 +1967,7 @@ init(int argc, char *argv[])
  	print_pid_pfx = (outfname && followfork < 2 && (followfork == 1 || 
nprocs > 1));
  }

-static struct tcb *
+struct tcb *
  pid2tcb(int pid)
  {
  	unsigned int i;
@@ -1936,10 +2006,12 @@ cleanup(void)
  			kill(tcp->pid, SIGCONT);
  			kill(tcp->pid, fatal_sig);
  		}
-		detach(tcp);
+		backend.detach(tcp);
  	}
  	if (cflag)
  		call_summary(shared_log);
+
+	backend.cleanup();
  }

  static void
@@ -2077,7 +2149,7 @@ maybe_switch_tcbs(struct tcb *tcp, const int pid)
  	return tcp;
  }

-static void
+void
  print_signalled(struct tcb *tcp, const int pid, int status)
  {
  	if (pid == strace_child) {
@@ -2100,7 +2172,7 @@ print_signalled(struct tcb *tcp, const int pid, 
int status)
  	}
  }

-static void
+void
  print_exited(struct tcb *tcp, const int pid, int status)
  {
  	if (pid == strace_child) {
@@ -2116,7 +2188,7 @@ print_exited(struct tcb *tcp, const int pid, int 
status)
  	}
  }

-static void
+void
  print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int 
sig)
  {
  	if (cflag != CFLAG_ONLY_STATS
@@ -2194,67 +2266,10 @@ print_event_exit(struct tcb *tcp)
  	line_ended();
  }

-enum trace_event {
-	/* Break the main loop. */
-	TE_BREAK,
-
-	/* Call next_event() again. */
-	TE_NEXT,
-
-	/* Restart the tracee with signal 0 and call next_event() again. */
-	TE_RESTART,
-
-	/*
-	 * For all the events below, current_tcp is set to current tracee's
-	 * tcb.  All the suggested actions imply that you want to continue
-	 * tracing of the current tracee; alternatively, you can detach it.
-	 */
-
-	/*
-	 * Syscall entry or exit.
-	 * Restart the tracee with signal 0, or with an injected signal number.
-	 */
-	TE_SYSCALL_STOP,
-
-	/*
-	 * Tracee received signal with number WSTOPSIG(*pstatus); signal info
-	 * is written to *si.  Restart the tracee (with that signal number
-	 * if you want to deliver it).
-	 */
-	TE_SIGNAL_DELIVERY_STOP,
-
-	/*
-	 * Tracee was killed by a signal with number WTERMSIG(*pstatus).
-	 */
-	TE_SIGNALLED,
-
-	/*
-	 * Tracee was stopped by a signal with number WSTOPSIG(*pstatus).
-	 * Restart the tracee with that signal number.
-	 */
-	TE_GROUP_STOP,
-
-	/*
-	 * Tracee exited with status WEXITSTATUS(*pstatus).
-	 */
-	TE_EXITED,
-
-	/*
-	 * Tracee is going to perform execve().
-	 * Restart the tracee with signal 0.
-	 */
-	TE_STOP_BEFORE_EXECVE,
-
-	/*
-	 * Tracee is going to terminate.
-	 * Restart the tracee with signal 0.
-	 */
-	TE_STOP_BEFORE_EXIT,
-};
-
  static enum trace_event
-next_event(int *pstatus, siginfo_t *si)
+next_event(int *pstatus, void *si_p)
  {
+	siginfo_t *si = (siginfo_t*)si_p;
  	int pid;
  	int wait_errno;
  	int status;
@@ -2406,7 +2421,7 @@ next_event(int *pstatus, siginfo_t *si)
  	}
  }

-static int
+int
  trace_syscall(struct tcb *tcp, unsigned int *sig)
  {
  	if (entering(tcp)) {
@@ -2432,8 +2447,9 @@ trace_syscall(struct tcb *tcp, unsigned int *sig)

  /* Returns true iff the main trace loop has to continue. */
  static bool
-dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
+dispatch_event(enum trace_event ret, int *pstatus, void *si_p)
  {
+	siginfo_t *si = (siginfo_t*)si_p;
  	unsigned int restart_op = PTRACE_SYSCALL;
  	unsigned int restart_sig = 0;

@@ -2517,7 +2533,7 @@ dispatch_event(enum trace_event ret, int *pstatus, 
siginfo_t *si)
  			if (current_tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
  				current_tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
  			} else {
-				detach(current_tcp); /* do "-b execve" thingy */
+				backend.detach(current_tcp); /* do "-b execve" thingy */
  				return true;
  			}
  		}
@@ -2584,6 +2600,7 @@ terminate(void)
  	exit(exit_code);
  }

+
  int
  main(int argc, char *argv[])
  {
@@ -2593,7 +2610,7 @@ main(int argc, char *argv[])

  	int status;
  	siginfo_t si;
-	while (dispatch_event(next_event(&status, &si), &status, &si))
+	while (backend.dispatch_event(backend.next_event(&status, &si), 
&status, &si))
  		;
  	terminate();
  }
diff --git a/syscall.c b/syscall.c
index b1047fe..e3e0947 100644
--- a/syscall.c
+++ b/syscall.c
@@ -296,7 +296,7 @@ set_personality(int personality)
  # endif
  }

-static void
+void
  update_personality(struct tcb *tcp, unsigned int personality)
  {
  	if (personality == current_personality)
@@ -338,7 +338,7 @@ decode_socket_subcall(struct tcb *tcp)
  	const unsigned int nargs = sysent[scno].nargs;
  	uint64_t buf[nargs];

-	if (umoven(tcp, tcp->u_arg[1], nargs * current_wordsize, buf) < 0)
+	if (backend.umoven(tcp, tcp->u_arg[1], nargs * current_wordsize, buf) < 0)
  		return;

  	tcp->scno = scno;
@@ -409,7 +409,7 @@ decode_mips_subcall(struct tcb *tcp)
  	 * see linux/mips/get_syscall_args.c
  	 */
  	if (tcp->s_ent->nargs == MAX_ARGS) {
-		if (umoven(tcp,
+		if (backend.umoven(tcp,
  			   mips_REG_SP + MAX_ARGS * sizeof(tcp->u_arg[0]),
  			   sizeof(tcp->u_arg[0]),
  			   &tcp->u_arg[MAX_ARGS - 1]) < 0)
@@ -1111,6 +1111,9 @@ ptrace_setregs(pid_t pid)
  static void
  get_regs(pid_t pid)
  {
+	if (backend.get_regs)
+		get_regs_error = backend.get_regs (pid, (struct 
iovec*)&ARCH_IOVEC_FOR_GETREGSET);
+
  #undef USE_GET_SYSCALL_RESULT_REGS
  #ifdef ptrace_getregset_or_getregs

@@ -1155,6 +1158,9 @@ get_regs(pid_t pid)
  static int
  set_regs(pid_t pid)
  {
+	if (backend.set_regs)
+		return = backend.set_regs (pid, (struct 
iovec*)&ARCH_IOVEC_FOR_GETREGSET);
+
  	return ptrace_setregset_or_setregs(pid);
  }
  #endif /* ptrace_setregset_or_setregs */
@@ -1174,6 +1180,16 @@ free_sysent_buf(void *ptr)
  }

  /*
+ * Default case calls arch_get_scno which sets tcp->scno.
+ * A backend can redefine this to set that in a customized way.
+ */
+int
+default_arch_get_scno(struct tcb *tcp)
+{
+	return arch_get_scno(tcp);
+}
+
+/*
   * Returns:
   * 0: "ignore this ptrace stop", syscall_entering_decode() should 
return a "bail
   *    out silently" code.
@@ -1189,7 +1205,9 @@ get_scno(struct tcb *tcp)
  	if (get_regs_error)
  		return -1;

-	int rc = arch_get_scno(tcp);
+	int rc;
+	rc = backend.get_scno(tcp);
+
  	if (rc != 1)
  		return rc;

diff --git a/sysctl.c b/sysctl.c
index 176973a..3d2a9ed 100644
--- a/sysctl.c
+++ b/sysctl.c
@@ -56,7 +56,7 @@ SYS_FUNC(sysctl)
  	size = sizeof(int) * (unsigned long) info.nlen;
  	name = (size / sizeof(int) != (unsigned long) info.nlen) ? NULL : 
malloc(size);
  	if (name == NULL ||
-	    umoven(tcp, (unsigned long) info.name, size, name) < 0) {
+	    backend.umoven(tcp, (unsigned long) info.name, size, name) < 0) {
  		free(name);
  		if (entering(tcp))
  			tprintf("{%p, %d, %p, %p, %p, %lu}",
diff --git a/sysmips.c b/sysmips.c
index e095cb3..c57e2d5 100644
--- a/sysmips.c
+++ b/sysmips.c
@@ -55,7 +55,7 @@ SYS_FUNC(sysmips)

  		if (!verbose(tcp))
  			break;
-		if (umovestr(tcp, tcp->u_arg[1], (__NEW_UTS_LEN + 1),
+		if (backend.umovestr(tcp, tcp->u_arg[1], (__NEW_UTS_LEN + 1),
  			     nodename) < 0) {
  			printaddr(tcp->u_arg[1]);
  		} else {
diff --git a/tests/.gitignore b/tests/.gitignore
index dc92930..e7429ad 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -85,6 +85,7 @@ ftruncate
  ftruncate64
  futex
  futimesat
+gdbrsp
  gen_tests.am
  get_mempolicy
  getcpu
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f2109fd..3db3721 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -96,6 +96,7 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
  	execveat-v \
  	filter-unavailable \
  	fork-f \
+	gdbrsp \
  	getpid	\
  	getppid	\
  	gettid \
@@ -202,6 +203,7 @@ DECODER_TESTS = \
  	execve.test \
  	fadvise64.test \
  	futex.test \
+	gdbrsp.test \
  	getuid.test \
  	ioctl.test \
  	ioctl_dm-v.test \
diff --git a/tests/gdbrsp.c b/tests/gdbrsp.c
new file mode 100644
index 0000000..f39a7c9
--- /dev/null
+++ b/tests/gdbrsp.c
@@ -0,0 +1,79 @@
+/* This file is used to test the 'catch syscall' feature on GDB.
+
+   Please, if you are going to edit this file DO NOT change the syscalls
+   being called (nor the order of them).  If you really must do this, then
+   take a look at catch-syscall.exp and modify there too.
+
+   Written by Sergio Durigan Junior <sergiodj at linux.vnet.ibm.com>
+   September, 2008
+
+   This is the gdb catch-syscall.c test */
+
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+/* These are the syscalls numbers used by the test.  */
+
+int close_syscall = SYS_close;
+int chroot_syscall = SYS_chroot;
+/* GDB had a bug where it couldn't catch syscall number 0 (PR 16297).
+   In most GNU/Linux architectures, syscall number 0 is
+   restart_syscall, which can't be called from userspace.  However,
+   the "read" syscall is zero on x86_64.  */
+int read_syscall = SYS_read;
+#ifdef SYS_pipe
+int pipe_syscall = SYS_pipe;
+#else
+int pipe2_syscall = SYS_pipe2;
+#endif
+int write_syscall = SYS_write;
+#if defined(__arm__)
+/* Although 123456789 is an illegal syscall umber on arm linux, kernel
+   sends SIGILL rather than returns -ENOSYS.  However, arm linux kernel
+   returns -ENOSYS if syscall number is within 0xf0001..0xf07ff, so we
+   can use 0xf07ff for unknown_syscall in test.  */
+int unknown_syscall = 0x0f07ff;
+#else
+int unknown_syscall = 123456789;
+#endif
+int exit_group_syscall = SYS_exit_group;
+
+/* Set by the test when it wants execve.  */
+int do_execve = 0;
+
+int
+main (int argc, char *const argv[])
+{
+	int fd[2];
+	char buf1[2] = "a";
+	char buf2[2];
+
+	/* Test a simple self-exec, but only on request.  */
+	if (do_execve)
+	  execv (*argv, argv);
+
+	/* A close() with a wrong argument.  We are only
+	   interested in the syscall.  */
+	close (-1);
+
+	chroot (".");
+
+	pipe (fd);
+
+	write (fd[1], buf1, sizeof (buf1));
+	read (fd[0], buf2, sizeof (buf2));
+
+	/* Test vfork-event interactions.  Child exits immediately.
+	   (Plain fork won't work on no-mmu kernel configurations.)  */
+	if (vfork () == 0)
+	  _exit (0);
+
+	/* Trigger an intentional ENOSYS.  */
+	syscall (unknown_syscall);
+
+	/* The last syscall.  Do not change this.  */
+	_exit (0);
+}
diff --git a/tests/gdbrsp.test b/tests/gdbrsp.test
new file mode 100755
index 0000000..a0b83d7
--- /dev/null
+++ b/tests/gdbrsp.test
@@ -0,0 +1,107 @@
+#!/bin/sh
+#
+# Check -G option: gdb remote serial protocol
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+#    derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+. "${srcdir=.}/init.sh"
+
+check_prog gdb
+check_prog gdbserver
+
+gdb -batch -iex 'target remote | gdbserver --remote-debug - 
/usr/bin/true' >| /tmp/,gdb 2>&1
+if grep QCatchSyscalls /tmp/,gdb > /dev/null
+then :
+else framework_skip_ "Correct version of gdbserver is not available"
+fi
+
+run_prog > /dev/null
+
+# all-stop
+gdbserver --once --multi :65432 &
+if [ $? -gt 0 ] ; then
+	framework_skip_ "Unable to start gdbserver"
+fi
+$STRACE -G localhost:65432 $(readlink -f ../gdbrsp) >& $LOG
+
+EXPECTED="$LOG.expected"
+cat > "$EXPECTED" << __EOF__
+close.-1..* = -1 EBADF .Bad file descriptor.
+chroot.* = -1 EPERM .Operation not permitted.*
+pipe..3, 4...* = 0
+write.4, "a.0", 2..* = 2
+read.3, "a.0", 2..* = 2
+vfork.*unfinished.*
+.pid.*exited with 0.*
+.*vfork resumed.* =.*
+__EOF__
+
+match_grep "$LOG" "$EXPECTED"
+rm -f $EXPECTED
+
+# non-stop
+gdbserver --once --multi :65432 &
+if [ $? -gt 0 ] ; then
+	framework_skip_ "Unable to start gdbserver"
+fi
+$STRACE -G 'localhost:65432;non-stop' $(readlink -f ../gdbrsp) >& $LOG
+
+EXPECTED="$LOG.expected"
+cat > "$EXPECTED" << __EOF__
+close.-1..* = -1 EBADF .Bad file descriptor.
+chroot.* = -1 EPERM .Operation not permitted.*
+pipe..3, 4...* = 0
+write.4, "a.0", 2..* = 2
+read.3, "a.0", 2..* = 2
+vfork.*= [0-9][0-9]*
+syscall_123456789.*= -1.*
+exit_group.*
+__EOF__
+
+match_grep "$LOG" "$EXPECTED"
+rm -f $EXPECTED
+
+# -e write,read
+
+if [ $STRACE_ARCH = x86_64 ] ; then
+    gdbserver --once --multi :65432 &
+    if [ $? -gt 0 ] ; then
+	framework_skip_ "Unable to start gdbserver"
+    fi
+    $STRACE -G localhost:65432 -e write,read $(readlink -f ../gdbrsp) 
 >& $LOG
+
+    EXPECTED="$LOG.expected"
+    cat > "$EXPECTED" << __EOF__
+write.4, "a.0", 2..* = 2
+read.3, "a.0", 2..* = 2
+__EOF__
+
+    match_grep "$LOG" "$EXPECTED"
+    rm -f $EXPECTED
+fi
+
+# TODO -pPID
+
+exit 0
diff --git a/upeek.c b/upeek.c
index 22ee82d..d3f96a1 100644
--- a/upeek.c
+++ b/upeek.c
@@ -35,6 +35,8 @@
  #include "defs.h"
  #include "ptrace.h"

+#undef upeek
+
  int
  upeek(int pid, unsigned long off, kernel_ulong_t *res)
  {
diff --git a/upoke.c b/upoke.c
index dda0b8d..be4cea6 100644
--- a/upoke.c
+++ b/upoke.c
@@ -28,6 +28,8 @@
  #include "defs.h"
  #include "ptrace.h"

+#undef upoke
+
  int
  upoke(int pid, unsigned long off, kernel_ulong_t val)
  {
diff --git a/util.c b/util.c
index 05c9fb8..4e77084 100644
--- a/util.c
+++ b/util.c
@@ -427,7 +427,7 @@ void
  printfd(struct tcb *tcp, int fd)
  {
  	char path[PATH_MAX + 1];
-	if (show_fd_path && getfdpath(tcp, fd, path, sizeof(path)) >= 0) {
+	if (show_fd_path && backend.getfdpath(tcp, fd, path, sizeof(path)) >= 0) {
  		const char *str;
  		size_t len;
  		unsigned long inode;
@@ -696,7 +696,7 @@ printpathn(struct tcb *const tcp, const 
kernel_ulong_t addr, unsigned int n)
  		n = sizeof(path) - 1;

  	/* Fetch one byte more to find out whether path length > n. */
-	nul_seen = umovestr(tcp, addr, n + 1, path);
+	nul_seen = backend.umovestr(tcp, addr, n + 1, path);
  	if (nul_seen < 0)
  		printaddr(addr);
  	else {
@@ -755,9 +755,9 @@ printstr_ex(struct tcb *const tcp, const 
kernel_ulong_t addr,
  	if (size > len)
  		size = len;
  	if (style & QUOTE_0_TERMINATED)
-		rc = umovestr(tcp, addr, size, str);
+		rc = backend.umovestr(tcp, addr, size, str);
  	else
-		rc = umoven(tcp, addr, size, str);
+		rc = backend.umoven(tcp, addr, size, str);

  	if (rc < 0) {
  		printaddr(addr);
@@ -814,7 +814,7 @@ dumpiov_upto(struct tcb *const tcp, const int len, 
const kernel_ulong_t addr,
  		error_msg("Out of memory");
  		return;
  	}
-	if (umoven(tcp, addr, size, iov) >= 0) {
+	if (backend.umoven(tcp, addr, size, iov) >= 0) {
  		for (i = 0; i < len; i++) {
  			kernel_ulong_t iov_len = iov_iov_len(i);
  			if (iov_len > data_size)
@@ -864,7 +864,7 @@ dumpstr(struct tcb *const tcp, const kernel_ulong_t 
addr, const int len)
  		strsize = len + 16;
  	}

-	if (umoven(tcp, addr, len, str) < 0)
+	if (backend.umoven(tcp, addr, len, str) < 0)
  		return;

  	/* Space-pad to 16 bytes */
@@ -906,12 +906,13 @@ dumpstr(struct tcb *const tcp, const 
kernel_ulong_t addr, const int len)
  	}
  }

+
  int
  umoven_or_printaddr(struct tcb *const tcp, const kernel_ulong_t addr,
  		    const unsigned int len, void *const our_addr)
  {
  	if (!addr || !verbose(tcp) || (exiting(tcp) && syserror(tcp)) ||
-	    umoven(tcp, addr, len, our_addr) < 0) {
+	    backend.umoven(tcp, addr, len, our_addr) < 0) {
  		printaddr(addr);
  		return -1;
  	}
@@ -924,7 +925,7 @@ umoven_or_printaddr_ignore_syserror(struct tcb 
*const tcp,
  				    const unsigned int len,
  				    void *const our_addr)
  {
-	if (!addr || !verbose(tcp) || umoven(tcp, addr, len, our_addr) < 0) {
+	if (!addr || !verbose(tcp) || backend.umoven(tcp, addr, len, our_addr) 
< 0) {
  		printaddr(addr);
  		return -1;
  	}




More information about the Strace-devel mailing list