[PATCH v2] Add gdbserver backend
Stan Cox
scox at redhat.com
Thu Apr 30 19:23:59 UTC 2020
Add gdbserver backend as an alternate backend, which uses the GDB
remote system protocol to communicate with the gdbserver. The
main loop gets a packet from gdbserver, massages packet into
struct tcb_wait_data, registers into ARCH_IOVEC_FOR_GETREGSET,
returns to dispatch_event, which continues gdbsserver.
Usage gdbserver --multi :54321 & strace --gdbserver 54321 program
The protocol is described in: info gdb 'Remote Protocol' Packets
This is a simplification of the previous patch. This patch uses
#ifdef ENABLE_GDBSERVER gdb_<action>(...) in lieu of the dispatch table
mechanism, which added a layer of complexity that obscured the gdbserver
hooks.
The branch is: scox/gdbserver git at github.com:stanfordcox/strace.git
There is a build of this branch at:
https://copr.fedorainfracloud.org/coprs/scox/strace/build/1360929/
* (configure.ac, Makefile.am, tests/Makefile.am): Support gdbserver
* defs.h (trace_syscall, update_personality)
(arch_iovec_for_getreset): Allow access by gdbserver
* gdbserver/(gdbserver.c, protocol.c, gdbserver.h, protocol.h)
(signals.def, signals.h, x86_64/gdb_arch_defs.h)
(x86_64/gdb_get_regs.c): New gdbserver backend.
* strace.1.in: Document --gdbserver option.
* strace.c (detach, startup_attach, startup_child, init, cleanup)
(next_event, dispatch_event): Support gdbserver
* syscall.c (+arch_iovec_for_getregset, get_regs, get_scno): Support
gdbserver
* ucopy (umoven): Likewise.
* upeek.c (upeek): Likewise.
* upoke (upoke): Likewise
* (tests/gdbrsp.test, tests/gdbrsp.c): New gdbserver test.
---
Makefile.am | 11 +
configure.ac | 23 +
defs.h | 4 +
gdbserver/gdbserver.c | 1381 ++++++++++++++++++++++++++++++
gdbserver/gdbserver.h | 50 ++
gdbserver/protocol.c | 809 +++++++++++++++++
gdbserver/protocol.h | 84 ++
gdbserver/signals.def | 200 +++++
gdbserver/signals.h | 58 ++
gdbserver/x86_64/gdb_arch_defs.h | 1 +
gdbserver/x86_64/gdb_get_regs.c | 134 +++
strace.1.in | 14 +
strace.c | 96 ++-
syscall.c | 21 +-
tests/Makefile.am | 13 +-
tests/gdbrsp.c | 79 ++
tests/gdbrsp.test | 119 +++
ucopy.c | 11 +
upeek.c | 7 +
upoke.c | 7 +
20 files changed, 3104 insertions(+), 18 deletions(-)
create mode 100644 gdbserver/gdbserver.c
create mode 100644 gdbserver/gdbserver.h
create mode 100644 gdbserver/protocol.c
create mode 100644 gdbserver/protocol.h
create mode 100644 gdbserver/signals.def
create mode 100644 gdbserver/signals.h
create mode 100644 gdbserver/x86_64/gdb_arch_defs.h
create mode 100644 gdbserver/x86_64/gdb_get_regs.c
create mode 100644 tests/gdbrsp.c
create mode 100755 tests/gdbrsp.test
diff --git a/Makefile.am b/Makefile.am
index addbc223..f6b5da40 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,6 +36,12 @@ AM_CPPFLAGS = -I$(builddir)/$(OS)/$(ARCH) \
-I$(builddir) \
-I$(srcdir)
+if ENABLE_GDBSERVER
+AM_CPPFLAGS += -I$(builddir)/gdbserver/$(ARCH) \
+ -I$(srcdir)/gdbserver/$(ARCH) \
+ -I$(builddir)/gdbserver \
+ -I$(srcdir)/gdbserver
+endif
AM_CFLAGS_FOR_BUILD = $(WARN_CFLAGS_FOR_BUILD)
AM_CPPFLAGS_FOR_BUILD = $(AM_CPPFLAGS)
@@ -376,6 +382,11 @@ libstrace_a_SOURCES = \
strace_SOURCES_check = bpf_attr_check.c $(TYPES_CHECK_FILES)
+if ENABLE_GDBSERVER
+strace_SOURCES += \
+ gdbserver/gdbserver.c \
+ gdbserver/protocol.c
+endif
if ENABLE_STACKTRACE
libstrace_a_SOURCES += unwind.c unwind.h
if USE_LIBDW
diff --git a/configure.ac b/configure.ac
index 24a0f2ba..bea53207 100644
--- a/configure.ac
+++ b/configure.ac
@@ -940,6 +940,29 @@ AC_CHECK_TOOL([READELF], [readelf])
st_STACKTRACE
+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])
+# m4_define([additional_tracing_backends], [1])
+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 \
diff --git a/defs.h b/defs.h
index 0231cbf2..ffe9864a 100644
--- a/defs.h
+++ b/defs.h
@@ -515,6 +515,10 @@ extern int syscall_exiting_decode(struct tcb *,
struct timespec *);
extern int syscall_exiting_trace(struct tcb *, struct timespec *, int);
extern void syscall_exiting_finish(struct tcb *);
+extern struct iovec* arch_iovec_for_getregset(void);
+extern int trace_syscall(struct tcb *, unsigned int *);
+extern void update_personality(struct tcb *tcp, unsigned int personality);
+
extern void count_syscall(struct tcb *, const struct timespec *);
extern void call_summary(FILE *);
diff --git a/gdbserver/gdbserver.c b/gdbserver/gdbserver.c
new file mode 100644
index 00000000..e31a5ebf
--- /dev/null
+++ b/gdbserver/gdbserver.c
@@ -0,0 +1,1381 @@
+ /* Implementation of strace features over the GDB remote protocol.
+ *
+ * Copyright (c) 2015-2020 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 "gdbserver.h"
+#include "protocol.h"
+#include "scno.h"
+#include "signals.h"
+#include "ptrace_syscall_info.h"
+
+struct tcb *pid2tcb(int pid);
+struct tcb *alloctcb(int pid);
+void droptcb(struct tcb *tcp);
+void after_successful_attach(struct tcb *tcp, const unsigned int flags);
+void set_sighandler(int signo, void (*sighandler)(int), struct
sigaction *oldact);
+void set_sigaction(int signo, void (*sighandler)(int), struct sigaction
*oldact);
+
+extern struct tcb *current_tcp;
+extern int strace_child; /* referenced by print_signalled, print_exited */
+extern int detach_on_execve; /* set in init */
+
+static volatile int interrupted;
+static pid_t gdb_group_pid; /* the primary group process id */
+static pid_t gdb_exit_group_pid; /* pid of last __NR_exit_group syscall */
+static pid_t gdb_exit_pid; /* pid of last __NR_exit syscall */
+static pid_t gdb_w0_pid; /* pid of last W0;process packet */
+static int general_pid; /* process id that gdbserver is focused on */
+static int general_tid; /* thread id that gdbserver is focused on */
+static int thread_count = 0; /* */
+
+
+static const char process_needle[] = ";process:";
+char *gdbserver = NULL;
+
+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,
+ GDB_STOP_FORK,
+ GDB_STOP_VFORK,
+ GDB_STOP_VFORKDONE,
+};
+
+
+struct gdb_stop_reply {
+ char *reply;
+ size_t size;
+
+ enum gdb_stop type;
+ int code; /* error, signal, exit status, scno */
+ pid_t pid; /* process id, aka kernel tgid */
+ pid_t tid; /* thread id, aka kernel tid */
+};
+
+/* Note: Same as strace.c */
+#include "trace_event.h"
+struct tcb_wait_data {
+ enum trace_event te; /**< Event passed to dispatch_event() */
+ int status; /**< status, returned by wait4() */
+ unsigned long msg; /**< Value returned by PTRACE_GETEVENTMSG */
+ siginfo_t si; /**< siginfo, returned by PTRACE_GETSIGINFO */
+};
+
+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)
+{
+ if (tcp == NULL)
+ return -1;
+ 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;
+
+ debug_msg("\t%s %s", __FUNCTION__, reply);
+ /* 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)) {
+ int pid, tid;
+ char *n = strtok(nr, ":");
+ char *r = strtok(NULL, "");
+
+ if (!n || !r)
+ continue;
+
+ if (!strcmp(n, "thread")) {
+ gdb_parse_thread(r, &pid, &tid);
+ if (pid != gdb_w0_pid
+ && stop->type != GDB_STOP_VFORK
+ && stop->type != GDB_STOP_FORK) {
+ general_pid = stop->pid = pid;
+ general_tid = stop->tid = 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);
+ debug_msg("\t%s syscall_entry %d", __FUNCTION__, stop->code);
+ }
+ } 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);
+ debug_msg("\t%s syscall_return %d", __FUNCTION__, stop->code);
+ }
+ } else if (!strcmp(n, "fork")) {
+ if (stop->type == GDB_STOP_TRAP) {
+ stop->type = GDB_STOP_FORK;
+ gdb_parse_thread(r, &pid, &tid);
+ if (pid != gdb_w0_pid) {
+ general_pid = stop->pid = pid;
+ general_tid = stop->tid = tid;
+ }
+ }
+ } else if (!strcmp(n, "vfork")) {
+ if (stop->type == GDB_STOP_TRAP) {
+ stop->type = GDB_STOP_VFORK;
+ gdb_parse_thread(r, &pid, &tid);
+ if (pid != gdb_w0_pid) {
+ general_pid = stop->pid = pid;
+ general_tid = stop->tid = tid;
+ }
+ }
+ } else if (!strcmp(n, "vforkdone")) {
+ if (stop->type == GDB_STOP_TRAP) {
+ stop->type = GDB_STOP_VFORKDONE;
+ }
+ } else if (!strcmp(n, "exec")) {
+ }
+ }
+}
+
+static bool
+gdb_ok(void)
+{
+ size_t size;
+ char *reply = gdb_recv(gdb, &size, recv_want_ok);
+ bool ok = size == 2 && !strcmp(reply, "OK");
+ free(reply);
+ return ok;
+}
+
+
+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_needle);
+
+ if (process) {
+ stop->pid = gdb_decode_hex_str(process +
+ sizeof(process_needle) - 1);
+
+ /* we don't really know the tid, so just use PID for now */
+ /* TODO should exits enumerate all threads we know of a process? */
+ stop->tid = stop->pid;
+ }
+ if (gdb_has_non_stop(gdb)) {
+ do {
+ /* non-stop mode awaits a reply; see gdb_recv_stop */
+
+ size_t this_size;
+ gdb_send_cstr(gdb, "vStopped");
+ reply = gdb_recv(gdb, &this_size, recv_want_stop);
+ if (strcmp(reply, "OK") == 0)
+ break;
+ push_notification(reply, this_size);
+ } while (true);
+ }
+}
+
+static struct gdb_stop_reply
+gdb_recv_stop(struct gdb_stop_reply *cached_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 reply_size;
+
+
+ if (cached_reply)
+ /* pop_notification gave us a cached notification */
+ stop = *cached_reply;
+ else
+ stop.reply = gdb_recv(gdb, &stop.size, recv_want_stop);
+
+ /* non-stop packet order:
+ * 1) client sends: $vCont;c (in gdb_restart_process)
+ * 2) server sends: OK
+ * 3) server sends: %Stop:T05syscall_entry (possibly out of order)
+ * 4) client sends: $vStopped
+ * 5) server possibly sends 0 or more: T05syscall_entry
+ * 6) client sends to each: $vStopped
+ * 7) server sends: OK
+ */
+
+ if (gdb_has_non_stop(gdb)) {
+ /* Do we have an out of order notification? (see gdb_recv) */
+ if (cached_reply)
+ gdb_ok(); /* Only 2) required */
+ else { /* 2) 3) */
+ while (stop.reply[0] != 'T' && stop.reply[0] != 'W')
+ stop.reply = gdb_recv(gdb, &stop.size, recv_want_stop);
+ }
+ }
+ if (!cached_reply && gdb_has_non_stop(gdb) && (stop.reply[0] == 'T')) {
+ do { /* 4) 5) 6) 7) */
+ gdb_send_cstr(gdb, "vStopped");
+ reply = gdb_recv(gdb, &reply_size, recv_want_stop);
+ if (strcmp(reply, "OK") == 0)
+ break;
+ push_notification(reply, reply_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;
+}
+
+bool
+gdb_start_init(int argc, char *argv[])
+{
+ gdb_signal_map_init();
+
+ if (gdbserver[0] == '|')
+ gdb = gdb_begin_command(gdbserver + 1);
+ else if (strchr(gdbserver, ':') && !strchr(gdbserver, '/')) {
+ /* An optional fragment ":nonstop" can be given to use
+ * nonstop protocol
+ */
+ const char *node;
+ const char *service;
+ const char *stop_option;
+ if (gdbserver[0] == ':') {
+ node = "localhost";
+ service = strtok(gdbserver, ":");
+ stop_option = strtok(NULL, ":");
+ } else {
+ node = strtok(gdbserver, ":");
+ service = strtok(NULL, ":");
+ stop_option = strtok(NULL, ":");
+ }
+ if (stop_option && (!strcmp (stop_option, "non-stop") || !strcmp
(stop_option, "nonstop")))
+ gdb_nonstop = true;
+ 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+";
+
+ snprintf(multi_cmd, sizeof(multi_cmd), "qSupported:multiprocess+;"
+ "QThreadEvents+%s%s",
+ followfork ? ";fork-events+;vfork-events+" : "",
+ detach_on_execve ? ";exec-events" : "");
+
+ gdb_send_str(gdb, multi_cmd);
+
+ size_t size;
+ bool gdb_fork;
+ char *reply = gdb_recv(gdb, &size, recv_want_other);
+ gdb_multiprocess = strstr(reply, "multiprocess+") != NULL;
+ if (!gdb_multiprocess)
+ error_msg("couldn't enable GDB server multiprocess mode");
+ if (followfork) {
+ gdb_fork = strstr(reply, "vfork-events+") != NULL;
+ if (!gdb_fork)
+ error_msg("couldn't enable GDB server vfork events handling");
+ gdb_fork = strstr(reply, "fork-events+") != NULL;
+ if (!gdb_fork)
+ error_msg("couldn't enable GDB server fork events handling");
+ }
+ if (!detach_on_execve) {
+ if (!strstr(reply, "exec-events+"))
+ error_msg("couldn't enable GDB server exec events handling");
+ }
+ free(reply);
+
+ gdb_send_cstr(gdb, "!");
+ gdb_extended = gdb_ok();
+ if (!gdb_extended)
+ error_msg("couldn't enable GDB server extended mode");
+
+ /* TODO allow for strace's -I setting */
+ gdb_send_cstr(gdb,
+ "QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97;");
+ if (!gdb_ok())
+ error_msg("couldn't enable GDB server signal passing");
+
+ /* TODO generate this list programmatically. */
+
+ 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_cstr(gdb, program_signals);
+ if (!gdb_ok())
+ error_msg("couldn't enable GDB server signal passing");
+
+ gdb_send_cstr(gdb, "vCont?");
+ reply = gdb_recv(gdb, &size, recv_want_other);
+ 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_str(gdb, syscall_set);
+ if (!gdb_ok())
+ error_msg("couldn't enable GDB server syscall catching");
+}
+
+static struct tcb*
+gdb_find_thread(int tid, bool current, bool multiprocess)
+{
+ if (tid < 0)
+ return NULL;
+
+ /* Look up 'tid' in our table. */
+ struct tcb *tcp = pid2tcb(tid);
+ if (!tcp) {
+ if (tid == gdb_exit_group_pid)
+ return NULL;
+ tcp = alloctcb(tid);
+ after_successful_attach(tcp, 0);
+
+ if (!current) {
+ char cmd[] = "Hgpxxxxxxxx.xxxxxxxx";
+ if (multiprocess)
+ snprintf(cmd, sizeof(cmd), "Hgp%x.%x", tid, tid);
+ else
+ snprintf(cmd, sizeof(cmd), "Hg%x", tid);
+ gdb_send_str(gdb, cmd);
+ current = gdb_ok();
+ if (!current)
+ error_msg("couldn't set GDB server 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) */
+
+ gdb_send_cstr(gdb, "qfThreadInfo");
+
+ size_t size;
+ char *reply = gdb_recv(gdb, &size, recv_want_other);
+ 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, false);
+
+ if (tcp && !current_tcp)
+ current_tcp = tcp;
+ }
+
+ free(reply);
+
+ gdb_send_cstr(gdb, "qsThreadInfo");
+ reply = gdb_recv(gdb, &size, recv_want_other);
+ }
+
+ free(reply);
+}
+
+static void
+interrupt(int sig)
+{
+ interrupted = sig;
+}
+
+void
+gdb_end_init(void)
+{
+ ptrace_get_syscall_info_supported = false;
+
+ /* TODO interface with -I? */
+ set_sighandler(SIGHUP, interrupt, NULL);
+ set_sighandler(SIGINT, interrupt, NULL);
+ set_sighandler(SIGQUIT, interrupt, NULL);
+ set_sighandler(SIGPIPE, interrupt, NULL);
+ set_sighandler(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();
+ gdb_group_pid = current_tcp->pid;
+
+ /* Everything was stopped from startup_child/startup_attach,
+ * now continue them all so the next reply will be a stop
+ * packet */
+ gdb_send_str(gdb, gdb_vcont ? "vCont;c" : "c");
+ /* TODO Factor out process restarting */
+}
+
+void
+gdb_cleanup(int fatal_sig)
+{
+ 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 */
+
+ /* Get the realpath of the program path argument */
+ char *real_argv0 = realpath(argv[0], NULL);
+ if (!real_argv0)
+ error_msg_and_die("Can't access '%s': No such file or directory",
argv[0]);
+ argv[0] = real_argv0;
+
+ for (i = 0; argv[i]; ++i) {
+ size += 1 + 2 * strlen(argv[i]); /*;hexified-argument */
+ }
+
+ if (gdb_nonstop) {
+ gdb_send_cstr(gdb, "QNonStop: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 of %s %s",
+ argv[0], argv[1]);
+ 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);
+
+ after_successful_attach(tcp, 0);
+ 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;
+ free (real_argv0);
+}
+
+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;
+ char vattach_cmd[] = "vAttach;XXXXXXXX";
+ snprintf(vattach_cmd, sizeof(vattach_cmd), "vAttach;%x", tcp->pid);
+
+ gdb_send_cstr(gdb, "QNonStop:1");
+ if (!gdb_ok())
+ stop.type = GDB_STOP_UNKNOWN;
+ else 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
+ */
+ gdb_set_non_stop(gdb, true);
+ gdb_send_str(gdb, vattach_cmd);
+ if (!gdb_ok()) {
+ stop.type = GDB_STOP_UNKNOWN;
+ break;
+ }
+
+ char vcont_cmd[] = "vCont;t:pXXXXXXXXXXX";
+ snprintf(vcont_cmd, sizeof(vcont_cmd),
+ "vCont;t:p%x.-1", tcp->pid);
+ gdb_send_str(gdb, vcont_cmd);
+ stop = gdb_recv_stop(NULL);
+ } while (0);
+
+ if (stop.type == GDB_STOP_UNKNOWN) {
+ gdb_send_cstr(gdb, "QNonStop:0");
+
+ 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_str(gdb, 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);
+ break;
+ case GDB_STOP_TRAP:
+ break;
+ case GDB_STOP_SIGNAL:
+ if (stop.code == 0)
+ break;
+ __attribute__ ((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);
+ }
+
+ after_successful_attach(tcp, 0);
+ gdb_init_syscalls();
+
+ /* TODO check QUIET_ATTACH */
+ if (true)
+ 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 || gdb == NULL)
+ return;
+ if (gdb_multiprocess) {
+ char cmd[] = "D;XXXXXXXXXXX";
+ snprintf(cmd, sizeof(cmd), "D;%x", gdb_exit_group_pid);
+ gdb_send_str(gdb, cmd);
+ } else {
+ gdb_send_cstr(gdb, "D");
+ }
+
+ if (!gdb_ok()) {
+ /* is it still alive? */
+ char cmd[] = "T;XXXXXXXXXXX";
+ snprintf(cmd, sizeof(cmd), "T;%x", tcp->pid);
+ gdb_send_str(gdb, cmd);
+ if (gdb_ok())
+ error_msg("GDB server failed to detach %d", tcp->pid);
+ /* otherwise it's dead, or already detached, fine. */
+ }
+
+ /* TODO check QUIET_ATTACH */
+ if ((tcp->flags & TCB_ATTACHED))
+ error_msg("Process %u detached", tcp->pid);
+
+ if (! already_detaching)
+ already_detaching = true;
+
+ droptcb(tcp);
+}
+
+
+/* The typical strace.c::main loop path is:
+ * gdb_next_event to get a syscall packet from gdbserver
+ * gdb_recv_stop to handle "T"/"W" packets
+ * gdb_recv_signal to parse the packet
+ * gdb_parse_thread to parse the thread
+ * gdb_find_thread to add the thread to strace tcb
+ * dispatch_event to handle the syscall
+ * trace_syscall does the work of displaying the syscall
+ * gdb_restart_process sends a continue to gdbserver
+ * (gdb_next_event, dispatch_event, ...)
+*/
+
+static const char *trace_event_str [] = {"BREAK", "NEXT", "RESTART",
"SYSCALL STOP", "SIGNAL DELIVERY STOP", "SIGNALLED", "GROUP STOP",
"EXITED", "STOP BEFORE EXECVE", "STOP BEFORE EXIT" };
+#define GDB_NEXT_EVENT_RETURN(wd) \
+ do { \
+ debug_msg("\tDispatching %s trace event\tcode=%d",
trace_event_str[wd->te], stop.code); \
+ return wd; \
+ } while (0)
+
+struct tcb_wait_data *
+gdb_next_event(void)
+{
+ static struct tcb_wait_data wait_data;
+ struct tcb_wait_data *wd = &wait_data;
+ int gdb_sig = 0;
+ pid_t tid;
+ struct tcb *tcp = NULL;
+ siginfo_t *si = &wd->si;
+
+ debug_msg("Entering %s previous_state %s\n", __FUNCTION__,
trace_event_str[wd->te]);
+ if (interrupted) {
+ wd->te = TE_BREAK;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+
+ /* If we previously received a process exit reply then exit strace
dispatch */
+ if (stop.reply && stop.reply[0] == 'W' && wd->te == TE_EXITED &&
stop.pid == gdb_group_pid) {
+ wd->te = TE_BREAK;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+
+ 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 we received a process exit reply then exit */
+ if (stop.reply && stop.reply[0] == 'W') {
+ const char *process = strstr(stop.reply, process_needle);
+ if (process) {
+ gdb_w0_pid = gdb_decode_hex_str(process +
+ sizeof(process_needle) - 1);
+ if (gdb_w0_pid == gdb_group_pid) {
+ current_tcp = current_tcp ? : gdb_find_thread(stop.pid, true, false);
+ wd->status = W_EXITCODE (gdb_signal_to_target(current_tcp,
gdb_sig), 0);
+ wd->te = TE_EXITED;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+ }
+ }
+
+ if (stop.size == 0)
+ error_msg_and_die("GDB server gave an empty stop reply!?");
+ else if (stop.type == GDB_STOP_EXITED)
+ /* If we previously exited then we need to continue before waiting
for a stop */
+ gdb_restart_process (0, current_tcp, 0);
+ else {
+ char cmd[] = "Hgpxxxxxxxx.xxxxxxxx";
+ snprintf(cmd, sizeof(cmd), "Hgp%x.%x", stop.pid, stop.tid);
+ gdb_send_str(gdb, cmd);
+ gdb_ok();
+ }
+
+ 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);
+ wd->te = TE_BREAK;
+ GDB_NEXT_EVENT_RETURN (wd);
+ default:
+ break;
+ }
+
+
+ tid = -1;
+ tcp = NULL;
+
+ if (gdb_multiprocess) {
+ tid = stop.tid;
+ tcp = gdb_find_thread(tid, true, false);
+ 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 %d from stop reply: %s", tid,
stop.reply);
+
+ /* Exit if the process has gone away */
+ if (tcp == 0)
+ return NULL;
+
+ tid = tcp->pid;
+
+ /* 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;
+ }
+
+ switch (stop.type) {
+ case GDB_STOP_EXITED:
+ if (tcp->pid == gdb_group_pid) {
+ wd->status = W_EXITCODE(stop.code, 0);
+ wd->te = TE_EXITED;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+ wd->status = W_EXITCODE(0, gdb_signal_to_target(tcp, stop.code));
+ wd->te = TE_EXITED;
+ GDB_NEXT_EVENT_RETURN (wd);
+ break;
+ case GDB_STOP_TERMINATED:
+ wd->status = W_EXITCODE(0, gdb_signal_to_target(tcp, stop.code));
+ wd->te = TE_SIGNALLED;
+ GDB_NEXT_EVENT_RETURN (wd);
+ 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. */
+
+ debug_msg("\t%s GDB_STOP_SYSCALL_ENTRY %d", __FUNCTION__, stop.code);
+ tcp->scno = stop.code;
+ gdb_sig = stop.code;
+ wd->status = gdb_signal_to_target(tcp, 0);
+ if (stop.code == __NR_exit_group) {
+ gdb_exit_group_pid = tcp->pid;
+ tcp->flags |= TCB_HIDE_LOG;
+ wd->te = TE_GROUP_STOP;
+ GDB_NEXT_EVENT_RETURN (wd);
+ } else if (stop.code == __NR_clone) {
+ thread_count += 1;
+ wd->te = TE_SYSCALL_STOP;
+ GDB_NEXT_EVENT_RETURN (wd);
+ } else if (stop.code == __NR_exit) {
+ thread_count -= 1;
+ gdb_exit_pid = tcp->pid;
+ /* this thread is exiting so focus on another */
+ char cmd[] = "Hgpxxxxxxxx.xxxxxxxx";
+ snprintf(cmd, sizeof(cmd), "Hgp%x.0", stop.pid);
+ gdb_send_str(gdb, cmd);
+ gdb_ok();
+ } else {
+ wd->te = TE_SYSCALL_STOP;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+ 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. */
+ debug_msg("\t%s GDB_STOP_SYSCALL_RETURN %d", __FUNCTION__, stop.code);
+ if (exiting(tcp)) {
+ tcp->scno = stop.code;
+ gdb_sig = stop.code;
+ wd->status = gdb_signal_to_target(tcp, gdb_sig);
+ if (stop.code == __NR_exit_group) {
+ wd->te = TE_GROUP_STOP;
+ tcp->flags |= TCB_HIDE_LOG;
+ GDB_NEXT_EVENT_RETURN (wd);
+ } else if (stop.code == __NR_exit) {
+ gdb_exit_pid = tcp->pid;
+ } else {
+ wd->te = TE_SYSCALL_STOP;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+ }
+ break;
+
+ case GDB_STOP_SIGNAL:
+ {
+ size_t siginfo_size;
+
+ /* TODO 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)) */
+
+ if (stop.code == SIGABRT) {
+ /* strace.c::print_signalled handles this by checking WTERMSIG */
+ wd->status = gdb_signal_to_target(tcp, gdb_sig);
+ wd->te = TE_BREAK;
+ GDB_NEXT_EVENT_RETURN (wd);
+ }
+ else {
+ char *siginfo_reply =
+ gdb_xfer_read(gdb, "siginfo", "", &siginfo_size);
+ if (siginfo_reply && siginfo_size == sizeof(siginfo_t))
+ *si = *((siginfo_t *) siginfo_reply);
+
+ gdb_sig = si->si_signo;
+ free(siginfo_reply);
+ }
+ wd->status = W_EXITCODE (gdb_signal_to_target(tcp, gdb_sig), 0);
+ wd->te = TE_SIGNAL_DELIVERY_STOP;
+ GDB_NEXT_EVENT_RETURN (wd);
+ break;
+ }
+ case GDB_STOP_FORK:
+ {
+ gdb_find_thread(stop.pid, false, true);
+ break;
+ }
+
+ case GDB_STOP_VFORK:
+ {
+ gdb_find_thread(stop.pid, false, true);
+ break;
+ }
+
+ default:
+ /* TODO Do we need to handle gdb_multiprocess here? */
+ break;
+ }
+
+ wd->te = TE_RESTART;
+ GDB_NEXT_EVENT_RETURN (wd);
+}
+
+
+char *
+gdb_get_all_regs(pid_t tid, size_t *size)
+{
+ if (!gdb)
+ return NULL;
+
+ if (tid == gdb_w0_pid) {
+ if (current_tcp->pid == tid)
+ current_tcp = pid2tcb(gdb_group_pid);
+ /*
+ * 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_w0_pid = 0;
+ }
+
+ gdb_send_cstr(gdb, "g");
+
+ return gdb_recv(gdb, size, recv_want_other);
+}
+
+
+#ifdef GDBSERVER_ARCH_HAS_GET_REGS
+# include "gdb_get_regs.c"
+#else
+long gdb_get_regs(pid_t pid, void *io) { return -1; }
+#endif
+
+long
+gdb_get_registers(struct tcb * const tcp)
+{
+ /* PTRACE_GETREGSET fetches registers into ARCH_IOVEC_FOR_GETREGSET.
+ * Fetch registers from gdbserver then transfer to same struct
+ */
+ return gdb_get_regs(tcp->pid, arch_iovec_for_getregset());
+}
+
+
+#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_set_scno(struct tcb *tcp, kernel_ulong_t scno)
+{
+ return -1;
+}
+
+void *
+gdb_get_siginfo(void *data)
+{
+ struct tcb_wait_data *wd = data;
+
+ return &wd->si;
+}
+
+int
+gdb_read_mem(pid_t tid, long addr, unsigned int len, bool check_nil,
char *out)
+{
+ unsigned int chunk_limit = 0x40;
+ unsigned int original_len = 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.
+ */
+ while (len) {
+ char cmd[] = "mxxxxxxxxxxxxxxxx,xxxx";
+ unsigned int chunk_len = len < chunk_limit ? len : chunk_limit;
+
+ snprintf(cmd, sizeof(cmd), "m%lx,%x", addr, chunk_len);
+ gdb_send_str(gdb, cmd);
+
+ size_t size;
+ char *reply = gdb_recv(gdb, &size, recv_want_other);
+
+ /*
+ * Try fetching a buffer. gdbserver may return an error
+ * because the initial address failed or there were fewer
+ * than len bytes. If the latter try fetching one byte at
+ * a time.
+ */
+ if (size < 2 || reply[0] == 'E') {
+ if (chunk_limit != 1) {
+ chunk_limit = 1;
+ continue;
+ } else if (original_len == len) {
+ free(reply);
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ if (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. */
+ snprintf(cmd, sizeof(cmd), "X%lx,%x:", addr, len);
+ j = strlen(cmd);
+
+ for (i = 0; i < len; i++)
+ cmd[j++] = buffer[i];
+
+ cmd[j] = '\0';
+ gdb_send_str(gdb, 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(struct tcb *tcp, unsigned long off, kernel_ulong_t *res)
+{
+ return gdb_read_mem(tcp->pid, off, current_wordsize, false, (char*)res);
+}
+
+
+int
+gdb_upoke(struct tcb *tcp, unsigned long off, kernel_ulong_t res)
+{
+ kernel_ulong_t buffer = res;
+ return gdb_write_mem(tcp->pid, off, current_wordsize, (char*)&buffer);
+}
+
+
+bool
+gdb_handle_arg(char arg, char *optarg)
+{
+ if (arg == 'f')
+ return true;
+ else if (arg != 'G')
+ return false;
+
+ gdbserver = optarg;
+ return true;
+}
+
+
+/* Send gdbserver a vCont continue packet */
+
+int
+gdb_restart_process(const unsigned int op, struct tcb *current_tcp,
unsigned int restart_sig)
+{
+ debug_msg("%s after %-.32s... type=%d code=%d\n",__FUNCTION__,
+ stop.reply, stop.type, stop.code);
+ /* gdb_restart_process <- restart_process <- dispatch_event
(next_event) <- main */
+ int gdb_sig = stop.type;
+ pid_t tid = current_tcp ? current_tcp->pid : 0;
+
+ if (gdb_w0_pid == gdb_group_pid)
+ return true;
+ else if (gdb_sig == GDB_STOP_TRAP) {
+ if (gdb_vcont) {
+ /* send the signal to this target and continue everyone else */
+ char cmd[] = "vCont;Cxx:xxxxxxxxxxx;c";
+
+ snprintf(cmd, sizeof(cmd),
+ "vCont;C%02x:%x;c", gdb_sig, tid);
+ gdb_send_str(gdb, cmd);
+ } else {
+ /* just send the signal */
+ char cmd[] = "Cxx";
+
+ snprintf(cmd, sizeof(cmd), "C%02x", gdb_sig);
+ gdb_send_str(gdb, cmd);
+ }
+ return true;
+ } else if (!gdb_vcont){
+ gdb_send_cstr(gdb, "c");
+ return true;
+ }
+
+ /* Default case: SIGTRAP and gdb_vcont */
+ char cmd[] = "vCont;c:xxxxxxxx.xxxxxxxx";
+ pid_t this_current_pid = current_tcp ? current_tcp->pid : 0;
+ debug_msg("current %x/%d general %x.%x/%d.%d group %x/%d exit group
%x/%d w0 %x/%d\n",
+ this_current_pid, this_current_pid, general_pid, general_tid,
general_pid, general_tid,
+ gdb_group_pid, gdb_group_pid, gdb_exit_group_pid,
gdb_exit_group_pid, gdb_w0_pid, gdb_w0_pid);
+ /* Unlike a gdb client we don't have "set inferior" so juggle all
processes/threads */
+ if (stop.type == GDB_STOP_VFORK) {
+ /* Continue the vforked thread */
+ snprintf(cmd, sizeof(cmd), "vCont;c:p%x.%x",
+ general_pid, general_tid);
+ } else if (gdb_has_non_stop(gdb) && thread_count
+ && general_pid != gdb_w0_pid) {
+ if (gdb_exit_pid == general_tid || stop.reply[0] == 'W')
+ /* Continue another thread since this one exited */
+ snprintf(cmd, sizeof(cmd), "vCont;c:p%x.0",
+ general_pid);
+ else /* Continue current thread */
+ snprintf(cmd, sizeof(cmd), "vCont;c:p%x.%x",
+ general_pid, general_tid);
+ } else if (current_tcp == NULL) {
+ /* Continue gdb inferior if no strace process */
+ if (gdb_has_non_stop(gdb))
+ snprintf(cmd, sizeof(cmd), "vCont;c:p%x.%x",
+ gdb_group_pid, gdb_group_pid);
+ else
+ snprintf(cmd, sizeof(cmd), "vCont;c:p%x.-1", gdb_group_pid);
+ } else if (general_pid != gdb_group_pid && general_pid != gdb_w0_pid) {
+ /* Continue current gdb process */
+ snprintf(cmd, sizeof(cmd), "vCont;c:p%x.0", general_pid);
+ } else
+ /* Continue all as default */
+ snprintf(cmd, sizeof(cmd), "vCont;c");
+
+ gdb_send_str(gdb, cmd);
+
+ return true;
+}
diff --git a/gdbserver/gdbserver.h b/gdbserver/gdbserver.h
new file mode 100644
index 00000000..e7e44752
--- /dev/null
+++ b/gdbserver/gdbserver.h
@@ -0,0 +1,50 @@
+/* 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;
+
+bool gdb_handle_arg(char arg, char *optarg);
+bool gdb_start_init(int argc, char *argv[]);
+void gdb_end_init(void);
+void gdb_startup_child(char **argv);
+void gdb_attach_tcb(struct tcb *tcp);
+void gdb_detach(struct tcb *tcp);
+void gdb_cleanup(int fatal_sig);
+struct tcb_wait_data *gdb_next_event(void);
+void * gdb_get_siginfo(void *data);
+int gdb_restart_process(const unsigned int restart_op, struct tcb
*current_tcp, unsigned int restart_sig);
+long gdb_get_registers(struct tcb * const tcp);
+int gdb_get_scno(struct tcb *tcp);
+int gdb_set_scno(struct tcb *tcp, kernel_ulong_t scno);
+int gdb_umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int
len, void *const our_addr);
+int gdb_umovestr(struct tcb *const tcp, kernel_ulong_t addr, unsigned
int len, char *laddr);
+int gdb_upeek(struct tcb *tcp, unsigned long off, kernel_ulong_t *res);
+int gdb_upoke(struct tcb *tcp, unsigned long off, kernel_ulong_t res);
diff --git a/gdbserver/protocol.c b/gdbserver/protocol.c
new file mode 100644
index 00000000..d2915dbd
--- /dev/null
+++ b/gdbserver/protocol.c
@@ -0,0 +1,809 @@
+/* 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;
+};
+
+/* TODO move inside gdb_conn */
+/* 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;
+ 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");
+
+ pid = fork ();
+ if (pid < 0)
+ perror_msg_and_die("fork failed");
+ else if (pid == 0) {
+ /* Close our end in the child. */
+ close(fds[0]);
+ /* Copy the child's end to its stdout and stdin. */
+ if (fds[1] != STDOUT_FILENO) {
+ dup2(fds[1], STDOUT_FILENO);
+ close(fds[1]);
+ }
+ dup2(STDOUT_FILENO, STDIN_FILENO);
+ execve(sh, argv, environ);
+ } else {
+ 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]);
+ }
+ return NULL;
+}
+
+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);
+}
+
+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;
+}
+
+bool
+gdb_has_all_stop(struct gdb_conn *conn)
+{
+ return !conn->non_stop;
+}
+
+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. */
+
+ debug_msg("\tSending packet: $%s", command);
+
+ fputc('$', out); /* packet start */
+ /* TODO Check for partial writes. */
+ /* TODO Why not fputs? Is \0 allowed in the payload? */
+ fwrite(command, 1, size, out); /* payload */
+ fprintf(out, "#%02x", sum); /* packet end, checksum */
+ fflush(out);
+
+ if (ferror(out)) {
+ error_msg_and_die("Error sending message \"$%s\" to GDB server",
+ command);
+ } else if (feof(out)) {
+ error_msg_and_die("Connection to GDB server has been closed");
+ }
+}
+
+static char *recv_packet(FILE *in, size_t *ret_size, bool* ret_sum_ok);
+void
+gdb_send(struct gdb_conn *conn, const char *command, size_t size)
+{
+ bool acked = false;
+ fd_set rfds;
+ struct timeval tv;
+ int retval;
+
+ FD_ZERO (&rfds);
+ FD_SET (fileno(conn->in), &rfds);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ retval = select (1, &rfds, NULL, NULL, &tv);
+
+ if (retval) {
+ size_t size;
+ char *reply = gdb_recv(conn, &size, recv_want_other);
+ debug_msg("readahead of %s\n",reply);
+ }
+
+ do {
+ send_packet(conn->out, command, size);
+
+ if (!conn->ack)
+ break;
+
+ /* TODO
+ * https://sourceware.org/gdb/onlinedocs/gdb/Notification-Packets.html
+ * "Specifically, notifications may appear when GDB is not
+ * otherwise reading input from the stub, or when GDB is
+ * expecting to read a normal synchronous response or a '+'/'-'
+ * acknowledgment to a packet it has sent."
+ */
+ /* look for '+' ACK or '-' NACK/resend */
+ acked = fgetc_unlocked(conn->in) == '+';
+ } while (!acked);
+}
+
+void
+gdb_send_str(struct gdb_conn *conn, const char *command)
+{
+ gdb_send(conn, command, strlen(command));
+}
+
+/* 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 && strncmp(packet+3, "vfork",
5) != 0 && packet[0] != 'W')
+ return;
+
+ /* TODO
+ * https://sourceware.org/gdb/onlinedocs/gdb/Notification-Packets.html
+ * states that "Only one notification at a time may be pending; if
+ * additional events occur before GDB has acknowledged the previous
+ * notification, they must be queued by the stub for later synchronous
+ * transmission in response to ack packets from GDB. Because the
+ * notification mechanism is unreliable, the stub is permitted to resend
+ * a notification if it believes GDB may not have received it." Do we
+ * really need a multi-item buffer for it? We should overwrite the
+ * last received one, shouldn't we?
+ */
+ if (notifications.size == 0) {
+ notifications.size = 32;
+ notifications.start = 0;
+ notifications.count = 0;
+ notifications.packet = xcalloc(notifications.size,
+ sizeof(notifications.packet));
+ }
+
+ 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;
+ }
+
+ debug_msg("Pushed %s (%d items are now in queue)\n",
+ packet, notifications.count);
+}
+
+void
+dump_notifications(void)
+{
+ int idx;
+
+ if (!debug_flag)
+ return;
+ int start = notifications.start;
+ for (idx = 0; idx < notifications.count; idx++) {
+ int notify_idx = start + idx;
+ if (notify_idx >= notifications.size) {
+ start = 0;
+ notify_idx = idx;
+ }
+ if (notifications.packet[notify_idx] != NULL)
+ debug_msg ("Notify Dump: %s\n", notifications.packet[notify_idx]);
+ }
+}
+
+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;
+ }
+
+ debug_msg("Popped %s (%d items left in queue)\n",
+ packet, notifications.count);
+ dump_notifications ();
+
+ return packet;
+}
+
+bool
+have_notification(void)
+{
+ debug_msg("have_notification %d items\n", notifications.count);
+ return (notifications.count == 0 ? false : true);
+}
+
+static char *
+recv_packet(FILE *in, size_t *ret_size, bool* ret_sum_ok)
+{
+ size_t i = 0;
+ size_t size = 0;
+ char *reply = NULL;
+
+ 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;
+ /* TODO Rewrite to FSM */
+ 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 = xgrowarray(reply, &size, 1);
+
+ reply[i] = '\0';
+
+ debug_msg("\tPacket received: %s", 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 */
+ while (i + count > size)
+ reply = xgrowarray(reply, &size,
+ 1);
+
+ /* fill the repeated character */
+ memset(&reply[i], reply[i - 1], count);
+ i += count;
+ sum += c2;
+ continue;
+ }
+ } /* TODO handle "else" clause */
+ }
+
+ /* XOR an escaped character */
+ if (escape) {
+ c ^= 0x20;
+ escape = false;
+ }
+
+ /* get a bigger buffer if needed */
+ if (i == size)
+ reply = xgrowarray(reply, &size, 1);
+
+ /* add one character */
+ reply[i++] = c;
+ }
+
+ if (ferror(in)) {
+ error_msg("got stream error while receiving GDB server "
+ "packet");
+ } else if (feof(in)) {
+ error_msg("connection closed unexpectedly while "
+ "receiving GDB server packet");
+ } else {
+ error_msg("unknown GDB server connection error");
+ }
+ /* error_msg_and_die may result in endless loop doing cleanup */
+ _exit(1);
+}
+
+char *
+gdb_recv(struct gdb_conn *conn, size_t *size, enum gdb_recv_type recv_type)
+{
+ char *reply;
+ bool acked = false;
+
+ do {
+ bool want_ok_got_ok = false;
+ reply = recv_packet(conn->in, size, &acked);
+ const char *recv_type_str [] = {"Expected other", "Expected stop",
"Expected ok"};
+ debug_msg ("gdb_recv Got %.32s %s\n",reply,recv_type_str[recv_type]);
+
+ /* We received an asynchronous non-stop notification
+ * while expecting another packet. Cache it for later
+ * (See gdb_recv_stop for non-stop protocol description)
+ * If we were expecting "OK" then we need that OK plus
+ * the final OK terminating the notification sequence.
+ */
+
+ if (gdb_has_non_stop(conn) && recv_type != recv_want_stop
+ && (strncmp(reply, "T05", 3) == 0 || reply[0] == 'W')) do {
+ push_notification(reply, *size);
+ gdb_send(conn, "vStopped", 8);
+ reply = recv_packet(conn->in, size, &acked);
+ if (strcmp(reply, "OK") == 0) {
+ if (recv_type == recv_want_ok && want_ok_got_ok == false)
+ want_ok_got_ok = true;
+ else {
+ reply = recv_packet(conn->in, size, &acked);
+ if (strncmp(reply, "T05", 3) != 0)
+ break;
+ }
+ }
+ } while (true);
+
+ 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, recv_want_other);
+ bool ok = size == 2 && !strcmp(reply, "OK");
+ free(reply);
+
+ if (ok)
+ conn->ack = false;
+ return ok ? "OK" : "";
+}
+
+/**
+ * Read complete qXfer data, returned as binary with the size.
+ * On error, returns NULL with size set to the error code.
+ *
+ * @param[out] ret_size
+ */
+char *
+gdb_xfer_read(struct gdb_conn *conn, const char *object, const char *annex,
+ 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 /* TODO PacketSize */);
+ if (cmd_size < 0)
+ break;
+
+ gdb_send(conn, cmd, strlen(cmd));
+ free(cmd);
+
+ size_t size;
+ char *reply = gdb_recv(conn, &size, recv_want_other);
+ 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;
+
+ /* TODO handle other cases? */
+ }
+
+ free(reply);
+ break;
+ } while (0); /* TODO handle greater size */
+
+ 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)
+ /* TODO Returns automatic variable! */
+ return res;
+
+ gdb_send(conn, cmd, strlen(cmd));
+ free(cmd);
+
+ size_t size;
+ res.reply = gdb_recv(conn, &size, recv_want_other);
+ 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);
+ }
+ /* TODO Returns automatic variable! */
+ 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; /* TODO 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 00000000..69da15b7
--- /dev/null
+++ b/gdbserver/protocol.h
@@ -0,0 +1,84 @@
+#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);
+void gdb_send_str(struct gdb_conn *conn, const char *command);
+
+#define gdb_send_cstr(_conn, _str) \
+ gdb_send((_conn), _str, sizeof(_str) + MUST_BE_ARRAY(_str) - 1)
+
+enum gdb_recv_type {recv_want_other = 0, recv_want_stop = 1,
recv_want_ok = 2};
+char *gdb_recv(struct gdb_conn *conn, /* out */ size_t *size, enum
gdb_recv_type);
+
+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);
+
+bool gdb_has_all_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 00000000..3f49980f
--- /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 00000000..f7ed9732
--- /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_arch_defs.h
b/gdbserver/x86_64/gdb_arch_defs.h
new file mode 100644
index 00000000..7827f38c
--- /dev/null
+++ b/gdbserver/x86_64/gdb_arch_defs.h
@@ -0,0 +1 @@
+#define GDBSERVER_ARCH_HAS_GET_REGS 1
diff --git a/gdbserver/x86_64/gdb_get_regs.c
b/gdbserver/x86_64/gdb_get_regs.c
new file mode 100644
index 00000000..9a6d9a38
--- /dev/null
+++ b/gdbserver/x86_64/gdb_get_regs.c
@@ -0,0 +1,134 @@
+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;
+ do {
+ regs = gdb_get_all_regs(pid, &size);
+ } while (strncmp(regs, "OK", 2) == 0);
+ 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);
+
+// 00d00408 4f000000 cccffff7 00000000 d8d3ffff c01afdf7 cccffff7
01000000 3bd7fef7 86020000 23000000 2b000000 2b000000 2b000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 7f030000 00000000 ffff0000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00840000 2d000000
+
+ /* specified in 32bit-core.xml */
+ i386_regs.eax = be32toh(gdb_decode_hex_n(®s[0], 8));
+ i386_regs.ecx = be32toh(gdb_decode_hex_n(®s[8], 8));
+ i386_regs.edx = be32toh(gdb_decode_hex_n(®s[16], 8));
+ i386_regs.ebx = be32toh(gdb_decode_hex_n(®s[24], 8));
+ i386_regs.esp = be32toh(gdb_decode_hex_n(®s[32], 8));
+ i386_regs.ebp = be32toh(gdb_decode_hex_n(®s[40], 8));
+ i386_regs.esi = be32toh(gdb_decode_hex_n(®s[48], 8));
+ i386_regs.edi = be32toh(gdb_decode_hex_n(®s[56], 8));
+ i386_regs.eip = be32toh(gdb_decode_hex_n(®s[60], 8));
+ i386_regs.eflags = be32toh(gdb_decode_hex_n(®s[68], 8));
+ i386_regs.xcs = be32toh(gdb_decode_hex_n(®s[76], 8));
+ i386_regs.xss = be32toh(gdb_decode_hex_n(®s[84], 8));
+ i386_regs.xds = be32toh(gdb_decode_hex_n(®s[92], 8));
+ i386_regs.xes = be32toh(gdb_decode_hex_n(®s[100], 8));
+ i386_regs.xfs = be32toh(gdb_decode_hex_n(®s[108], 8));
+ i386_regs.xgs = be32toh(gdb_decode_hex_n(®s[116], 8));
+
+ /* specified in 32bit-linux.xml */
+ i386_regs.orig_eax = be32toh(gdb_decode_hex_n(®s[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(®s[0], 16));
+ x86_64_regs.rbx = be64toh(gdb_decode_hex_n(®s[16], 16));
+ x86_64_regs.rcx = be64toh(gdb_decode_hex_n(®s[32], 16));
+ x86_64_regs.rdx = be64toh(gdb_decode_hex_n(®s[48], 16));
+ x86_64_regs.rsi = be64toh(gdb_decode_hex_n(®s[64], 16));
+ x86_64_regs.rdi = be64toh(gdb_decode_hex_n(®s[80], 16));
+ x86_64_regs.rbp = be64toh(gdb_decode_hex_n(®s[96], 16));
+ x86_64_regs.rsp = be64toh(gdb_decode_hex_n(®s[112], 16));
+ x86_64_regs.r8 = be64toh(gdb_decode_hex_n(®s[128], 16));
+ x86_64_regs.r9 = be64toh(gdb_decode_hex_n(®s[144], 16));
+ x86_64_regs.r10 = be64toh(gdb_decode_hex_n(®s[160], 16));
+ x86_64_regs.r11 = be64toh(gdb_decode_hex_n(®s[176], 16));
+ x86_64_regs.r12 = be64toh(gdb_decode_hex_n(®s[192], 16));
+ x86_64_regs.r13 = be64toh(gdb_decode_hex_n(®s[208], 16));
+ x86_64_regs.r14 = be64toh(gdb_decode_hex_n(®s[224], 16));
+ x86_64_regs.r15 = be64toh(gdb_decode_hex_n(®s[240], 16));
+ x86_64_regs.rip = be64toh(gdb_decode_hex_n(®s[256], 16));
+ x86_64_regs.eflags = be32toh(gdb_decode_hex_n(®s[272], 8));
+ x86_64_regs.cs = be32toh(gdb_decode_hex_n(®s[280], 8));
+ x86_64_regs.ss = be32toh(gdb_decode_hex_n(®s[288], 8));
+ x86_64_regs.ds = be32toh(gdb_decode_hex_n(®s[296], 8));
+ x86_64_regs.es = be32toh(gdb_decode_hex_n(®s[304], 8));
+ x86_64_regs.fs = be32toh(gdb_decode_hex_n(®s[312], 8));
+ x86_64_regs.gs = be32toh(gdb_decode_hex_n(®s[320], 8));
+
+ /* specified in 64bit-linux.xml */
+ x86_64_regs.orig_rax = be64toh(gdb_decode_hex_n(®s[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/strace.1.in b/strace.1.in
index 05601945..abe3d998 100644
--- a/strace.1.in
+++ b/strace.1.in
@@ -455,6 +455,20 @@ not stop on
default if
.BR \-D ).
.RE
+.if '@ENABLE_GDBSERVER_FALSE@'#' \{\
+.TP
+.B \-G <comm>
+.TQ
+.BR \-\-gdbserver = \fI<comm>\fR
+Trace child processes using the GNU Debugger Remote Server
+(see (\fBgdbserver\fR(1)).
+.B strace
+communicates with
+.B gdbserver
+via \fI<comm>\fR. To use a TCP connection, you
+could say: host:2345 to connect to a gdbserver on port 2345 of host,
+or :2345 to connect to a gdbsrver on local port 2345.
+.\}
.SS Filtering
.TP 12
\fB\-e\ trace\fR=\,\fIsyscall_set\fR
diff --git a/strace.c b/strace.c
index e862f5eb..9653dddc 100644
--- a/strace.c
+++ b/strace.c
@@ -40,7 +40,9 @@
#include "xstring.h"
#include "delay.h"
#include "wait.h"
-
+#ifdef ENABLE_GDBSERVER
+#include "gdbserver.h"
+#endif
/* In some libc, these aren't declared. Do it ourself: */
extern char **environ;
extern int optind;
@@ -133,10 +135,10 @@ static unsigned int daemonized_tracer;
static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP;
#define use_seize (post_attach_sigstop == 0)
-static bool detach_on_execve;
+bool detach_on_execve;
static int exit_code;
-static int strace_child;
+int strace_child;
static int strace_tracer_pid;
static const char *username;
@@ -156,7 +158,7 @@ static FILE *shared_log;
static bool open_append;
struct tcb *printing_tcp;
-static struct tcb *current_tcp;
+struct tcb *current_tcp;
struct tcb_wait_data {
enum trace_event te; /**< Event passed to dispatch_event() */
@@ -299,8 +301,12 @@ Tracing:\n\
2, waiting: fatal signals are blocked while decoding syscall
(default)\n\
3, never: fatal signals are always blocked (default if '-o
FILE PROG')\n\
4, never_tstp: fatal signals and SIGTSTP (^Z) are always blocked\n\
- (useful to make 'strace -o FILE PROG' not stop on
^Z)\n\
-\n\
+ (useful to make 'strace -o FILE PROG' not stop on
^Z)\n"
+#ifdef ENABLE_GDBSERVER
+" -G <comm>, --gdbserver=<comm>\n\
+ trace child processes using gdbserver via <comm>\n"
+#endif
+"\n\
Filtering:\n\
-e trace=[!]{[?]SYSCALL[@64|@32|@x32]|[?]/REGEX|GROUP|all|none},\n\
--trace=[!]{[?]SYSCALL[@64|@32|@x32]|[?]/REGEX|GROUP|all|none}\n\
@@ -826,7 +832,7 @@ tabto(void)
* Otherwise, "strace -oFILE -ff -p<nonexistant_pid>"
* may create bogus empty FILE.<nonexistant_pid>, and then die.
*/
-static void
+void
after_successful_attach(struct tcb *tcp, const unsigned int flags)
{
tcp->flags |= TCB_ATTACHED | TCB_STARTUP | flags;
@@ -865,7 +871,7 @@ expand_tcbtab(void)
*tcb_ptr = newtcbs;
}
-static struct tcb *
+struct tcb *
alloctcb(int pid)
{
unsigned int i;
@@ -923,7 +929,7 @@ free_tcb_priv_data(struct tcb *tcp)
}
}
-static void
+void
droptcb(struct tcb *tcp)
{
if (tcp->pid == 0)
@@ -997,6 +1003,13 @@ detach(struct tcb *tcp)
int error;
int status;
+#ifdef ENABLE_GDBSERVER
+ if (gdbserver) {
+ gdb_detach(tcp);
+ goto drop;
+ }
+#endif
+
/*
* Linux wrongly insists the child be stopped
* before detaching. Arghh. We go through hoops
@@ -1291,6 +1304,12 @@ startup_attach(void)
if (tcp->flags & TCB_ATTACHED)
continue; /* no, we already attached it */
+#ifdef ENABLE_GDBSERVER
+ if (gdbserver) {
+ gdb_attach_tcb(tcp);
+ }
+#endif
+
if (tcp->pid == parent_pid || tcp->pid == strace_tracer_pid) {
errno = EPERM;
perror_msg("attach: pid %d", tcp->pid);
@@ -1463,6 +1482,14 @@ startup_child(char **argv, char **env)
int pid;
struct tcb *tcp;
+
+#ifdef ENABLE_GDBSERVER
+ if (gdbserver) {
+ gdb_startup_child(argv);
+ return;
+ }
+#endif
+
filename = argv[0];
filename_len = strlen(filename);
@@ -1719,7 +1746,7 @@ get_os_release(void)
return rel;
}
-static void
+void
set_sighandler(int signo, void (*sighandler)(int), struct sigaction
*oldact)
{
const struct sigaction sa = { .sa_handler = sighandler };
@@ -2013,7 +2040,11 @@ init(int argc, char *argv[])
qualify_signals("all");
static const char optstring[] =
- "+a:Ab:cCdDe:E:fFhiI:ko:O:p:P:qrs:S:tTu:U:vVwxX:yzZ";
+ "+a:Ab:cCdDe:E:fF"
+#ifdef ENABLE_GDBSERVER
+ "G:"
+#endif
+ "hiI:ko:O:p:P:qrs:S:tTu:U:vVwxX:yzZ";
enum {
GETOPT_SECCOMP = 0x100,
@@ -2049,6 +2080,9 @@ init(int argc, char *argv[])
{ "daemonized", optional_argument, 0, GETOPT_DAEMONIZE },
{ "env", required_argument, 0, 'E' },
{ "follow-forks", no_argument, 0, GETOPT_FOLLOWFORKS },
+#ifdef ENABLE_GDBSERVER
+ { "gdbserver", required_argument, 0, 'G' },
+#endif
{ "output-separately", no_argument, 0,
GETOPT_OUTPUT_SEPARATELY },
{ "help", no_argument, 0, 'h' },
@@ -2338,11 +2372,20 @@ init(int argc, char *argv[])
qualify_decode_fd(optarg ?: yflag_qual);
break;
default:
+#ifdef ENABLE_GDBSERVER
+ if (! gdb_handle_arg(c, optarg))
+#endif
error_msg_and_help(NULL);
break;
}
}
+#ifdef ENABLE_GDBSERVER
+ /* gdbserver always follows the fork */
+ if (gdbserver)
+ followfork_short++;
+#endif
+
argv += optind;
argc -= optind;
@@ -2596,6 +2639,11 @@ init(int argc, char *argv[])
* no 1 1 INTR_WHILE_WAIT
*/
+#ifdef ENABLE_GDBSERVER
+ if (gdbserver)
+ gdb_start_init(argc, argv);
+#endif
+
if (daemonized_tracer && !opt_intr)
opt_intr = INTR_BLOCK_TSTP_TOO;
if (outfname && argc) {
@@ -2665,6 +2713,11 @@ init(int argc, char *argv[])
if (nprocs != 0 || daemonized_tracer)
startup_attach();
+#ifdef ENABLE_GDBSERVER
+ if (gdbserver)
+ gdb_end_init();
+#endif
+
/* 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
@@ -2674,7 +2727,7 @@ init(int argc, char *argv[])
((followfork && !output_separately) || nprocs > 1);
}
-static struct tcb *
+struct tcb *
pid2tcb(const int pid)
{
if (pid <= 0)
@@ -2719,6 +2772,10 @@ cleanup(int fatal_sig)
}
detach(tcp);
}
+
+#ifdef ENABLE_GDBSERVER
+ gdb_cleanup(fatal_sig);
+#endif
}
static void
@@ -3063,6 +3120,11 @@ tcb_wait_tab_check_size(const size_t size)
static const struct tcb_wait_data *
next_event(void)
{
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect(gdbserver != NULL, 0))
+ return gdb_next_event();
+#endif
+
if (interrupted)
return NULL;
@@ -3345,7 +3407,7 @@ next_event_exit:
return tcb_wait_tab + tcp->wait_data_idx;
}
-static int
+int
trace_syscall(struct tcb *tcp, unsigned int *sig)
{
if (entering(tcp)) {
@@ -3559,6 +3621,14 @@ dispatch_event(const struct tcb_wait_data *wd)
return true;
}
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect(gdbserver != NULL, 0)) {
+ if (gdb_restart_process(restart_op, current_tcp, restart_sig) < 0) {
+ exit_code = 1;
+ return false;
+ }
+ } else
+#endif
if (ptrace_restart(restart_op, current_tcp, restart_sig) < 0) {
/* Note: ptrace_restart emitted error message */
exit_code = 1;
diff --git a/syscall.c b/syscall.c
index fd7a3d84..8439ad7a 100644
--- a/syscall.c
+++ b/syscall.c
@@ -23,6 +23,9 @@
#include "delay.h"
#include "retval.h"
#include <limits.h>
+#ifdef ENABLE_GDBSERVER
+#include "gdbserver.h"
+#endif
/* for struct iovec */
#include <sys/uio.h>
@@ -256,7 +259,7 @@ set_personality(unsigned int personality)
# endif
}
-static void
+void
update_personality(struct tcb *tcp, unsigned int personality)
{
static bool need_mpers_warning[] =
@@ -1108,9 +1111,25 @@ clear_regs(struct tcb *tcp)
get_regs_error = -1;
}
+struct iovec*
+arch_iovec_for_getregset(void)
+{
+#ifdef ARCH_IOVEC_FOR_GETREGSET
+ return (struct iovec*)&ARCH_IOVEC_FOR_GETREGSET;
+#else
+ return null;
+#endif
+}
+
+
static long
get_regs(struct tcb *const tcp)
{
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect (gdbserver != NULL, 0))
+ return gdb_get_registers(tcp);
+#endif
+
#ifdef ptrace_getregset_or_getregs
if (get_regs_error != -1)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6c2c7084..c749e1a6 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -219,8 +219,11 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
vfork-f \
wait4-v \
waitid-v \
- zeroargc \
- # end of check_PROGRAMS
+ zeroargc
+if ENABLE_GDBSERVER
+check_PROGRAMS += gdbrsp
+endif # end of check_PROGRAMS
+
attach_f_p_LDADD = -lpthread $(LDADD)
count_f_LDADD = -lpthread $(LDADD)
@@ -355,8 +358,10 @@ DECODER_TESTS = \
uname.test \
unix-pair-send-recv.test \
unix-pair-sendto-recvfrom.test \
- xet_thread_area_x86.test \
- # end of DECODER_TESTS
+ xet_thread_area_x86.test
+if ENABLE_GDBSERVER
+DECODER_TESTS += gdbrsp.test
+endif # end of DECODER_TESTS
MISC_TESTS = \
attach-f-p.test \
diff --git a/tests/gdbrsp.c b/tests/gdbrsp.c
new file mode 100644
index 00000000..1c50c6b5
--- /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 fork-event interactions. Child exits immediately. */
+ if (fork () == 0)
+ _exit (0);
+ else
+ {
+ /* 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 00000000..596e8d1e
--- /dev/null
+++ b/tests/gdbrsp.test
@@ -0,0 +1,119 @@
+#!/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
+
+# determine gdbserver port
+GDBSERVER_PORT=0
+for P in $(seq 65432 65450) ; do
+ if ss -lnt | grep -q ":$P" ; then
+ true
+ else
+ GDBSERVER_PORT=$P
+ break
+ fi
+done
+if [ $GDBSERVER_PORT -eq 0 ] ; then
+ framework_skip_ "Unable to start gdbserver"
+fi
+
+# all-stop
+gdbserver --once --multi :$GDBSERVER_PORT &
+$STRACE -G localhost:$GDBSERVER_PORT -q $(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
+clone.*flags=.*
+.pid.*exited with 0.*
+syscall_0x75bcd15.*= -1.*
+__EOF__
+
+match_grep "$LOG" "$EXPECTED" "running all-stop mode"
+rm -f $EXPECTED
+
+# non-stop
+gdbserver --once --multi :$GDBSERVER_PORT &
+if [ $? -gt 0 ] ; then
+ framework_skip_ "Unable to start gdbserver"
+fi
+timeout -s KILL 30s $STRACE -G "localhost:$GDBSERVER_PORT: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
+clone.*unfinished.*
+syscall_0x75bcd15.*= -1.*
+.pid.*exited with 0.*
+.*clone resumed.* =.*
+__EOF__
+
+match_grep "$LOG" "$EXPECTED" "running non-stop mode"
+rm -f $EXPECTED
+
+# -e write,read
+
+if [ ${STRACE_ARCH:-x86_64} = x86_64 ] ; then
+ gdbserver --once --multi :$GDBSERVER_PORT &
+ if [ $? -gt 0 ] ; then
+ framework_skip_ "Unable to start gdbserver"
+ fi
+ $STRACE -G localhost:$GDBSERVER_PORT -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/ucopy.c b/ucopy.c
index 13c4103b..394a6139 100644
--- a/ucopy.c
+++ b/ucopy.c
@@ -17,6 +17,9 @@
#include "scno.h"
#include "ptrace.h"
+#ifdef ENABLE_GDBSERVER
+#include "gdbserver.h"
+#endif
static bool process_vm_readv_not_supported;
@@ -243,6 +246,10 @@ int
umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len,
void *const our_addr)
{
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect(gdbserver != NULL, 0))
+ return gdb_umoven(tcp, addr, len, our_addr);
+#endif
if (tracee_addr_is_invalid(addr))
return -1;
@@ -350,6 +357,10 @@ int
umovestr(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len,
char *laddr)
{
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect(gdbserver != NULL, 0))
+ return gdb_umovestr(tcp, addr, len, laddr);
+#endif
if (tracee_addr_is_invalid(addr))
return -1;
diff --git a/upeek.c b/upeek.c
index b28b73cc..fbedda10 100644
--- a/upeek.c
+++ b/upeek.c
@@ -14,12 +14,19 @@
#include "defs.h"
#include "ptrace.h"
+#ifdef ENABLE_GDBSERVER
+#include "gdbserver.h"
+#endif
int
upeek(struct tcb *tcp, unsigned long off, kernel_ulong_t *res)
{
long val;
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect(gdbserver != NULL, 0))
+ return gdb_upeek(tcp, off, res);
+#endif
errno = 0;
val = ptrace(PTRACE_PEEKUSER, (pid_t) tcp->pid, (void *) off, 0);
if (val == -1 && errno) {
diff --git a/upoke.c b/upoke.c
index bb629c54..9652d29f 100644
--- a/upoke.c
+++ b/upoke.c
@@ -9,10 +9,17 @@
#include "defs.h"
#include "ptrace.h"
#include "ptrace_pokeuser.c"
+#ifdef ENABLE_GDBSERVER
+#include "gdbserver.h"
+#endif
int
upoke(struct tcb *tcp, unsigned long off, kernel_ulong_t val)
{
+#ifdef ENABLE_GDBSERVER
+ if (__builtin_expect(gdbserver != NULL, 0))
+ return gdb_upoke(tcp, off, val);
+#endif
if (ptrace_pokeuser(tcp->pid, off, val) < 0) {
if (errno != ESRCH)
perror_func_msg("PTRACE_POKEUSER pid:%d @%#lx)",
--
2.25.2
More information about the Strace-devel
mailing list