From e8f1ad9a8b8548dbc79c01b3df218ff2a97f1c05 Mon Sep 17 00:00:00 2001 From: Fred Fish Date: Wed, 24 Jan 1996 21:30:37 +0000 Subject: * NEWS: Make note of new record and replay feature for remote debug sessions. * serial.c (gdbcmd.h): Include. (serial_logfile, serial_logfp, serial_reading, serial_writing): Define here, for remote debug session logging. (serial_log_command, serial_logchar, serial_write, serial_readchar): New functions for remote debug session logging. (serial_open): Open remote debug session log file when needed. (serial_close): Close remote debug session log file when needed. (_initialize_serial): Add set/show commands for name of remote debug session log file. * serial.h (serial_readchar): Declare (SERIAL_READCHAR): Call serial_readchar(). (SERIAL_WRITE): Call serial_write(). (serial_close): Declare as extern. (serial_logfile, serial_logfp): Declare. * top.c (execute_command): Declare serial_logfp. Log user command in remote debug session log if log file is open. * remote-array.c (array_wait): #ifdef out echo to gdb_stdout. (array_read_inferior_memory): Rewrite to fix memory overwrite bug. * remote-array.c (SREC_SIZE): Remove, duplicates define in monitor.h. * remote-array.c (hexchars, hex2mem): Remove, unused. * gdbserver/low-linux.c (store_inferior_registers): Remove unnecessary extern declaration of registers[]. * gdbserver/Makefile.in (all): Add gdbreplay. * gdbserver/gdbreplay.c: New file. * gdbserver/README: Give example of recording a remote debug session with gdb and then replaying it with gdbreplay. --- gdb/ChangeLog | 32 +++++ gdb/NEWS | 10 ++ gdb/gdbserver/gdbreplay.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++ gdb/gdbserver/low-linux.c | 1 - gdb/remote-array.c | 81 ++++++------ gdb/serial.c | 134 +++++++++++++++++++ gdb/serial.h | 15 ++- gdb/top.c | 4 + 8 files changed, 551 insertions(+), 45 deletions(-) create mode 100644 gdb/gdbserver/gdbreplay.c diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 69faad2..1cbdf9b 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,35 @@ +Wed Jan 24 13:19:10 1996 Fred Fish + + * NEWS: Make note of new record and replay feature for + remote debug sessions. + * serial.c (gdbcmd.h): Include. + (serial_logfile, serial_logfp, serial_reading, serial_writing): + Define here, for remote debug session logging. + (serial_log_command, serial_logchar, serial_write, serial_readchar): + New functions for remote debug session logging. + (serial_open): Open remote debug session log file when needed. + (serial_close): Close remote debug session log file when needed. + (_initialize_serial): Add set/show commands for name of remote + debug session log file. + * serial.h (serial_readchar): Declare + (SERIAL_READCHAR): Call serial_readchar(). + (SERIAL_WRITE): Call serial_write(). + (serial_close): Declare as extern. + (serial_logfile, serial_logfp): Declare. + * top.c (execute_command): Declare serial_logfp. Log user command + in remote debug session log if log file is open. + * remote-array.c (array_wait): #ifdef out echo to gdb_stdout. + (array_read_inferior_memory): Rewrite to fix memory overwrite bug. + * remote-array.c (SREC_SIZE): Remove, duplicates define in + monitor.h. + * remote-array.c (hexchars, hex2mem): Remove, unused. + * gdbserver/low-linux.c (store_inferior_registers): Remove + unnecessary extern declaration of registers[]. + * gdbserver/Makefile.in (all): Add gdbreplay. + * gdbserver/gdbreplay.c: New file. + * gdbserver/README: Give example of recording a remote + debug session with gdb and then replaying it with gdbreplay. + Tue Jan 23 18:02:35 1996 Per Bothner * stabsread.c (rs6000_builtin_type): Make bool type unsigned. diff --git a/gdb/NEWS b/gdb/NEWS index 0867bde..813b60a 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -32,6 +32,16 @@ Note this feature does not work on hpux8. On hpux9 you must link /usr/lib/end.o into your program. This feature should work automatically on hpux10. +* Recording and replaying remote debug sessions + +If you set "remotelogfile" gdb will use that filename to make a +"recording" of a remote debug session which can be replayed back to +gdb using "gdbreplay". See gdbserver/README for details. This is +useful when you have a problem with gdb while doing remote debugging. +By making a recording of the session and sending it to the gdb +maintainers, it is possible to recreate your problem without access to +the remote hardware you are using. + *** Changes in GDB-4.15: * Psymtabs for XCOFF diff --git a/gdb/gdbserver/gdbreplay.c b/gdb/gdbserver/gdbreplay.c new file mode 100644 index 0000000..4d5795a --- /dev/null +++ b/gdb/gdbserver/gdbreplay.c @@ -0,0 +1,319 @@ +/* Replay a remote debug session logfile for GDB. + Copyright (C) 1996 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 2 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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Sort of a hack... */ +#define EOL (EOF - 1) + +static int remote_desc; + +/* 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. */ + +void +perror_with_name (string) + char *string; +{ + extern int sys_nerr; + extern char *sys_errlist[]; + extern int errno; + char *err; + char *combined; + + err = (errno < sys_nerr) ? sys_errlist[errno] : "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 (fp, desc, expect, got) + 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); +} + +void +remote_close() +{ + close (remote_desc); +} + +/* Open a connection to a remote debugger. + NAME is the filename used for communication. */ + +void +remote_open (name) + char *name; +{ + struct sgttyb sg; + extern char *strchr (); + + if (!strchr (name, ':')) + { + fprintf (stderr, "%s: Must specify tcp connection as host:addr\n", name); + fflush (stderr); + exit (1); + } + else + { + char *port_str; + int port; + struct sockaddr_in sockaddr; + int tmp; + struct protoent *protoent; + int tmp_desc; + + port_str = strchr (name, ':'); + + port = atoi (port_str + 1); + + tmp_desc = socket (PF_INET, SOCK_STREAM, 0); + if (tmp_desc < 0) + 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"); + + protoent = getprotobyname ("tcp"); + if (!protoent) + perror_with_name ("getprotobyname"); + + /* 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, protoent->p_proto, TCP_NODELAY, + (char *)&tmp, sizeof(tmp)); + + 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. */ + } + + fcntl (remote_desc, F_SETFL, FASYNC); + + fprintf (stderr, "Replay logfile using %s\n", name); + fflush (stderr); +} + +static int tohex (ch) + 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 (fp) + 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 = tohex (ch2) << 4; + ch2 = fgetc (fp); + fputc (ch2, stdout); + fflush (stdout); + ch |= tohex (ch2); + break; + default: + /* Treat any other char as just itself */ + break; + } + default: + break; + } + return (ch); +} + +/* 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) */ + +void +expect (fp) + FILE *fp; +{ + int fromlog; + unsigned char 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; + } + read (remote_desc, &fromgdb, 1); + } while (fromlog == fromgdb); + if (fromlog != EOL) + { + sync_error (fp, "Sync error during read of gdb packet", 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). */ + +void +play (fp) + 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; + write (remote_desc, &ch, 1); + } +} + +int +main (argc, argv) + int argc; + char *argv[]; +{ + FILE *fp; + int ch; + + if (argc < 3) + { + fprintf (stderr, "Usage: gdbreplay \n"); + fflush (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); +} + diff --git a/gdb/gdbserver/low-linux.c b/gdb/gdbserver/low-linux.c index 046a9be..34dc643 100644 --- a/gdb/gdbserver/low-linux.c +++ b/gdb/gdbserver/low-linux.c @@ -273,7 +273,6 @@ store_inferior_registers (regno) { register unsigned int regaddr; char buf[80]; - extern char registers[]; register int i; unsigned int offset = U_REGS_OFFSET; int scratch; diff --git a/gdb/remote-array.c b/gdb/remote-array.c index 0b36af1..0ed84ca 100644 --- a/gdb/remote-array.c +++ b/gdb/remote-array.c @@ -43,10 +43,6 @@ extern int baud_rate; -static const char hexchars[]="0123456789abcdef"; -static char *hex2mem(); - -#define SREC_SIZE 160 #define ARRAY_PROMPT ">> " #define SWAP_TARGET_AND_HOST(buffer,len) \ @@ -758,8 +754,10 @@ array_wait (pid, status) /* do this so it looks like there's keyboard echo */ if (c == 3) /* exit on Control-C */ break; +#if 0 fputc_unfiltered (c, gdb_stdout); fflush (stdout); +#endif } } SERIAL_SET_TTY_STATE (tty_desc, ttystate); @@ -947,21 +945,12 @@ array_read_inferior_memory(memaddr, myaddr, len) char *myaddr; int len; { - int i, j; + int j; char buf[20]; char packet[PBUFSIZ]; - - /* Number of bytes read so far. */ - int count; - - /* Starting address of this pass. */ - unsigned long startaddr; - - /* Starting address of this pass. */ - unsigned long endaddr; - - /* Number of bytes to read in this pass. */ - int len_this_pass; + int count; /* Number of bytes read so far. */ + unsigned long startaddr; /* Starting address of this pass. */ + int len_this_pass; /* Number of bytes to read in this pass. */ debuglogs (1, "array_read_inferior_memory (memaddr=0x%x, myaddr=0x%x, len=%d)", memaddr, myaddr, len); @@ -979,36 +968,46 @@ array_read_inferior_memory(memaddr, myaddr, len) return 0; } - startaddr = memaddr; - count = 0; - while (count < len) { - len_this_pass = 16; - if ((startaddr % 16) != 0) - len_this_pass -= startaddr % 16; - if (len_this_pass > (len - count)) - len_this_pass = (len - count); - - debuglogs (3, "Display %d bytes at %x for Big Endian host", len_this_pass, startaddr); - - for (i = 0; i < len_this_pass; i++) { + for (count = 0, startaddr = memaddr; count < len; startaddr += len_this_pass) + { + /* Try to align to 16 byte boundry (why?) */ + len_this_pass = 16; + if ((startaddr % 16) != 0) + { + len_this_pass -= startaddr % 16; + } + /* Only transfer bytes we need */ + if (len_this_pass > (len - count)) + { + len_this_pass = (len - count); + } + /* Fetch the bytes */ + debuglogs (3, "read %d bytes from inferior address %x", len_this_pass, + startaddr); sprintf (buf, "m%08x,%04x", startaddr, len_this_pass); make_gdb_packet (packet, buf); if (array_send_packet (packet) == 0) - error ("Couldn't transmit packet\n"); + { + error ("Couldn't transmit packet\n"); + } if (array_get_packet (packet) == 0) - error ("Couldn't receive packet\n"); + { + error ("Couldn't receive packet\n"); + } if (*packet == 0) - error ("Got no data in the GDB packet\n"); - debuglogs (4, "array_read_inferior: Got a \"%s\" back\n", packet); - for (j = 0; j < len_this_pass ; j++) { /* extract the byte values */ - myaddr[count++] = from_hex (*(packet+(j*2))) * 16 + from_hex (*(packet+(j*2)+1)); - debuglogs (5, "myaddr set to %x\n", myaddr[count-1]); - } - startaddr += 1; + { + error ("Got no data in the GDB packet\n"); + } + /* Pick packet apart and xfer bytes to myaddr */ + debuglogs (4, "array_read_inferior_memory: Got a \"%s\" back\n", packet); + for (j = 0; j < len_this_pass ; j++) + { + /* extract the byte values */ + myaddr[count++] = from_hex (*(packet+(j*2))) * 16 + from_hex (*(packet+(j*2)+1)); + debuglogs (5, "myaddr[%d] set to %x\n", count-1, myaddr[count-1]); + } } - - } - return len; + return (count); } /* FIXME-someday! merge these two. */ diff --git a/gdb/serial.c b/gdb/serial.c index 72bfc50..c984d7a 100644 --- a/gdb/serial.c +++ b/gdb/serial.c @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "defs.h" #include "serial.h" #include "gdb_string.h" +#include "gdbcmd.h" /* Linked list of serial I/O handlers */ @@ -33,6 +34,110 @@ static serial_t last_serial_opened = NULL; static serial_t scb_base; +/* Non-NULL gives filename which contains a recording of the remote session, + suitable for playback by gdbserver. */ + +char *serial_logfile = NULL; +FILE *serial_logfp = NULL; + + +static int serial_reading = 0; +static int serial_writing = 0; + +void +serial_log_command (cmd) + const char *cmd; +{ + if (serial_reading || serial_writing) + { + fputc ('\n', serial_logfp); + serial_reading = 0; + serial_writing = 0; + } + fprintf (serial_logfp, "c %s\n", cmd); + /* Make sure that the log file is as up-to-date as possible, + in case we are getting ready to dump core or something. */ + fflush (serial_logfp); +} + +static void +serial_logchar (ch) + int ch; +{ + switch (ch) + { + case '\\': fputs ("\\\\", serial_logfp); break; + case '\b': fputs ("\\b", serial_logfp); break; + case '\f': fputs ("\\f", serial_logfp); break; + case '\n': fputs ("\\n", serial_logfp); break; + case '\r': fputs ("\\r", serial_logfp); break; + case '\t': fputs ("\\t", serial_logfp); break; + case '\v': fputs ("\\v", serial_logfp); break; + default: fprintf (serial_logfp, isprint (ch) ? "%c" : "\\x%02x", ch & 0xFF); break; + } +} + +int +serial_write (scb, str, len) + serial_t scb; + const char *str; + int len; +{ + int count; + + if (serial_logfp != NULL) + { + if (serial_reading) + { + fputc ('\n', serial_logfp); + serial_reading = 0; + } + if (!serial_writing) + { + serial_logchar ('w'); + serial_logchar (' '); + serial_writing = 1; + } + for (count = 0; count < len; count++) + { + serial_logchar (str[count]); + } + /* Make sure that the log file is as up-to-date as possible, + in case we are getting ready to dump core or something. */ + fflush (serial_logfp); + } + return (scb -> ops -> write (scb, str, len)); +} + +int +serial_readchar (scb, timeout) + serial_t scb; + int timeout; +{ + int ch; + + ch = scb -> ops -> readchar (scb, timeout); + if (serial_logfp != NULL) + { + if (serial_writing) + { + fputc ('\n', serial_logfp); + serial_writing = 0; + } + if (!serial_reading) + { + serial_logchar ('r'); + serial_logchar (' '); + serial_reading = 1; + } + serial_logchar (ch); + /* Make sure that the log file is as up-to-date as possible, + in case we are getting ready to dump core or something. */ + fflush (serial_logfp); + } + return (ch); +} + static struct serial_ops * serial_interface_lookup (name) char *name; @@ -102,6 +207,15 @@ serial_open (name) last_serial_opened = scb; + if (serial_logfile != NULL) + { + serial_logfp = fopen (serial_logfile, "w"); + if (serial_logfp == NULL) + { + perror_with_name (serial_logfile); + } + } + return scb; } @@ -152,6 +266,18 @@ serial_close(scb, really_close) last_serial_opened = NULL; + if (serial_logfp) + { + if (serial_reading || serial_writing) + { + fputc ('\n', serial_logfp); + serial_reading = 0; + serial_writing = 0; + } + fclose (serial_logfp); + serial_logfp = NULL; + } + /* This is bogus. It's not our fault if you pass us a bad scb...! Rob, you should fix your code instead. */ @@ -346,4 +472,12 @@ _initialize_serial () "Connect the terminal directly up to the command monitor.\n\ Use ~. or ~^D to break out."); #endif /* 0 */ + + add_show_from_set (add_set_cmd ("remotelogfile", no_class, + var_filename, (char *)&serial_logfile, + "Set filename for remote session recording.\n\ +This file is used to record the remote session for future playback\n\ +by gdbserver.", &setlist), + &showlist); + } diff --git a/gdb/serial.h b/gdb/serial.h index 2cf06f3..8abcb3d 100644 --- a/gdb/serial.h +++ b/gdb/serial.h @@ -136,7 +136,9 @@ serial_t serial_fdopen PARAMS ((const int fd)); #define SERIAL_TIMEOUT -2 #define SERIAL_EOF -3 -#define SERIAL_READCHAR(SERIAL_T, TIMEOUT) ((SERIAL_T)->ops->readchar((SERIAL_T), TIMEOUT)) +extern int serial_readchar PARAMS ((serial_t scb, int timeout)); + +#define SERIAL_READCHAR(SERIAL_T, TIMEOUT) serial_readchar (SERIAL_T, TIMEOUT) /* Set the baudrate to the decimal value supplied. Returns 0 for success, -1 for failure. */ @@ -155,11 +157,13 @@ serial_t serial_fdopen PARAMS ((const int fd)); /* Write LEN chars from STRING to the port SERIAL_T. Returns 0 for success, non-zero for failure. */ -#define SERIAL_WRITE(SERIAL_T, STRING, LEN) ((SERIAL_T)->ops->write((SERIAL_T), STRING, LEN)) +extern int serial_write PARAMS ((serial_t scb, const char *str, int len)); + +#define SERIAL_WRITE(SERIAL_T, STRING,LEN) serial_write (SERIAL_T, STRING, LEN) /* Push out all buffers, close the device and destroy SERIAL_T. */ -void serial_close PARAMS ((serial_t, int)); +extern void serial_close PARAMS ((serial_t, int)); #define SERIAL_CLOSE(SERIAL_T) serial_close(SERIAL_T, 1) @@ -170,4 +174,9 @@ void serial_close PARAMS ((serial_t, int)); extern void serial_printf PARAMS ((serial_t desc, const char *, ...)) ATTR_FORMAT(printf, 2, 3); +/* File in which to record the remote debugging session */ + +extern char *serial_logfile; +extern FILE *serial_logfp; + #endif /* SERIAL_H */ diff --git a/gdb/top.c b/gdb/top.c index 675ad80..cf22bce 100644 --- a/gdb/top.c +++ b/gdb/top.c @@ -1139,6 +1139,7 @@ execute_command (p, from_tty) register struct cmd_list_element *c; register enum language flang; static int warned = 0; + extern FILE *serial_logfp; free_all_values (); @@ -1146,6 +1147,9 @@ execute_command (p, from_tty) if (p == NULL) return; + if (serial_logfp != NULL) + serial_log_command (p); + while (*p == ' ' || *p == '\t') p++; if (*p) { -- cgit v1.1