The GNU General Public License is contained in the file COPYING.
*/
+/* For accept4. */
+#define _GNU_SOURCE
#include "vgdb.h"
#include "config.h"
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
+#include <sys/wait.h>
/* vgdb has two usages:
1. relay application between gdb and the gdbserver embedded in valgrind.
Bool timestamp = False;
char timestamp_out[20];
static char *vgdb_prefix = NULL;
+static char *valgrind_path = NULL;
+static char **vargs;
+static char cvargs = 0;
char *timestamp_str (Bool produce)
{
{
struct stat fdstat;
void **s;
- shared_mem_fd = open(shared_mem, O_RDWR);
+ int tries = 50;
+ int err;
+
+ /* valgrind might still be starting up, give it 5 seconds. */
+ do {
+ shared_mem_fd = open(shared_mem, O_RDWR | O_CLOEXEC);
+ err = errno;
+ if (shared_mem_fd == -1 && err == ENOENT && tries > 0)
+ usleep (100000); /* wait 0.1 seconds */
+ } while (shared_mem_fd == -1 && err == ENOENT && tries-- > 0);
+
/* shared_mem_fd will not be closed till vgdb exits. */
if (shared_mem_fd == -1)
{
int fd;
DEBUG(1, "opening %s %s\n", name, desc);
- fd = open(name, flags);
+ fd = open(name, flags | O_CLOEXEC);
if (fd == -1)
XERROR(errno, "error opening %s %s\n", name, desc);
static int from_gdb = 0; /* stdin by default, changed if --port is given. */
static char *from_gdb_to_pid; /* fifo name to write gdb command to pid */
+
+static int to_gdb = 1; /* stdout by default, changed if --port is given. */
+static char *to_gdb_from_pid; /* fifo name to read pid replies */
+
/* Returns True in case read/write operations were done properly.
Returns False in case of error.
to_pid is the file descriptor to write to the process pid. */
{
char buf[PBUFSIZ+1]; // +1 for trailing \0
int nrread;
+ Bool ret;
nrread = read_buf(from_gdb, buf, "from gdb on stdin");
if (nrread <= 0) {
shutting_down = True;
return False;
}
- return write_buf(to_pid, buf, nrread, "to_pid", /* notify */ True);
+ ret = write_buf(to_pid, buf, nrread, "to_pid", /* notify */ True);
+ if (!ret) {
+ /* Let gdb know the packet couldn't be delivered. */
+ write_buf(to_gdb, "$E01#a6", 8, "error back to gdb", False);
+ }
+ return ret;
}
-static int to_gdb = 1; /* stdout by default, changed if --port is given. */
-static char *to_gdb_from_pid; /* fifo name to read pid replies */
/* Returns True in case read/write operations were done properly.
Returns False in case of error.
from_pid is the file descriptor to read data from the process pid. */
{
struct sockaddr_in addr;
+#ifdef SOCK_CLOEXEC
+ int listen_gdb = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
+#else
int listen_gdb = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+#endif
+
int gdb_connect;
if (-1 == listen_gdb) {
XERROR(errno, "error listen failed\n");
}
+#ifdef SOCK_CLOEXEC
+ gdb_connect = accept4(listen_gdb, NULL, NULL, SOCK_CLOEXEC);
+#else
gdb_connect = accept(listen_gdb, NULL, NULL);
+#endif
+
if (gdb_connect < 0) {
XERROR(errno, "accept failed\n");
}
map_vgdbshared(shared_mem);
}
+static void
+cleanup_fifos_and_shared_mem(void)
+{
+ free(from_gdb_to_pid);
+ free(to_gdb_from_pid);
+ free(shared_mem);
+ close(shared_mem_fd);
+}
+
/* Convert hex digit A to a number. */
static int
#endif
}
-/* Relay data between gdb and Valgrind gdbserver, till EOF or an
- error is encountered. */
static
-void gdb_relay(int pid)
+int tohex (int nib)
+{
+ if (nib < 10)
+ return '0' + nib;
+ else
+ return 'a' + nib - 10;
+}
+
+/* Returns an allocated hex-decoded string from the buf starting at offset
+ off. Will update off to where the buf has been decoded. Stops decoding
+ at end of buf (zero) or when seeing the delim char. */
+static
+char *decode_hexstring (const char *buf, size_t prefixlen, size_t len)
+{
+ int buflen;
+ char *buf_print;
+
+ if (len)
+ buflen = len;
+ else
+ buflen = strlen(buf) - prefixlen;
+
+ buf_print = vmalloc (buflen/2 + 1);
+
+ for (int i = 0; i < buflen; i = i + 2) {
+ buf_print[i/2] = ((fromhex(buf[i+prefixlen]) << 4)
+ + fromhex(buf[i+prefixlen+1]));
+ }
+ buf_print[buflen/2] = '\0';
+ DEBUG(1, "decode_hexstring: %s\n", buf_print);
+ return buf_print;
+}
+
+static Bool
+write_to_gdb (const char *m, int cnt)
+{
+ int written = 0;
+ while (written < cnt) {
+ int res = write (to_gdb, m + written, cnt - written);
+ if (res < 0) {
+ perror ("write_to_gdb");
+ return False;
+ }
+ written += res;
+ }
+
+ return True;
+}
+
+static Bool
+write_checksum (const char *str)
+{
+ unsigned char csum = 0;
+ int i = 0;
+ while (str[i] != 0)
+ csum += str[i++];
+
+ char p[2];
+ p[0] = tohex ((csum >> 4) & 0x0f);
+ p[1] = tohex (csum & 0x0f);
+ return write_to_gdb (p, 2);
+}
+
+static Bool
+write_reply(const char *reply)
+{
+ write_to_gdb ("$", 1);
+ write_to_gdb (reply, strlen (reply));
+ write_to_gdb ("#", 1);
+ return write_checksum (reply);
+}
+
+/* Creates a packet from a string message, called needs to free. */
+static char *
+create_packet(const char *msg)
+{
+ unsigned char csum = 0;
+ int i = 1;
+ char *p = vmalloc (strlen (msg) + 5); /* $ + msg + # + hexhex + 0 */
+ strcpy (&p[1], msg);
+ p[0] = '$';
+ while (p[i] != 0)
+ csum += p[i++];
+ p[i++] = '#';
+ p[i++] = tohex ((csum >> 4) & 0x0f);
+ p[i++] = tohex (csum & 0x0f);
+ p[i] = '\0';
+ return p;
+}
+
+static int read_one_char (char *c)
+{
+ int i;
+ do
+ i = read (from_gdb, c, 1);
+ while (i == -1 && errno == EINTR);
+
+ return i;
+}
+
+static Bool
+send_packet(const char *reply, int noackmode)
+{
+ int ret;
+ char c;
+
+send_packet_start:
+ if (!write_reply(reply))
+ return False;
+ if (!noackmode) {
+ // Look for '+' or '-'.
+ // We must wait for "+" if !noackmode.
+ do {
+ ret = read_one_char(&c);
+ if (ret <= 0)
+ return False;
+ // And if in !noackmode if we get "-" we should resent the packet.
+ if (c == '-')
+ goto send_packet_start;
+ } while (c != '+');
+ DEBUG(1, "sent packet to gdb got: %c\n",c);
+ }
+ return True;
+}
+
+// Reads one packet from_gdb starting with $ into buf.
+// Skipping any other characters.
+// Returns the size of the packet, 0 for end of input,
+// or -1 if no packet could be read.
+static int receive_packet(char *buf, int noackmode)
+{
+ int bufcnt = 0, ret;
+ char c, c1, c2;
+ unsigned char csum = 0;
+
+ // Look for first '$' (start of packet) or error.
+ receive_packet_start:
+ do {
+ ret = read_one_char(&c);
+ if (ret <= 0)
+ return ret;
+ } while (c != '$');
+
+ // Found start of packet ('$')
+ while (bufcnt < (PBUFSIZ+1)) {
+ ret = read_one_char(&c);
+ if (ret <= 0)
+ return ret;
+ if (c == '#') {
+ if ((ret = read_one_char(&c1)) <= 0
+ || (ret = read_one_char(&c2)) <= 0) {
+ return ret;
+ }
+ c1 = fromhex(c1);
+ c2 = fromhex(c2);
+ break;
+ }
+ buf[bufcnt] = c;
+ csum += buf[bufcnt];
+ bufcnt++;
+ }
+
+ // Packet complete, add terminator.
+ buf[bufcnt] ='\0';
+
+ if (!(csum == (c1 << 4) + c2)) {
+ TSFPRINTF(stderr, "Bad checksum, sentsum=0x%x, csum=0x%x, buf=%s\n",
+ (c1 << 4) + c2, csum, buf);
+ if (!noackmode)
+ if (!write_to_gdb ("-", 1))
+ return -1;
+ /* Try again, gdb should resend the packet. */
+ bufcnt = 0;
+ csum = 0;
+ goto receive_packet_start;
+ }
+
+ if (!noackmode)
+ if (!write_to_gdb ("+", 1))
+ return -1;
+ return bufcnt;
+}
+
+// Returns a pointer to the char after the next delim char.
+static const char *next_delim_string (const char *buf, char delim)
+{
+ while (*buf) {
+ if (*buf++ == delim)
+ break;
+ }
+ return buf;
+}
+
+// Throws away the packet name and decodes the hex string, which is placed in
+// decoded_string (the caller owns this and is responsible for freeing it).
+static int split_hexdecode(const char *buf, const char *string,
+ const char *delim, char **decoded_string)
+{
+ const char *next_str = next_delim_string(buf, *delim);
+ if (next_str) {
+ *decoded_string = decode_hexstring (next_str, 0, 0);
+ DEBUG(1, "split_hexdecode decoded %s\n", *decoded_string);
+ return 1;
+ } else {
+ TSFPRINTF(stderr, "%s decoding error: finding the hex string in %s failed!\n", string, buf);
+ return 0;
+ }
+}
+
+static int count_delims(char delim, char *buf)
+{
+ size_t count = 0;
+ char *ptr = buf;
+
+ while (*ptr)
+ count += *ptr++ == delim;
+ return count;
+}
+
+// Determine the length of the arguments.
+// This depends on the len array being initialized to -1 for each element.
+// We first skip the command (e.g. vRun;arg0;arg1)
+static void count_len(char delim, char *buf, size_t *len)
+{
+ int i = 0;
+ char *ptr = buf;
+
+ // Skip the command
+ while (*ptr && *ptr != delim)
+ ptr++;
+
+ // Delimiter counts towards the first arg0
+ if (*ptr == delim) {
+ ptr++;
+ len[i]++;
+ }
+
+ // For each arg0... count chars (delim counts towards next arg)
+ while (*ptr) {
+ i += *ptr++ == delim;
+ len[i]++;
+ }
+}
+
+/* Declare here, will be used early, implementation follows later. */
+static void gdb_relay(int pid, int send_noack_mode, char *q_buf);
+
+/* Returns zero on success (and the pid of the valgrind process),
+ or the errno from the child on failure. */
+static
+int fork_and_exec_valgrind (int argc, char **argv, const char *working_dir,
+ pid_t *pid)
+{
+ int err = 0;
+ // We will use a pipe to track what the child does,
+ // so we can report failure.
+ int pipefd[2];
+ if (pipe2 (pipefd, O_CLOEXEC) == -1) {
+ err = errno;
+ perror ("pipe2 failed");
+ return err;
+ }
+
+ pid_t p = fork ();
+ if (p < 0) {
+ err = errno;
+ perror ("fork failed");
+ return err;
+ } else if (p > 0) {
+ // I am the parent (vgdb), p is the pid of the child (valgrind)
+ // We only read from the child to see if everything is OK.
+ // If the pipe closes, we get zero back, which is good.
+ // An error reading the pipe is bad (and really shouldn't happen).
+ // Otherwise the child sent us an errno code about what went wrong.
+ close (pipefd[1]);
+
+ while (err == 0) {
+ int r = read (pipefd[0], &err, sizeof (int));
+ if (r == 0) // end of file, good pipe closed after execve
+ break;
+ if (r == -1) {
+ if (errno == EINTR)
+ continue;
+ else {
+ err = errno;
+ perror ("pipe read");
+ }
+ }
+ }
+
+ close (pipefd[0]);
+ if (err != 0)
+ return err;
+ else {
+ *pid = p;
+ return 0;
+ }
+ } else {
+ // p == 0, I am the child (will start valgrind)
+ // We write success to the pipe, no need to read from it.
+ close (pipefd[0]);
+
+ if (working_dir != NULL && working_dir[0] != '\0') {
+ if (chdir (working_dir) != 0) {
+ err = errno;
+ perror("chdir");
+ write (pipefd[1], &err, sizeof (int));
+ _exit (-1);
+ }
+ }
+
+ /* Try to launch valgrind. Add --vgdb-error=0 to stop immediately so we
+ can attach and --launched-with-multi to let valgrind know it doesn't
+ need to show a banner how to connect to gdb, we will do that
+ automagically. And add --vgdb-shadow-registers=yes to make shadow
+ registers available by default. Add any other valgrind arguments the
+ user gave with --vargs. Then the rest of the arguments to valgrind are
+ the program to exec plus its arguments. */
+ const int extra_vargs = 3;
+ /* vargv[0] == "valgrind",
+ vargv[1..extra_vargs] == static valgrind arguments vgdb needs,
+ vargv[extra_vargs+1..extra_vargs+1+cvargs] == user valgrind arguments,
+ vargv[extra_vargs+1+cvargs..extra_vargs+1+cvargs+args] == prog + args,
+ vargs[arguments - 1] = NULL */
+ int arguments = 1 + extra_vargs + cvargs + argc + 1;
+ // We combine const and non-const char[]. This is mildly annoying
+ // since we then need a char *const * for execvp. So we strdup the
+ // const char*. Not pretty :{
+ char **vargv = vmalloc (arguments * sizeof (char *));
+ vargv[0] = strdup ("valgrind");
+ vargv[1] = strdup ("--vgdb-error=0");
+ vargv[2] = strdup ("--launched-with-multi=yes");
+ vargv[3] = strdup ("--vgdb-shadow-registers=yes");
+ // Add --vargs
+ for (int i = 0; i < cvargs; i++) {
+ vargv[i + extra_vargs + 1] = vargs[i];
+ }
+ // Add command and args
+ for (int i = 0; i < argc; i++) {
+ vargv[i + extra_vargs + 1 + cvargs] = argv[i];
+ }
+ vargv[arguments - 1] = NULL;
+
+ if (!valgrind_path) {
+ // TODO use execvpe (or something else if not on GNU/Linux
+ /* We want to make a copy of the environ on start. When we
+ get a QEnvironmentReset we copy that back. If we get an
+ EvironSet/Add/Remove we update the copy. */
+ execvp ("valgrind", vargv);
+ }
+ else {
+ vargv[0] = valgrind_path;
+ execvp (vargv[0], vargv);
+ }
+
+ // We really shouldn't get here...
+ err = errno;
+ /* Note we are after fork and exec failed, we cannot really call
+ perror or printf in this situation since they aren't async-safe. */
+ // perror ("execvp valgrind");
+ // printf ("execve returned??? confusing: %d\n", res);
+ write (pipefd[1], &err, sizeof (int));
+ _exit (-1);
+ }
+
+ abort (); // Impossible
+}
+
+/* Do multi stuff. */
+static
+void do_multi_mode(void)
+{
+ char *buf = vmalloc(PBUFSIZ+1);
+ char *q_buf = vmalloc(PBUFSIZ+1); //save the qSupported packet sent by gdb
+ //to send it to the valgrind gdbserver later
+ q_buf[0] = '\0';
+ int noackmode = 0, pkt_size = 0, bad_unknown_packets = 0;
+ char *string = NULL;
+ char *working_dir = NULL;
+ DEBUG(1, "doing multi stuff...\n");
+ while (1){
+ /* We get zero if the pipe was closed (EOF), or -1 on error reading from
+ the pipe to gdb. */
+ pkt_size = receive_packet(buf, noackmode);
+ if (pkt_size <= 0) {
+ DEBUG(1, "receive_packet: %d\n", pkt_size);
+ break;
+ }
+
+ DEBUG(1, "packet recieved: '%s'\n", buf);
+
+#define QSUPPORTED "qSupported:"
+#define STARTNOACKMODE "QStartNoAckMode"
+#define QRCMD "qRcmd" // This is the monitor command in gdb
+#define VRUN "vRun"
+#define XFER "qXfer"
+#define QATTACHED "qAttached"
+#define QENVIRONMENTHEXENCODED "QEnvironmentHexEncoded"
+#define QENVIRONMENTRESET "QEnvironmentReset"
+#define QENVIRONMENTUNSET "QEnvironmentUnset"
+#define QSETWORKINGDIR "QSetWorkingDir"
+#define QTSTATUS "qTStatus"
+
+ if (strncmp(QSUPPORTED, buf, strlen(QSUPPORTED)) == 0) {
+ DEBUG(1, "CASE %s\n", QSUPPORTED);
+ // And here is our reply.
+ // XXX error handling? We don't check the arguments.
+ char *reply;
+ strcpy(q_buf, buf);
+ // Keep this in sync with coregrind/m_gdbserver/server.c
+ asprintf (&reply,
+ "PacketSize=%x;"
+ "QStartNoAckMode+;"
+ "QPassSignals+;"
+ "QCatchSyscalls+;"
+ /* Just report support always. */
+ "qXfer:auxv:read+;"
+ /* We'll force --vgdb-shadow-registers=yes */
+ "qXfer:features:read+;"
+ "qXfer:exec-file:read+;"
+ "qXfer:siginfo:read+;"
+ /* Some extra's vgdb support before valgrind starts up. */
+ "QEnvironmentHexEncoded+;"
+ "QEnvironmentReset+;"
+ "QEnvironmentUnset+;"
+ "QSetWorkingDir+", (UInt)PBUFSIZ - 1);
+ send_packet(reply, noackmode);
+ free (reply);
+ }
+ else if (strncmp(STARTNOACKMODE, buf, strlen(STARTNOACKMODE)) == 0) {
+ // We have to ack this one
+ send_packet("OK", 0);
+ noackmode = 1;
+ }
+ else if (buf[0] == '!') {
+ send_packet("OK", noackmode);
+ }
+ else if (buf[0] == '?') {
+ send_packet("W00", noackmode);
+ }
+ else if (strncmp("H", buf, strlen("H")) == 0) {
+ // Set thread packet, but we are not running yet.
+ send_packet("E01", noackmode);
+ }
+ else if (strncmp("vMustReplyEmpty", buf, strlen("vMustReplyEmpty")) == 0) {
+ send_packet ("", noackmode);
+ }
+ else if (strncmp(QRCMD, buf, strlen(QRCMD)) == 0) {
+ send_packet ("No running target, monitor commands not available yet.", noackmode);
+
+ char *decoded_string = decode_hexstring (buf, strlen (QRCMD) + 1, 0);
+ DEBUG(1, "qRcmd decoded: %s\n", decoded_string);
+ free (decoded_string);
+ }
+ else if (strncmp(VRUN, buf, strlen(VRUN)) == 0) {
+ // vRun;filename[;argument]*
+ // vRun, filename and arguments are split on ';',
+ // no ';' at the end.
+ // If there are no arguments count is one (just the filename).
+ // Otherwise it is the number of arguments plus one (the filename).
+ // The filename must be there and starts after the first ';'.
+ // TODO: Handle vRun;[;argument]*
+ // https://www.sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets
+ // If filename is an empty string, the stub may use a default program
+ // (e.g. the last program run).
+ size_t count = count_delims(';', buf);
+ size_t *len = vmalloc(count * sizeof(count));
+ const char *delim = ";";
+ const char *next_str = next_delim_string(buf, *delim);
+ char **decoded_string = vmalloc(count * sizeof (char *));
+
+ // Count the lenghts of each substring, init to -1 to compensate for
+ // each substring starting with a delim char.
+ for (int i = 0; i < count; i++)
+ len[i] = -1;
+ count_len(';', buf, len);
+ if (next_str) {
+ DEBUG(1, "vRun: next_str %s\n", next_str);
+ for (int i = 0; i < count; i++) {
+ /* Handle the case when the arguments
+ * was specified to gdb's run command
+ * but no remote exec-file was set,
+ * so the first vRun argument is missing.
+ * For example vRun;;6c. */
+ if (*next_str == *delim) {
+ next_str++;
+ /* empty string that can be freed. */
+ decoded_string[i] = strdup("");
+ }
+ else {
+ decoded_string[i] = decode_hexstring (next_str, 0, len[i]);
+ if (i < count - 1)
+ next_str = next_delim_string(next_str, *delim);
+ }
+ DEBUG(1, "vRun decoded: %s, next_str %s, len[%d] %d\n", decoded_string[i], next_str, i, len[i]);
+ }
+
+ /* If we didn't get any arguments or the filename is an empty
+ string, valgrind won't know which program to run. */
+ DEBUG (1, "count: %d, len[0]: %d\n", count, len[0]);
+ if (! count || len[0] == 0) {
+ free(len);
+ for (int i = 0; i < count; i++)
+ free (decoded_string[i]);
+ free (decoded_string);
+ send_packet ("E01", noackmode);
+ continue;
+ }
+
+ /* We have collected the decoded strings so we can use them to
+ launch valgrind with the correct arguments... We then use the
+ valgrind pid to start relaying packets. */
+ pid_t valgrind_pid = -1;
+ int res = fork_and_exec_valgrind (count,
+ decoded_string,
+ working_dir,
+ &valgrind_pid);
+
+ if (res == 0) {
+ // Lets report we Stopped with SIGTRAP (05).
+ send_packet ("S05", noackmode);
+ prepare_fifos_and_shared_mem(valgrind_pid);
+ DEBUG(1, "from_gdb_to_pid %s, to_gdb_from_pid %s\n", from_gdb_to_pid, to_gdb_from_pid);
+ // gdb_rely is an endless loop till valgrind quits.
+ shutting_down = False;
+
+ gdb_relay (valgrind_pid, 1, q_buf);
+ cleanup_fifos_and_shared_mem();
+ DEBUG(1, "valgrind relay done\n");
+ int status;
+ pid_t p = waitpid (valgrind_pid, &status, 0);
+ DEBUG(2, "waitpid: %d\n", (int) p);
+ if (p == -1)
+ DEBUG(1, "waitpid error %s\n", strerror (errno));
+ else {
+ if (WIFEXITED(status))
+ DEBUG(1, "valgrind exited with %d\n",
+ WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ DEBUG(1, "valgrind kill by signal %d\n",
+ WTERMSIG(status));
+ else
+ DEBUG(1, "valgind unexpectedly stopped or continued");
+ }
+ } else {
+ send_packet ("E01", noackmode);
+ DEBUG(1, "OOPS! couldn't launch valgrind %s\n",
+ strerror (res));
+ }
+
+ free(len);
+ for (int i = 0; i < count; i++)
+ free (decoded_string[i]);
+ free (decoded_string);
+ } else {
+ free(len);
+ send_packet ("E01", noackmode);
+ DEBUG(1, "vRun decoding error: no next_string!\n");
+ continue;
+ }
+ } else if (strncmp(QATTACHED, buf, strlen(QATTACHED)) == 0) {
+ send_packet ("1", noackmode);
+ DEBUG(1, "qAttached sent: '1'\n");
+ const char *next_str = next_delim_string(buf, ':');
+ if (next_str) {
+ char *decoded_string = decode_hexstring (next_str, 0, 0);
+ DEBUG(1, "qAttached decoded: %s, next_str %s\n", decoded_string, next_str);
+ free (decoded_string);
+ } else {
+ DEBUG(1, "qAttached decoding error: strdup of %s failed!\n", buf);
+ continue;
+ }
+ } /* Reset the state of environment variables in the remote target before starting
+ the inferior. In this context, reset means unsetting all environment variables
+ that were previously set by the user (i.e., were not initially present in the environment). */
+ else if (strncmp(QENVIRONMENTRESET, buf,
+ strlen(QENVIRONMENTRESET)) == 0) {
+ send_packet ("OK", noackmode);
+ // TODO clear all environment strings. We're not using
+ // environment strings now. But we should.
+ } else if (strncmp(QENVIRONMENTHEXENCODED, buf,
+ strlen(QENVIRONMENTHEXENCODED)) == 0) {
+ send_packet ("OK", noackmode);
+ if (!split_hexdecode(buf, QENVIRONMENTHEXENCODED, ":", &string))
+ break;
+ // TODO Collect all environment strings and add them to environ
+ // before launcing valgrind.
+ free (string);
+ string = NULL;
+ } else if (strncmp(QENVIRONMENTUNSET, buf,
+ strlen(QENVIRONMENTUNSET)) == 0) {
+ send_packet ("OK", noackmode);
+ if (!split_hexdecode(buf, QENVIRONMENTUNSET, ":", &string))
+ break;
+ // TODO Remove this environment string from the collection.
+ free (string);
+ string = NULL;
+ } else if (strncmp(QSETWORKINGDIR, buf,
+ strlen(QSETWORKINGDIR)) == 0) {
+ // Silly, but we can only reply OK, even if the working directory is
+ // bad. Errors will be reported when we try to execute the actual
+ // process.
+ send_packet ("OK", noackmode);
+ // Free any previously set working_dir
+ free (working_dir);
+ working_dir = NULL;
+ if (!split_hexdecode(buf, QSETWORKINGDIR, ":", &working_dir)) {
+ continue; // We cannot report the error to gdb...
+ }
+ DEBUG(1, "set working dir to: %s\n", working_dir);
+ } else if (strncmp(XFER, buf, strlen(XFER)) == 0) {
+ char *buf_dup = strdup(buf);
+ DEBUG(1, "strdup: buf_dup %s\n", buf_dup);
+ if (buf_dup) {
+ const char *delim = ":";
+ size_t count = count_delims(delim[0], buf);
+ if (count < 4) {
+ strsep(&buf_dup, delim);
+ strsep(&buf_dup, delim);
+ strsep(&buf_dup, delim);
+ char *decoded_string = decode_hexstring (buf_dup, 0, 0);
+ DEBUG(1, "qXfer decoded: %s, buf_dup %s\n", decoded_string, buf_dup);
+ free (decoded_string);
+ }
+ free (buf_dup);
+ } else {
+ DEBUG(1, "qXfer decoding error: strdup of %s failed!\n", buf);
+ free (buf_dup);
+ continue;
+ }
+ // Whether we could decode it or not, we cannot handle it now. We
+ // need valgrind gdbserver to properly reply. So error out here.
+ send_packet ("E00", noackmode);
+ } else if (strncmp(QTSTATUS, buf, strlen(QTSTATUS)) == 0) {
+ // We don't support trace experiments
+ DEBUG(1, "Got QTSTATUS\n");
+ send_packet ("", noackmode);
+ } else if (strcmp("qfThreadInfo", buf) == 0) {
+ DEBUG(1, "Got qfThreadInfo\n");
+ /* There are no threads yet, reply 'l' end of list. */
+ send_packet ("l", noackmode);
+ } else if (buf[0] != '\0') {
+ // We didn't understand.
+ DEBUG(1, "Unknown packet received: '%s'\n", buf);
+ bad_unknown_packets++;
+ if (bad_unknown_packets > 10) {
+ DEBUG(1, "Too many bad/unknown packets received\n");
+ break;
+ }
+ send_packet ("", noackmode);
+ }
+ }
+ DEBUG(1, "done doing multi stuff...\n");
+ free(working_dir);
+ free(buf);
+ free(q_buf);
+
+ shutting_down = True;
+ close (to_gdb);
+ close (from_gdb);
+}
+
+/* Relay data between gdb and Valgrind gdbserver, till EOF or an error is
+ encountered. q_buf is the qSupported packet received from gdb. */
+static
+void gdb_relay(int pid, int send_noack_mode, char *q_buf)
{
int from_pid = -1; /* fd to read from pid */
int to_pid = -1; /* fd to write to pid */
sigusr1_fd = to_pid; /* allow simulating user typing control-c */
+ Bool waiting_for_noack_mode = False;
+ Bool waiting_for_qsupported = False;
+ if(send_noack_mode) {
+ DEBUG(1, "gdb_relay: to_pid %d, from_pid: %d\n", to_pid, from_pid);
+ write_buf(to_pid, "$QStartNoAckMode#b0", 19,
+ "write start no ack mode",
+ /* notify */ True);
+ waiting_for_noack_mode = True;
+ }
+
while (1) {
ConnectionKind ck;
int ret;
if (pollfds[ck].revents & POLLIN) {
switch (ck) {
case FROM_GDB:
+ if (waiting_for_noack_mode || waiting_for_qsupported)
+ break; /* Don't add any messages while vgdb is talking. */
if (!read_from_gdb_write_to_pid(to_pid))
shutting_down = True;
break;
case FROM_PID:
- if (!read_from_pid_write_to_gdb(from_pid))
+ // First handle any messages from vgdb
+ if (waiting_for_noack_mode) {
+ char buf[PBUFSIZ+1]; // +1 for trailing \0
+ size_t buflen;
+ buflen = getpkt(buf, from_pid, to_pid);
+ if (buflen != 2 || strcmp(buf, "OK") != 0) {
+ if (buflen != 2)
+ ERROR(0, "no ack mode: unexpected buflen %d, buf %s\n",
+ buflen, buf);
+ else
+ ERROR(0, "no ack mode: unexpected packet %s\n", buf);
+ }
+ waiting_for_noack_mode = False;
+
+ /* Propagate qSupported to valgrind, we already replied. */
+ if (q_buf != NULL && q_buf[0] != '\0') {
+ char *pkt = create_packet (q_buf);
+ write_buf(to_pid, pkt, strlen(pkt),
+ "write qSupported", /* notify */ True);
+ free(pkt);
+ waiting_for_qsupported = True;
+ }
+ } else if (waiting_for_qsupported) {
+ char buf[PBUFSIZ+1]; // +1 for trailing \0
+ size_t buflen;
+ buflen = getpkt(buf, from_pid, to_pid);
+ /* Should we sanity check the result? */
+ if (buflen > 0) {
+ waiting_for_qsupported = False;
+ } else {
+ ERROR(0, "Unexpected getpkt for qSupported reply: %d\n",
+ buflen);
+ }
+ } else if (!read_from_pid_write_to_gdb(from_pid))
shutting_down = True;
break;
default: XERROR(0, "unexpected POLLIN on %s\n",
TSFPRINTF(out, "use --pid=%d for ", pid);
sprintf(cmdline_file, "/proc/%d/cmdline", pid);
- fd = open(cmdline_file, O_RDONLY);
+ fd = open(cmdline_file, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
DEBUG(1, "error opening cmdline file %s %s\n",
cmdline_file, strerror(errno));
" [--wait=<number>] [--max-invoke-ms=<number>]\n"
" [--port=<portnr>\n"
" [--cmd-time-out=<number>] [-l] [-T] [-D] [-d]\n"
+" [--multi]\n"
" \n"
" --pid arg must be given if multiple Valgrind gdbservers are found.\n"
" --vgdb-prefix arg must be given to both Valgrind and vgdb utility\n"
" --port instructs vgdb to listen for gdb on the specified port nr.\n"
" --cmd-time-out (default 99999999) tells vgdb to exit if the found Valgrind\n"
" gdbserver has not processed a command after number seconds\n"
+" --multi start in extended-remote mode, wait for gdb to tell us what to run\n"
+" --valgrind, pass the path to valgrind to use. If not specified, the system valgrind will be launched.\n"
+" --vargs everything that follows is an argument for valgrind."
" -l arg tells to show the list of running Valgrind gdbserver and then exit.\n"
" -T arg tells to add timestamps to vgdb information messages.\n"
" -D arg tells to show shared mem status and then exit.\n"
void parse_options(int argc, char** argv,
Bool *p_show_shared_mem,
Bool *p_show_list,
+ Bool *p_multi_mode,
int *p_arg_pid,
int *p_check_trials,
int *p_port,
{
Bool show_shared_mem = False;
Bool show_list = False;
+ Bool multi_mode = False;
int arg_pid = -1;
int check_trials = 1;
int last_command = -1;
show_shared_mem = True;
} else if (is_opt(argv[i], "-l")) {
show_list = True;
+ } else if (is_opt(argv[i], "--multi")) {
+ multi_mode = True;
} else if (is_opt(argv[i], "-T")) {
timestamp = True;
} else if (is_opt(argv[i], "--pid=")) {
arg_errors++;
}
} else if (is_opt(argv[i], "--vgdb-prefix=")) {
- vgdb_prefix = argv[i] + 14;
+ vgdb_prefix = strdup (argv[i] + 14);
+ } else if (is_opt(argv[i], "--valgrind=")) {
+ char *path = argv[i] + 11;
+ /* Compute the absolute path. */
+ valgrind_path = realpath(path, NULL);
+ if (!valgrind_path) {
+ TSFPRINTF(stderr, "%s is not a correct path. %s, exiting.\n", path, strerror (errno));
+ exit(1);
+ }
+ DEBUG(2, "valgrind's real path: %s\n", valgrind_path);
+ } else if (is_opt(argv[i], "--vargs")) {
+ // Everything that follows now is an argument for valgrind
+ // No other options (or commands) can follow
+ // argc - i is the number of left over arguments
+ // allocate enough space, but all args in it.
+ cvargs = argc - i - 1;
+ vargs = vmalloc (cvargs * sizeof(vargs));
+ i++;
+ for (int j = 0; i < argc; i++) {
+ vargs[j] = argv[i];
+ j++;
+ }
} else if (is_opt(argv[i], "-c")) {
last_command++;
commands[last_command] = vmalloc(1);
if (vgdb_prefix == NULL)
vgdb_prefix = vgdb_prefix_default();
+ if (multi_mode
+ && (show_shared_mem
+ || show_list
+ || last_command != -1)) {
+ arg_errors++;
+ TSFPRINTF(stderr,
+ "Cannot use -D, -l or COMMANDs when using --multi mode\n");
+ }
+
if (isatty(0)
&& !show_shared_mem
&& !show_list
*p_show_shared_mem = show_shared_mem;
*p_show_list = show_list;
+ *p_multi_mode = multi_mode;
*p_arg_pid = arg_pid;
*p_check_trials = check_trials;
*p_port = int_port;
Bool show_shared_mem;
Bool show_list;
+ Bool multi_mode;
int arg_pid;
int check_trials;
int in_port;
parse_options(argc, argv,
&show_shared_mem,
&show_list,
+ &multi_mode,
&arg_pid,
&check_trials,
&in_port,
if (max_invoke_ms > 0 || last_command == -1)
install_handlers();
- pid = search_arg_pid(arg_pid, check_trials, show_list);
+ if (!multi_mode) {
+ pid = search_arg_pid(arg_pid, check_trials, show_list);
- prepare_fifos_and_shared_mem(pid);
+ prepare_fifos_and_shared_mem(pid);
+ } else {
+ pid = 0;
+ }
if (in_port > 0)
wait_for_gdb_connect(in_port);
exit(0);
}
- if (last_command >= 0) {
+ if (multi_mode) {
+ do_multi_mode ();
+ } else if (last_command >= 0) {
standalone_send_commands(pid, last_command, commands);
} else {
- gdb_relay(pid);
+ gdb_relay(pid, 0, NULL);
}
-
- free(from_gdb_to_pid);
- free(to_gdb_from_pid);
- free(shared_mem);
+ free(vgdb_prefix);
+ free(valgrind_path);
+ if (!multi_mode)
+ cleanup_fifos_and_shared_mem();
for (i = 0; i <= last_command; i++)
free(commands[i]);