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