[PATCH] Add gdb remote protocol handling to strace
Stan Cox
scox at redhat.com
Fri Nov 10 04:43:52 UTC 2017
The last posted patch lost a printsiginfo patch that used macro tricks
to avoid that signal.h problem but I changed to use an opaque pointer
for next_event/dispatch_event to avoid the problem. It is assumed that
a gdbserver is running and the strace gdbserver backend communicates
with that, typically using tcp. This is analogous to gdb/gdbserver
behavior.
diff --git a/Makefile.am b/Makefile.am
index 4aa9846..1f216b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -47,13 +47,22 @@ ARCH = @arch@
ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS = $(WARN_CFLAGS)
-AM_CPPFLAGS = -I$(builddir)/$(OS)/$(ARCH) \
+OS_CPPFLAGS = -I$(builddir)/$(OS)/$(ARCH) \
-I$(srcdir)/$(OS)/$(ARCH) \
-I$(builddir)/$(OS) \
-I$(srcdir)/$(OS) \
-I$(builddir) \
-I$(srcdir)
+if ENABLE_GDBSERVER
+GDBSERVER_CPPFLAGS = -I$(builddir)/gdbserver/$(ARCH) \
+ -I$(srcdir)/gdbserver/$(ARCH) \
+ -I$(builddir)/gdbserver \
+ -I$(srcdir)/gdbserver $(OS_CPPFLAGS)
+endif
+
+AM_CPPFLAGS = $(OS_CPPFLAGS) $(GDBSERVER_CPPFLAGS)
+
AM_CFLAGS_FOR_BUILD = $(WARN_CFLAGS_FOR_BUILD)
AM_CPPFLAGS_FOR_BUILD = $(AM_CPPFLAGS)
@@ -325,6 +334,18 @@ strace_SOURCES = \
xmalloc.h \
# end of strace_SOURCES
+if ENABLE_GDBSERVER
+# Note that (some of) the files inside this directory are GPLv3-licensed,
+# so the package which includes them will also be tainted by GPLv3 license.
+strace_SOURCES += \
+ gdbserver/gdbserver.c \
+ gdbserver/protocol.c \
+ gdbserver/protocol.h \
+ gdbserver/signals.def \
+ gdbserver/signals.h \
+ # end of gdbserver strace_SOURCES
+endif
+
if USE_LIBUNWIND
strace_SOURCES += unwind.c
strace_CPPFLAGS += $(libunwind_CPPFLAGS)
@@ -873,6 +894,13 @@ EXTRA_DIST = \
xlat/gen.sh \
xlate.el
+if ENABLE_GDBSERVER
+EXTRA_DIST += \
+ gdbserver/x86_64/gdb_arch_defs.h \
+ gdbserver/x86_64/gdb_get_regs.c \
+ # End of gdbserver EXTRA_DIS
+endif
+
.PHONY: check-valgrind-local
check-valgrind-local:
diff --git a/README b/README
index bc70155..278c80a 100644
--- a/README
+++ b/README
@@ -3,6 +3,10 @@ This is strace, a system call tracer for Linux.
strace is released under a Berkeley-style license at the request
of Paul Kranenburg.
+The gdbserver feature of strace is released under the same license,
+except for a few sources copied from gdb which are required to use
+GPL version 3 or later. See COPYING3 for details.
+
See the file CREDITS for a list of authors and other contributors.
See the file INSTALL for compilation and installation instructions.
See the file NEWS for information on what has changed in recent versions.
diff --git a/configure.ac b/configure.ac
index 29285db..770c543 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,7 +42,7 @@ AC_COPYRIGHT([Copyright (c) 1999-]copyright_year[ The
strace developers.])
AC_CONFIG_SRCDIR([strace.c])
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_HEADERS([config.h])
-AM_INIT_AUTOMAKE([foreign nostdinc dist-xz silent-rules parallel-tests
1.13])
+AM_INIT_AUTOMAKE([foreign nostdinc dist-xz silent-rules parallel-tests
subdir-objects 1.13])
AM_MAINTAINER_MODE
AC_CANONICAL_HOST
@@ -868,6 +868,30 @@ fi
AM_CONDITIONAL([USE_LIBUNWIND], [test "x$use_libunwind" = xyes])
AC_MSG_RESULT([$use_libunwind])
+
+dnl gdbserver
+AC_ARG_ENABLE([gdbserver],
+ [AS_HELP_STRING([--enable-gdbserver],
+ [enable gdbserver backend (includes GPLv3 code)])],
+ [], [enable_gdbserver=check])
+
+use_gdbserver=no
+AS_IF([test "x$enable_gdbserver" != xno],
+ [AC_CHECK_FILE([$srcdir/gdbserver/$arch/gdb_arch_defs.h],
+ [use_gdbserver=yes],
+ [if test "x$enable_gdbserver" != xcheck; then
+ AC_MSG_FAILURE([failed to find arch-specific
gdbserver definitions $srcdir/gdbserver/$arch/gdb_arch_defs.h])
+ fi])]
+)
+
+AC_MSG_CHECKING([whether to enable gdbserver backend support])
+if test "x$use_gdbserver" = xyes; then
+ AC_DEFINE([ENABLE_GDBSERVER], 1, [Enable gdbserver backend support])
+fi
+AM_CONDITIONAL([ENABLE_GDBSERVER], [test "x$use_gdbserver" = xyes])
+AC_MSG_RESULT([$use_gdbserver])
+
+
if test "$arch" = mips && test "$no_create" != yes; then
mkdir -p linux/mips
if $srcdir/linux/mips/genstub.sh linux/mips; then
diff --git a/defs.h b/defs.h
index 06a4baf..3607140 100644
--- a/defs.h
+++ b/defs.h
@@ -253,6 +253,7 @@ struct tcb {
#define TCB_TAMPERED 0x40 /* A syscall has been tampered with */
#define TCB_HIDE_LOG 0x80 /* We should hide everything (until execve) */
#define TCB_SKIP_DETACH_ON_FIRST_EXEC 0x100 /* -b execve should skip
detach on first execve */
+#define TCB_GDB_CONT_PID_TID 0x200 /* Use vCont;c:pPID.TID for for gdb
backend */
/* qualifier flags */
#define QUAL_TRACE 0x001 /* this system call should be traced */
@@ -274,6 +275,98 @@ struct tcb {
#define filtered(tcp) ((tcp)->flags & TCB_FILTERED)
#define hide_log(tcp) ((tcp)->flags & TCB_HIDE_LOG)
+enum trace_event {
+ /* Break the main loop. */
+ TE_BREAK,
+
+ /* Call next_event() again. */
+ TE_NEXT,
+
+ /* Restart the tracee with signal 0 and call next_event() again. */
+ TE_RESTART,
+
+ /*
+ * For all the events below, current_tcp is set to current tracee's
+ * tcb. All the suggested actions imply that you want to continue
+ * tracing of the current tracee; alternatively, you can detach it.
+ */
+
+ /*
+ * Syscall entry or exit.
+ * Restart the tracee with signal 0, or with an injected signal number.
+ */
+ TE_SYSCALL_STOP,
+
+ /*
+ * Tracee received signal with number WSTOPSIG(*pstatus); signal info
+ * is written to *si. Restart the tracee (with that signal number
+ * if you want to deliver it).
+ */
+ TE_SIGNAL_DELIVERY_STOP,
+
+ /*
+ * Tracee was killed by a signal with number WTERMSIG(*pstatus).
+ */
+ TE_SIGNALLED,
+
+ /*
+ * Tracee was stopped by a signal with number WSTOPSIG(*pstatus).
+ * Restart the tracee with that signal number.
+ */
+ TE_GROUP_STOP,
+
+ /*
+ * Tracee exited with status WEXITSTATUS(*pstatus).
+ */
+ TE_EXITED,
+
+ /*
+ * Tracee is going to perform execve().
+ * Restart the tracee with signal 0.
+ */
+ TE_STOP_BEFORE_EXECVE,
+
+ /*
+ * Tracee is going to terminate.
+ * Restart the tracee with signal 0.
+ */
+ TE_STOP_BEFORE_EXIT,
+};
+
+/* Backend Dispatch Table */
+struct backend {
+ void (*attach_tcb) (struct tcb *tcp);
+ void (*cleanup) (void);
+ void (*detach) (struct tcb *tcp);
+ bool (*dispatch_event) (enum trace_event, int *pstatus, void *si);
+ void (*end_init) (void);
+ long (*get_regs) (pid_t pid, void *io);
+ int (*get_scno) (struct tcb *tcp);
+ int (*getfdpath) (struct tcb *tcp, int fd, char *buf, unsigned bufsize);
+ bool (*handle_arg) (char arg, char *optarg);
+ enum trace_event (*next_event) (int *pstatus, void *si);
+ bool (*prog_pid_check) (char *exec_name, int nprocs);
+ bool (*start_init) (void);
+ void (*startup_child) (char **argv);
+ long (*set_regs) (pid_t pid, void *io);
+ bool (*trace) (void);
+ int (*umoven) (struct tcb *const tcp, kernel_ulong_t addr, unsigned
int len,
+ void *const our_addr);
+ int (*umovestr) (struct tcb *const tcp, kernel_ulong_t addr, unsigned
int len, char *laddr);
+ int (*upeek_) (int pid, unsigned long off, kernel_ulong_t *res);
+ int (*upoke_) (int pid, unsigned long off, kernel_ulong_t val);
+ bool (*verify_args) (const char *username, bool daemon, unsigned int
*follow_fork);
+ void *data;
+} backend;
+
+
+extern int upeek(int pid, unsigned long, kernel_ulong_t *);
+extern int upoke(int pid, unsigned long, kernel_ulong_t);
+extern bool gdb_handle_arg (char arg, char *optarg);
+
+#define upeek(pid,off,res) backend.upeek_(pid, off, res)
+#define upoke(pid,off,val) backend.upoke_(pid, off, val)
+
#include "xlat.h"
extern const struct xlat addrfams[];
@@ -394,6 +487,8 @@ extern int read_int_from_file(const char *, int *);
extern void set_sortby(const char *);
extern void set_overhead(int);
extern void print_pc(struct tcb *);
+extern int trace_syscall(struct tcb *, unsigned int *);
+extern void update_personality(struct tcb *tcp, unsigned int personality);
extern int syscall_entering_decode(struct tcb *);
extern int syscall_entering_trace(struct tcb *, unsigned int *);
@@ -428,6 +523,7 @@ extern void *get_tcb_priv_data(const struct tcb *);
extern int set_tcb_priv_data(struct tcb *, void *priv_data,
void (*free_priv_data)(void *));
extern void free_tcb_priv_data(struct tcb *);
+extern struct tcb *pid2tcb(int pid);
static inline unsigned long get_tcb_priv_ulong(const struct tcb *tcp)
{
@@ -442,7 +538,7 @@ static inline int set_tcb_priv_ulong(struct tcb
*tcp, unsigned long val)
extern int
umoven(struct tcb *, kernel_ulong_t addr, unsigned int len, void *laddr);
#define umove(pid, addr, objp) \
- umoven((pid), (addr), sizeof(*(objp)), (void *) (objp))
+ backend.umoven((pid), (addr), sizeof(*(objp)), (void *) (objp))
extern int
umoven_or_printaddr(struct tcb *, kernel_ulong_t addr,
@@ -457,9 +553,6 @@ umoven_or_printaddr_ignore_syserror(struct tcb *,
kernel_ulong_t addr,
extern int
umovestr(struct tcb *, kernel_ulong_t addr, unsigned int len, char
*laddr);
-extern int upeek(int pid, unsigned long, kernel_ulong_t *);
-extern int upoke(int pid, unsigned long, kernel_ulong_t);
-
extern bool
print_array(struct tcb *,
kernel_ulong_t start_addr,
diff --git a/desc.c b/desc.c
index 52e58c8..6472a94 100644
--- a/desc.c
+++ b/desc.c
@@ -148,7 +148,7 @@ decode_select(struct tcb *const tcp, const
kernel_ulong_t *const args,
int first = 1;
addr = args[i+1];
- if (!addr || !fds || umoven(tcp, addr, fdsize, fds) < 0)
+ if (!addr || !fds || backend.umoven(tcp, addr, fdsize, fds) < 0)
continue;
for (j = 0;; j++) {
j = next_set_bit(fds, j, nfds);
diff --git a/dirent.c b/dirent.c
index 2ab626b..52be6be 100644
--- a/dirent.c
+++ b/dirent.c
@@ -101,7 +101,7 @@ SYS_FUNC(getdents)
if (len) {
buf = malloc(len);
- if (!buf || umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
+ if (!buf || backend.umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
tprints(", ");
printaddr(tcp->u_arg[1]);
tprintf(", %u", count);
diff --git a/dirent64.c b/dirent64.c
index 4172d64..8927b9f 100644
--- a/dirent64.c
+++ b/dirent64.c
@@ -69,7 +69,7 @@ SYS_FUNC(getdents64)
if (len) {
buf = malloc(len);
- if (!buf || umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
+ if (!buf || backend.umoven(tcp, tcp->u_arg[1], len, buf) < 0) {
tprints(", ");
printaddr(tcp->u_arg[1]);
tprintf(", %u", count);
diff --git a/dm.c b/dm.c
index 28863a8..1e9bd53 100644
--- a/dm.c
+++ b/dm.c
@@ -455,7 +455,7 @@ dm_known_ioctl(struct tcb *const tcp, const unsigned
int code,
ioc = alloca(sizeof(*ioc));
}
- if ((umoven(tcp, arg, offsetof(struct dm_ioctl, data), ioc) < 0) ||
+ if ((backend.umoven(tcp, arg, offsetof(struct dm_ioctl, data), ioc) <
0) ||
(ioc->data_size < offsetof(struct dm_ioctl, data_size))) {
if (entering(tcp))
free(ioc);
diff --git a/execve.c b/execve.c
index 5455b15..f40e142 100644
--- a/execve.c
+++ b/execve.c
@@ -54,7 +54,7 @@ printargv(struct tcb *const tcp, kernel_ulong_t addr)
char data[sizeof(kernel_ulong_t)];
} cp;
- if (umoven(tcp, addr, wordsize, cp.data)) {
+ if (backend.umoven(tcp, addr, wordsize, cp.data)) {
if (sep == start_sep)
printaddr(addr);
else
@@ -89,7 +89,7 @@ printargc(struct tcb *const tcp, kernel_ulong_t addr)
char *cp = NULL;
for (; addr; addr += current_wordsize, ++count) {
- if (umoven(tcp, addr, current_wordsize, &cp)) {
+ if (backend.umoven(tcp, addr, current_wordsize, &cp)) {
if (!count)
return;
diff --git a/file_handle.c b/file_handle.c
index 46a61c5..e1b21ac 100644
--- a/file_handle.c
+++ b/file_handle.c
@@ -84,7 +84,7 @@ SYS_FUNC(name_to_handle_at)
tprintf(", handle_type=%d", h.handle_type);
if (h.handle_bytes > MAX_HANDLE_SZ)
h.handle_bytes = MAX_HANDLE_SZ;
- if (!umoven(tcp, addr + sizeof(h), h.handle_bytes,
+ if (!backend.umoven(tcp, addr + sizeof(h), h.handle_bytes,
f_handle)) {
tprints(", f_handle=0x");
for (i = 0; i < h.handle_bytes; ++i)
@@ -121,7 +121,7 @@ SYS_FUNC(open_by_handle_at)
h.handle_bytes, h.handle_type);
if (h.handle_bytes > MAX_HANDLE_SZ)
h.handle_bytes = MAX_HANDLE_SZ;
- if (!umoven(tcp, addr + sizeof(h), h.handle_bytes, &f_handle)) {
+ if (!backend.umoven(tcp, addr + sizeof(h), h.handle_bytes, &f_handle)) {
unsigned int i;
tprints(", f_handle=0x");
diff --git a/gdbserver/gdbserver.c b/gdbserver/gdbserver.c
new file mode 100644
index 0000000..68b8266
--- /dev/null
+++ b/gdbserver/gdbserver.c
@@ -0,0 +1,1223 @@
+ /* Implementation of strace features over the GDB remote protocol.
+ *
+ * Copyright (c) 2015, 2016 Red Hat Inc.
+ * Copyright (c) 2015 Josh Stone <cuviper at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "defs.h"
+
+#define _GNU_SOURCE 1
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "gdb_arch_defs.h"
+#include "protocol.h"
+#include "scno.h"
+#include "signals.h"
+
+/* FIXME jistone: export hacks */
+struct tcb *pid2tcb(int pid);
+struct tcb *alloctcb(int pid);
+void droptcb(struct tcb *tcp);
+void newoutf(struct tcb *tcp);
+void print_signalled(struct tcb *tcp, const int pid, int status);
+void print_exited(struct tcb *tcp, const int pid, int status);
+void print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned
int sig);
+void set_sigaction(int signo, void (*sighandler)(int), struct sigaction
*oldact);
+
+struct tcb *current_tcp;
+int strace_child;
+int detach_on_execve;
+static volatile int interrupted;
+char *gdbserver = NULL;
+static int general_pid; /* process id that gdbserver is focused on */
+static int general_tid; /* thread id that gdbserver is focused on */
+static struct gdb_stop_reply stop;
+static struct gdb_conn* gdb = NULL;
+static bool gdb_extended = false;
+static bool gdb_multiprocess = false;
+static bool gdb_vcont = false;
+static bool gdb_nonstop = false;
+
+static const char * const gdb_signal_names[] = {
+#define SET(symbol, constant, name, string) \
+ [constant] = name,
+#include "signals.def"
+#undef SET
+};
+
+static int gdb_signal_map[SUPPORTED_PERSONALITIES][GDB_SIGNAL_LAST];
+
+enum gdb_stop {
+ gdb_stop_unknown, /* O or F or anything else */
+ gdb_stop_error, /* E */
+ gdb_stop_signal, /* S or T */
+ gdb_stop_exited, /* W */
+ gdb_stop_terminated, /* X */
+
+ /* specific variants of gdb_stop_signal 05 */
+ gdb_stop_trap, /* missing or unrecognized stop reason */
+ gdb_stop_syscall_entry,
+ gdb_stop_syscall_return,
+};
+
+
+struct gdb_stop_reply {
+ char *reply;
+ size_t size;
+
+ enum gdb_stop type;
+ int code; /* error, signal, exit status, scno */
+ int pid; /* process id, aka kernel tgid */
+ int tid; /* thread id, aka kernel tid */
+};
+
+static int
+gdb_map_signal(unsigned int gdb_sig) {
+ /* strace "SIG_0" vs. gdb "0" -- it's all zero */
+ if (gdb_sig == GDB_SIGNAL_0)
+ return 0;
+
+ /* real-time signals are "special", not even fully contiguous */
+ if (gdb_sig == GDB_SIGNAL_REALTIME_32)
+ return 32;
+ if (GDB_SIGNAL_REALTIME_33 <= gdb_sig &&
+ gdb_sig <= GDB_SIGNAL_REALTIME_63)
+ return gdb_sig - GDB_SIGNAL_REALTIME_33 + 33;
+ if (GDB_SIGNAL_REALTIME_64 <= gdb_sig &&
+ gdb_sig <= GDB_SIGNAL_REALTIME_127)
+ return gdb_sig - GDB_SIGNAL_REALTIME_64 + 64;
+
+ const char *gdb_signame = gdb_signal_names[gdb_sig];
+ if (!gdb_signame)
+ return -1;
+
+ /* many of the other signals line up, but not all. */
+ if (gdb_sig < nsignals && !strcmp(gdb_signame, signame(gdb_sig)))
+ return gdb_sig;
+
+ /* scan the rest for a match */
+ unsigned int sig;
+ for (sig = 1; sig < nsignals; ++sig) {
+ if (sig == gdb_sig)
+ continue;
+ if (!strcmp(gdb_signame, signame(sig)))
+ return sig;
+ }
+
+ return -1;
+}
+
+static void
+gdb_signal_map_init(void)
+{
+ unsigned int pers, old_pers = current_personality;
+
+ for (pers = 0; pers < SUPPORTED_PERSONALITIES; ++pers) {
+ if (current_personality != pers)
+ set_personality(pers);
+
+ unsigned int gdb_sig;
+ int *map = gdb_signal_map[pers];
+ for (gdb_sig = 0; gdb_sig < GDB_SIGNAL_LAST; ++gdb_sig)
+ map[gdb_sig] = gdb_map_signal(gdb_sig);
+ }
+
+ if (old_pers != current_personality)
+ set_personality(old_pers);
+}
+
+static int
+gdb_signal_to_target(struct tcb *tcp, unsigned int signal)
+{
+ unsigned int pers = tcp->currpers;
+ if (pers < SUPPORTED_PERSONALITIES && signal < GDB_SIGNAL_LAST)
+ return gdb_signal_map[pers][signal];
+ return -1;
+}
+
+static void
+gdb_parse_thread(const char *id, int *pid, int *tid)
+{
+ if (*id == 'p') {
+ /* pPID or pPID.TID */
+ ++id;
+ *pid = gdb_decode_hex_str(id);
+
+ /* stop messages should always have the TID, */
+ /* but if not, just use the PID. */
+ char *dot = strchr(id, '.');
+ if (!dot) {
+ *tid = *pid;
+ } else {
+ *tid = gdb_decode_hex_str(dot + 1);
+ }
+ } else {
+ /* just TID, assume same PID */
+ *tid = gdb_decode_hex_str(id);
+ *pid = *tid;
+ }
+}
+
+static void
+gdb_recv_signal(struct gdb_stop_reply *stop)
+{
+ char *reply = stop->reply;
+
+ stop->code = gdb_decode_hex_n(&reply[1], 2);
+ stop->type = (stop->code == GDB_SIGNAL_TRAP ||
+ stop->code == GDB_SIGNAL_0)
+ ? gdb_stop_trap : gdb_stop_signal;
+
+ /* tokenize the n:r pairs */
+ char *info = strdupa(reply + 3);
+ char *savetok = NULL, *nr;
+ for (nr = strtok_r(info, ";", &savetok); nr;
+ nr = strtok_r(NULL, ";", &savetok)) {
+ char *n = strtok(nr, ":");
+ char *r = strtok(NULL, "");
+ if (!n || !r)
+ continue;
+
+ if (!strcmp(n, "thread")) {
+ if (stop->pid == -1) {
+ gdb_parse_thread(r, &stop->pid, &stop->tid);
+ general_pid = stop->pid;
+ general_tid = stop->tid;
+ }
+ else
+ /* an optional 2nd thread component is the */
+ /* thread that gdbserver is focused on */
+ gdb_parse_thread(r, &general_pid, &general_tid);
+ }
+ else if (!strcmp(n, "syscall_entry")) {
+ if (stop->type == gdb_stop_trap) {
+ stop->type = gdb_stop_syscall_entry;
+ stop->code = gdb_decode_hex_str(r);
+ }
+ }
+ else if (!strcmp(n, "syscall_return")) {
+ if (stop->type == gdb_stop_trap) {
+ stop->type = gdb_stop_syscall_return;
+ stop->code = gdb_decode_hex_str(r);
+ }
+ }
+ }
+
+ /* TODO guess architecture by the size of reported registers? */
+}
+
+static void
+gdb_recv_exit(struct gdb_stop_reply *stop)
+{
+ char *reply = stop->reply;
+
+ stop->type = reply[0] == 'W' ?
+ gdb_stop_exited : gdb_stop_terminated;
+ stop->code = gdb_decode_hex_str(&reply[1]);
+
+ const char *process = strstr(reply, ";process:");
+ if (process) {
+ stop->pid = gdb_decode_hex_str(process + 9);
+
+ /* we don't really know the tid, so just use PID for now */
+ /* XXX should exits enumerate all threads we know of a process? */
+ stop->tid = stop->pid;
+ }
+}
+
+static struct gdb_stop_reply
+gdb_recv_stop(struct gdb_stop_reply *stop_reply)
+{
+ struct gdb_stop_reply stop = {
+ .reply = NULL,
+ .size = 0,
+
+ .type = gdb_stop_unknown,
+ .code = -1,
+ .pid = -1,
+ .tid = -1,
+ };
+ char *reply = NULL;
+ size_t stop_size;
+
+
+ if (stop_reply)
+ /* pop_notification gave us a cached notification */
+ stop = *stop_reply;
+ else
+ stop.reply = gdb_recv(gdb, &stop.size, true);
+
+ if (debug_flag)
+ {
+ error_msg("%s\n", stop.reply);
+ fflush(stdout);
+ }
+
+ if (gdb_has_non_stop(gdb) && !stop_reply) {
+ /* non-stop packet order:
+ client sends: $vCont;c
+ server sends: OK
+ server sends: %Stop:T05syscall_entry (possibly out of order)
+ client sends: $vStopped
+ server possibly sends 0 or more: T05syscall_entry
+ client sends to each: $vStopped
+ server sends: OK
+ */
+ /* Do we have an out of order notification? (see gdb_recv) */
+ reply = pop_notification(&stop_size);
+ if (reply) {
+ if (debug_flag)
+ error_msg("popped %s\n", reply);
+ stop.reply = reply;
+ reply = gdb_recv(gdb, &stop_size, false); /* vContc OK */
+ }
+ else {
+ while (stop.reply[0] != 'T' && stop.reply[0] != 'W')
+ stop.reply = gdb_recv(gdb, &stop.size, true);
+ }
+ }
+ if (gdb_has_non_stop(gdb) && (stop.reply[0] == 'T')) {
+ do {
+ size_t this_size;
+ gdb_send(gdb,"vStopped",8);
+ reply = gdb_recv(gdb, &this_size, true);
+ if (strcmp(reply, "OK") == 0)
+ break;
+ push_notification(reply, this_size);
+ } while (true);
+ }
+
+ /* all good packets are at least 3 bytes */
+ switch (stop.size >= 3 ? stop.reply[0] : 0) {
+ case 'E':
+ stop.type = gdb_stop_error;
+ stop.code = gdb_decode_hex_n(stop.reply + 1, 2);
+ break;
+ case 'S':
+ case 'T':
+ gdb_recv_signal(&stop);
+ break;
+ case 'W':
+ case 'X':
+ gdb_recv_exit(&stop);
+ break;
+ default:
+ stop.type = gdb_stop_unknown;
+ break;
+ }
+
+ return stop;
+}
+
+static bool
+gdb_ok(void)
+{
+ size_t size;
+ char *reply = gdb_recv(gdb, &size, false);
+ bool ok = size == 2 && !strcmp(reply, "OK");
+ free(reply);
+ return ok;
+}
+
+
+bool
+gdb_prog_pid_check (char *exec_name, int nprocs)
+{
+ /* under gdbserver, we can reasonably allow having neither to use
existing targets. */
+ if (!exec_name && !nprocs && !gdbserver)
+ return false;
+ return true;
+}
+
+
+bool
+gdb_start_init(void)
+{
+# if ! defined X86_64
+ error_msg("-G is not supported on this target.");
+ return false; /* Only supported on x86_64 */
+# endif
+
+ gdb_signal_map_init();
+
+ if (gdbserver[0] == '|')
+ gdb = gdb_begin_command(gdbserver + 1);
+ else if (strchr(gdbserver, ':') && !strchr(gdbserver, '/')) {
+ if (strchr(gdbserver, ';')) {
+ const char *stop_option;
+ gdbserver = strtok(gdbserver, ";");
+ stop_option = strtok(NULL, "");
+ stop_option += strspn(" ", stop_option);
+ if (!strcmp(stop_option, "non-stop"))
+ gdb_nonstop = true;
+ }
+ const char *node = strtok(gdbserver, ":");
+ const char *service = strtok(NULL, "");
+ gdb = gdb_begin_tcp(node, service);
+ } else
+ gdb = gdb_begin_path(gdbserver);
+
+ if (!gdb_start_noack(gdb))
+ error_msg("couldn't enable GDB server noack mode");
+
+ char multi_cmd[] = "qSupported:multiprocess+;QThreadEvents+"
+ ";fork-events+;vfork-events+;exec-events+";
+
+ sprintf(multi_cmd, "qSupported:multiprocess+;QThreadEvents+;%s%s",
+ followfork ? ";fork-events+;vfork-events+" : "",
+ detach_on_execve ? ";exec-events" : "");
+
+ gdb_send(gdb, multi_cmd, sizeof(multi_cmd) - 1);
+
+ size_t size;
+ bool gdb_fork;
+ char *reply = gdb_recv(gdb, &size, false);
+ gdb_multiprocess = strstr(reply, "multiprocess+") != NULL;
+ if (!gdb_multiprocess)
+ error_msg("couldn't enable GDB server multiprocess mode");
+ if (followfork) {
+ gdb_fork = strstr(reply, "fork-events+") != NULL;
+ if (!gdb_fork)
+ error_msg("couldn't enable GDB server fork events handling");
+ gdb_fork = strstr(reply, "vfork-events+") != NULL;
+ if (!gdb_fork)
+ error_msg("couldn't enable GDB server vfork events handling");
+ }
+ if (!detach_on_execve) {
+ if (!strstr(reply, "exec-events+"))
+ error_msg("couldn't enable GDB server exec events handling");
+ }
+ free(reply);
+
+ static const char extended_cmd[] = "!";
+ gdb_send(gdb, extended_cmd, sizeof(extended_cmd) - 1);
+ gdb_extended = gdb_ok();
+ if (!gdb_extended)
+ error_msg("couldn't enable GDB server extended mode");
+
+ static const char pass_signals[] =
"QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97;";
+ gdb_send(gdb, pass_signals, sizeof(pass_signals) - 1);
+ if (!gdb_ok())
+ error_msg("couldn't enable GDB server signal passing");
+
+ static const char program_signals[] =
"QProgramSignals:0;1;3;4;6;7;8;9;a;b;c;d;e;f;10;11;12;13;14;15;16;17;18;19;1a;1b;1c;1d;1e;1f;20;21;22;23;24;25;26;27;28;29;2a;2b;2c;2d;2e;2f;30;31;32;33;34;35;36;37;38;39;3a;3b;3c;3d;3e;3f;40;41;42;43;44;45;46;47;48;49;4a;4b;4c;4d;4e;4f;50;51;52;53;54;55;56;57;58;59;5a;5b;5c;5d;5e;5f;60;61;62;63;64;65;66;67;68;69;6a;6b;6c;6d;6e;6f;70;71;72;73;74;75;76;77;78;79;7a;7b;7c;7d;7e;7f;80;81;82;83;84;85;86;87;88;89;8a;8b;8c;8d;8e;8f;90;91;92;93;94;95;96;97;";
+ gdb_send(gdb, program_signals, sizeof(program_signals) - 1);
+ if (!gdb_ok())
+ error_msg("couldn't enable GDB server signal passing");
+
+ static const char vcont_cmd[] = "vCont?";
+ gdb_send(gdb, vcont_cmd, sizeof(vcont_cmd) - 1);
+ reply = gdb_recv(gdb, &size, false);
+ gdb_vcont = strncmp(reply, "vCont", 5) == 0;
+ if (!gdb_vcont)
+ error_msg("GDB server doesn't support vCont");
+ free(reply);
+ return true;
+}
+
+
+static void
+gdb_init_syscalls(void)
+{
+ static const char syscall_cmd[] = "QCatchSyscalls:1";
+ const char *syscall_set = "";
+ bool want_syscall_set = false;
+ unsigned sci;
+
+ /* Only send syscall list if a filtered list was given with -e */
+ for (sci = 0; sci < nsyscalls; sci++)
+ if (! (qual_flags(sci) & QUAL_TRACE)) {
+ want_syscall_set = true;
+ break;
+ }
+
+ for (sci = 0; want_syscall_set && sci < nsyscalls; sci++)
+ if (qual_flags(sci) & QUAL_TRACE)
+ if (asprintf ((char**)&syscall_set, "%s;%x", syscall_set, sci) < 0)
+ error_msg("couldn't enable GDB server syscall catching");
+
+ if (want_syscall_set)
+ asprintf ((char**)&syscall_set, "%s%s", syscall_cmd, syscall_set);
+ else
+ syscall_set = syscall_cmd;
+ gdb_send(gdb, syscall_set, strlen(syscall_set));
+ if (!gdb_ok())
+ error_msg("couldn't enable GDB server syscall catching");
+}
+
+static struct tcb*
+gdb_find_thread(int tid, bool current)
+{
+ if (tid < 0)
+ return NULL;
+
+ /* Look up 'tid' in our table. */
+ struct tcb *tcp = pid2tcb(tid);
+ if (!tcp) {
+ tcp = alloctcb(tid);
+ tcp->flags |= TCB_GDB_CONT_PID_TID;
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+ newoutf(tcp);
+
+ if (!current) {
+ char cmd[] = "Hgxxxxxxxx";
+ sprintf(cmd, "Hg%x", tid);
+ gdb_send(gdb, cmd, strlen(cmd));
+ current = gdb_ok();
+ if (!current)
+ error_msg("couldn't set GDB server to thread %d", tid);
+ }
+ }
+ return tcp;
+}
+
+static void
+gdb_enumerate_threads(void)
+{
+ /* qfThreadInfo [qsThreadInfo]...
+ * -> m thread
+ * -> m thread,thread
+ * -> l (finished) */
+
+ static const char qfcmd[] = "qfThreadInfo";
+
+ gdb_send(gdb, qfcmd, sizeof(qfcmd) - 1);
+
+ size_t size;
+ char *reply = gdb_recv(gdb, &size, false);
+ while (reply[0] == 'm') {
+ char *thread;
+ for (thread = strtok(reply + 1, ","); thread;
+ thread = strtok(NULL, "")) {
+ int pid, tid;
+ gdb_parse_thread(thread, &pid, &tid);
+
+ struct tcb *tcp = gdb_find_thread(tid, false);
+ if (tcp && !current_tcp)
+ current_tcp = tcp;
+ }
+
+ free(reply);
+
+ static const char qscmd[] = "qsThreadInfo";
+ gdb_send(gdb, qscmd, sizeof(qscmd) - 1);
+ reply = gdb_recv(gdb, &size, false);
+ }
+
+ free(reply);
+}
+
+static void
+interrupt(int sig)
+{
+ interrupted = sig;
+}
+
+void
+gdb_end_init(void)
+{
+ /* TODO interface with -I? */
+ set_sigaction(SIGHUP, interrupt, NULL);
+ set_sigaction(SIGINT, interrupt, NULL);
+ set_sigaction(SIGQUIT, interrupt, NULL);
+ set_sigaction(SIGPIPE, interrupt, NULL);
+ set_sigaction(SIGTERM, interrupt, NULL);
+
+ /* We enumerate all attached threads to be sure, especially
+ * since we get all threads on vAttach, not just the one
+ * pid. */
+ gdb_enumerate_threads();
+
+ /* Everything was stopped from startup_child/startup_attach,
+ * now continue them all so the next reply will be a stop
+ * packet */
+ if (gdb_vcont) {
+ static const char cmd[] = "vCont;c";
+ gdb_send(gdb, cmd, sizeof(cmd) - 1);
+ } else {
+ static const char cmd[] = "c";
+ gdb_send(gdb, cmd, sizeof(cmd) - 1);
+ }
+}
+
+void
+gdb_cleanup(void)
+{
+ if (gdb)
+ gdb_end(gdb);
+ gdb = NULL;
+}
+
+void
+gdb_startup_child(char **argv)
+{
+ if (!gdb)
+ error_msg_and_die("GDB server not connected!");
+
+ if (!gdb_extended)
+ error_msg_and_die("GDB server doesn't support starting processes!");
+
+ /* Without knowing gdb's current tid, vCont of the correct thread for
+ the multithreaded nonstop case is difficult, so default to all-stop */
+
+ size_t i;
+ size_t size = 4; /*vRun */
+ for (i = 0; argv[i]; ++i) {
+ size += 1 + 2 * strlen(argv[i]); /*;hexified-argument */
+ }
+
+ if (gdb_nonstop) {
+ static const char nonstop_cmd[] = "QNonStop:1";
+ gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+ if (!gdb_ok())
+ gdb_nonstop = false;
+ }
+
+ {
+ char cmd[size];
+ char *cmd_ptr = cmd;
+ memcpy(cmd_ptr, "vRun", 4);
+ cmd_ptr += 4;
+ for (i = 0; argv[i]; ++i) {
+ *cmd_ptr++ = ';';
+ const char *arg = argv[i];
+ while (*arg) {
+ gdb_encode_hex(*arg++, cmd_ptr);
+ cmd_ptr += 2;
+ }
+ }
+
+ gdb_send(gdb, cmd, size);
+ }
+
+ struct gdb_stop_reply stop = gdb_recv_stop(NULL);
+ if (stop.size == 0)
+ error_msg_and_die("GDB server doesn't support vRun!");
+ switch (stop.type) {
+ case gdb_stop_error:
+ error_msg_and_die("GDB server failed vRun with %.*s",
+ (int)stop.size, stop.reply);
+ case gdb_stop_trap:
+ break;
+ default:
+ error_msg_and_die("GDB server expected vRun trap, got: %.*s",
+ (int)stop.size, stop.reply);
+ }
+
+ pid_t tid = stop.tid;
+ free(stop.reply);
+
+ strace_child = tid;
+
+ struct tcb *tcp = alloctcb(tid);
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+ newoutf(tcp);
+ gdb_init_syscalls();
+
+ if (gdb_nonstop)
+ gdb_set_non_stop(gdb, true);
+ else
+ gdb_set_non_stop(gdb, false);
+ /* TODO normal strace attaches right before exec, so the first
+ * syscall seen is the execve with all its arguments. Need to
+ * emulate that here? */
+ tcp->flags &= ~TCB_HIDE_LOG;
+}
+
+void
+gdb_attach_tcb(struct tcb *tcp)
+{
+ if (!gdb)
+ error_msg_and_die("GDB server not connected!");
+
+ if (!gdb_extended)
+ error_msg_and_die("GDB server doesn't support attaching "
+ "processes");
+
+ struct gdb_stop_reply stop;
+ static const char nonstop_cmd[] = "QNonStop:1";
+ char vattach_cmd[] = "vAttach;XXXXXXXX";
+
+ gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+ if (gdb_ok())
+ gdb_set_non_stop(gdb, true);
+
+ sprintf(vattach_cmd, "vAttach;%x", tcp->pid);
+ gdb_send(gdb, vattach_cmd, strlen(vattach_cmd));
+
+ do {
+ /*
+ non-stop packet order:
+ client sends: vCont;t
+ server sends: OK
+ server sends: Stop:T05swbreak:;
+ client sends: vStopped
+ [ server sends: T05swbreak:;
+ client sends: vStopped ]
+ server sends: OK
+ */
+ char h_cmd[] = "Hgxxxxxxxx";
+ char vcont_cmd[] = "vCont;t:pXXXXXXXX";
+ if (!gdb_ok()) {
+ stop.type = gdb_stop_unknown;
+ break;
+ }
+ sprintf(h_cmd, "Hg%x.-1", tcp->pid);
+ gdb_send(gdb, h_cmd, strlen(h_cmd));
+ if (!gdb_ok()) {
+ stop.type = gdb_stop_unknown;
+ break;
+ }
+ sprintf(vcont_cmd, "vCont;t:p%x.-1", tcp->pid);
+ gdb_send(gdb, vcont_cmd, strlen(vcont_cmd));
+ stop = gdb_recv_stop(NULL);
+ } while (0);
+
+ if (stop.type == gdb_stop_unknown) {
+ static const char nonstop_cmd[] = "QNonStop:0";
+ gdb_send(gdb, nonstop_cmd, sizeof(nonstop_cmd) - 1);
+ if (gdb_ok())
+ gdb_set_non_stop(gdb, false);
+ else
+ error_msg_and_die("Cannot connect to process %d: "
+ "GDB server doesn't support vAttach!",
+ tcp->pid);
+ gdb_send(gdb, vattach_cmd, strlen(vattach_cmd));
+ stop = gdb_recv_stop(NULL);
+ if (stop.size == 0)
+ error_msg_and_die("Cannot connect to process %d: "
+ "GDB server doesn't support vAttach!",
+ tcp->pid);
+ switch (stop.type) {
+ case gdb_stop_error:
+ error_msg_and_die("Cannot connect to process %d: "
+ "GDB server failed vAttach with %.*s",
+ tcp->pid, (int)stop.size, stop.reply);
+ case gdb_stop_trap:
+ break;
+ case gdb_stop_signal:
+ if (stop.code == 0)
+ break;
+ /* fallthrough */
+ default:
+ error_msg_and_die("Cannot connect to process %d: "
+ "GDB server expected vAttach trap, got: %.*s",
+ tcp->pid, (int)stop.size, stop.reply);
+ }
+ }
+
+ pid_t tid = stop.tid;
+ free(stop.reply);
+
+ if (tid != tcp->pid) {
+ droptcb(tcp);
+ tcp = alloctcb(tid);
+ }
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+ newoutf(tcp);
+ gdb_init_syscalls();
+
+ if (!qflag)
+ fprintf(stderr, "Process %u attached in %s mode\n", tcp->pid,
+ gdb_has_non_stop(gdb) ? "non-stop" : "all-stop");
+}
+
+void
+gdb_detach(struct tcb *tcp)
+{
+ static bool already_detaching = false;
+
+ if (! already_detaching)
+ already_detaching = true;
+ if (already_detaching || gdb == NULL)
+ return;
+ if (gdb_multiprocess) {
+ char cmd[] = "D;XXXXXXXX";
+ sprintf(cmd, "D;%x", tcp->pid);
+ gdb_send(gdb, cmd, strlen(cmd));
+ } else {
+ static const char cmd[] = "D";
+ gdb_send(gdb, cmd, sizeof(cmd) - 1);
+ }
+
+ if (!gdb_ok()) {
+ /* is it still alive? */
+ char cmd[] = "T;XXXXXXXX";
+ sprintf(cmd, "T;%x", tcp->pid);
+ gdb_send(gdb, cmd, strlen(cmd));
+ if (gdb_ok())
+ error_msg("GDB server failed to detach %d", tcp->pid);
+ /* otherwise it's dead, or already detached, fine. */
+ }
+
+ if (!qflag && (tcp->flags & TCB_ATTACHED))
+ error_msg("Process %u detached", tcp->pid);
+
+ droptcb(tcp);
+}
+
+
+enum trace_event
+gdb_next_event(int *pstatus, siginfo_t *si)
+{
+ int gdb_sig = 0;
+ pid_t tid;
+ struct tcb *tcp = NULL;
+
+ if (interrupted)
+ return TE_BREAK;
+
+ stop.reply = pop_notification(&stop.size);
+ if (stop.reply) /* cached out of order notification? */
+ stop = gdb_recv_stop(&stop);
+ else
+ stop = gdb_recv_stop(NULL);
+ if (stop.size == 0)
+ error_msg_and_die("GDB server gave an empty stop reply!?");
+
+ switch (stop.type) {
+ case gdb_stop_unknown:
+ error_msg_and_die("GDB server stop reply unknown: %.*s",
+ (int)stop.size, stop.reply);
+ break;
+ case gdb_stop_error:
+ /* vCont error -> no more processes */
+ free(stop.reply);
+ return TE_BREAK;
+ default:
+ break;
+ }
+
+
+ tid = -1;
+ tcp = NULL;
+
+ if (gdb_multiprocess) {
+ tid = stop.tid;
+ tcp = gdb_find_thread(tid, true);
+ /* Set current output file */
+ current_tcp = tcp;
+ } else if (current_tcp) {
+ tcp = current_tcp;
+ tid = tcp->pid;
+ }
+ if (tid < 0 || tcp == NULL)
+ error_msg_and_die("couldn't read tid from stop reply: %.*s",
+ (int)stop.size, stop.reply);
+
+ switch (stop.type) {
+ case gdb_stop_exited:
+ *pstatus = W_EXITCODE(stop.code, 0);
+ return TE_EXITED;
+
+ case gdb_stop_terminated:
+ *pstatus = W_EXITCODE(0, gdb_signal_to_target(tcp, stop.code));
+ return TE_SIGNALLED;
+
+ case gdb_stop_unknown: /* already handled above */
+ case gdb_stop_error: /* already handled above */
+ case gdb_stop_trap: /* misc trap */
+ break;
+ case gdb_stop_syscall_entry:
+ /* If we thought we were already in a syscall --
+ * missed a return? -- skipping this report doesn't do
+ * much good. Might as well force it to be a new
+ * entry regardless to sync up. */
+ tcp->flags &= ~TCB_INSYSCALL;
+ tcp->scno = stop.code;
+ gdb_sig = stop.code;
+ *pstatus = gdb_signal_to_target(tcp, gdb_sig);
+ if (stop.code == __NR_exit_group)
+ return TE_GROUP_STOP;
+ else
+ return TE_SYSCALL_STOP;
+
+ case gdb_stop_syscall_return:
+ /* If we missed the entry, recording a return will
+ * only confuse things, so let's just report the good
+ * ones. */
+ if (exiting(tcp)) {
+ tcp->scno = stop.code;
+ gdb_sig = stop.code;
+ *pstatus = gdb_signal_to_target(tcp, gdb_sig);
+ return TE_SYSCALL_STOP;
+ }
+ break;
+
+ case gdb_stop_signal:
+ {
+ size_t siginfo_size;
+ char *siginfo_reply =
+ gdb_xfer_read(gdb, "siginfo", "", &siginfo_size);
+ if (siginfo_reply && siginfo_size == sizeof(siginfo_t))
+ si = (siginfo_t *) siginfo_reply;
+
+ /* XXX gdbserver returns "native" siginfo of 32/64-bit
+ * target but strace expects its own format as
+ * PTRACE_GETSIGINFO would have given it. (i.e. need
+ * to reverse siginfo_fixup)
+ * ((i.e. siginfo_from_compat_siginfo)) */
+
+ gdb_sig = stop.code;
+ *pstatus = gdb_signal_to_target(tcp, gdb_sig);
+ free(siginfo_reply);
+ return TE_SIGNAL_DELIVERY_STOP;
+ }
+
+ default:
+ /* TODO Do we need to handle gdb_multiprocess here? */
+ break;
+ }
+
+ return TE_RESTART;
+}
+
+/* Returns true iff the main trace loop has to continue. The gdb
+ * connection should be ready for a stop reply on entry,p and we'll
+ * leave it the same way if we return true. */
+
+bool
+gdb_dispatch_event(enum trace_event ret, int *pstatus, void *si_p)
+{
+ siginfo_t *si = (siginfo_t*)si_p;
+ int gdb_sig = 0;
+ struct tcb *tcp = current_tcp;
+ pid_t tid;
+ unsigned int sig = 0;
+
+
+ /* Exit if the process has gone away */
+ if (tcp == 0)
+ return false;
+ tid = tcp->pid;
+ if (! (tcp->flags & TCB_GDB_CONT_PID_TID)) {
+ char cmd[] = "Hgxxxxxxxx";
+ sprintf(cmd, "Hg%x.%x", general_pid, general_tid);
+ if (debug_flag)
+ error_msg("%s %s\n", __FUNCTION__, cmd);
+ }
+ /* TODO need code equivalent to PTRACE_EVENT_EXEC? */
+
+ /* Is this the very first time we see this tracee stopped? */
+ if (tcp->flags & TCB_STARTUP) {
+ tcp->flags &= ~TCB_STARTUP;
+ if (get_scno(tcp) == 1)
+ tcp->s_prev_ent = tcp->s_ent;
+ }
+
+ /* TODO cflag means we need to update tcp->dtime/stime usually
+ * through wait rusage, but how can we do it? */
+
+ free (stop.reply);
+
+ switch (ret) {
+ case TE_BREAK:
+ return false;
+
+ case TE_RESTART:
+ break;
+
+ case TE_SYSCALL_STOP:
+ trace_syscall(tcp, &sig);
+ break;
+
+ case TE_SIGNAL_DELIVERY_STOP:
+ sig = *pstatus;
+ gdb_sig = stop.code;
+ print_stopped(tcp, si, *pstatus);
+ break;
+
+ case TE_SIGNALLED:
+ print_signalled(tcp, tid, *pstatus);
+ droptcb(tcp);
+ return false;
+
+ case TE_EXITED:
+ print_exited(tcp, tid, *pstatus);
+ droptcb(tcp);
+ /* Don't continue if the process exited */
+ if (!gdb_multiprocess || gdb_has_non_stop(gdb))
+ return false;
+ break;
+
+ case TE_STOP_BEFORE_EXECVE:
+ case TE_STOP_BEFORE_EXIT:
+ /* TODO handle this? */
+ return false;
+
+ case TE_GROUP_STOP:
+ trace_syscall(tcp, &sig);
+ sig = *pstatus;
+ return false;
+
+ case TE_NEXT:
+ break;
+ }
+
+ /* We handled quick cases, we are permitted to interrupt now. */
+ if (interrupted)
+ return false;
+
+ /* Don't continue gdbserver until we handle any queued notifications */
+ if (have_notification())
+ return true;
+
+ if (gdb_sig) {
+ if (gdb_vcont) {
+ /* send the signal to this target and continue everyone else */
+ char cmd[] = "vCont;Cxx:xxxxxxxx;c";
+ sprintf(cmd, "vCont;C%02x:%x;c", gdb_sig, tid);
+ gdb_send(gdb, cmd, strlen(cmd));
+ } else {
+ /* just send the signal */
+ char cmd[] = "Cxx";
+ sprintf(cmd, "C%02x", gdb_sig);
+ gdb_send(gdb, cmd, strlen(cmd));
+ }
+ } else {
+ if (gdb_vcont) {
+ /* For non-stop use $vCont;c:pid.tid where
+ * pid.tid is the thread gdbserver is focused
+ * on */
+ char cmd[] = "vCont;c:xxxxxxxx.xxxxxxxx";
+
+ struct tcb *general_tcp = gdb_find_thread(general_tid, true);
+ if (gdb_has_non_stop(gdb) && general_pid != general_tid
+ && general_tcp->flags & TCB_GDB_CONT_PID_TID)
+ sprintf(cmd, "vCont;c:p%x.%x", general_pid, general_tid);
+ else
+ sprintf(cmd, "vCont;c");
+ gdb_send(gdb, cmd, sizeof(cmd) - 1);
+ } else {
+ static const char cmd[] = "c";
+ gdb_send(gdb, cmd, sizeof(cmd) - 1);
+ }
+ }
+
+ return true;
+}
+
+
+char *
+gdb_get_all_regs(pid_t tid, size_t *size)
+{
+ if (!gdb)
+ return NULL;
+
+ /* NB: this assumes gdbserver's current thread is also tid. If that
+ * may not be the case, we should send "HgTID" first, and restore. */
+ gdb_send(gdb, "g", 1);
+ return gdb_recv(gdb, size, false);
+}
+
+
+#ifdef GDBSERVER_ARCH_HAS_GET_REGS
+# include "gdb_get_regs.c"
+#else
+long gdb_get_regs(pid_t pid, void *io) { return -1; }
+#endif
+
+
+#ifdef GDBSERVER_ARCH_HAS_SET_REGS
+# include "gdb_set_regs.c"
+#else
+long gdb_set_regs(pid_t pid, void *io) { return -1; }
+#endif
+
+
+int
+gdb_get_scno(struct tcb *tcp)
+{
+ return 1;
+}
+
+int
+gdb_read_mem(pid_t tid, long addr, unsigned int len, bool check_nil,
char *out)
+{
+ if (!gdb) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* NB: this assumes gdbserver's current thread is also tid. If that
+ * may not be the case, we should send "HgTID" first, and restore. */
+ while (len) {
+ char cmd[] = "mxxxxxxxxxxxxxxxx,xxxx";
+ unsigned int chunk_len = len < 0x1000 ? len : 0x1000;
+ sprintf(cmd, "m%lx,%x", addr, chunk_len);
+ gdb_send(gdb, cmd, strlen(cmd));
+
+ size_t size;
+ char *reply = gdb_recv(gdb, &size, false);
+ if (size < 2 || reply[0] == 'E' || size > len * 2
+ || gdb_decode_hex_buf(reply, size, out) < 0) {
+ free(reply);
+ errno = EINVAL;
+ return -1;
+ }
+
+ chunk_len = size / 2;
+ if (check_nil && strnlen(out, chunk_len) < chunk_len)
+ {
+ free(reply);
+ return 1;
+ }
+
+ addr += chunk_len;
+ out += chunk_len;
+ len -= chunk_len;
+ free(reply);
+ }
+
+ return 0;
+}
+
+
+int
+gdb_write_mem(pid_t tid, long addr, unsigned int len, char *buffer)
+{
+ unsigned int i, j;
+ const char packet_template[] = "Xxxxxxxxxxxxxxxxx,xxxx:";
+ char cmd[strlen(packet_template) + len];
+
+ if (!gdb) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* NB: this assumes gdbserver's current thread is also tid. If that
+ * may not be the case, we should send "HgTID" first, and restore. */
+ sprintf(cmd, "X%lx,%x:", addr, len);
+ j = strlen(cmd);
+ for (i = 0; i < len; i++)
+ cmd[j++] = buffer[i];
+ cmd[j] = '\0';
+ gdb_send(gdb, cmd, strlen(cmd));
+ if (!gdb_ok())
+ error_msg("Failed to poke data to GDB server");
+
+ return 0;
+}
+
+
+int
+gdb_umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len,
+ void *const our_addr)
+{
+ return gdb_read_mem(tcp->pid, addr, len, false, our_addr);
+}
+
+
+int
+gdb_umovestr(struct tcb *const tcp, kernel_ulong_t addr, unsigned int
len, char *laddr)
+{
+ return gdb_read_mem(tcp->pid, addr, len, true, laddr);
+}
+
+int
+gdb_upeek(int pid, unsigned long off, kernel_ulong_t *res)
+{
+ return gdb_read_mem(pid, off, current_wordsize, false, (char*)res);
+}
+
+
+int
+gdb_upoke(int pid, unsigned long off, kernel_ulong_t res)
+{
+ kernel_ulong_t buffer = res;
+ return gdb_write_mem(pid, off, current_wordsize, (char*)&buffer);
+}
+
+
+int
+gdb_getfdpath(struct tcb *tcp, int fd, char *buf, unsigned bufsize)
+{
+ if (!gdb || fd < 0)
+ return -1;
+
+ /*
+ * As long as we assume a Linux target, we can peek at their procfs
+ * just like normal getfdpath does. Maybe that won't always be true.
+ */
+ char linkpath[sizeof("/proc/%u/fd/%u") + 2 * sizeof(int)*3];
+ sprintf(linkpath, "/proc/%u/fd/%u", tcp->pid, fd);
+ return gdb_readlink(gdb, linkpath, buf, bufsize);
+}
+
+
+bool
+gdb_verify_args(const char *username, bool daemon, unsigned int
*follow_fork)
+{
+ if (username) {
+ error_msg_and_die("-u and -G are mutually exclusive");
+ }
+
+ if (daemon) {
+ error_msg_and_die("-D and -G are mutually exclusive");
+ }
+
+ if (!*follow_fork) {
+ error_msg("-G is always multithreaded, implies -f");
+ *follow_fork = 1;
+ }
+
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ error_msg_and_die("Simultaneous usage of gdbserver backend (-G) and "
+ "stack tracing (-k) is not supported");
+#endif
+
+ return true;
+}
+
+
+bool
+gdb_handle_arg(char arg, char *optarg)
+{
+ if (arg != 'G')
+ return false;
+
+ gdbserver = optarg;
+ backend.attach_tcb = gdb_attach_tcb;
+ backend.cleanup = gdb_cleanup;
+ backend.detach = gdb_detach;
+ backend.dispatch_event = gdb_dispatch_event;
+ backend.end_init = gdb_end_init;
+ backend.get_regs = gdb_get_regs;
+ backend.get_scno = gdb_get_scno;
+ backend.getfdpath = gdb_getfdpath;
+ backend.next_event = gdb_next_event;
+ backend.prog_pid_check = gdb_prog_pid_check;
+ backend.start_init = gdb_start_init;
+ backend.startup_child = gdb_startup_child;
+ backend.umoven = gdb_umoven;
+ backend.umovestr = gdb_umovestr;
+ backend.upeek_ = gdb_upeek;
+ backend.upoke_ = gdb_upoke;
+ backend.verify_args = gdb_verify_args;
+ return true;
+}
diff --git a/gdbserver/protocol.c b/gdbserver/protocol.c
new file mode 100644
index 0000000..1ef5660
--- /dev/null
+++ b/gdbserver/protocol.c
@@ -0,0 +1,734 @@
+/* Simple implementation of a GDB remote protocol client.
+ *
+ * Copyright (c) 2015 Red Hat Inc.
+ * Copyright (c) 2015 Josh Stone <cuviper at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE 1
+#include <err.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+
+#include "protocol.h"
+#include "defs.h"
+
+struct gdb_conn {
+ FILE *in;
+ FILE *out;
+ bool ack;
+ bool non_stop;
+};
+
+/* non-stop notifications (see gdb_recv_stop) */
+struct notifications_s {
+ int size;
+ int start;
+ int count;
+ char **packet;
+} notifications;
+
+
+void
+gdb_encode_hex(uint8_t byte, char* out) {
+ static const char value_hex[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+ };
+ *out++ = value_hex[byte >> 4];
+ *out++ = value_hex[byte & 0xf];
+}
+
+char *
+gdb_encode_hex_string(const char *str)
+{
+ char *out = malloc(2 * strlen(str) + 1);
+ if (out) {
+ char *out_ptr = out;
+ while (*str) {
+ gdb_encode_hex(*str++, out_ptr);
+ out_ptr += 2;
+ }
+ *out_ptr = '\0';
+ }
+ return out;
+}
+
+
+static inline uint8_t
+hex_nibble(uint8_t hex)
+{
+ static const uint8_t hex_value[256] = {
+ [0 ... '0'-1] = UINT8_MAX,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ ['9'+1 ... 'A'-1] = UINT8_MAX,
+ 10, 11, 12, 13, 14, 15,
+ ['F'+1 ... 'a'-1] = UINT8_MAX,
+ 10, 11, 12, 13, 14, 15,
+ ['f'+1 ... 255] = UINT8_MAX,
+ };
+ return hex_value[hex];
+}
+
+uint16_t gdb_decode_hex(char msb, char lsb)
+{
+ uint8_t high_nibble = hex_nibble(msb);
+ uint8_t low_nibble = hex_nibble(lsb);
+ if (high_nibble >= 16 || low_nibble >= 16)
+ return UINT16_MAX;
+ return 16 * hex_nibble(msb) + hex_nibble(lsb);
+}
+
+uint64_t gdb_decode_hex_n(const char *bytes, size_t n)
+{
+ uint64_t value = 0;
+ while (n--) {
+ uint8_t nibble = hex_nibble(*bytes++);
+ if (nibble >= 16)
+ break;
+ value = 16 * value + nibble;
+ }
+ return value;
+}
+
+uint64_t gdb_decode_hex_str(const char *bytes)
+{
+ uint64_t value = 0;
+ while (*bytes) {
+ uint8_t nibble = hex_nibble(*bytes++);
+ if (nibble >= 16)
+ break;
+ value = 16 * value + nibble;
+ }
+ return value;
+}
+
+int64_t gdb_decode_signed_hex_str(const char *bytes)
+{
+ return (*bytes == '-')
+ ? -(int64_t)gdb_decode_hex_str(bytes + 1)
+ : (int64_t)gdb_decode_hex_str(bytes);
+}
+
+int gdb_decode_hex_buf(const char *bytes, size_t n, char *out)
+{
+ if (n & 1)
+ return -1;
+
+ while (n > 1) {
+ uint16_t byte = gdb_decode_hex(bytes[0], bytes[1]);
+ if (byte > UINT8_MAX)
+ return -1;
+
+ *out++ = byte;
+ bytes += 2;
+ n -= 2;
+ }
+ return 0;
+}
+
+
+static struct gdb_conn *
+gdb_begin(int fd)
+{
+ struct gdb_conn *conn = xcalloc(1, sizeof(struct gdb_conn));
+
+ conn->ack = true;
+
+ /* duplicate the handle to separate read/write state */
+ int fd2 = dup(fd);
+ if (fd2 < 0)
+ perror_msg_and_die("dup");
+
+ /* open a FILE* for reading */
+ conn->in = fdopen(fd, "rb");
+ if (conn->in == NULL)
+ perror_msg_and_die("fdopen in");
+
+ /* open a FILE* for writing */
+ conn->out = fdopen(fd2, "wb");
+ if (conn->out == NULL)
+ perror_msg_and_die("fdopen out");
+
+ /* reset line state by acking any earlier input */
+ fputc('+', conn->out);
+ fflush(conn->out);
+
+ return conn;
+}
+
+#define ZERO_OR_DIE(f, ...) \
+ do { \
+ int ret = f(__VA_ARGS__); \
+ if (ret) \
+ perror_msg_and_die(#f); \
+ } while (0)
+
+struct gdb_conn *
+gdb_begin_command(const char *command)
+{
+ int fds[2];
+ pid_t pid;
+ posix_spawn_file_actions_t file_actions;
+ const char* sh = "/bin/sh";
+ const char *const const_argv[] = {"sh", "-c", command, NULL};
+ char *const *argv = (char *const *) const_argv;
+
+ /* Create a bidirectional "pipe", [0] for us and [1] for the command
stdio. */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0)
+ perror_msg_and_die("socketpair");
+
+ ZERO_OR_DIE(posix_spawn_file_actions_init, &file_actions);
+
+ /* Close our end in the child. */
+ ZERO_OR_DIE(posix_spawn_file_actions_addclose, &file_actions, fds[0]);
+
+ /* Copy the child's end to its stdout and stdin. */
+ if (fds[1] != STDOUT_FILENO) {
+ ZERO_OR_DIE(posix_spawn_file_actions_adddup2, &file_actions,
+ fds[1], STDOUT_FILENO);
+ ZERO_OR_DIE(posix_spawn_file_actions_addclose, &file_actions,
+ fds[1]);
+ }
+ ZERO_OR_DIE(posix_spawn_file_actions_adddup2, &file_actions,
+ STDOUT_FILENO, STDIN_FILENO);
+
+ /* Spawn the actual command. */
+ ZERO_OR_DIE(posix_spawn, &pid, sh, &file_actions, NULL, argv, environ);
+
+ /* Cleanup. */
+ ZERO_OR_DIE(posix_spawn_file_actions_destroy, &file_actions);
+
+ close(fds[1]);
+
+ /* Avoid SIGPIPE when the command quits. */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* initialize the rest of gdb on this handle */
+ return gdb_begin(fds[0]);
+}
+
+struct gdb_conn *
+gdb_begin_tcp(const char *node, const char *service)
+{
+ /* NB: gdb doesn't support IPv6 - should we? */
+ const struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ };
+
+ struct addrinfo *result = NULL;
+ int s = getaddrinfo(node, service, &hints, &result);
+ if (s)
+ error_msg_and_die("getaddrinfo: %s", gai_strerror(s));
+
+ int fd = -1;
+ struct addrinfo *ai;
+ for (ai = result; ai; ai = ai->ai_next) {
+ /* open the socket and start the tcp connection */
+ fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (fd < 0)
+ continue;
+
+ if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0)
+ break;
+
+ close(fd);
+ fd = -1;
+ }
+
+ freeaddrinfo(result);
+ if (fd < 0)
+ error_msg_and_die("Cannot connect to GDB server");
+
+ /* initialize the rest of gdb on this handle */
+ return gdb_begin(fd);
+}
+
+struct gdb_conn *
+gdb_begin_path(const char *path)
+{
+ int fd = open(path, O_RDWR);
+ if (fd < 0)
+ perror_msg_and_die("open");
+
+ /* initialize the rest of gdb on this handle */
+ return gdb_begin(fd);
+}
+
+
+void
+gdb_end(struct gdb_conn *conn)
+{
+ fclose(conn->in);
+ fclose(conn->out);
+ free(conn);
+}
+
+
+static void
+send_packet(FILE *out, const char *command, size_t size)
+{
+ /* compute the checksum -- simple mod256 addition */
+ size_t i;
+ uint8_t sum = 0;
+ for (i = 0; i < size; ++i)
+ sum += (uint8_t)command[i];
+
+ /* NB: seems neither escaping nor RLE is generally expected by
+ * gdbserver. e.g. giving "invalid hex digit" on an RLE'd
+ * address. So just write raw here, and maybe let higher levels
+ * escape/RLE. */
+
+ if (debug_flag) {
+ error_msg("\tSending packet: $%s", command);
+ fflush(stdout);
+ }
+ fputc('$', out); /* packet start */
+ fwrite(command, 1, size, out); /* payload */
+ fprintf(out, "#%02x", sum); /* packet end, checksum */
+ fflush(out);
+
+ if (ferror(out))
+ error_msg("Error sending message \"$%s\" to GDB server",
+ command);
+ else if (feof(out))
+ error_msg_and_die("Connection to GDB server has been closed");
+}
+
+void
+gdb_send(struct gdb_conn *conn, const char *command, size_t size)
+{
+ bool acked = false;
+ do {
+ send_packet(conn->out, command, size);
+
+ if (!conn->ack)
+ break;
+
+ /* look for '+' ACK or '-' NACK/resend */
+ acked = fgetc_unlocked(conn->in) == '+';
+ } while (!acked);
+}
+
+
+/* push_notification/pop_notification caches notifications which
+ * arrive via the following dialogue:
+ * [ server: %Stop:T05syscall_entry...
+ * client: $vStopped ]*
+ * server: OK
+ */
+
+void
+push_notification(char *packet, size_t packet_size)
+{
+ int idx;
+
+ if (strncmp(packet+3, "syscall", 7) != 0)
+ return;
+
+ if (notifications.size == 0) {
+ notifications.size = 32;
+ notifications.start = 0;
+ notifications.count = 0;
+ notifications.packet = xmalloc(sizeof(notifications.packet) *
notifications.size);
+ }
+
+ if (notifications.count == notifications.size) {
+ error_msg("Buffer overflow");
+ } else {
+ idx = notifications.start + notifications.count++;
+ if (idx >= notifications.size) {
+ idx = 0;
+ }
+ notifications.packet[idx] = packet;
+ }
+
+ if (debug_flag)
+ printf /*error_msg*/("Pushed %s (%d items in queue)\n", packet,
notifications.count);
+}
+
+
+char*
+pop_notification(size_t *size)
+{
+ char *packet;
+ if (notifications.count == 0) {
+ return (char*)NULL;
+ } else {
+ packet = notifications.packet[notifications.start];
+ notifications.start++;
+ notifications.count--;
+ if (notifications.start == notifications.size)
+ notifications.start = 0;
+ }
+
+ if (debug_flag) {
+ error_msg("Popped %s (%d items in queue)", packet, notifications.count);
+ }
+
+ return packet;
+}
+
+
+bool
+have_notification(void)
+{
+ return (notifications.count == 0 ? false : true);
+}
+
+
+void
+dump_notifications(char *packet, int pid, int tid)
+{
+ int idx;
+
+ for (idx = notifications.start; idx < notifications.count; idx++) {
+ if (notifications.packet[idx] != NULL)
+ printf ("Notify Dump: %s\n", notifications.packet[idx]);
+ }
+}
+
+
+static char *
+recv_packet(FILE *in, size_t *ret_size, bool* ret_sum_ok)
+{
+ size_t i = 0;
+ size_t size = 4096;
+ char *reply = xmalloc(size);
+
+ int c;
+ uint8_t sum = 0;
+ bool escape = false;
+
+ /* fast-forward to the first start of packet */
+ while ((c = fgetc_unlocked(in)) != EOF && (c != '$' && c != '%'));
+ if (c == '%')
+ ungetc (c, in);
+
+ while ((c = fgetc_unlocked(in)) != EOF) {
+ sum += (uint8_t)c;
+ switch (c) {
+ case '$': /* new packet? start over... */
+ i = 0;
+ sum = 0;
+ escape = false;
+ continue;
+ case '%':
+ {
+ char pcr[6];
+
+ int idx = 0;
+
+ i = 0;
+ sum = 0;
+ escape = false;
+ for (idx = 0; idx < 5; idx++)
+ {
+ pcr[idx] = fgetc_unlocked(in);
+ sum += (uint8_t)pcr[idx];
+ }
+ if (strncmp(pcr, "Stop:", 5) == 0)
+ continue;
+ continue;
+ }
+ case '#': /* end of packet */
+ sum -= c; /* not part of the checksum */
+ {
+ uint8_t msb = fgetc_unlocked(in);
+ uint8_t lsb = fgetc_unlocked(in);
+ *ret_sum_ok = sum == gdb_decode_hex(msb, lsb);
+ }
+ *ret_size = i;
+
+ /* terminate it for good measure */
+ if (i == size) {
+ reply = realloc(reply, size + 1);
+ if (reply == NULL)
+ perror_msg_and_die("realloc");
+ }
+ reply[i] = '\0';
+
+ if (debug_flag) {
+ error_msg("\tPacket received: %s", reply);
+ fflush(stdout);
+ }
+ return reply;
+
+ case '}': /* escape: next char is XOR 0x20 */
+ escape = true;
+ continue;
+
+ case '*':
+ /* run-length-encoding The next character tells how
+ * many times to repeat the last character we saw.
+ * The count is added to 29, so that the
+ * minimum-beneficial RLE 3 is the first printable
+ * character ' '. The count character can't be >126
+ * or '$'/'#' packet markers. */
+
+ if (i > 0) { /* need something to repeat! */
+ int c2 = fgetc_unlocked(in);
+ if (c2 < 29 || c2 > 126 || c2 == '$' || c2 == '#') {
+ /* invalid count character! */
+ ungetc(c2, in);
+ } else {
+ int count = c2 - 29;
+
+ /* get a bigger buffer if needed */
+ if (i + count > size) {
+ size *= 2;
+ reply = realloc(reply, size);
+ if (reply == NULL)
+ perror_msg_and_die("realloc");
+ }
+
+ /* fill the repeated character */
+ memset(&reply[i], reply[i - 1], count);
+ i += count;
+ sum += c2;
+ continue;
+ }
+ }
+ }
+
+ /* XOR an escaped character */
+ if (escape) {
+ c ^= 0x20;
+ escape = false;
+ }
+
+ /* get a bigger buffer if needed */
+ if (i == size) {
+ size *= 2;
+ reply = realloc(reply, size);
+ if (reply == NULL)
+ perror_msg_and_die("realloc");
+ }
+
+ /* add one character */
+ reply[i++] = c;
+ }
+
+ if (ferror(in))
+ error_msg_and_die("got stream error while receiving GDB server"
+ " packet");
+ else if (feof(in)) {
+ error_msg_and_die("connection closed unexpectedly while "
+ "receiving GDB server packet");
+ }
+
+ error_msg_and_die("unknown GDB server connection error");
+}
+
+char *
+gdb_recv(struct gdb_conn *conn, size_t *size, bool want_stop)
+{
+ char *reply;
+ bool acked = false;
+
+ do {
+ reply = recv_packet(conn->in, size, &acked);
+
+ /* (See gdb_recv_stop for non-stop packet order)
+ If a notification arrived while expecting another packet
+ type, then cache the notification. */
+ if (! want_stop && strncmp(reply, "T05syscall", 10) == 0) {
+ push_notification(reply, *size);
+ reply = recv_packet(conn->in, size, &acked);
+ }
+
+ if (conn->ack) {
+ /* send +/- depending on checksum result, retry if needed */
+ fputc(acked ? '+' : '-', conn->out);
+ fflush(conn->out);
+ if (!acked)
+ free(reply);
+ }
+ } while (conn->ack && !acked);
+
+ return reply;
+}
+
+bool
+gdb_start_noack(struct gdb_conn *conn)
+{
+ static const char cmd[] = "QStartNoAckMode";
+ gdb_send(conn, cmd, sizeof(cmd) - 1);
+
+ size_t size;
+ char *reply = gdb_recv(conn, &size, false);
+ bool ok = size == 2 && !strcmp(reply, "OK");
+ free(reply);
+
+ if (ok)
+ conn->ack = false;
+ return ok ? "OK" : "";
+}
+
+void
+gdb_set_non_stop(struct gdb_conn *conn, bool val)
+{
+ conn->non_stop = val;
+}
+
+bool
+gdb_has_non_stop(struct gdb_conn *conn)
+{
+ return conn->non_stop;
+}
+
+/* Read complete qXfer data, returned as binary with the size.
+ * On error, returns NULL with size set to the error code. */
+char *
+gdb_xfer_read(struct gdb_conn *conn,
+ const char *object, const char *annex,
+ /* out */ size_t *ret_size)
+{
+ size_t error = 0;
+ size_t offset = 0;
+ char *data = NULL;
+ do {
+ char *cmd;
+ int cmd_size = asprintf(&cmd, "qXfer:%s:read:%s:%zx,%x",
+ object ?: "", annex ?: "", offset, 0xfff /* XXX PacketSize */);
+ if (cmd_size < 0) {
+ break;
+ }
+
+ gdb_send(conn, cmd, strlen(cmd));
+ free(cmd);
+
+ size_t size;
+ char *reply = gdb_recv(conn, &size, false);
+ char c = reply[0];
+ switch (c) {
+ case 'm':
+ case 'l':
+ data = realloc(data, offset + size - 1);
+ memcpy(data + offset, reply + 1, size - 1);
+ free(reply);
+ offset += size - 1;
+ if (c == 'l') {
+ *ret_size = offset;
+ return data;
+ }
+ continue;
+ case 'E':
+ error = gdb_decode_hex_str(reply + 1);
+ break;
+ }
+ free(reply);
+ break;
+ } while (0);
+
+ free(data);
+ *ret_size = error;
+ return NULL;
+}
+
+
+struct vfile_response {
+ char *reply;
+ int64_t result;
+ int64_t errnum; /* avoid 'errno' macros */
+ size_t attachment_size;
+ const char *attachment;
+};
+
+static struct vfile_response
+gdb_vfile(struct gdb_conn *conn, const char *operation, const char
*parameters)
+{
+ struct vfile_response res = { NULL, -1, 0, 0, NULL };
+
+ char *cmd;
+ int cmd_size = asprintf(&cmd, "vFile:%s:%s", operation, parameters);
+ if (cmd_size < 0) {
+ return res;
+ }
+
+ gdb_send(conn, cmd, strlen(cmd));
+ free(cmd);
+
+ size_t size;
+ res.reply = gdb_recv(conn, &size, false);
+ if (size > 1 && res.reply[0] == 'F') {
+ /* F result [, errno] [; attachment] */
+ res.result = gdb_decode_signed_hex_str(res.reply + 1);
+
+ const char *attachment = memchr(res.reply, ';', size);
+ if (attachment) {
+ res.attachment = attachment + 1;
+ res.attachment_size = size - (res.attachment - res.reply);
+ }
+
+ const char *errnum = memchr(res.reply, ',', size - res.attachment_size);
+ if (errnum)
+ res.errnum = gdb_decode_signed_hex_str(errnum + 1);
+ }
+ return res;
+}
+
+int
+gdb_readlink(struct gdb_conn *conn, const char *linkpath,
+ char *buf, unsigned bufsize)
+{
+ char *parameters = gdb_encode_hex_string(linkpath);
+ if (!parameters)
+ return -1;
+
+ struct vfile_response res = gdb_vfile(conn, "readlink", parameters);
+ free(parameters);
+
+ int ret = -1;
+ if (res.result >= 0 && res.attachment != NULL
+ && res.result == (int64_t)res.attachment_size) {
+ size_t data_len = res.attachment_size;
+ if (data_len >= bufsize)
+ data_len = bufsize - 1; /* truncate -- ok? */
+ memcpy(buf, res.attachment, data_len);
+ buf[data_len] = 0;
+ ret = data_len;
+ }
+ free(res.reply);
+ return ret;
+}
diff --git a/gdbserver/protocol.h b/gdbserver/protocol.h
new file mode 100644
index 0000000..7220052
--- /dev/null
+++ b/gdbserver/protocol.h
@@ -0,0 +1,77 @@
+#ifndef STRACE_GDBSERVER_PROTOCOL_H
+#define STRACE_GDBSERVER_PROTOCOL_H
+
+/* Simple interface of a GDB remote protocol client.
+ *
+ * Copyright (c) 2015 Red Hat Inc.
+ * Copyright (c) 2015 Josh Stone <cuviper at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct gdb_conn;
+
+void gdb_encode_hex(uint8_t byte, char *out);
+uint16_t gdb_decode_hex(char msb, char lsb);
+uint64_t gdb_decode_hex_n(const char *bytes, size_t n);
+uint64_t gdb_decode_hex_str(const char *bytes);
+int gdb_decode_hex_buf(const char *bytes, size_t n, char *out);
+
+struct gdb_conn *gdb_begin_command(const char *command);
+struct gdb_conn *gdb_begin_tcp(const char *node, const char *service);
+struct gdb_conn *gdb_begin_path(const char *path);
+
+void gdb_end(struct gdb_conn *conn);
+
+void gdb_send(struct gdb_conn *conn, const char *command, size_t size);
+
+char *gdb_recv(struct gdb_conn *conn, /* out */ size_t *size, bool
want_stop);
+
+bool gdb_start_noack(struct gdb_conn *conn);
+
+void gdb_set_non_stop(struct gdb_conn *conn, bool val);
+
+bool gdb_has_non_stop(struct gdb_conn *conn);
+
+char* pop_notification(size_t *size);
+
+void push_notification(char *packet, size_t packet_size);
+
+bool have_notification();
+
+/* Read complete qXfer data, returned as binary with the size.
+ * On error, returns NULL with size set to the error code.
+ */
+char *gdb_xfer_read(struct gdb_conn *conn,
+ const char *object, const char *annex,
+ /* out */ size_t *size);
+
+int gdb_readlink(struct gdb_conn *conn, const char *linkpath,
+ char *buf, unsigned bufsize);
+
+#endif /* !STRACE_GDBSERVER_PROTOCOL_H */
diff --git a/gdbserver/signals.def b/gdbserver/signals.def
new file mode 100644
index 0000000..3f49980
--- /dev/null
+++ b/gdbserver/signals.def
@@ -0,0 +1,200 @@
+/* Target signal numbers for GDB and the GDB remote protocol.
+ Copyright (C) 2010-2015 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
<http://www.gnu.org/licenses/>. */
+
+/* Used some places (e.g. stop_signal) to record the concept that
+ there is no signal. */
+SET (GDB_SIGNAL_0, 0, "0", "Signal 0")
+#define GDB_SIGNAL_FIRST GDB_SIGNAL_0
+SET (GDB_SIGNAL_HUP, 1, "SIGHUP", "Hangup")
+SET (GDB_SIGNAL_INT, 2, "SIGINT", "Interrupt")
+SET (GDB_SIGNAL_QUIT, 3, "SIGQUIT", "Quit")
+SET (GDB_SIGNAL_ILL, 4, "SIGILL", "Illegal instruction")
+SET (GDB_SIGNAL_TRAP, 5, "SIGTRAP", "Trace/breakpoint trap")
+SET (GDB_SIGNAL_ABRT, 6, "SIGABRT", "Aborted")
+SET (GDB_SIGNAL_EMT, 7, "SIGEMT", "Emulation trap")
+SET (GDB_SIGNAL_FPE, 8, "SIGFPE", "Arithmetic exception")
+SET (GDB_SIGNAL_KILL, 9, "SIGKILL", "Killed")
+SET (GDB_SIGNAL_BUS, 10, "SIGBUS", "Bus error")
+SET (GDB_SIGNAL_SEGV, 11, "SIGSEGV", "Segmentation fault")
+SET (GDB_SIGNAL_SYS, 12, "SIGSYS", "Bad system call")
+SET (GDB_SIGNAL_PIPE, 13, "SIGPIPE", "Broken pipe")
+SET (GDB_SIGNAL_ALRM, 14, "SIGALRM", "Alarm clock")
+SET (GDB_SIGNAL_TERM, 15, "SIGTERM", "Terminated")
+SET (GDB_SIGNAL_URG, 16, "SIGURG", "Urgent I/O condition")
+SET (GDB_SIGNAL_STOP, 17, "SIGSTOP", "Stopped (signal)")
+SET (GDB_SIGNAL_TSTP, 18, "SIGTSTP", "Stopped (user)")
+SET (GDB_SIGNAL_CONT, 19, "SIGCONT", "Continued")
+SET (GDB_SIGNAL_CHLD, 20, "SIGCHLD", "Child status changed")
+SET (GDB_SIGNAL_TTIN, 21, "SIGTTIN", "Stopped (tty input)")
+SET (GDB_SIGNAL_TTOU, 22, "SIGTTOU", "Stopped (tty output)")
+SET (GDB_SIGNAL_IO, 23, "SIGIO", "I/O possible")
+SET (GDB_SIGNAL_XCPU, 24, "SIGXCPU", "CPU time limit exceeded")
+SET (GDB_SIGNAL_XFSZ, 25, "SIGXFSZ", "File size limit exceeded")
+SET (GDB_SIGNAL_VTALRM, 26, "SIGVTALRM", "Virtual timer expired")
+SET (GDB_SIGNAL_PROF, 27, "SIGPROF", "Profiling timer expired")
+SET (GDB_SIGNAL_WINCH, 28, "SIGWINCH", "Window size changed")
+SET (GDB_SIGNAL_LOST, 29, "SIGLOST", "Resource lost")
+SET (GDB_SIGNAL_USR1, 30, "SIGUSR1", "User defined signal 1")
+SET (GDB_SIGNAL_USR2, 31, "SIGUSR2", "User defined signal 2")
+SET (GDB_SIGNAL_PWR, 32, "SIGPWR", "Power fail/restart")
+/* Similar to SIGIO. Perhaps they should have the same number. */
+SET (GDB_SIGNAL_POLL, 33, "SIGPOLL", "Pollable event occurred")
+SET (GDB_SIGNAL_WIND, 34, "SIGWIND", "SIGWIND")
+SET (GDB_SIGNAL_PHONE, 35, "SIGPHONE", "SIGPHONE")
+SET (GDB_SIGNAL_WAITING, 36, "SIGWAITING", "Process's LWPs are blocked")
+SET (GDB_SIGNAL_LWP, 37, "SIGLWP", "Signal LWP")
+SET (GDB_SIGNAL_DANGER, 38, "SIGDANGER", "Swap space dangerously low")
+SET (GDB_SIGNAL_GRANT, 39, "SIGGRANT", "Monitor mode granted")
+SET (GDB_SIGNAL_RETRACT, 40, "SIGRETRACT",
+ "Need to relinquish monitor mode")
+SET (GDB_SIGNAL_MSG, 41, "SIGMSG", "Monitor mode data available")
+SET (GDB_SIGNAL_SOUND, 42, "SIGSOUND", "Sound completed")
+SET (GDB_SIGNAL_SAK, 43, "SIGSAK", "Secure attention")
+SET (GDB_SIGNAL_PRIO, 44, "SIGPRIO", "SIGPRIO")
+SET (GDB_SIGNAL_REALTIME_33, 45, "SIG33", "Real-time event 33")
+SET (GDB_SIGNAL_REALTIME_34, 46, "SIG34", "Real-time event 34")
+SET (GDB_SIGNAL_REALTIME_35, 47, "SIG35", "Real-time event 35")
+SET (GDB_SIGNAL_REALTIME_36, 48, "SIG36", "Real-time event 36")
+SET (GDB_SIGNAL_REALTIME_37, 49, "SIG37", "Real-time event 37")
+SET (GDB_SIGNAL_REALTIME_38, 50, "SIG38", "Real-time event 38")
+SET (GDB_SIGNAL_REALTIME_39, 51, "SIG39", "Real-time event 39")
+SET (GDB_SIGNAL_REALTIME_40, 52, "SIG40", "Real-time event 40")
+SET (GDB_SIGNAL_REALTIME_41, 53, "SIG41", "Real-time event 41")
+SET (GDB_SIGNAL_REALTIME_42, 54, "SIG42", "Real-time event 42")
+SET (GDB_SIGNAL_REALTIME_43, 55, "SIG43", "Real-time event 43")
+SET (GDB_SIGNAL_REALTIME_44, 56, "SIG44", "Real-time event 44")
+SET (GDB_SIGNAL_REALTIME_45, 57, "SIG45", "Real-time event 45")
+SET (GDB_SIGNAL_REALTIME_46, 58, "SIG46", "Real-time event 46")
+SET (GDB_SIGNAL_REALTIME_47, 59, "SIG47", "Real-time event 47")
+SET (GDB_SIGNAL_REALTIME_48, 60, "SIG48", "Real-time event 48")
+SET (GDB_SIGNAL_REALTIME_49, 61, "SIG49", "Real-time event 49")
+SET (GDB_SIGNAL_REALTIME_50, 62, "SIG50", "Real-time event 50")
+SET (GDB_SIGNAL_REALTIME_51, 63, "SIG51", "Real-time event 51")
+SET (GDB_SIGNAL_REALTIME_52, 64, "SIG52", "Real-time event 52")
+SET (GDB_SIGNAL_REALTIME_53, 65, "SIG53", "Real-time event 53")
+SET (GDB_SIGNAL_REALTIME_54, 66, "SIG54", "Real-time event 54")
+SET (GDB_SIGNAL_REALTIME_55, 67, "SIG55", "Real-time event 55")
+SET (GDB_SIGNAL_REALTIME_56, 68, "SIG56", "Real-time event 56")
+SET (GDB_SIGNAL_REALTIME_57, 69, "SIG57", "Real-time event 57")
+SET (GDB_SIGNAL_REALTIME_58, 70, "SIG58", "Real-time event 58")
+SET (GDB_SIGNAL_REALTIME_59, 71, "SIG59", "Real-time event 59")
+SET (GDB_SIGNAL_REALTIME_60, 72, "SIG60", "Real-time event 60")
+SET (GDB_SIGNAL_REALTIME_61, 73, "SIG61", "Real-time event 61")
+SET (GDB_SIGNAL_REALTIME_62, 74, "SIG62", "Real-time event 62")
+SET (GDB_SIGNAL_REALTIME_63, 75, "SIG63", "Real-time event 63")
+
+/* Used internally by Solaris threads. See signal(5) on Solaris. */
+SET (GDB_SIGNAL_CANCEL, 76, "SIGCANCEL", "LWP internal signal")
+
+/* Yes, this pains me, too. But LynxOS didn't have SIG32, and now
+ GNU/Linux does, and we can't disturb the numbering, since it's
+ part of the remote protocol. Note that in some GDB's
+ GDB_SIGNAL_REALTIME_32 is number 76. */
+SET (GDB_SIGNAL_REALTIME_32, 77, "SIG32", "Real-time event 32")
+/* Yet another pain, IRIX 6 has SIG64. */
+SET (GDB_SIGNAL_REALTIME_64, 78, "SIG64", "Real-time event 64")
+/* Yet another pain, GNU/Linux MIPS might go up to 128. */
+SET (GDB_SIGNAL_REALTIME_65, 79, "SIG65", "Real-time event 65")
+SET (GDB_SIGNAL_REALTIME_66, 80, "SIG66", "Real-time event 66")
+SET (GDB_SIGNAL_REALTIME_67, 81, "SIG67", "Real-time event 67")
+SET (GDB_SIGNAL_REALTIME_68, 82, "SIG68", "Real-time event 68")
+SET (GDB_SIGNAL_REALTIME_69, 83, "SIG69", "Real-time event 69")
+SET (GDB_SIGNAL_REALTIME_70, 84, "SIG70", "Real-time event 70")
+SET (GDB_SIGNAL_REALTIME_71, 85, "SIG71", "Real-time event 71")
+SET (GDB_SIGNAL_REALTIME_72, 86, "SIG72", "Real-time event 72")
+SET (GDB_SIGNAL_REALTIME_73, 87, "SIG73", "Real-time event 73")
+SET (GDB_SIGNAL_REALTIME_74, 88, "SIG74", "Real-time event 74")
+SET (GDB_SIGNAL_REALTIME_75, 89, "SIG75", "Real-time event 75")
+SET (GDB_SIGNAL_REALTIME_76, 90, "SIG76", "Real-time event 76")
+SET (GDB_SIGNAL_REALTIME_77, 91, "SIG77", "Real-time event 77")
+SET (GDB_SIGNAL_REALTIME_78, 92, "SIG78", "Real-time event 78")
+SET (GDB_SIGNAL_REALTIME_79, 93, "SIG79", "Real-time event 79")
+SET (GDB_SIGNAL_REALTIME_80, 94, "SIG80", "Real-time event 80")
+SET (GDB_SIGNAL_REALTIME_81, 95, "SIG81", "Real-time event 81")
+SET (GDB_SIGNAL_REALTIME_82, 96, "SIG82", "Real-time event 82")
+SET (GDB_SIGNAL_REALTIME_83, 97, "SIG83", "Real-time event 83")
+SET (GDB_SIGNAL_REALTIME_84, 98, "SIG84", "Real-time event 84")
+SET (GDB_SIGNAL_REALTIME_85, 99, "SIG85", "Real-time event 85")
+SET (GDB_SIGNAL_REALTIME_86, 100, "SIG86", "Real-time event 86")
+SET (GDB_SIGNAL_REALTIME_87, 101, "SIG87", "Real-time event 87")
+SET (GDB_SIGNAL_REALTIME_88, 102, "SIG88", "Real-time event 88")
+SET (GDB_SIGNAL_REALTIME_89, 103, "SIG89", "Real-time event 89")
+SET (GDB_SIGNAL_REALTIME_90, 104, "SIG90", "Real-time event 90")
+SET (GDB_SIGNAL_REALTIME_91, 105, "SIG91", "Real-time event 91")
+SET (GDB_SIGNAL_REALTIME_92, 106, "SIG92", "Real-time event 92")
+SET (GDB_SIGNAL_REALTIME_93, 107, "SIG93", "Real-time event 93")
+SET (GDB_SIGNAL_REALTIME_94, 108, "SIG94", "Real-time event 94")
+SET (GDB_SIGNAL_REALTIME_95, 109, "SIG95", "Real-time event 95")
+SET (GDB_SIGNAL_REALTIME_96, 110, "SIG96", "Real-time event 96")
+SET (GDB_SIGNAL_REALTIME_97, 111, "SIG97", "Real-time event 97")
+SET (GDB_SIGNAL_REALTIME_98, 112, "SIG98", "Real-time event 98")
+SET (GDB_SIGNAL_REALTIME_99, 113, "SIG99", "Real-time event 99")
+SET (GDB_SIGNAL_REALTIME_100, 114, "SIG100", "Real-time event 100")
+SET (GDB_SIGNAL_REALTIME_101, 115, "SIG101", "Real-time event 101")
+SET (GDB_SIGNAL_REALTIME_102, 116, "SIG102", "Real-time event 102")
+SET (GDB_SIGNAL_REALTIME_103, 117, "SIG103", "Real-time event 103")
+SET (GDB_SIGNAL_REALTIME_104, 118, "SIG104", "Real-time event 104")
+SET (GDB_SIGNAL_REALTIME_105, 119, "SIG105", "Real-time event 105")
+SET (GDB_SIGNAL_REALTIME_106, 120, "SIG106", "Real-time event 106")
+SET (GDB_SIGNAL_REALTIME_107, 121, "SIG107", "Real-time event 107")
+SET (GDB_SIGNAL_REALTIME_108, 122, "SIG108", "Real-time event 108")
+SET (GDB_SIGNAL_REALTIME_109, 123, "SIG109", "Real-time event 109")
+SET (GDB_SIGNAL_REALTIME_110, 124, "SIG110", "Real-time event 110")
+SET (GDB_SIGNAL_REALTIME_111, 125, "SIG111", "Real-time event 111")
+SET (GDB_SIGNAL_REALTIME_112, 126, "SIG112", "Real-time event 112")
+SET (GDB_SIGNAL_REALTIME_113, 127, "SIG113", "Real-time event 113")
+SET (GDB_SIGNAL_REALTIME_114, 128, "SIG114", "Real-time event 114")
+SET (GDB_SIGNAL_REALTIME_115, 129, "SIG115", "Real-time event 115")
+SET (GDB_SIGNAL_REALTIME_116, 130, "SIG116", "Real-time event 116")
+SET (GDB_SIGNAL_REALTIME_117, 131, "SIG117", "Real-time event 117")
+SET (GDB_SIGNAL_REALTIME_118, 132, "SIG118", "Real-time event 118")
+SET (GDB_SIGNAL_REALTIME_119, 133, "SIG119", "Real-time event 119")
+SET (GDB_SIGNAL_REALTIME_120, 134, "SIG120", "Real-time event 120")
+SET (GDB_SIGNAL_REALTIME_121, 135, "SIG121", "Real-time event 121")
+SET (GDB_SIGNAL_REALTIME_122, 136, "SIG122", "Real-time event 122")
+SET (GDB_SIGNAL_REALTIME_123, 137, "SIG123", "Real-time event 123")
+SET (GDB_SIGNAL_REALTIME_124, 138, "SIG124", "Real-time event 124")
+SET (GDB_SIGNAL_REALTIME_125, 139, "SIG125", "Real-time event 125")
+SET (GDB_SIGNAL_REALTIME_126, 140, "SIG126", "Real-time event 126")
+SET (GDB_SIGNAL_REALTIME_127, 141, "SIG127", "Real-time event 127")
+
+SET (GDB_SIGNAL_INFO, 142, "SIGINFO", "Information request")
+
+/* Some signal we don't know about. */
+SET (GDB_SIGNAL_UNKNOWN, 143, NULL, "Unknown signal")
+
+/* Use whatever signal we use when one is not specifically specified
+ (for passing to proceed and so on). */
+SET (GDB_SIGNAL_DEFAULT, 144, NULL,
+ "Internal error: printing GDB_SIGNAL_DEFAULT")
+
+/* Mach exceptions. In versions of GDB before 5.2, these were just before
+ GDB_SIGNAL_INFO if you were compiling on a Mach host (and missing
+ otherwise). */
+SET (GDB_EXC_BAD_ACCESS, 145, "EXC_BAD_ACCESS", "Could not access memory")
+SET (GDB_EXC_BAD_INSTRUCTION, 146, "EXC_BAD_INSTRUCTION",
+ "Illegal instruction/operand")
+SET (GDB_EXC_ARITHMETIC, 147, "EXC_ARITHMETIC", "Arithmetic exception")
+SET (GDB_EXC_EMULATION, 148, "EXC_EMULATION", "Emulation instruction")
+SET (GDB_EXC_SOFTWARE, 149, "EXC_SOFTWARE", "Software generated exception")
+SET (GDB_EXC_BREAKPOINT, 150, "EXC_BREAKPOINT", "Breakpoint")
+
+/* If you are adding a new signal, add it just above this comment. */
+
+/* Last and unused enum value, for sizing arrays, etc. */
+SET (GDB_SIGNAL_LAST, 151, NULL, "GDB_SIGNAL_LAST")
diff --git a/gdbserver/signals.h b/gdbserver/signals.h
new file mode 100644
index 0000000..f7ed973
--- /dev/null
+++ b/gdbserver/signals.h
@@ -0,0 +1,58 @@
+/* Target signal numbers for GDB and the GDB remote protocol.
+ Copyright (C) 1986-2015 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see
<http://www.gnu.org/licenses/>. */
+
+#ifndef GDB_SIGNALS_H
+#define GDB_SIGNALS_H
+
+/* The numbering of these signals is chosen to match traditional unix
+ signals (insofar as various unices use the same numbers, anyway).
+ It is also the numbering of the GDB remote protocol. Other remote
+ protocols, if they use a different numbering, should make sure to
+ translate appropriately.
+
+ Since these numbers have actually made it out into other software
+ (stubs, etc.), you mustn't disturb the assigned numbering. If you
+ need to add new signals here, add them to the end of the explicitly
+ numbered signals, at the comment marker. Add them unconditionally,
+ not within any #if or #ifdef.
+
+ This is based strongly on Unix/POSIX signals for several reasons:
+ (1) This set of signals represents a widely-accepted attempt to
+ represent events of this sort in a portable fashion, (2) we want a
+ signal to make it from wait to child_wait to the user intact, (3) many
+ remote protocols use a similar encoding. However, it is
+ recognized that this set of signals has limitations (such as not
+ distinguishing between various kinds of SIGSEGV, or not
+ distinguishing hitting a breakpoint from finishing a single step).
+ So in the future we may get around this either by adding additional
+ signals for breakpoint, single-step, etc., or by adding signal
+ codes; the latter seems more in the spirit of what BSD, System V,
+ etc. are doing to address these issues. */
+
+/* For an explanation of what each signal means, see
+ gdb_signal_to_string. */
+
+enum gdb_signal
+ {
+#define SET(symbol, constant, name, string) \
+ symbol = constant,
+#include "signals.def"
+#undef SET
+ };
+
+#endif /* #ifndef GDB_SIGNALS_H */
diff --git a/gdbserver/x86_64/gdb_get_regs.c
b/gdbserver/x86_64/gdb_get_regs.c
new file mode 100644
index 0000000..ecd103a
--- /dev/null
+++ b/gdbserver/x86_64/gdb_get_regs.c
@@ -0,0 +1,129 @@
+long
+gdb_get_regs (pid_t pid, void *io)
+{
+#include <sys/user.h>
+/* for struct iovec */
+#include <sys/uio.h>
+
+// Begin adapted from linux/x86_64/arch_regs.c
+ struct i386_user_regs_struct {
+ uint32_t ebx;
+ uint32_t ecx;
+ uint32_t edx;
+ uint32_t esi;
+ uint32_t edi;
+ uint32_t ebp;
+ uint32_t eax;
+ uint32_t xds;
+ uint32_t xes;
+ uint32_t xfs;
+ uint32_t xgs;
+ uint32_t orig_eax;
+ uint32_t eip;
+ uint32_t xcs;
+ uint32_t eflags;
+ uint32_t esp;
+ uint32_t xss;
+ };
+ struct iovec *x86_io;
+ static union {
+ struct user_regs_struct x86_64_r;
+ struct i386_user_regs_struct i386_r;
+ } *x86_regs_union;
+
+ x86_io = (struct iovec*)io;
+ x86_regs_union = x86_io->iov_base;
+
+#define x86_64_regs x86_regs_union->x86_64_r
+#define i386_regs x86_regs_union->i386_r
+// End adapted from linux/x86_64/arch_regs.c
+
+ int get_regs_error;
+ size_t size;
+ char *regs = gdb_get_all_regs(pid, &size);
+ struct tcb *tcp = pid2tcb(pid);
+ if (regs) {
+ if (size == 0 || regs[0] == 'E') {
+ get_regs_error = -1;
+ free(regs);
+ return get_regs_error;
+ }
+
+ if (size == 624) {
+ get_regs_error = 0;
+ x86_io->iov_len = sizeof(i386_regs);
+
+ /* specified in 32bit-core.xml */
+ i386_regs.eax = be32toh(gdb_decode_hex_n(®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 = be64toh(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/ipc_shm.c b/ipc_shm.c
index 7284b18..9503f81 100644
--- a/ipc_shm.c
+++ b/ipc_shm.c
@@ -78,7 +78,7 @@ SYS_FUNC(shmat)
uint64_t r64;
uint32_t r32;
} u;
- if (umoven(tcp, tcp->u_arg[2], current_wordsize, &u) < 0)
+ if (backend.umoven(tcp, tcp->u_arg[2], current_wordsize, &u) < 0)
return RVAL_NONE;
tcp->u_rval = (sizeof(u.r32) == current_wordsize)
? u.r32 : u.r64;
diff --git a/mem.c b/mem.c
index 00f5610..78b2bd0 100644
--- a/mem.c
+++ b/mem.c
@@ -276,7 +276,7 @@ SYS_FUNC(mincore)
len = len / page_size + (len & page_mask ? 1 : 0);
if (syserror(tcp) || !verbose(tcp) ||
!tcp->u_arg[2] || !(vec = malloc(len)) ||
- umoven(tcp, tcp->u_arg[2], len, vec) < 0)
+ backend.umoven(tcp, tcp->u_arg[2], len, vec) < 0)
printaddr(tcp->u_arg[2]);
else {
unsigned long i;
diff --git a/msghdr.c b/msghdr.c
index dd9c430..00b60cf 100644
--- a/msghdr.c
+++ b/msghdr.c
@@ -297,7 +297,7 @@ decode_msg_control(struct tcb *const tcp, const
kernel_ulong_t addr,
? get_optmem_max() : in_control_len;
unsigned int buf_len = control_len;
char *buf = buf_len < cmsg_size ? NULL : malloc(buf_len);
- if (!buf || umoven(tcp, addr, buf_len, buf) < 0) {
+ if (!buf || backend.umoven(tcp, addr, buf_len, buf) < 0) {
printaddr(addr);
free(buf);
return;
diff --git a/pathtrace.c b/pathtrace.c
index 5258238..39f5b17 100644
--- a/pathtrace.c
+++ b/pathtrace.c
@@ -59,7 +59,7 @@ upathmatch(struct tcb *const tcp, const kernel_ulong_t
upath,
{
char path[PATH_MAX + 1];
- return umovestr(tcp, upath, sizeof(path), path) > 0 &&
+ return backend.umovestr(tcp, upath, sizeof(path), path) > 0 &&
pathmatch(path, set);
}
@@ -70,7 +70,7 @@ static bool
fdmatch(struct tcb *tcp, int fd, struct path_set *set)
{
char path[PATH_MAX + 1];
- int n = getfdpath(tcp, fd, path, sizeof(path));
+ int n = backend.getfdpath(tcp, fd, path, sizeof(path));
return n >= 0 && pathmatch(path, set);
}
@@ -296,7 +296,7 @@ pathtrace_match_set(struct tcb *tcp, struct path_set
*set)
for (i = 1; i <= 3; ++i) {
if (args[i] == 0)
continue;
- if (umoven(tcp, args[i], fdsize, fds) < 0) {
+ if (backend.umoven(tcp, args[i], fdsize, fds) < 0) {
continue;
}
for (j = 0;; j++) {
diff --git a/rt_sigreturn.c b/rt_sigreturn.c
index af705c3..38f1e94 100644
--- a/rt_sigreturn.c
+++ b/rt_sigreturn.c
@@ -27,6 +27,29 @@
#include "defs.h"
+/* Avoids the "#define si_pid _sifields._kill.si_pid" in siginfo.h */
+
+#pragma push_macro ("si_pid")
+#undef si_pid
+#pragma push_macro ("si_overrun")
+#undef si_overrun
+#pragma push_macro ("si_addr")
+#undef si_addr
+#pragma push_macro ("si_band")
+#undef si_band
+#pragma push_macro ("si_uid")
+#undef si_uid
+#pragma push_macro ("si_status")
+#undef si_status
+#pragma push_macro ("si_utime")
+#undef si_utime
+#pragma push_macro ("si_fd")
+#undef si_fd
+#pragma push_macro ("si_addr_lsb")
+#undef si_addr_lsb
+#pragma push_macro ("si_stime")
+#undef si_stime
+
#include DEF_MPERS_TYPE(struct_rt_sigframe)
#include "rt_sigframe.h"
@@ -38,6 +61,17 @@
offsetof(struct_rt_sigframe, uc.uc_sigmask)
#endif
+#pragma pop_macro ("si_pid")
+#pragma pop_macro ("si_overrun")
+#pragma pop_macro ("si_addr")
+#pragma pop_macro ("si_band")
+#pragma pop_macro ("si_uid")
+#pragma pop_macro ("si_status")
+#pragma pop_macro ("si_utime")
+#pragma pop_macro ("si_fd")
+#pragma pop_macro ("si_addr_lsb")
+#pragma pop_macro ("si_stime")
+
SYS_FUNC(rt_sigreturn)
{
const kernel_ulong_t sf_addr = get_rt_sigframe_addr(tcp);
diff --git a/strace.c b/strace.c
index 6ed86a6..bec7e4f 100644
--- a/strace.c
+++ b/strace.c
@@ -129,11 +129,11 @@ bool not_failing_only;
/* Show path associated with fd arguments */
unsigned int show_fd_path;
-static bool detach_on_execve;
+bool detach_on_execve;
static int exit_code;
-static int strace_child;
-static int strace_tracer_pid;
+int strace_child = 0;
+static int strace_tracer_pid = 0;
static const char *username;
static uid_t run_uid;
@@ -147,8 +147,8 @@ static const char *outfname;
/* If -ff, points to stderr. Else, it's our common output log */
static FILE *shared_log;
-struct tcb *printing_tcp;
-static struct tcb *current_tcp;
+struct tcb *printing_tcp = NULL;
+struct tcb *current_tcp;
static struct tcb **tcbtab;
static unsigned int nprocs, tcbtabsize;
@@ -163,6 +163,12 @@ static void detach(struct tcb *tcp);
static void cleanup(void);
static void interrupt(int sig);
static sigset_t start_set, blocked_set;
+static enum trace_event next_event (int *pstatus, void *si);
+static bool dispatch_event (enum trace_event, int *pstatus, void *si);
+
+int umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len,
+ void *const our_addr);
+int default_arch_get_scno(struct tcb *tcp);
#ifdef HAVE_SIG_ATOMIC_T
static volatile sig_atomic_t interrupted;
@@ -664,7 +670,7 @@ tabto(void)
* Otherwise, "strace -oFILE -ff -p<nonexistant_pid>"
* may create bogus empty FILE.<nonexistant_pid>, and then die.
*/
-static void
+void
newoutf(struct tcb *tcp)
{
tcp->outf = shared_log; /* if not -ff mode, the same file is for all */
@@ -699,7 +705,7 @@ expand_tcbtab(void)
tcbtab[tcbtabsize++] = newtcbs++;
}
-static struct tcb *
+struct tcb *
alloctcb(int pid)
{
unsigned int i;
@@ -763,7 +769,7 @@ free_tcb_priv_data(struct tcb *tcp)
}
}
-static void
+void
droptcb(struct tcb *tcp)
{
if (tcp->pid == 0)
@@ -1111,7 +1117,7 @@ startup_attach(void)
continue;
}
- attach_tcb(tcp);
+ backend.attach_tcb(tcp);
if (interactive) {
sigprocmask(SIG_SETMASK, &start_set, NULL);
@@ -1539,7 +1545,22 @@ get_os_release(void)
return rel;
}
-static void
+
+static bool
+prog_pid_check (char *exec_name, int nprocs)
+{
+ return exec_name || nprocs;
+}
+
+
+static bool
+handle_arg (char arg, char *optarg)
+{
+ return false;
+}
+
+
+void
set_sigaction(int signo, void (*sighandler)(int), struct sigaction
*oldact)
{
/* if signal handler is a function, add the signal to blocked_set */
@@ -1550,6 +1571,21 @@ set_sigaction(int signo, void (*sighandler)(int),
struct sigaction *oldact)
sigaction(signo, &sa, oldact);
}
+
+static void
+end_init(void) {}
+
+static void
+final_cleanup(void) {}
+
+static bool
+verify_args(const char *username, bool daemon, unsigned int *follow_fork)
+{return true;}
+
+static bool
+start_init(void)
+{return true;}
+
/*
* Initialization part of main() was eating much stack (~0.5k),
* which was unused after init.
@@ -1564,6 +1600,25 @@ init(int argc, char *argv[])
int c, i;
int optF = 0;
+ backend.attach_tcb = attach_tcb;
+ backend.cleanup = final_cleanup;
+ backend.detach = detach;
+ backend.dispatch_event = dispatch_event;
+ backend.end_init = end_init;
+ backend.get_regs = NULL;
+ backend.get_scno = default_arch_get_scno;
+ backend.getfdpath = getfdpath;
+ backend.handle_arg = handle_arg;
+ backend.next_event = next_event;
+ backend.prog_pid_check = prog_pid_check;
+ backend.start_init = start_init;
+ backend.startup_child = startup_child;
+ backend.umoven = umoven;
+ backend.umovestr = umovestr;
+ backend.upeek_ = upeek;
+ backend.upoke_ = upoke;
+ backend.verify_args = verify_args;
+
if (!program_invocation_name || !*program_invocation_name) {
static char name[] = "strace";
program_invocation_name =
@@ -1590,6 +1645,7 @@ init(int argc, char *argv[])
"k"
#endif
"D"
+ "G:"
"a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
switch (c) {
case 'b':
@@ -1694,6 +1750,11 @@ init(int argc, char *argv[])
case 'u':
username = optarg;
break;
+#ifdef ENABLE_GDBSERVER
+ case 'G':
+ gdb_handle_arg (c, optarg);
+ break;
+#endif
#ifdef USE_LIBUNWIND
case 'k':
stack_trace_enabled = true;
@@ -1709,7 +1770,8 @@ init(int argc, char *argv[])
error_opt_arg(c, optarg);
break;
default:
- error_msg_and_help(NULL);
+ if (! backend.handle_arg(c, optarg))
+ error_msg_and_help(NULL);
break;
}
}
@@ -1717,7 +1779,7 @@ init(int argc, char *argv[])
argv += optind;
argc -= optind;
- if (argc < 0 || (!argv[0] && !nprocs)) {
+ if (argc < 0 || ! backend.prog_pid_check(argv[0], nprocs)) {
error_msg_and_help("must have PROG [ARGS] or -p PID");
}
@@ -1728,6 +1790,8 @@ init(int argc, char *argv[])
if (!followfork)
followfork = optF;
+ backend.verify_args(username, daemonized_tracer, &followfork);
+
if (followfork >= 2 && cflag) {
error_msg_and_help("(-c or -C) and -ff are mutually exclusive");
}
@@ -1759,6 +1823,9 @@ init(int argc, char *argv[])
tflag = 1;
}
+ if (backend.start_init && ! backend.start_init())
+ error_msg_and_die("Cannot initialize backend on this target.");
+
acolumn_spaces = xmalloc(acolumn + 1);
memset(acolumn_spaces, ' ', acolumn);
acolumn_spaces[acolumn] = '\0';
@@ -1865,7 +1932,7 @@ init(int argc, char *argv[])
* in the startup_child() mode we kill the spawned process anyway.
*/
if (argv[0]) {
- startup_child(argv);
+ backend.startup_child(argv);
}
set_sigaction(SIGTTOU, SIG_IGN, NULL);
@@ -1889,6 +1956,9 @@ init(int argc, char *argv[])
if (nprocs != 0 || daemonized_tracer)
startup_attach();
+ if (backend.end_init)
+ backend.end_init();
+
/* Do we want pids printed in our -o OUTFILE?
* -ff: no (every pid has its own file); or
* -f: yes (there can be more pids in the future); or
@@ -1897,7 +1967,7 @@ init(int argc, char *argv[])
print_pid_pfx = (outfname && followfork < 2 && (followfork == 1 ||
nprocs > 1));
}
-static struct tcb *
+struct tcb *
pid2tcb(int pid)
{
unsigned int i;
@@ -1936,10 +2006,12 @@ cleanup(void)
kill(tcp->pid, SIGCONT);
kill(tcp->pid, fatal_sig);
}
- detach(tcp);
+ backend.detach(tcp);
}
if (cflag)
call_summary(shared_log);
+
+ backend.cleanup();
}
static void
@@ -2077,7 +2149,7 @@ maybe_switch_tcbs(struct tcb *tcp, const int pid)
return tcp;
}
-static void
+void
print_signalled(struct tcb *tcp, const int pid, int status)
{
if (pid == strace_child) {
@@ -2100,7 +2172,7 @@ print_signalled(struct tcb *tcp, const int pid,
int status)
}
}
-static void
+void
print_exited(struct tcb *tcp, const int pid, int status)
{
if (pid == strace_child) {
@@ -2116,7 +2188,7 @@ print_exited(struct tcb *tcp, const int pid, int
status)
}
}
-static void
+void
print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int
sig)
{
if (cflag != CFLAG_ONLY_STATS
@@ -2194,67 +2266,10 @@ print_event_exit(struct tcb *tcp)
line_ended();
}
-enum trace_event {
- /* Break the main loop. */
- TE_BREAK,
-
- /* Call next_event() again. */
- TE_NEXT,
-
- /* Restart the tracee with signal 0 and call next_event() again. */
- TE_RESTART,
-
- /*
- * For all the events below, current_tcp is set to current tracee's
- * tcb. All the suggested actions imply that you want to continue
- * tracing of the current tracee; alternatively, you can detach it.
- */
-
- /*
- * Syscall entry or exit.
- * Restart the tracee with signal 0, or with an injected signal number.
- */
- TE_SYSCALL_STOP,
-
- /*
- * Tracee received signal with number WSTOPSIG(*pstatus); signal info
- * is written to *si. Restart the tracee (with that signal number
- * if you want to deliver it).
- */
- TE_SIGNAL_DELIVERY_STOP,
-
- /*
- * Tracee was killed by a signal with number WTERMSIG(*pstatus).
- */
- TE_SIGNALLED,
-
- /*
- * Tracee was stopped by a signal with number WSTOPSIG(*pstatus).
- * Restart the tracee with that signal number.
- */
- TE_GROUP_STOP,
-
- /*
- * Tracee exited with status WEXITSTATUS(*pstatus).
- */
- TE_EXITED,
-
- /*
- * Tracee is going to perform execve().
- * Restart the tracee with signal 0.
- */
- TE_STOP_BEFORE_EXECVE,
-
- /*
- * Tracee is going to terminate.
- * Restart the tracee with signal 0.
- */
- TE_STOP_BEFORE_EXIT,
-};
-
static enum trace_event
-next_event(int *pstatus, siginfo_t *si)
+next_event(int *pstatus, void *si_p)
{
+ siginfo_t *si = (siginfo_t*)si_p;
int pid;
int wait_errno;
int status;
@@ -2406,7 +2421,7 @@ next_event(int *pstatus, siginfo_t *si)
}
}
-static int
+int
trace_syscall(struct tcb *tcp, unsigned int *sig)
{
if (entering(tcp)) {
@@ -2432,8 +2447,9 @@ trace_syscall(struct tcb *tcp, unsigned int *sig)
/* Returns true iff the main trace loop has to continue. */
static bool
-dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
+dispatch_event(enum trace_event ret, int *pstatus, void *si_p)
{
+ siginfo_t *si = (siginfo_t*)si_p;
unsigned int restart_op = PTRACE_SYSCALL;
unsigned int restart_sig = 0;
@@ -2517,7 +2533,7 @@ dispatch_event(enum trace_event ret, int *pstatus,
siginfo_t *si)
if (current_tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
current_tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
} else {
- detach(current_tcp); /* do "-b execve" thingy */
+ backend.detach(current_tcp); /* do "-b execve" thingy */
return true;
}
}
@@ -2584,6 +2600,7 @@ terminate(void)
exit(exit_code);
}
+
int
main(int argc, char *argv[])
{
@@ -2593,7 +2610,7 @@ main(int argc, char *argv[])
int status;
siginfo_t si;
- while (dispatch_event(next_event(&status, &si), &status, &si))
+ while (backend.dispatch_event(backend.next_event(&status, &si),
&status, &si))
;
terminate();
}
diff --git a/syscall.c b/syscall.c
index b1047fe..e3e0947 100644
--- a/syscall.c
+++ b/syscall.c
@@ -296,7 +296,7 @@ set_personality(int personality)
# endif
}
-static void
+void
update_personality(struct tcb *tcp, unsigned int personality)
{
if (personality == current_personality)
@@ -338,7 +338,7 @@ decode_socket_subcall(struct tcb *tcp)
const unsigned int nargs = sysent[scno].nargs;
uint64_t buf[nargs];
- if (umoven(tcp, tcp->u_arg[1], nargs * current_wordsize, buf) < 0)
+ if (backend.umoven(tcp, tcp->u_arg[1], nargs * current_wordsize, buf) < 0)
return;
tcp->scno = scno;
@@ -409,7 +409,7 @@ decode_mips_subcall(struct tcb *tcp)
* see linux/mips/get_syscall_args.c
*/
if (tcp->s_ent->nargs == MAX_ARGS) {
- if (umoven(tcp,
+ if (backend.umoven(tcp,
mips_REG_SP + MAX_ARGS * sizeof(tcp->u_arg[0]),
sizeof(tcp->u_arg[0]),
&tcp->u_arg[MAX_ARGS - 1]) < 0)
@@ -1111,6 +1111,9 @@ ptrace_setregs(pid_t pid)
static void
get_regs(pid_t pid)
{
+ if (backend.get_regs)
+ get_regs_error = backend.get_regs (pid, (struct
iovec*)&ARCH_IOVEC_FOR_GETREGSET);
+
#undef USE_GET_SYSCALL_RESULT_REGS
#ifdef ptrace_getregset_or_getregs
@@ -1155,6 +1158,9 @@ get_regs(pid_t pid)
static int
set_regs(pid_t pid)
{
+ if (backend.set_regs)
+ return = backend.set_regs (pid, (struct
iovec*)&ARCH_IOVEC_FOR_GETREGSET);
+
return ptrace_setregset_or_setregs(pid);
}
#endif /* ptrace_setregset_or_setregs */
@@ -1174,6 +1180,16 @@ free_sysent_buf(void *ptr)
}
/*
+ * Default case calls arch_get_scno which sets tcp->scno.
+ * A backend can redefine this to set that in a customized way.
+ */
+int
+default_arch_get_scno(struct tcb *tcp)
+{
+ return arch_get_scno(tcp);
+}
+
+/*
* Returns:
* 0: "ignore this ptrace stop", syscall_entering_decode() should
return a "bail
* out silently" code.
@@ -1189,7 +1205,9 @@ get_scno(struct tcb *tcp)
if (get_regs_error)
return -1;
- int rc = arch_get_scno(tcp);
+ int rc;
+ rc = backend.get_scno(tcp);
+
if (rc != 1)
return rc;
diff --git a/sysctl.c b/sysctl.c
index 176973a..3d2a9ed 100644
--- a/sysctl.c
+++ b/sysctl.c
@@ -56,7 +56,7 @@ SYS_FUNC(sysctl)
size = sizeof(int) * (unsigned long) info.nlen;
name = (size / sizeof(int) != (unsigned long) info.nlen) ? NULL :
malloc(size);
if (name == NULL ||
- umoven(tcp, (unsigned long) info.name, size, name) < 0) {
+ backend.umoven(tcp, (unsigned long) info.name, size, name) < 0) {
free(name);
if (entering(tcp))
tprintf("{%p, %d, %p, %p, %p, %lu}",
diff --git a/sysmips.c b/sysmips.c
index e095cb3..c57e2d5 100644
--- a/sysmips.c
+++ b/sysmips.c
@@ -55,7 +55,7 @@ SYS_FUNC(sysmips)
if (!verbose(tcp))
break;
- if (umovestr(tcp, tcp->u_arg[1], (__NEW_UTS_LEN + 1),
+ if (backend.umovestr(tcp, tcp->u_arg[1], (__NEW_UTS_LEN + 1),
nodename) < 0) {
printaddr(tcp->u_arg[1]);
} else {
diff --git a/tests/.gitignore b/tests/.gitignore
index dc92930..e7429ad 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -85,6 +85,7 @@ ftruncate
ftruncate64
futex
futimesat
+gdbrsp
gen_tests.am
get_mempolicy
getcpu
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f2109fd..3db3721 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -96,6 +96,7 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
execveat-v \
filter-unavailable \
fork-f \
+ gdbrsp \
getpid \
getppid \
gettid \
@@ -202,6 +203,7 @@ DECODER_TESTS = \
execve.test \
fadvise64.test \
futex.test \
+ gdbrsp.test \
getuid.test \
ioctl.test \
ioctl_dm-v.test \
diff --git a/tests/gdbrsp.c b/tests/gdbrsp.c
new file mode 100644
index 0000000..f39a7c9
--- /dev/null
+++ b/tests/gdbrsp.c
@@ -0,0 +1,79 @@
+/* This file is used to test the 'catch syscall' feature on GDB.
+
+ Please, if you are going to edit this file DO NOT change the syscalls
+ being called (nor the order of them). If you really must do this, then
+ take a look at catch-syscall.exp and modify there too.
+
+ Written by Sergio Durigan Junior <sergiodj at linux.vnet.ibm.com>
+ September, 2008
+
+ This is the gdb catch-syscall.c test */
+
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+/* These are the syscalls numbers used by the test. */
+
+int close_syscall = SYS_close;
+int chroot_syscall = SYS_chroot;
+/* GDB had a bug where it couldn't catch syscall number 0 (PR 16297).
+ In most GNU/Linux architectures, syscall number 0 is
+ restart_syscall, which can't be called from userspace. However,
+ the "read" syscall is zero on x86_64. */
+int read_syscall = SYS_read;
+#ifdef SYS_pipe
+int pipe_syscall = SYS_pipe;
+#else
+int pipe2_syscall = SYS_pipe2;
+#endif
+int write_syscall = SYS_write;
+#if defined(__arm__)
+/* Although 123456789 is an illegal syscall umber on arm linux, kernel
+ sends SIGILL rather than returns -ENOSYS. However, arm linux kernel
+ returns -ENOSYS if syscall number is within 0xf0001..0xf07ff, so we
+ can use 0xf07ff for unknown_syscall in test. */
+int unknown_syscall = 0x0f07ff;
+#else
+int unknown_syscall = 123456789;
+#endif
+int exit_group_syscall = SYS_exit_group;
+
+/* Set by the test when it wants execve. */
+int do_execve = 0;
+
+int
+main (int argc, char *const argv[])
+{
+ int fd[2];
+ char buf1[2] = "a";
+ char buf2[2];
+
+ /* Test a simple self-exec, but only on request. */
+ if (do_execve)
+ execv (*argv, argv);
+
+ /* A close() with a wrong argument. We are only
+ interested in the syscall. */
+ close (-1);
+
+ chroot (".");
+
+ pipe (fd);
+
+ write (fd[1], buf1, sizeof (buf1));
+ read (fd[0], buf2, sizeof (buf2));
+
+ /* Test vfork-event interactions. Child exits immediately.
+ (Plain fork won't work on no-mmu kernel configurations.) */
+ if (vfork () == 0)
+ _exit (0);
+
+ /* Trigger an intentional ENOSYS. */
+ syscall (unknown_syscall);
+
+ /* The last syscall. Do not change this. */
+ _exit (0);
+}
diff --git a/tests/gdbrsp.test b/tests/gdbrsp.test
new file mode 100755
index 0000000..a0b83d7
--- /dev/null
+++ b/tests/gdbrsp.test
@@ -0,0 +1,107 @@
+#!/bin/sh
+#
+# Check -G option: gdb remote serial protocol
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+. "${srcdir=.}/init.sh"
+
+check_prog gdb
+check_prog gdbserver
+
+gdb -batch -iex 'target remote | gdbserver --remote-debug -
/usr/bin/true' >| /tmp/,gdb 2>&1
+if grep QCatchSyscalls /tmp/,gdb > /dev/null
+then :
+else framework_skip_ "Correct version of gdbserver is not available"
+fi
+
+run_prog > /dev/null
+
+# all-stop
+gdbserver --once --multi :65432 &
+if [ $? -gt 0 ] ; then
+ framework_skip_ "Unable to start gdbserver"
+fi
+$STRACE -G localhost:65432 $(readlink -f ../gdbrsp) >& $LOG
+
+EXPECTED="$LOG.expected"
+cat > "$EXPECTED" << __EOF__
+close.-1..* = -1 EBADF .Bad file descriptor.
+chroot.* = -1 EPERM .Operation not permitted.*
+pipe..3, 4...* = 0
+write.4, "a.0", 2..* = 2
+read.3, "a.0", 2..* = 2
+vfork.*unfinished.*
+.pid.*exited with 0.*
+.*vfork resumed.* =.*
+__EOF__
+
+match_grep "$LOG" "$EXPECTED"
+rm -f $EXPECTED
+
+# non-stop
+gdbserver --once --multi :65432 &
+if [ $? -gt 0 ] ; then
+ framework_skip_ "Unable to start gdbserver"
+fi
+$STRACE -G 'localhost:65432;non-stop' $(readlink -f ../gdbrsp) >& $LOG
+
+EXPECTED="$LOG.expected"
+cat > "$EXPECTED" << __EOF__
+close.-1..* = -1 EBADF .Bad file descriptor.
+chroot.* = -1 EPERM .Operation not permitted.*
+pipe..3, 4...* = 0
+write.4, "a.0", 2..* = 2
+read.3, "a.0", 2..* = 2
+vfork.*= [0-9][0-9]*
+syscall_123456789.*= -1.*
+exit_group.*
+__EOF__
+
+match_grep "$LOG" "$EXPECTED"
+rm -f $EXPECTED
+
+# -e write,read
+
+if [ $STRACE_ARCH = x86_64 ] ; then
+ gdbserver --once --multi :65432 &
+ if [ $? -gt 0 ] ; then
+ framework_skip_ "Unable to start gdbserver"
+ fi
+ $STRACE -G localhost:65432 -e write,read $(readlink -f ../gdbrsp)
>& $LOG
+
+ EXPECTED="$LOG.expected"
+ cat > "$EXPECTED" << __EOF__
+write.4, "a.0", 2..* = 2
+read.3, "a.0", 2..* = 2
+__EOF__
+
+ match_grep "$LOG" "$EXPECTED"
+ rm -f $EXPECTED
+fi
+
+# TODO -pPID
+
+exit 0
diff --git a/upeek.c b/upeek.c
index 22ee82d..d3f96a1 100644
--- a/upeek.c
+++ b/upeek.c
@@ -35,6 +35,8 @@
#include "defs.h"
#include "ptrace.h"
+#undef upeek
+
int
upeek(int pid, unsigned long off, kernel_ulong_t *res)
{
diff --git a/upoke.c b/upoke.c
index dda0b8d..be4cea6 100644
--- a/upoke.c
+++ b/upoke.c
@@ -28,6 +28,8 @@
#include "defs.h"
#include "ptrace.h"
+#undef upoke
+
int
upoke(int pid, unsigned long off, kernel_ulong_t val)
{
diff --git a/util.c b/util.c
index 05c9fb8..4e77084 100644
--- a/util.c
+++ b/util.c
@@ -427,7 +427,7 @@ void
printfd(struct tcb *tcp, int fd)
{
char path[PATH_MAX + 1];
- if (show_fd_path && getfdpath(tcp, fd, path, sizeof(path)) >= 0) {
+ if (show_fd_path && backend.getfdpath(tcp, fd, path, sizeof(path)) >= 0) {
const char *str;
size_t len;
unsigned long inode;
@@ -696,7 +696,7 @@ printpathn(struct tcb *const tcp, const
kernel_ulong_t addr, unsigned int n)
n = sizeof(path) - 1;
/* Fetch one byte more to find out whether path length > n. */
- nul_seen = umovestr(tcp, addr, n + 1, path);
+ nul_seen = backend.umovestr(tcp, addr, n + 1, path);
if (nul_seen < 0)
printaddr(addr);
else {
@@ -755,9 +755,9 @@ printstr_ex(struct tcb *const tcp, const
kernel_ulong_t addr,
if (size > len)
size = len;
if (style & QUOTE_0_TERMINATED)
- rc = umovestr(tcp, addr, size, str);
+ rc = backend.umovestr(tcp, addr, size, str);
else
- rc = umoven(tcp, addr, size, str);
+ rc = backend.umoven(tcp, addr, size, str);
if (rc < 0) {
printaddr(addr);
@@ -814,7 +814,7 @@ dumpiov_upto(struct tcb *const tcp, const int len,
const kernel_ulong_t addr,
error_msg("Out of memory");
return;
}
- if (umoven(tcp, addr, size, iov) >= 0) {
+ if (backend.umoven(tcp, addr, size, iov) >= 0) {
for (i = 0; i < len; i++) {
kernel_ulong_t iov_len = iov_iov_len(i);
if (iov_len > data_size)
@@ -864,7 +864,7 @@ dumpstr(struct tcb *const tcp, const kernel_ulong_t
addr, const int len)
strsize = len + 16;
}
- if (umoven(tcp, addr, len, str) < 0)
+ if (backend.umoven(tcp, addr, len, str) < 0)
return;
/* Space-pad to 16 bytes */
@@ -906,12 +906,13 @@ dumpstr(struct tcb *const tcp, const
kernel_ulong_t addr, const int len)
}
}
+
int
umoven_or_printaddr(struct tcb *const tcp, const kernel_ulong_t addr,
const unsigned int len, void *const our_addr)
{
if (!addr || !verbose(tcp) || (exiting(tcp) && syserror(tcp)) ||
- umoven(tcp, addr, len, our_addr) < 0) {
+ backend.umoven(tcp, addr, len, our_addr) < 0) {
printaddr(addr);
return -1;
}
@@ -924,7 +925,7 @@ umoven_or_printaddr_ignore_syserror(struct tcb
*const tcp,
const unsigned int len,
void *const our_addr)
{
- if (!addr || !verbose(tcp) || umoven(tcp, addr, len, our_addr) < 0) {
+ if (!addr || !verbose(tcp) || backend.umoven(tcp, addr, len, our_addr)
< 0) {
printaddr(addr);
return -1;
}
More information about the Strace-devel
mailing list