[PATCH 2/3] Add gdb remote protocol handling to strace

Stan Cox scox at redhat.com
Fri Feb 10 20:23:20 UTC 2017


The gdbserver directory contains the gdbserver backend, which 
communicates with gdbserver via the gdb remote protocol, e.g.
  strace sends packet: $vCont;c  (continue)
  strace receives packet: 
T05syscall_entry:16;06:b0e2ffffff7f0000;07:68e2ffffff7f0000;10:27a9b0f7ff7f0000;thread:p2162.2162;core:5;
  strace sends packet: $g (get registers)
  strace receives packet: daffffffffffffff0000000000000000...

The backend supports both x86_64 and i386.  This patch is large (sorry 
about that)  as all of these files are new.

Brief summary of changes:

gdbserver/x86_64/gdb_get_regs.c: handles the register fetching and is 
included in get_regs.

gdbserver/signals.{c,def}:  handles the signal conversion between strace 
and the gdb remote protocol.

gdbserver/gdbserver.c:  gdb_recv_exit, gdb_recv_stop, gdb_ok:  handle
parsing the remote packets.
gdb_init, gdb_init_syscalls, gdb_finalize_init:  Initialize the
gdbserver backend.
gdb_cleanup, gdb_detach:  Cleanup.
gdb_startup_child:  starts a child.
gdb_startup_attach:  attaches to an existing process.
gdb_trace: Primary tracing loop:  Get the packet,  call trace_syscall for
syscall_entry and syscall_return packets, send continue to
gdbserver.
gdb_get_regs:  handle 'g' packet.
gdb_read_mem:  Read memory from gdbserver.

gdbserver/protocol.c:  The knowledge of how to encode, send and
receive remote packets is localized here.  This is described in
"info gdb 'Remote Protocol' 'Overview'"
gdb_encode_hex, gdb_encode_hex_string, hex_nibble, gdb_decode_hex,
gdb_decode_hex_string:  handle the remote protocol encoding scheme.
gdb_begin_command, gdb_begin_tcp, gdb_begin_path:  Setup the
protocol.
gdb_send, send_packet:  Send a packet.
push_notification, pop_notification:  Handle the remote protocol
non-stop notifications.
gdb_recv, gdb_recv_packet:  Receive and parse a packet.



diff --git a/gdbserver/gdbserver.c b/gdbserver/gdbserver.c
new file mode 100644
index 0000000..ba2634e
--- /dev/null
+++ b/gdbserver/gdbserver.c
@@ -0,0 +1,950 @@
+/* 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.
+ */
+
+#define _GNU_SOURCE 1
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "defs.h"
+#include "gdbserver.h"
+#include "protocol.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);
+struct tcb *current_tcp;
+int strace_child;
+
+char* gdbserver = NULL;
+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
+        int general_pid; // process id that gdbserver is focused on
+        int general_tid; // thread id that gdbserver is focused on
+};
+
+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);
+				stop->general_pid = stop->pid;
+				stop->general_tid = stop->tid;
+			}
+			else
+				// an optional 2nd thread component is the
+				// thread that gdbserver is focused on
+				gdb_parse_thread(r, &stop->general_pid, &stop->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 (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)
+		       printf ("popped %s\n", reply);
+		  stop.reply = reply;
+		  reply = gdb_recv(gdb, &stop_size, false); /* vContc OK */
+	     }
+	     else {
+		  if (stop.reply[0] == 'T') {
+		       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;
+}
+
+int
+gdb_init(void)
+{
+# if ! defined X86_64
+	return -1;		/* 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 noack mode");
+
+        static char multi_cmd[] = "qSupported:multiprocess+"
+		";fork-events+;vfork-events+";
+
+	if (!followfork) {
+		/* Remove fork and vfork */
+		char *multi_cmd_semi = strchr (multi_cmd, ';');
+		*multi_cmd_semi = '\0';
+	}
+
+        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 multiprocess mode");
+	if (followfork) {
+		gdb_fork = strstr(reply, "fork-events+") != NULL;
+		if (!gdb_fork)
+			error_msg("couldn't enable gdb fork events handling");
+		gdb_fork = strstr(reply, "vfork-events+") != NULL;
+		if (!gdb_fork)
+			error_msg("couldn't enable gdb vfork 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 extended mode");
+
+        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 0;
+}
+
+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 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 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 to thread 
%d", tid);
+                }
+                if (current)
+                        gdb_init_syscalls();
+        }
+        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);
+}
+
+void
+gdb_finalize_init(void)
+{
+        // 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 = malloc(size);
+        if (!cmd)
+                error_msg_and_die("malloc failed!");
+        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);
+        free(cmd);
+
+        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);
+        // TODO normal strace attaches right before exec, so the first 
syscall
+        // seen is the execve with all its arguments.  Need to emulate 
that here?
+	// Need to handle TCB_HIDE_LOG and hide_log(tcp) ?
+}
+
+void
+gdb_startup_attach(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!");
+
+        char cmd[] = "vAttach;XXXXXXXX";
+        struct gdb_stop_reply stop;
+	static const char nonstop_cmd[] = "QNonStop:1";
+
+	gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+	if (gdb_ok())
+	       gdb_set_non_stop(gdb, true);
+
+        sprintf(cmd, "vAttach;%x", tcp->pid);
+        gdb_send(gdb, cmd, strlen(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 cmd[] = "vCont;t:pXXXXXXXX";
+		sprintf(cmd, "vCont;t:p%x.-1", tcp->pid);
+		if (!gdb_ok()) {
+		     stop.type = gdb_stop_unknown;
+		     break;
+		}
+		gdb_send(gdb, cmd, strlen(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, cmd, strlen(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)
+{
+        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.
+        }
+}
+
+// Returns true iff the main trace loop has to continue.
+// The gdb connection should be ready for a stop reply on entry,
+// and we'll leave it the same way if we return true.
+bool
+gdb_trace(void)
+{
+	struct gdb_stop_reply stop;
+	int gdb_sig = 0;
+	pid_t tid;
+	struct tcb *tcp = NULL;
+	unsigned int sig = 0;
+
+	stop = gdb_recv_stop(NULL);
+	do {
+	    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);
+                    case gdb_stop_error:
+                            // vCont error -> no more processes
+                            free(stop.reply);
+                            return false;
+                    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);
+
+            bool exited = false;
+            switch (stop.type) {
+                    case gdb_stop_exited:
+                            print_exited(tcp, tid, 
W_EXITCODE(stop.code, 0));
+                            droptcb(tcp);
+                            exited = true;
+                            break;
+
+                    case gdb_stop_terminated:
+                            print_signalled(tcp, tid, W_EXITCODE(0,
+                                            gdb_signal_to_target(tcp, 
stop.code)));
+                            droptcb(tcp);
+                            exited = true;
+                            break;
+
+                    default:
+                            break;
+            }
+
+            if (exited && !gdb_multiprocess) {
+                    free(stop.reply);
+                    return false;
+            }
+
+            get_regs(tid);
+
+            // 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?
+
+            switch (stop.type) {
+                    case gdb_stop_unknown:
+                    case gdb_stop_error:
+                    case gdb_stop_exited:
+                    case gdb_stop_terminated:
+                            // already handled above
+                            break;
+
+                    case gdb_stop_trap:
+                            // misc trap, nothing to do...
+                            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;
+                            trace_syscall(tcp, &sig);
+                            break;
+
+                    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;
+                                    trace_syscall(tcp, &sig);
+                            }
+                            break;
+
+                    case gdb_stop_signal:
+                            {
+                                    siginfo_t *si = NULL;
+                                    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;
+                                    print_stopped(tcp, si, 
gdb_signal_to_target(tcp, gdb_sig));
+                                    free(siginfo_reply);
+                            }
+                            break;
+            }
+
+	    free(stop.reply);
+	    stop.reply = pop_notification(&stop.size);
+	    if (stop.reply)	// cached out of order notification?
+	         stop = gdb_recv_stop(&stop);
+	    else
+	         break;
+	    } while (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 {
+                // just continue everyone
+                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(stop.general_tid, true);
+			if (gdb_has_non_stop (gdb) && stop.general_pid != stop.general_tid
+				&& general_tcp->flags & TCB_GDB_CONT_PID_TID)
+				sprintf(cmd, "vCont;c:p%x.%x", stop.general_pid, stop.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_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);
+}
+
+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) {
+                        errno = EINVAL;
+                        return -1;
+                }
+
+                chunk_len = size / 2;
+                if (check_nil && strnlen(out, chunk_len) < chunk_len)
+                        return 1;
+
+                addr += chunk_len;
+                out += chunk_len;
+                len -= chunk_len;
+        }
+
+        return 0;
+}
+
+int
+gdb_getfdpath(pid_t tid, 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", tid, fd);
+        return gdb_readlink(gdb, linkpath, buf, bufsize);
+}

diff --git a/gdbserver/gdbserver.h b/gdbserver/gdbserver.h
new file mode 100644
index 0000000..78e715f
--- /dev/null
+++ b/gdbserver/gdbserver.h
@@ -0,0 +1,43 @@
+/* Interface of strace features over the GDB remote protocol.
+ *
+ * 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 "protocol.h"
+
+char* gdbserver;
+
+int gdb_init(void);
+void gdb_finalize_init(void);
+void gdb_cleanup(void);
+void gdb_detach(struct tcb *tcp);
+void gdb_startup_child(char **argv);
+void gdb_startup_attach(struct tcb *tcp);
+bool gdb_trace(void);
+char *gdb_get_regs(pid_t tid, size_t *size);
+int gdb_read_mem(pid_t tid, long addr, unsigned int len, bool 
check_nil, char *out);
+int gdb_getfdpath(pid_t tid, int fd, char *buf, unsigned bufsize);

diff --git a/gdbserver/protocol.c b/gdbserver/protocol.c
new file mode 100644
index 0000000..ae21b2b
--- /dev/null
+++ b/gdbserver/protocol.c
@@ -0,0 +1,717 @@
+/* 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)
+static char** notifications;
+static int notifications_size;
+
+
+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 = calloc(1, sizeof(struct gdb_conn));
+    if (conn == NULL)
+        err(1, "calloc");
+
+    conn->ack = true;
+
+    // duplicate the handle to separate read/write state
+    int fd2 = dup(fd);
+    if (fd2 < 0)
+        err(1, "dup");
+
+    // open a FILE* for reading
+    conn->in = fdopen(fd, "rb");
+    if (conn->in == NULL)
+        err(1, "fdopen");
+
+    // open a FILE* for writing
+    conn->out = fdopen(fd2, "wb");
+    if (conn->out == NULL)
+        err(1, "fdopen");
+
+    // reset line state by acking any earlier input
+    fputc('+', conn->out);
+    fflush(conn->out);
+
+    return conn;
+}
+
+struct gdb_conn *
+gdb_begin_command(const char *command)
+{
+    int ret;
+    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)
+        err(1, "socketpair");
+
+    if ((ret = posix_spawn_file_actions_init(&file_actions)))
+        errx(1, "posix_spawn_file_actions_init: %s", strerror(ret));
+
+    // Close our end in the child.
+    if ((ret = posix_spawn_file_actions_addclose(&file_actions, fds[0])))
+        errx(1, "posix_spawn_file_actions_addclose: %s", strerror(ret));
+
+    // Copy the child's end to its stdout and stdin.
+    if (fds[1] != STDOUT_FILENO) {
+        if ((ret = posix_spawn_file_actions_adddup2(&file_actions, 
fds[1], STDOUT_FILENO)))
+            errx(1, "posix_spawn_file_actions_adddup2: %s", strerror(ret));
+        if ((ret = posix_spawn_file_actions_addclose(&file_actions, 
fds[1])))
+            errx(1, "posix_spawn_file_actions_addclose: %s", 
strerror(ret));
+    }
+    if ((ret = posix_spawn_file_actions_adddup2(&file_actions, 
STDOUT_FILENO, STDIN_FILENO)))
+        errx(1, "posix_spawn_file_actions_adddup2: %s", strerror(ret));
+
+    // Spawn the actual command.
+    if ((ret = posix_spawn(&pid, sh, &file_actions, NULL, argv, environ)))
+        errx(1, "posix_spawn: %s", strerror(ret));
+
+    // Cleanup.
+    if ((ret = posix_spawn_file_actions_destroy(&file_actions)))
+        errx(1, "posix_spawn_file_actions_destroy: %s", strerror(ret));
+    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)
+        errx(1, "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)
+        err(1, "connect");
+
+    // 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)
+        err(1, "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)
+      printf("\tSending packet: $%s\n", command);
+    fputc('$', out); // packet start
+    fwrite(command, 1, size, out); // payload
+    fprintf(out, "#%02x", sum); // packet end, checksum
+    fflush(out);
+
+    if (ferror(out))
+        err(1, "send");
+    else if (feof(out))
+        errx(0, "send: Connection 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 = 10;
+	 notifications = malloc(sizeof(void*) * notifications_size);
+	 memset(notifications, 0, sizeof(void*) * notifications_size);
+     }
+	
+     while (true) {
+	 for (idx = 0; idx < notifications_size; idx++) {
+	     if (notifications[idx] == NULL)
+		  break;
+	 }
+	 if (idx == notifications_size) {
+	     notifications_size *= 2;
+	     notifications = realloc(notifications, sizeof(void*) * 
notifications_size);
+	 }
+	 else {
+	     notifications[idx] = malloc(packet_size);
+	     memcpy(notifications[idx], packet, packet_size);
+	     break;
+	 }
+     }
+}
+
+
+char*
+pop_notification(size_t *size)
+{
+     int idx;
+     char *notification;
+
+     *size = 0;
+     for (idx = 0; idx < notifications_size; idx++) {
+	 if (notifications[idx] != NULL)
+	      break;
+     }
+
+     if (idx == notifications_size)
+	 return NULL;
+
+     notification = notifications[idx];
+     notifications[idx] = NULL;
+     *size = strlen(notification);
+     return notification;
+}
+
+
+void
+dump_notifications(char *packet, int pid, int tid)
+{
+     int idx;
+
+     for (idx = 0; idx < notifications_size; idx++) {
+	 if (notifications[idx] != NULL)
+	     printf ("Notify Dump: %s\n", notifications[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 = malloc(size);
+    if (reply == NULL)
+        err(1, "malloc");
+
+    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;
+		errx (1,"unknown non stop packet");
+	      }
+            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)
+                        err(1, "realloc");
+                }
+                reply[i] = '\0';
+
+		if (debug_flag)
+		    printf("\tPacket received: %s\n", reply);
+                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)
+                                err(1, "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)
+                err(1, "realloc");
+        }
+
+        // add one character
+        reply[i++] = c;
+    }
+
+    if (ferror(in))
+        err(1, "recv");
+    else if (feof(in))
+        errx(0, "recv: Connection closed");
+    else
+        errx(1, "recv: Unknown 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);
+	    if (debug_flag)
+	        printf ("Pushed %s\n", reply);
+	    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..adcfe42
--- /dev/null
+++ b/gdbserver/protocol.h
@@ -0,0 +1,69 @@
+/* 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 <stddef.h>
+#include <stdint.h>
+#include <stdbool.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);
+
+/* 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);

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..428f2c7
--- /dev/null
+++ b/gdbserver/x86_64/gdb_get_regs.c
@@ -0,0 +1,87 @@
+/* included in syscall.c:get_regs() */
+{
+        size_t size;
+        char *regs = gdb_get_regs(pid, &size);
+	struct tcb *tcp = pid2tcb(pid);
+        if (regs) {
+                if (size == 0 || regs[0] == 'E') {
+                        get_regs_error = -1;
+                        free(regs);
+                        return;
+                }
+
+		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;
+		}
+
+		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;
+		}
+
+                else {
+                        get_regs_error = -1;
+                        free(regs);
+                        return;
+                }
+	}
+}




More information about the Strace-devel mailing list