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 <cuvi...@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 <cuvi...@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 <cuvi...@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 <cuvi...@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; + } + } +} ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, SlashDot.org! http://sdm.link/slashdot _______________________________________________ Strace-devel mailing list Strace-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/strace-devel