/* Replay a remote debug session logfile for GDB. Copyright (C) 1996-2015 Free Software Foundation, Inc. Written by Fred Fish (fnf@cygnus.com) from pieces of gdbserver. 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/>. */ #include "config.h" #include "build-gnulib-gdbserver/config.h" #include "version.h" #include <stdio.h> #if HAVE_SYS_FILE_H #include <sys/file.h> #endif #if HAVE_SIGNAL_H #include <signal.h> #endif #include <ctype.h> #if HAVE_FCNTL_H #include <fcntl.h> #endif #include <errno.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> #endif #if HAVE_NETDB_H #include <netdb.h> #endif #if HAVE_NETINET_TCP_H #include <netinet/tcp.h> #endif #include <alloca.h> #if USE_WIN32API #include <winsock2.h> #endif #ifndef HAVE_SOCKLEN_T typedef int socklen_t; #endif /* Sort of a hack... */ #define EOL (EOF - 1) static int remote_desc; #ifdef __MINGW32CE__ #ifndef COUNTOF #define COUNTOF(STR) (sizeof (STR) / sizeof ((STR)[0])) #endif #define errno (GetLastError ()) char * strerror (DWORD error) { static char buf[1024]; WCHAR *msgbuf; DWORD lasterr = GetLastError (); DWORD chars = FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, error, 0, /* Default language */ (LPVOID)&msgbuf, 0, NULL); if (chars != 0) { /* If there is an \r\n appended, zap it. */ if (chars >= 2 && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') { chars -= 2; msgbuf[chars] = 0; } if (chars > ((COUNTOF (buf)) - 1)) { chars = COUNTOF (buf) - 1; msgbuf [chars] = 0; } wcstombs (buf, msgbuf, chars + 1); LocalFree (msgbuf); } else sprintf (buf, "unknown win32 error (%ld)", error); SetLastError (lasterr); return buf; } #endif /* __MINGW32CE__ */ /* Print the system error message for errno, and also mention STRING as the file name for which the error was encountered. Then return to command level. */ static void perror_with_name (const char *string) { #ifndef STDC_HEADERS extern int errno; #endif const char *err; char *combined; err = strerror (errno); if (err == NULL) err = "unknown error"; combined = (char *) alloca (strlen (err) + strlen (string) + 3); strcpy (combined, string); strcat (combined, ": "); strcat (combined, err); fprintf (stderr, "\n%s.\n", combined); fflush (stderr); exit (1); } static void sync_error (FILE *fp, char *desc, int expect, int got) { fprintf (stderr, "\n%s\n", desc); fprintf (stderr, "At logfile offset %ld, expected '0x%x' got '0x%x'\n", ftell (fp), expect, got); fflush (stderr); exit (1); } static void remote_error (const char *desc) { fprintf (stderr, "\n%s\n", desc); fflush (stderr); exit (1); } static void remote_close (void) { #ifdef USE_WIN32API closesocket (remote_desc); #else close (remote_desc); #endif } /* Open a connection to a remote debugger. NAME is the filename used for communication. */ static void remote_open (char *name) { if (!strchr (name, ':')) { fprintf (stderr, "%s: Must specify tcp connection as host:addr\n", name); fflush (stderr); exit (1); } else { #ifdef USE_WIN32API static int winsock_initialized; #endif char *port_str; int port; struct sockaddr_in sockaddr; socklen_t tmp; int tmp_desc; port_str = strchr (name, ':'); port = atoi (port_str + 1); #ifdef USE_WIN32API if (!winsock_initialized) { WSADATA wsad; WSAStartup (MAKEWORD (1, 0), &wsad); winsock_initialized = 1; } #endif tmp_desc = socket (PF_INET, SOCK_STREAM, 0); if (tmp_desc == -1) perror_with_name ("Can't open socket"); /* Allow rapid reuse of this port. */ tmp = 1; setsockopt (tmp_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof (tmp)); sockaddr.sin_family = PF_INET; sockaddr.sin_port = htons (port); sockaddr.sin_addr.s_addr = INADDR_ANY; if (bind (tmp_desc, (struct sockaddr *) &sockaddr, sizeof (sockaddr)) || listen (tmp_desc, 1)) perror_with_name ("Can't bind address"); tmp = sizeof (sockaddr); remote_desc = accept (tmp_desc, (struct sockaddr *) &sockaddr, &tmp); if (remote_desc == -1) perror_with_name ("Accept failed"); /* Enable TCP keep alive process. */ tmp = 1; setsockopt (tmp_desc, SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp, sizeof (tmp)); /* Tell TCP not to delay small packets. This greatly speeds up interactive response. */ tmp = 1; setsockopt (remote_desc, IPPROTO_TCP, TCP_NODELAY, (char *) &tmp, sizeof (tmp)); #ifndef USE_WIN32API close (tmp_desc); /* No longer need this */ signal (SIGPIPE, SIG_IGN); /* If we don't do this, then gdbreplay simply exits when the remote side dies. */ #else closesocket (tmp_desc); /* No longer need this */ #endif } #if defined(F_SETFL) && defined (FASYNC) fcntl (remote_desc, F_SETFL, FASYNC); #endif fprintf (stderr, "Replay logfile using %s\n", name); fflush (stderr); } static int fromhex (int ch) { if (ch >= '0' && ch <= '9') { return (ch - '0'); } if (ch >= 'A' && ch <= 'F') { return (ch - 'A' + 10); } if (ch >= 'a' && ch <= 'f') { return (ch - 'a' + 10); } fprintf (stderr, "\nInvalid hex digit '%c'\n", ch); fflush (stderr); exit (1); } static int logchar (FILE *fp) { int ch; int ch2; ch = fgetc (fp); fputc (ch, stdout); fflush (stdout); switch (ch) { case '\n': ch = EOL; break; case '\\': ch = fgetc (fp); fputc (ch, stdout); fflush (stdout); switch (ch) { case '\\': break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case 'x': ch2 = fgetc (fp); fputc (ch2, stdout); fflush (stdout); ch = fromhex (ch2) << 4; ch2 = fgetc (fp); fputc (ch2, stdout); fflush (stdout); ch |= fromhex (ch2); break; default: /* Treat any other char as just itself */ break; } default: break; } return (ch); } static int gdbchar (int desc) { unsigned char fromgdb; if (read (desc, &fromgdb, 1) != 1) return -1; else return fromgdb; } /* Accept input from gdb and match with chars from fp (after skipping one blank) up until a \n is read from fp (which is not matched) */ static void expect (FILE *fp) { int fromlog; int fromgdb; if ((fromlog = logchar (fp)) != ' ') { sync_error (fp, "Sync error during gdb read of leading blank", ' ', fromlog); } do { fromlog = logchar (fp); if (fromlog == EOL) break; fromgdb = gdbchar (remote_desc); if (fromgdb < 0) remote_error ("Error during read from gdb"); } while (fromlog == fromgdb); if (fromlog != EOL) { sync_error (fp, "Sync error during read of gdb packet from log", fromlog, fromgdb); } } /* Play data back to gdb from fp (after skipping leading blank) up until a \n is read from fp (which is discarded and not sent to gdb). */ static void play (FILE *fp) { int fromlog; char ch; if ((fromlog = logchar (fp)) != ' ') { sync_error (fp, "Sync error skipping blank during write to gdb", ' ', fromlog); } while ((fromlog = logchar (fp)) != EOL) { ch = fromlog; if (write (remote_desc, &ch, 1) != 1) remote_error ("Error during write to gdb"); } } static void gdbreplay_version (void) { printf ("GNU gdbreplay %s%s\n" "Copyright (C) 2015 Free Software Foundation, Inc.\n" "gdbreplay is free software, covered by " "the GNU General Public License.\n" "This gdbreplay was configured as \"%s\"\n", PKGVERSION, version, host_name); } static void gdbreplay_usage (FILE *stream) { fprintf (stream, "Usage:\tgdbreplay <logfile> <host:port>\n"); if (REPORT_BUGS_TO[0] && stream == stdout) fprintf (stream, "Report bugs to \"%s\".\n", REPORT_BUGS_TO); } int main (int argc, char *argv[]) { FILE *fp; int ch; if (argc >= 2 && strcmp (argv[1], "--version") == 0) { gdbreplay_version (); exit (0); } if (argc >= 2 && strcmp (argv[1], "--help") == 0) { gdbreplay_usage (stdout); exit (0); } if (argc < 3) { gdbreplay_usage (stderr); exit (1); } fp = fopen (argv[1], "r"); if (fp == NULL) { perror_with_name (argv[1]); } remote_open (argv[2]); while ((ch = logchar (fp)) != EOF) { switch (ch) { case 'w': /* data sent from gdb to gdbreplay, accept and match it */ expect (fp); break; case 'r': /* data sent from gdbreplay to gdb, play it */ play (fp); break; case 'c': /* Command executed by gdb */ while ((ch = logchar (fp)) != EOL); break; } } remote_close (); exit (0); }