From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 9063 invoked by alias); 11 Sep 2008 00:23:38 -0000 Received: (qmail 9034 invoked by uid 22791); 11 Sep 2008 00:23:33 -0000 X-Spam-Check-By: sourceware.org Received: from smtp-outbound-2.vmware.com (HELO smtp-outbound-2.vmware.com) (65.115.85.73) by sourceware.org (qpsmtpd/0.31) with ESMTP; Thu, 11 Sep 2008 00:22:33 +0000 Received: from mailhost2.vmware.com (mailhost2.vmware.com [10.16.64.160]) by smtp-outbound-2.vmware.com (Postfix) with ESMTP id 7C56C3D00E for ; Wed, 10 Sep 2008 17:22:30 -0700 (PDT) Received: from [10.20.92.218] (promb-2s-dhcp218.eng.vmware.com [10.20.92.218]) by mailhost2.vmware.com (Postfix) with ESMTP id 6EA7C8E594 for ; Wed, 10 Sep 2008 17:22:30 -0700 (PDT) Message-ID: <48C86493.3000402@vmware.com> Date: Thu, 11 Sep 2008 00:23:00 -0000 From: Michael Snyder User-Agent: Thunderbird 1.5.0.12 (X11/20080411) MIME-Version: 1.0 To: "gdb-patches@sourceware.org" Subject: [RFA] gdb-freeplay Content-Type: multipart/mixed; boundary="------------070001090108080408040501" X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2008-09/txt/msg00231.txt.bz2 This is a multi-part message in MIME format. --------------070001090108080408040501 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Content-length: 425 Here's a 'bump' for the gdb-freeplay patch, now featuring support for reverse debugging. I have some rough-cut testsuites for reverse-step, reverse-continue, reverse-finish, and watchpoints and breakpoints in reverse. They pass with gdb-freeplay and with Teawater's native linux record/replay. They (the tests) are not really ready to submit, but maybe I'll post them, just to show how reverse debugging is coming along. --------------070001090108080408040501 Content-Type: text/plain; name="gdb-freeplay.txt" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="gdb-freeplay.txt" Content-length: 60713 2008-09-10 Michael Snyder * gdb-freeplay is an enhanced version of gdbreplay. * gdbfreeplay-front.c: New file. Extended gdbreplay. * gdbfreeplay-back.c: New file. * gdbfreeplay.h: New file. * remote-breakpoint.c: New file. * remote-breakpoint.h: New file. * Makefile.in: Add rules for gdb-freeplay. * configure.srv: Ditto. * configure.ac: Ditto. * configure: Regenerate. * gdbfreeplay-i386.c: New file. x86 back-end. * gdbfreeplay-mips64.c: New file. Mips64 back-end. * gdbfreeplay-x86-64.c: New file. X86-64 back-end. 2008-09-10 Michael Snyder * gdbfreeplay-back.c: Add support for reverse debugging. Index: Makefile.in =================================================================== RCS file: /cvs/src/src/gdb/gdbserver/Makefile.in,v retrieving revision 1.63 diff -u -p -r1.63 Makefile.in --- Makefile.in 24 Aug 2008 18:40:37 -0000 1.63 +++ Makefile.in 11 Sep 2008 00:09:23 -0000 @@ -136,6 +136,8 @@ SFILES= $(srcdir)/gdbreplay.c $(srcdir)/ DEPFILES = @GDBSERVER_DEPFILES@ +FREEPLAY_DEPFILES = @GDBFREEPLAY_DEPFILES@ + LIBOBJS = @LIBOBJS@ SOURCES = $(SFILES) @@ -200,6 +202,12 @@ gdbreplay$(EXEEXT): $(GDBREPLAY_OBS) ${CC-LD} $(INTERNAL_CFLAGS) $(INTERNAL_LDFLAGS) -o gdbreplay$(EXEEXT) $(GDBREPLAY_OBS) \ $(XM_CLIBS) +gdb-freeplay$(EXEEXT): gdbfreeplay-front.o gdbfreeplay-back.o version.o \ + $(FREEPLAY_DEPFILES) remote-breakpoint.o + rm -f gdb-freeplay$(EXEEXT) + ${CC-LD} $(INTERNAL_CFLAGS) $(INTERNAL_LDFLAGS) \ + -o gdb-freeplay$(EXEEXT) $^ $(XM_CLIBS) + # Put the proper machine-specific files first, so M-. on a machine # specific routine gets the one for the correct machine. # The xyzzy stuff below deals with empty DEPFILES @@ -218,7 +226,8 @@ tags: TAGS clean: rm -f *.o ${ADD_FILES} *~ rm -f version.c - rm -f gdbserver$(EXEEXT) gdbreplay$(EXEEXT) core make.log + rm -f gdbserver$(EXEEXT) gdbreplay$(EXEEXT) gdb-freeplay$(EXEEXT) + rm -f core make.log rm -f reg-arm.c reg-i386.c reg-ia64.c reg-m32r.c reg-m68k.c rm -f reg-sh.c reg-spu.c reg-x86-64.c reg-i386-linux.c rm -f reg-cris.c reg-crisv32.c reg-x86-64-linux.c reg-xtensa.c @@ -290,6 +299,11 @@ target.o: target.c $(server_h) thread-db.o: thread-db.c $(server_h) $(gdb_proc_service_h) utils.o: utils.c $(server_h) gdbreplay.o: gdbreplay.c config.h +gdbfreeplay-front.o: gdbfreeplay-front.c gdbfreeplay.h +gdbfreeplay-back.o: gdbfreeplay-back.c gdbfreeplay.h remote-breakpoint.h +gdbfreeplay-i386.o: gdbfreeplay-i386.c gdbfreeplay.h remote-breakpoint.h +gdbfreeplay-mips64.o: gdbfreeplay-mips64.c gdbfreeplay.h remote-breakpoint.h +remote-breakpoint.o: remote-breakpoint.c remote-breakpoint.h signals.o: ../signals/signals.c $(server_h) $(CC) -c $(CPPFLAGS) $(INTERNAL_CFLAGS) $< -DGDBSERVER Index: configure =================================================================== RCS file: /cvs/src/src/gdb/gdbserver/configure,v retrieving revision 1.37 diff -u -p -r1.37 configure --- configure 31 Jul 2008 17:46:33 -0000 1.37 +++ configure 11 Sep 2008 00:09:24 -0000 @@ -310,7 +310,7 @@ ac_includes_default="\ # include #endif" -ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CPP EGREP LIBOBJS PKGVERSION REPORT_BUGS_TO REPORT_BUGS_TEXI RDYNAMIC GDBSERVER_DEPFILES GDBSERVER_LIBS USE_THREAD_DB srv_xmlbuiltin srv_xmlfiles LTLIBOBJS' +ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CPP EGREP LIBOBJS PKGVERSION REPORT_BUGS_TO REPORT_BUGS_TEXI RDYNAMIC GDBSERVER_DEPFILES GDBSERVER_LIBS USE_THREAD_DB srv_xmlbuiltin srv_xmlfiles GDBFREEPLAY_DEPFILES LTLIBOBJS' ac_subst_files='' ac_pwd=`pwd` @@ -4579,6 +4579,8 @@ fi GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj $srv_hostio_err_objs $srv_thread_depfiles" GDBSERVER_LIBS="$srv_libs" +GDBFREEPLAY_DEPFILES="$freeplay_tgtobj" + @@ -5238,6 +5240,7 @@ s,@GDBSERVER_LIBS@,$GDBSERVER_LIBS,;t t s,@USE_THREAD_DB@,$USE_THREAD_DB,;t t s,@srv_xmlbuiltin@,$srv_xmlbuiltin,;t t s,@srv_xmlfiles@,$srv_xmlfiles,;t t +s,@GDBFREEPLAY_DEPFILES@,$GDBFREEPLAY_DEPFILES,;t t s,@LTLIBOBJS@,$LTLIBOBJS,;t t CEOF Index: configure.ac =================================================================== RCS file: /cvs/src/src/gdb/gdbserver/configure.ac,v retrieving revision 1.24 diff -u -p -r1.24 configure.ac --- configure.ac 31 Jul 2008 17:46:33 -0000 1.24 +++ configure.ac 11 Sep 2008 00:09:24 -0000 @@ -183,12 +183,14 @@ fi GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj $srv_hostio_err_objs $srv_thread_depfiles" GDBSERVER_LIBS="$srv_libs" +GDBFREEPLAY_DEPFILES="$freeplay_tgtobj" AC_SUBST(GDBSERVER_DEPFILES) AC_SUBST(GDBSERVER_LIBS) AC_SUBST(USE_THREAD_DB) AC_SUBST(srv_xmlbuiltin) AC_SUBST(srv_xmlfiles) +AC_SUBST(GDBFREEPLAY_DEPFILES) AC_OUTPUT(Makefile, [case x$CONFIG_HEADERS in Index: gdbfreeplay-back.c =================================================================== RCS file: gdbfreeplay-back.c diff -N gdbfreeplay-back.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdbfreeplay-back.c 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,1159 @@ +/* + * gdbfreeplay-back.c + * + * Backend for gdbfreeplay. + */ + +#include "config.h" +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +typedef struct STOPFRAME { + /* frame_id -- a unique identifier per stop frame. */ + unsigned int frame_id; + /* pc -- address of the next instruction to be "executed". */ + unsigned long long pc; + /* FIXME remove! predecr_pc -- address that will be reported by a breakpoint. + These two are different on some targets, see gdb source code, + DECR_PC_AFTER_BREAK. */ + unsigned long long predecr_pc; + unsigned long eventpos; + unsigned long Opos; +} StopFrame; + +StopFrame *stopframe; +int last_cached_frame = 0; +int framecache_size = 0; +int cur_frame = -1; + +#include "gdbfreeplay.h" + +/* + * scan_gdbreplay_file + * + * Make a pass through the replay log, noting the positions + * of the stop events and certain packets within them. + * + * Returns: FAIL, PASS. + */ + +#define INBUF_SIZE 4096 +static char inbuf[INBUF_SIZE]; + +#define GDB_LINEBUF_SIZE 4096 +static char gdb_linebuf[GDB_LINEBUF_SIZE]; + +static enum successcode +scan_gdbreplay_file (FILE *infile) +{ + /* Make a pass over the entire file -- cache the record positions. */ + char *line, *p; + unsigned long nextpos; + unsigned long long GPC; + + /* First skip empty lines. */ + do { + nextpos = ftell (infile); + line = fgets (inbuf, sizeof (inbuf), infile); + } while (line != NULL && inbuf[0] == '\n'); + + if (line == NULL) + return FAIL; /* End of file, nothing there. */ + + /* Now for the main loop. + * + * Scan the rest of the file, recording stop events etc. + */ + do { + /* 'g' packet message? */ + if (strstr (line, "$g#67") != NULL) + { + /* See if we need to grab the PC from this packet. */ + if (stopframe[last_cached_frame].pc == 0 || + stopframe[last_cached_frame].pc == (unsigned long long) -1) + { + nextpos = ftell (infile); + line = fgets (inbuf, sizeof (inbuf), infile); + stopframe[last_cached_frame].pc = target_pc_from_g (line); + } + } + + /* Reset PC after breakpoint? */ + else if ((p = strstr (line, "$G")) != NULL) + { + GPC = target_pc_from_G (p); + if (stopframe[last_cached_frame].pc == 0 || + stopframe[last_cached_frame].pc == (unsigned long long) -1) + { + /* Unlikely, but if we need to, we can just grab this PC. */ + stopframe[last_cached_frame].pc = GPC; + } + else if (stopframe[last_cached_frame].pc == GPC + 1) + { + /* FIXME remove! */ + /* OK, this is gdb decrementing the PC after a breakpoint. */ + stopframe[last_cached_frame].predecr_pc = + stopframe[last_cached_frame].pc; + stopframe[last_cached_frame].pc = GPC; + } + } + + /* 'O' packet(s)? Watch out for "$OK", we don't want that. */ + else if ((p = strstr (line, "$O")) != NULL && + strstr (p, "$OK#9a") == NULL) + { + /* If we know these, we can feed them back to gdb + during 'continue'. */ + stopframe[last_cached_frame].Opos = nextpos; + } + + /* Stop event message? */ + else if ((p = strstr (line, "$T")) != NULL || + (p = strstr (line, "$S")) != NULL || + (p = strstr (line, "$W")) != NULL) + /* FIXME || (p = strstr (line, "$X")) != NULL) + (ambiguity with binary write X packet). */ + { + if (last_cached_frame >= framecache_size - 1) + { + /* Time to grow the frame cache buffer. */ + framecache_size += 2048; + if (verbose) + fprintf (stdout, "Growing frame cache to %d\n", + framecache_size); + stopframe = realloc (stopframe, + framecache_size * sizeof (stopframe[0])); + } + /* Special case: 0th frame. + + On the first pass, the FIRST time I see a stop event + for the 0th frame, I will not increment the stopframe + index. + + The 0th frame is special, in that its "stop event" + happens not as a result of a real stop event, but + as a result of gdb sending the "?" query. Therefore + the apparent event falls in the middle of the frame. + */ + + if (last_cached_frame != 0 || + stopframe [last_cached_frame].eventpos != 0) + last_cached_frame++; + + /* Since we now have a known frame, default to using it. */ + cur_frame = 0; + stopframe[last_cached_frame].eventpos = nextpos + p - line; + /* The frame for this event will default to not having + any 'O' packets, until we recognize one. */ + stopframe[last_cached_frame].Opos = 0; + + if (p[1] == 'T') + stopframe[last_cached_frame].pc = target_pc_from_T (p); + + if (verbose) + fprintf (stdout, "Record event pos at %lu ('%c')\n", + stopframe [last_cached_frame].eventpos, + p[1]); + } + nextpos = ftell (infile); + line = fgets (inbuf, sizeof (inbuf), infile); + } while (line != NULL); + + return PASS; +} + +/* + * gdbfreeplay_open + * + * Open the gdbfreeplay "target", which mainly means opening + * the gdbreplay log file. + * + * Returns: FAIL, PASS. + */ + +static FILE *replay_file; + +enum successcode +gdbfreeplay_open (char *filename) +{ + if ((replay_file = fopen (filename, "r")) == NULL) + { + fprintf (stderr, "GDBFREEPLAY: could not open file %s for input.\n", + filename); + return FAIL; + } + + return scan_gdbreplay_file (replay_file); +} + +/* + * gdbreadline + * + * Read a line of input from gdb (from the socket). + */ + +static char * +gdbreadline (int fd) +{ + char *p = gdb_linebuf; + int saw_dollar = 0; + int saw_pound = 0; + int cksum_chars = 0; + + while (p < gdb_linebuf + sizeof (gdb_linebuf) - 1) + { + read (fd, p, 1); + p[1] = '\0'; + switch (p[0]) { + case '+': + /* Receive "ack" from gdb. + Assuming it is first char in buffer, ignore. */ + if (p - gdb_linebuf != 0) + fprintf (stdout, "gdbreadline: '+' ack in middle of line?\n"); + continue; + break; + case '-': + if (p - gdb_linebuf == 0) + { + /* Receive "nack" from gdb. FIXME what to do? + + Generally, this isn't going to be because of a + communication drop-out, but because gdb simply + did not like the last thing that we sent. + + Sending the same thing again probably isn't going to help... + */ + return gdb_linebuf; + } + goto default_label; + break; + case '$': + if (saw_dollar) + fprintf (stdout, "gdbreadline: two '$' in one line:\n\t'%s'\n", + gdb_linebuf); + + saw_dollar = 1; + break; + case '#': + if (!saw_dollar) + fprintf (stdout, "gdbreadline: '#' before '$':\n\t'%s'\n", + gdb_linebuf); + + if (saw_pound) + fprintf (stdout, "gdbreadline: two '#' in one line:\n\t'%s'\n", + gdb_linebuf); + + saw_pound = 1; + break; + default: + default_label: + if (saw_pound) + cksum_chars++; + if (cksum_chars >= 2) + { + /* Got a complete message. */ + return gdb_linebuf; + } + break; + } + ++p; + } + /* Shouldn't get here except for buffer overflow. */ + fprintf (stdout, "gdbreadline: buffer overflow?\n"); + return gdb_linebuf; +} + +/* + * find_reply + * + * Given a filepos pointing to just after a given gdb request, + * find and return a char * containing the reply to that request. + * + * Return NULL on failure. + */ + +static char * +find_reply (FILE *logfile, long filepos) +{ + char *line; + + if (filepos == -1) + return NULL; + + /* Position the file (it's probably already there...) */ + fseek (logfile, filepos, SEEK_SET); + /* In principle, the next input line should contain the reply. */ + line = fgets (inbuf, sizeof (inbuf), logfile); + + return line; +} + +/* + * frame_find_request + * + * Given a request from gdb, in the form of a string, + * find an identical request within the current frame + * of the gdbreplay log file. + * + * Return a file position immediately following the request. + */ + +static long +frame_find_request (FILE *logfile, char *request) +{ + long curpos; + char *line; + + if (cur_frame >= 0) + { + /* Position the input at the beginning of the frame. + SPECIAL CASE: for frame zero, go to the beginning of the log. + This is so we can find gdb requests that preceed the first + stop-event message. + */ + if (cur_frame == 0) + curpos = 0; + else + curpos = stopframe[cur_frame].eventpos; + fseek (logfile, curpos, SEEK_SET); + /* Now search for a matching request. */ + while ((line = fgets (inbuf, sizeof (inbuf), logfile)) != NULL) + { + /* End of current frame? + If we're the last frame, just read till the end of file. */ + if (cur_frame < last_cached_frame && + curpos >= stopframe[cur_frame + 1].eventpos) + break; + curpos = ftell (logfile); + if (strstr (line, request) != NULL) + { + /* Matching request found. Return position. */ + return curpos; + } + } + } + + /* Not found. */ + return -1; +} + +/* + * gdb_ack + * + * Send an ack to gdb. + * Returns void. + */ + +static void +gdb_ack (int fd) +{ + static const char *ack = "+"; + write (fd, ack, 1); +} + +/* + * hex_to_int + */ + +int +hex_to_int (int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'F' + 10; + else + return 0; +} + +/* + * int_to_hex + * + * (lifted from gdb) + */ + +int +int_to_hex (int nib) +{ + if (nib < 10) + return '0' + nib; + else + return 'a' + nib - 10; +} + +#define EOI 0xdeadbeef + +/* + * convert_logchar + * + * Adapted from gdbreplay. + * Some characters in the log are escaped, and + * need to be translated before being sent back + * to gdb. + * + * Returns a de-escaped char. + */ + +static int +convert_logchar (char **logp) +{ + int ch, ch2; + + ch = *(*logp)++; + switch (ch) + { + case '\0': + case '\n': + ch = EOI; /* End of input (out of band). */ + break; + case '\\': + ch = *(*logp)++; + 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 = *(*logp)++; + ch = hex_to_int (ch2) << 4; + ch2 = *(*logp)++; + ch |= hex_to_int (ch2); + break; + default: + /* Treat any other char as just itself */ + break; + } + default: + break; + } + return (ch); +} + +/* + * gdbwriteline + * + * Send a line of data to gdb, stripped of any + * prefix or suffix stuff. + * + * Prefix is anything preceeding a '$'. + * Suffix is anything following a "#xx" (including any newlines). + * + * Returns void. + */ + +static void +gdbwriteline (int fd, char *line) +{ + char *end; + int ich; + char ch; + + if (line) + { + /* Strip prefix. */ + while (*line && *line != '$') + line++; + if (*line) + { + /* Strip suffix. */ + end = strchr (line, '#'); + if (end && *end) + { + /* Got line, send it. */ + end[3] = '\0'; + while ((ich = convert_logchar (&line)) != EOI) + { + ch = ich; + write (fd, &ch, 1); + } + } + } + } +} + +/* + * stopframe_signal + * + * Return the signal number for which the stopframe stopped. + * This is a brute force implementation. Probably should plan + * on caching the value to speed things up. + */ + +static int +stopframe_signal (FILE *infile, int id) +{ + char *line, *p; + int sig; + + fseek (infile, stopframe[id].eventpos, SEEK_SET); + line = fgets (inbuf, sizeof (inbuf), infile); + + if ((p = strstr (line, "$T")) != NULL || + (p = strstr (line, "$S")) != NULL) + { + /* Signal value is two ascii/hex bytes following "$S" or "$T". */ + sig = (hex_to_int (p[2]) << 4) + hex_to_int (p[3]); + return sig; + } + return 0; +} + +/* + * freeplay_play_O_packets + * + * Send one or more 'O' packets back to gdb. + * Returns void. + */ + +static void +freeplay_play_O_packets (FILE *infile, int fd, unsigned long filepos) +{ + char *line, *p; + + fseek (infile, filepos, SEEK_SET); + + while ((line = fgets (inbuf, sizeof (inbuf), infile)) != NULL) + { + if ((p = strstr (line, "$O")) != NULL) + { + /* FIXME: do I need to send an ACK? */ + gdb_ack (fd); + gdbwriteline (fd, p); + } + /* When we reach the next stop event, we're done. */ + else if (strstr (line, "$T") != NULL || + strstr (line, "$S") != NULL) + { + return; /* done */ + } + } +} + +/* + * freeplay_find_event + * + * Scan (forward or backward) thru the stop events, + * looking for a reason to stop now (assuming gdb has + * asked us to continue). + * + * Returns event frame index, or -1 for failure. + */ + +static int +freeplay_find_event (FILE *infile, + int gdb_fd, + int start, + enum direction_code direction, + enum direction_code play_O_packets) +{ + int i; + int signum; + + /* Right here, we have to be conscious of the critical difference + between a replayer and a simulator. + + No matter what the reason for stopping, we MUST advance + beyond the current frame. Which is not the same as + advancing beyond the current PC. If, say, there is a + reason why we can't advance beyond the current PC (like + say it's an illegal instruction), then the next frame will + contain the same PC and the same signal. + + But we cannot just get stuck at the current stop event. + We have to advance (or go backward, as the case may be), + until and unles we hit the end of the recording. + + So, force the first step. + */ + + if (play_O_packets && stopframe[start].Opos != 0) + freeplay_play_O_packets (infile, gdb_fd, stopframe[start].Opos); + if (direction == DIR_FORWARD && start < last_cached_frame) + start++; + else if (direction == DIR_BACKWARD && start > 0) + start--; + + /* Here's a strange loop for you: goes forward or backward. */ + for (i = start; + i >= 0 && i <= last_cached_frame; + direction == DIR_FORWARD ? i++ : i--) + { + signum = stopframe_signal (infile, i); + /* FIXME we need some modal handling for SIGTRAP. */ + if (signum != 0 && signum != 5) + { + /* Here's an event we can stop at, regardless of breakpoints. */ + /* FIXME: there is some signal-ignoring stuff to be looked at. */ + return i; + } + else if (remote_breakpoint_here_p (SOFTWARE_BP, stopframe[i].pc) == PASS) + { + /* Found a breakpoint. + FIXME need some DECR_PC_AFTER_BREAK handling. */ + return i; + } + if (play_O_packets && stopframe[i].Opos != 0) + freeplay_play_O_packets (infile, gdb_fd, stopframe[i].Opos); + } + /* Found no reason to stop. */ + return -1; +} + +/* + * add_checksum + * + * Compute the checksum for a string that will go to gdb, + * and concatenate it onto the string. + * + * Returns input string modified in place (better have room!) + */ + +char * +add_checksum (char *inbuf) +{ + int cksum = 0; + char *p = inbuf; + + /* Sanity check. */ + if (inbuf == NULL) + return NULL; + + /* If the string doesn't start with a '$', it's broken. */ + if (*p++ != '$') + return inbuf; + + /* Now add up the cksum. */ + while (*p) + { + /* If there's already a '#', it doesn't count toward the cksum. + Stop there. */ + if (*p == '#') + break; + cksum += *p++; + } + + /* Add a '#' at the end, even if it's already there, and + overwrite any old checksum that may be there already. */ + *p++ = '#'; + *p++ = int_to_hex ((cksum >> 4) & 0xf); + *p++ = int_to_hex (cksum & 0xf); + *p++ = '\0'; + + /* Done. */ + return inbuf; +} + +/* + * freeplay_show_next_commands + * + * Output the gdb commands contained in the current frame. + */ + +static void +freeplay_show_next_commands (FILE *infile) +{ + char *line; + + if (cur_frame >= 0 && cur_frame <= last_cached_frame) + { + fseek (infile, stopframe[cur_frame].eventpos, SEEK_SET); + while ((line = fgets (inbuf, sizeof (inbuf), infile)) != NULL) + { + if (cur_frame + 1 <= last_cached_frame && + ftell (infile) >= stopframe[cur_frame + 1].eventpos) + /* Done with current frame. */ + break; + + if (line[0] == 'c' && line[1] == ' ') + fputs (line, stdout); + } + } +} + +/* + * handle_special_case + * + * Handle these requests locally, rather than through the replay buffer. + * + * Returns: a string reply for gdb. + */ + +static char OK[8] = "$OK#9a"; +static char EMPTY[8] = "$#00"; +static char STOP[8] = "$S00#44"; +static char E01[8] = "$E01#a6"; + +static char * +handle_special_case (FILE *infile, int fd, char *request) +{ + unsigned long long addr; + unsigned long len; + int next_event_frame; + char *p; + + static char *monitor_verbose_off = "$qRcmd,766572626f7365206f6666#13"; + static char *monitor_verbose_on = "$qRcmd,766572626f7365206f6e#d6"; + static char *monitor_gdbreplay_next = + "$qRcmd,6764627265706c61792d6e657874#83"; + + /* Handle 'k' (kill) request by exiting. */ + if (strstr (request, "$k#6b") != NULL) + { + /* Well, to PROPERLY honor the 'kill' request, + we need to actually shoot ourselves in the head... */ + gdb_ack (fd); + fprintf (stdout, "gdbfreeplay -- killed by gdb.\n"); + exit (0); + } + + /* Handle 'D' (detach) request by exiting. + FIXME might want to stay alive and offer the socket again. */ + if (strstr (request, "$D#44") != NULL) + { + /* OK, we're not yet set up to close and re-open the socket, + and a new gdb does not seem able to re-attach, so for now + let's actually quit. */ + gdb_ack (fd); + gdbwriteline (fd, OK); + fprintf (stdout, "gdbfreeplay -- gdb has detached. Exiting.\n"); + exit (0); + } + + /* Handle "monitor verbose on". */ + if (strstr (request, monitor_verbose_on) != NULL) + { + verbose = 1; + return OK; + } + + /* Handle "monitor verbose off". */ + if (strstr (request, monitor_verbose_off) != NULL) + { + verbose = 0; + return OK; + } + + /* Handle "monitor gdbreplay-next". */ + if (strstr (request, monitor_gdbreplay_next) != NULL) + { + /* Show the next gdb commands from the gdbreplay log, + just like gdbreplay would. */ + freeplay_show_next_commands (infile); + return OK; + } + + /* Handle 'R' (restart) request. + This is an extended-remote request that is not really used any more, + but we can send it out-of-band (using "maint packet") to effectively + set the replay buffer back to the beginning. */ + if (strstr (request, "$R#52") != NULL) + { + /* Reset replay buffer to beginning. */ + /* NOTE: gdb doesn't know about target state changing, so + if you use this, you must accompany it with "flushregs". */ + cur_frame = 0; + return OK; + } + + /* Handle 'S' (singlestep with a signal) like 's'. */ + if (strstr (request, "$S") != NULL) + { + /* This is gdb trying to step the target, but with a signal. + + Since in reality we can't have any influence on the execution + trace of the "target", we can just ignore the signal and + singlestep. + + Whatever happened before, will happen again... */ + + if (verbose) + fprintf (stdout, + "handle_special_case: demoting 'S' to 's'.\n"); + goto step_label; + } + + /* Handle 's' (step) by advancing the cur_frame index. */ + if (strstr (request, "$s#73") != NULL) + { + step_label: + if (cur_frame < last_cached_frame) + cur_frame++; + + if (stopframe[cur_frame].eventpos != 0) + { + return find_reply (infile, + stopframe[cur_frame].eventpos); + } + else + { + /* Not sure how this could even happen, but... */ + return STOP; + } + } + + /* Handle 'bs' (reverse stepi) by decrementing the cur_frame index. */ + if (strstr (request, "$bs#d5") != NULL) + { + if (cur_frame > 0) + cur_frame--; + + if (stopframe[cur_frame].eventpos != 0) + { + return find_reply (infile, + stopframe[cur_frame].eventpos); + } + else + { + /* Not sure how this could even happen, but... */ + return STOP; + } + } + + /* Handle 'C' (continue with a signal) like 'c'. */ + if (strstr (request, "$C") != NULL) + { + /* This is gdb trying to continue the target, but with a signal. + + Since in reality we can't have any influence on the execution + trace of the "target", we can just ignore the signal and + continue. + + Whatever happened before, will happen again... */ + + if (verbose) + fprintf (stdout, + "handle_special_case: demoting 'C' to 'c'.\n"); + goto continue_label; + } + + /* Handle 'c' (continue) by searching the cache for a stop event. */ + if (strstr (request, "$c#63") != NULL) + { + continue_label: + next_event_frame = freeplay_find_event (infile, fd, + cur_frame, + DIR_FORWARD, + PLAY_O_PACKETS); + if (next_event_frame != -1) + { + /* Got a stop event. Make it the current frame, and tell gdb. + */ + cur_frame = next_event_frame; + } + else + { + cur_frame = last_cached_frame; + } + + /* Find the original event message for this stop event. */ + fseek (infile, stopframe[cur_frame].eventpos, SEEK_SET); + fgets (inbuf, sizeof (inbuf), infile); + + /* If it's a "$T05" (SIGTRAP), give the target a chance to + re-compose it (possibly allowing for DECR_PC_AFTER_BREAK). + */ + if ((p = strstr (inbuf, "$T05")) != NULL) + return add_checksum (target_compose_T_packet (p, + stopframe[cur_frame].pc, + next_event_frame == -1 ? + 0 : + 1 /* breakpoint_p */)); + /* Otherwise, just return it. */ + else + return &inbuf[0]; + } + + /* Handle 'bc' (revese continue) by searching the cache for a stop event. */ + if (strstr (request, "$bc#c5") != NULL) + { + next_event_frame = freeplay_find_event (infile, fd, + cur_frame, + DIR_BACKWARD, + PLAY_O_PACKETS); + if (next_event_frame != -1) + { + /* Got a stop event. Make it the current frame, and tell gdb. + */ + cur_frame = next_event_frame; + } + else + { + /* WTF? */ + gdb_ack (fd); + strcpy (inbuf, "$O5768617420746865206675636b3f"); + if (verbose) + fprintf (stdout, "WTF? %s\n", add_checksum (inbuf)); + gdbwriteline (fd, add_checksum (inbuf)); + cur_frame = 0; + } + + /* Find the original event message for this stop event. */ + fseek (infile, stopframe[cur_frame].eventpos, SEEK_SET); + fgets (inbuf, sizeof (inbuf), infile); + + /* If it's a "$T05" (SIGTRAP, give the target a chance to + re-compose it (possibly allowing for DECR_PC_AFTER_BREAK). + */ + if ((p = strstr (inbuf, "$T05")) != NULL) + return add_checksum (target_compose_T_packet (p, + stopframe[cur_frame].pc, + next_event_frame == -1 ? + 0 : + 1 /* breakpoint_p */)); + /* If it's a "$S", just return it (FIXME?) */ + else + return &inbuf[0]; + } + + /* Handle Z0 set breakpoint. */ + if ((p = strstr (request, "$Z0")) != NULL) + { + if (p[3] == ',') + { + addr = strtoull (p + 4, &p, 16); + if (p[0] == ',') + { + len = strtoul (p + 1, NULL, 16); + remote_set_breakpoint (SOFTWARE_BP, addr, len); + return OK; + } + } + } + + /* Handle z0 remove breakpoint. */ + if ((p = strstr (request, "$z0")) != NULL) + { + if (p[3] == ',') + { + addr = strtoull (p + 4, &p, 16); + if (p[0] == ',') + { + len = strtoul (p + 1, NULL, 16); + remote_remove_breakpoint (SOFTWARE_BP, addr, len); + return OK; + } + } + } +#if 0 + /* + We can actually ignore QPassSignals, because if the original + target that made the recording handled it, it will be transparent + to us, and if not, it will still be transparent. It's not + important enough for us to want to handle locally. */ + + /* Handle QPassSignals. + FIXME we should implement this, but for the meantime, + don't let gdb think we're supporting it if we're actually not. */ + if (strstr (request, "$QPassSignals") != NULL) + { + if (verbose) + fprintf (stdout, "handle_special_case: punting QPassSignals.\n"); + return EMPTY; + } +#endif + /* Handle "vCont" by saying we can't do it. + + FIXME we could do it, mimicing 's' and 'c' etc., but + until such time we must intercept it here, lest we should + let it be handled by something in the replay buffer. + + That would be bad... */ + + if (strstr (request, "$vCont") != 0) + return EMPTY; + + /* TODO: tfind, QPassSignals... */ + + /* Let the replay buffer handle it. */ + return NULL; +} + +/* + * fallbacks + * + * Attempt to handle requests that were not handled otherwise. + * Returns: char * or NULL. + */ + +static char * +fallbacks (FILE *infile, int fd, char *request) +{ + char *p; + + /* Handle "Hc0" request. */ + if (strstr (request, "$Hc0#db") != NULL) + { + /* Debugger just wants to set the "continue thread" to zero. + Don't know why this isn't in the replay cache, but it's + harmless, just say OK. */ + if (verbose) + fprintf (stdout, "fallbacks: absorbing 'Hc0'\n"); + return OK; + } + /* Handle un-cached memory request (if not handled upstream). */ + if (strstr (request, "$m") != NULL) + { + /* FIXME this need not happen so often, + once we do a better job of simulating memory. */ + if (verbose) + fprintf (stdout, "fallbacks: failing memory request '%s'.\n", + request); + return E01; + } + /* Handle P/p requests (if they weren't handled upstream). */ + if (strstr (request, "$p") != NULL || + strstr (request, "$p") != NULL) + { + if (verbose) + fprintf (stdout, "fallbacks: absorbing P/p request.\n"); + return EMPTY; /* Tell gdb we don't know that one. */ + } + + /* Handle 'G' request (if not handled upstream). + Just tell gdb "OK", and otherwise ignore it. + The debugger now has its own idea of what the registers are... */ + if (strstr (request, "$G") != NULL) + { + if (verbose) + fprintf (stdout, "fallbacks: absorbing G request.\n"); + return OK; + } + + /* Handle 'M' or 'X' request (if not handled upstream). + There are two ways to go here -- just say "OK", without + actually doing anything, or return an error. + + Going to try just saying "OK", for now... */ + if (strstr (request, "$M") != NULL || + strstr (request, "$X") != NULL) + { + if (verbose) + fprintf (stdout, "fallbacks: absorbing memory write request.\n"); + return OK; + } + + /* Handle 'g' request (if not handled upstream). + This is usually at a singlestep event. + We need to construct a 'g' packet for gdb from the 'T' packet. */ + if (strstr (request, "$g#67") != NULL) + { + /* Find the original event message for this stop event. */ + fseek (infile, stopframe[cur_frame].eventpos, SEEK_SET); + fgets (inbuf, sizeof (inbuf), infile); + /* If it's a "$T", give the target a chance to compose a g packet. + (possibly allowing for DECR_PC_AFTER_BREAK). */ + if ((p = strstr (inbuf, "$T")) != NULL) + { + if (verbose) + fprintf (stdout, "fallbacks: constructing 'g' packet.\n"); + return add_checksum (target_compose_g_packet (p)); + } + /* If it's an 'S' packet, there ain't much we can do + (FIXME unles we at least know the PC? */ + else + { + if (verbose) + fprintf (stdout, "fallbacks: punting on 'g' packet request.\n"); + return EMPTY; + } + } + + /* Any unhandled qRcmd, just echo it to stdout. */ + if ((p = strstr (request, "$qRcmd,")) != NULL) + { + if (verbose) + fprintf (stdout, "fallbacks: ignoring %s\n", p); + return EMPTY; + } + + /* Default for any other un-handled request -- return empty string. */ + if (verbose) + fprintf (stdout, "fallbacks: absorbing unknown request '%s'.\n", + request); + return EMPTY; +} + +/* + * gdbfreeplay + * + * This is the function that manages the interaction with gdb. + */ + +void +gdbfreeplay (int fd) +{ + char *request; + char *reply; + + /* Process requests from gdb. */ + while (1) + { + /* Read next request from gdb. */ + request = gdbreadline (fd); + if (verbose) + fprintf (stdout, "gdb request: %s\n", request); + + /* FIXME -- this is where we should handle special cases, including: + -- vCont + -- tfind + */ + + reply = handle_special_case (replay_file, fd, request); + + if (reply == NULL) + { + /* Now see if we have a matching request in the current frame. */ + reply = find_reply (replay_file, + frame_find_request (replay_file, request)); + } + + if (reply == NULL) + { + /* Found no matching request in the current frame. + Do fall-backs and last resorts. */ + reply = fallbacks (replay_file, fd, request); + } + + if (verbose) + fprintf (stdout, "freeplay reply: %s\n", + reply == NULL ? "" : reply); + + if (reply) + { + gdb_ack (fd); + gdbwriteline (fd, reply); + } + else + { + /* FIXME -- gotta say something back... */ + } + } +} Index: gdbfreeplay-front.c =================================================================== RCS file: gdbfreeplay-front.c diff -N gdbfreeplay-front.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdbfreeplay-front.c 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,311 @@ +/* + * gdb-freeplay + * + * Freely replay a remote debug session logfile for GDB. + * + * Parts of gdb-freeplay are adapted from gdbreplay, + * by Stu Grossman and Fred Fish. + */ + +#include "config.h" +#include +#if HAVE_SYS_FILE_H +#include +#endif +#if HAVE_SIGNAL_H +#include +#endif +#include +#if HAVE_FCNTL_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#if HAVE_NETDB_H +#include +#endif +#if HAVE_NETINET_TCP_H +#include +#endif +#if HAVE_MALLOC_H +#include +#endif + +#if USE_WIN32API +#include +#endif + +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +/* Version information, from version.c. */ +extern const char version[]; +extern const char host_name[]; + +/* 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 (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); +} + +/* + * remote_close + * + * Close the gdb socket. + */ + +static void +remote_close (int remote_desc) +{ +#ifdef USE_WIN32API + closesocket (remote_desc); +#else + close (remote_desc); +#endif +} + +/* + * remote_open + * + * Open a connection to a remote debugger (gdb). + * NAME is the filename used for communication. + * + * Returns: file descriptor. + * Does not return in case of error. + */ + +static int +remote_open (char *name) +{ + int remote_desc; + + 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 < 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"); + + /* 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); + + return remote_desc; +} + +/* + * freeplay_version + * + * Print version information. + */ + +static void +freeplay_version (FILE *stream) +{ + fprintf (stream, "GNU gdb-freeplay %s%s\n" + "Copyright (C) 2008 Free Software Foundation, Inc.\n" + "gdb-freeplay is free software, covered by the GNU General Public License.\n" + "\n" + "This gdb-freeplay was configured as \"%s\"\n", + PKGVERSION, version, host_name); +} + +/* + * freeplay_usage + * + * Print usage information. + */ + +static void +freeplay_usage (FILE *stream) +{ + fprintf (stream, "Usage:\tgdb-freeplay [--verbose] [--version] \n"); + if (REPORT_BUGS_TO[0] && stream == stdout) + fprintf (stream, "Report bugs to \"%s\".\n", REPORT_BUGS_TO); +} + +#include "gdbfreeplay.h" + +/* + * TODO: + * + * Actually accept packets from gdb, with handshake where appropriate. + * Parse tfind requests and handle them in the frame cache. + * Try to match everything else from the frame cache and send cached reply. + */ + +int verbose; + +extern int +main (int argc, char **argv) +{ + char *default_socket = "localhost:2345"; + int logfile_arg = 1; + int socket_arg = 2; + int remote_desc = -1, i; + + for (i = 0; i < argc; i++) + { + if (strcmp (argv[i], "--version") == 0) + { + freeplay_version (stdout); + logfile_arg++; + socket_arg++; + } + if (strcmp (argv[i], "--verbose") == 0) + { + verbose = 1; + logfile_arg++; + socket_arg++; + } + else if (strcmp (argv[i], "--help") == 0) + { + freeplay_usage (stdout); + exit (0); + } + else if (i == logfile_arg) + { + if (gdbfreeplay_open (argv[i]) != PASS) + perror_with_name (argv[1]); + logfile_arg = 0; + } + else if (i == socket_arg) + { + if (verbose) + fprintf (stdout, "Open socket: %s\n", argv[i]); + remote_desc = remote_open (argv[i]); + socket_arg = 0; + } + } + + if (logfile_arg) + { + freeplay_usage (stderr); + exit (1); + } + + if (socket_arg) + { + if (verbose) + fprintf (stdout, "Open default socket: %s\n", default_socket); + remote_desc = remote_open (default_socket); + } + + if (remote_desc > 0) + { + gdbfreeplay (remote_desc); + + if (verbose) + fprintf (stdout, "Close remote socket.\n"); + remote_close (remote_desc); + } + + exit (0); +} + Index: gdbfreeplay-i386.c =================================================================== RCS file: gdbfreeplay-i386.c diff -N gdbfreeplay-i386.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdbfreeplay-i386.c 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,401 @@ +/* + * gdbfreeplay-i386.c + * + * Target-dependent component of gdbfreeplay for i386. + */ + +#include +#include +#include + +#include "gdbfreeplay.h" + +/* + * Utility functions + */ + +/* + * ix86_hex_to_unsigned_long + * + * Convert target-order ascii bytes to host unsigned long. + * Returns host unsigned long. + */ + +static unsigned long +ix86_hex_to_unsigned_long (char *hex) +{ + unsigned long ul; + int i; + + for (i = 6; i >= 0; i-=2) + { + ul <<= 8; + ul += hex_to_int (hex[i]) << 4; + ul += hex_to_int (hex[i+1]); + } + return ul; +} + +/* + * nybble_to_char + */ + +static char +nybble_to_char (int nybble) +{ + if (nybble < 10) + return '0' + nybble; + else + return 'a' + (nybble - 10); +} + +/* + * ix86_unsigned_long_to_hex + * + * Convert host unsigned long to target-order ascii string. + * Return char buffer, static, use before calling again. + */ + +static char * +ix86_unsigned_long_to_hex (unsigned long value) +{ + int i; + static char buf[16]; + char *p; + + p = buf; + for (i = 0; i < 4; i++) + { + /* Intel byte order. */ + *p++ = nybble_to_char ((value >> 4) & 0xf); + *p++ = nybble_to_char (value & 0xf); + value >>= 8; + } + *p = '\0'; + return buf; +} + +/* + * target_pc_from_T + * + * Extract the PC value from the gdb protocol 'T' packet. + * Returns PC as host unsigned long long. + */ + +unsigned long long +target_pc_from_T (char *tpacket) +{ + char *p; + + if (tpacket[0] == '$' && tpacket[1] == 'T' && + (p = strstr (tpacket, ";08:")) != NULL) + { + return (unsigned long long) ix86_hex_to_unsigned_long (p + 4); + } + + /* Fail -- just assume no legitimate PC will ever be -1... */ + return (unsigned long long) -1; +} + +/* + * ix86_pc_from_registers + * + * Extract the PC value from a gdb protocol registers file. + * Returns PC as host unsigned long long. + */ + +static unsigned long long +ix86_pc_from_registers (char *regs) +{ + return (unsigned long long) ix86_hex_to_unsigned_long (regs + 64); +} + +/* + * target_pc_from_G + * + * Extract the PC value from the gdb protocol 'G' packet. + * Returns PC as host unsigned long long. + */ + +unsigned long long +target_pc_from_G (char *gpacket) +{ + if (gpacket[0] == '$' && gpacket[1] == 'G') + { + return (unsigned long long) ix86_pc_from_registers (gpacket + 2); + } + + /* Fail -- just assume no legitimate PC will ever be -1... */ + return (unsigned long long) -1; +} + +/* + * expand_rle + * + * Stubs sometimes send us data with RLE compression. + * FIXME this code is generic, move into gdbfreeplay-back.c + * The code for this function lifted from gdb. + */ + +static char * +expand_rle (char *input) +{ + static char *buf = NULL; + static int sizeof_buf; + long bc = 0; + int c, repeat; + + /* Allocate buf on first time thru. */ + if (buf == NULL) + { + sizeof_buf = 4096; + buf = realloc (buf, sizeof_buf); + } + + while (1) + { + c = *input++; + switch (c) { + case '#': /* End of interesting data. We don't care about checksum. */ + case '\0':/* End of string data. */ + buf[bc] = '\0'; + return buf; + break; + case '$': + /* Shouldn't happen, but... */ + fprintf (stderr, "Warning: expand_rle saw '$' in middle of packet.\n"); + default: + if (bc >= sizeof_buf - 1) + { + /* Make some more room in the buffer. */ + sizeof_buf *= 2; + buf = realloc (buf, sizeof_buf); + } + buf[bc++] = c; + continue; + break; + case '*': /* Run length encoding. */ + c = *input++; + repeat = c - ' ' + 3; /* Compute repeat count. */ + + /* The character before '*' is repeated. */ + if (bc + repeat - 1 >= sizeof_buf - 1) + { + /* Make some more room in the buffer. */ + sizeof_buf *= 2; + buf = realloc (buf, sizeof_buf); + } + + memset (&buf[bc], buf[bc - 1], repeat); + bc += repeat; + continue; + break; + } + } +} + +/* + * target_pc_from_g + * + * Extract the PC value from the gdb protocol 'g' packet reply. + * + * Unlike the two above, this function accepts a FILE pointer + * rather than a char pointer, and must read data from the file. + * + * Returns PC as host unsigned long long. + */ + +unsigned long long +target_pc_from_g (char *gpacket) +{ + if (gpacket[0] == 'r' && gpacket[1] == ' ') + { + gpacket += 2; + if (gpacket[0] == '+') + gpacket++; + if (gpacket[0] == '$') + gpacket++; + } + + return (unsigned long long) ix86_pc_from_registers (expand_rle (gpacket)); +} + +/* + * target_compose_T_packet + * + * On targets where DECR_PC_AFTER_BREAK is zero, this is a no-op. + * We just send back the T packet that was sent to us. + * + * On targets like i386, where DECR_PC_AFTER_BREAK is non-zero, + * it gets complicated. We have two pieces of information: + * + * 1) The address of the current instruction, and + * 2) Whether we arrived at the current instruction + * by virtue of a breakpoint -- ie. whether gdb is + * expecting the PC to be off-by-one. + * + * Based on that info, we decide whether the T packet that was + * sent to us is suitable as it is, or else we compose one and + * send it back. + * + * Returns a string T packet, possibly WITHOUT CHECKSUM. + * We leave it to the caller to handle that, if necessary, + * because it is target independent and therefore inappropriate + * to do here. However the caller has to check for it, because + * if we simply return the T packet that we received, it will + * already have a checksum. + */ + +char * +target_compose_T_packet (char *origTpacket, + unsigned long long instruction_pc, + int breakpoint_p) +{ + unsigned long long origTpacket_pc = target_pc_from_T (origTpacket); + static char reply_buf[128]; + char *p; + + /* There are four possibilities. + 1) We got here by stepping, and instruction_pc == origTpacket_pc. + 2) We got here by stepping, and instruction_pc != origTpacket_pc. + 3) We got here by breakpoint, and instruction_pc == origTpacket_pc. + 4) We got here by breakpoint, and instruction_pc != origTpacket_pc. + + Actually, there's one more (not well understood): + 5) instruction_pc and origTpacket_pc bear no relationship to each other. + + In that case, we should bail and let gdb get the original Tpacket. */ + + /* Case #5. */ + if (instruction_pc != origTpacket_pc && + instruction_pc != origTpacket_pc - 1) + return origTpacket; + + /* For #1 and #4, we don't have to do anything, and can return + the original T packet. */ + + /* Case #1. */ + if (!breakpoint_p && (instruction_pc == origTpacket_pc)) + return origTpacket; + + /* Case #4. */ + if (breakpoint_p && (instruction_pc == origTpacket_pc - 1)) + return origTpacket; + + /* That's it for the easy cases. For the other two, we have work to do. + Start by making a copy of the original T packet. */ + + strcpy (reply_buf, origTpacket); + if ((p = strstr (reply_buf, ";08:")) != NULL) + { +#if 0 + /* Snip off the PC value from the original T packet. */ + p[4] = '\0'; + /* Case #2. */ + if (breakpoint_p) + { + /* Compose a T packet using instruction_pc + 1. */ + strcat (p, ix86_unsigned_long_to_hex (instruction_pc + 1)); + } + else + { + /* Compose a T packet using instruction_pc. */ + strcat (p, ix86_unsigned_long_to_hex (instruction_pc)); + } +#else + /* Insert the new PC value in place + (without disturbing whatever follows it). */ + + /* Case #2. */ + if (breakpoint_p) + { + /* Compose a T packet using instruction_pc + 1. */ + memcpy (p + 4, + ix86_unsigned_long_to_hex (instruction_pc + 1), + 8); + } + else + { + /* Compose a T packet using instruction_pc. */ + memcpy (p + 4, + ix86_unsigned_long_to_hex (instruction_pc), + 8); + } +#endif + /* Caller has to recompute checksum. */ + return reply_buf; + } + /* Bail... */ + return origTpacket; +} + +/* + * target_compose_g_packet + * + * Take the registers from the 'T' packet, and compose them into a + * 'g' packet response. Registers for which we have no values will + * be filled in with 'xxxx', in the manner of tracepoints. + * + * Returns: string, g packet reply. + */ + +#define IX86_TARGET_GBYTES 624 +static char gbuffer[IX86_TARGET_GBYTES + 1]; +#define IX86_BOGUS_NUMREGS (IX86_TARGET_GBYTES / 8) + +static unsigned long regval[IX86_BOGUS_NUMREGS]; +static int gotreg[IX86_BOGUS_NUMREGS]; + +char * +target_compose_g_packet (char *tpac) +{ + int i; + int regnum; + int signum; + + /* See which regs we can get from the T packet. Assume none... */ + for (i = 0; i < IX86_BOGUS_NUMREGS; i++) + gotreg[i] = 0; + + /* OK, scan off the prefix -- $T plus signal number. */ + if (*tpac++ == '$' && *tpac++ == 'T') + { + /* We won't actually use signum. */ + signum = (hex_to_int (*tpac++) << 4); + signum += hex_to_int (*tpac++); + while (*tpac) + { + regnum = (hex_to_int (*tpac++) << 4); + regnum += hex_to_int (*tpac++); + if (*tpac++ == ':') + { + gotreg[regnum] = 1; + regval[regnum] = ix86_hex_to_unsigned_long (expand_rle (tpac)); + tpac = strchr (tpac, ';'); + } + else goto gpacket_fail; + + if (tpac && *tpac == ';') + tpac++; + else goto gpacket_fail; + + if (strncmp (tpac, "thread", 6) == 0) + break; + } + + /* Got values, now get to composin'. */ + strcpy (gbuffer, "$"); + for (i = 0; i < IX86_BOGUS_NUMREGS; i++) + if (gotreg[i]) + strcat (gbuffer, ix86_unsigned_long_to_hex (regval[i])); + else + strcat (gbuffer, "xxxxxxxx"); + + /* Return composed g packet reply. + Caller has responsibility of appending checksum. */ + return gbuffer; + } + gpacket_fail: + /* Fail. */ + return NULL; +} Index: gdbfreeplay-mips64.c =================================================================== RCS file: gdbfreeplay-mips64.c diff -N gdbfreeplay-mips64.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdbfreeplay-mips64.c 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,115 @@ +/* + * gdbfreeplay-mips64.c + * + * Target-dependent component of gdbfreeplay for mips64. + */ + +#include +#include +#include + +#include "gdbfreeplay.h" + +/* + * target_pc_from_T + * + * Extract the PC value from the gdb protocol 'T' packet. + * Returns PC as host unsigned long long. + */ + +unsigned long long +target_pc_from_T (char *tpacket) +{ + /* Unimplimented -- make caller fall back to using g packet. */ + return (unsigned long long) -1; +} + +/* + * target_pc_from_G + * + * Extract the PC value from the gdb protocol 'G' packet. + * Returns PC as host unsigned long long. + */ + +unsigned long long +target_pc_from_G (char *gpacket) +{ + char localbuf [24]; + + if (gpacket[0] == '$' && gpacket[1] == 'G') + { + strncpy (localbuf, gpacket + 592, 16); + localbuf[16] = '\0'; + return strtoul (localbuf, NULL, 16); + } + + /* Fail -- just assume no legitimate PC will ever be -1... */ + return (unsigned long long) -1; +} + +/* + * target_pc_from_g + * + * Extract the PC value from the gdb protocol 'g' packet reply. + * + * Unlike the two above, this function accepts a FILE pointer + * rather than a char pointer, and must read data from the file. + * + * Returns PC as host unsigned long long. + */ + +unsigned long long +target_pc_from_g (char *gpacket) +{ + char localbuf [24]; + + if (gpacket[0] == 'r' && gpacket[1] == ' ') + { + gpacket += 2; + if (gpacket[0] == '+') + gpacket++; + if (gpacket[0] == '$') + gpacket++; + } + + strncpy (localbuf, gpacket + 592, 16); + localbuf[16] = '\0'; + return strtoull (localbuf, NULL, 16); +} + + + +/* + * target_compose_T_packet + * + * On targets where DECR_PC_AFTER_BREAK is zero, this is a no-op. + * We just send back the T packet that was sent to us. + * + */ + +char * +target_compose_T_packet (char *origTpacket, + unsigned long long instruction_pc, + int breakpoint_p) +{ + return origTpacket; +} + + + +/* + * target_compose_g_packet + * + * Take the registers from the 'T' packet, and compose them into a + * 'g' packet response. Registers for which we have no values will + * be filled in with 'xxxx', in the manner of tracepoints. + * + * Returns: string, g packet reply. + */ + +char * +target_compose_g_packet (char *tpac) +{ + /* stub */ + return NULL; +} Index: gdbfreeplay.h =================================================================== RCS file: gdbfreeplay.h diff -N gdbfreeplay.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdbfreeplay.h 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,26 @@ +/* + * gdbfreeplay -- interface + */ + +#ifndef GDBFREEPLAY_H +#define GDBFREEPLAY_H + +#include "remote-breakpoint.h" + +extern int verbose; + +extern enum successcode gdbfreeplay_open (char *filename); +extern void gdbfreeplay (int socket_fd); + +extern unsigned long long target_pc_from_T (char *tpacket); +extern unsigned long long target_pc_from_G (char *gpacket); +extern unsigned long long target_pc_from_g (char *gpacket); + +extern char *target_compose_T_packet (char *origTpacket, + unsigned long long pc, + int breakpoint_p); +extern char *target_compose_g_packet (char *origTpacket); + +extern int hex_to_int (int ch); + +#endif Index: remote-breakpoint.c =================================================================== RCS file: remote-breakpoint.c diff -N remote-breakpoint.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ remote-breakpoint.c 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,159 @@ +/* + * remote-breakpoint.c + * + * A breakpoint list for a remote gdb target. + */ + +#include +#include +#include +#include + +#include "remote-breakpoint.h" + +static breakpoint *bplist[5]; + +extern int verbose; + +/* + * insert_breakpoint + * + * returns: FAIL/PASS + */ + +static enum successcode +insert_breakpoint (enum breakpoint_type bptype, + unsigned long long addr, + unsigned long len) +{ + breakpoint *this_bp; + + switch (bptype) { + case ACCESS_BP: + case HARDWARE_BP: + case READ_BP: + case WRITE_BP: + default: + /* Can't do those. */ + return FAIL; + break; + case SOFTWARE_BP: + this_bp = malloc (sizeof (breakpoint)); + this_bp->addr = addr; + this_bp->len = len; + this_bp->next = bplist[bptype]; + bplist[bptype] = this_bp; + return PASS; + } +} + +/* + * unlink_breakpoint + * + * returns: 0 for fail, 1 for success + */ + +static int +unlink_breakpoint (enum breakpoint_type bptype, + unsigned long long addr, + unsigned long len) +{ + breakpoint *this_bp, *tmp; + + switch (bptype) { + case ACCESS_BP: + case HARDWARE_BP: + case READ_BP: + case WRITE_BP: + default: + /* Can't do those. */ + return FAIL; + break; + case SOFTWARE_BP: + /* Special case - list is empty. */ + if (bplist[bptype] == NULL) + return FAIL; + + /* Start from list head. */ + this_bp = bplist[bptype]; + /* Special case -- remove head of list. */ + if (this_bp->addr == addr && + this_bp->len == len) + { + bplist[bptype] = this_bp->next; + return PASS; + } + + /* Scan list. */ + for (; this_bp && this_bp->next; this_bp = this_bp->next) + if (this_bp->next->addr == addr && + this_bp->next->len == len) + { + /* Remove from middle of list. */ + tmp = this_bp->next->next; + free (this_bp->next); + this_bp->next = tmp; + return PASS; + } + + /* Not found. */ + return FAIL; + } +} + + +extern enum successcode +remote_remove_breakpoint (enum breakpoint_type bptype, + unsigned long long addr, + unsigned long len) +{ + if (verbose) + fprintf (stdout, "remote-breakpoint: Remove sw breakpoint type %d\n", + bptype); + if (unlink_breakpoint (bptype, addr, len) == 0) + { + fprintf (stderr, " FAILED!\n"); + return FAIL; + } + return PASS; +} + +extern enum successcode +remote_set_breakpoint (enum breakpoint_type bptype, + unsigned long long addr, + unsigned long len) +{ + if (verbose) + fprintf (stdout, "remote-breakpoint: Set sw breakpoint type %d\n", + bptype); + if (insert_breakpoint (bptype, addr, len) == 0) + { + fprintf (stderr, " FAILED!\n"); + return FAIL; + } + return PASS; +} + +/* + * remote_breakpoint_here_p + * + * Scan the list of breakpoints of type BPTYPE. + * Return PASS if there is one that matches ADDR, else FAIL. + * + * FIXME: do I need to consider the length? + */ + +enum successcode +remote_breakpoint_here_p (enum breakpoint_type bptype, + unsigned long long addr) +{ + breakpoint *bp = bplist[bptype]; + + while (bp != NULL) + { + if (bp->addr == addr) + return PASS; + bp = bp->next; + } + return FAIL; +} Index: remote-breakpoint.h =================================================================== RCS file: remote-breakpoint.h diff -N remote-breakpoint.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ remote-breakpoint.h 11 Sep 2008 00:09:24 -0000 @@ -0,0 +1,45 @@ +/* + * remote-breakpoint -- interface + */ + +#ifndef REMOTE_BREAKPOINT_H +#define REMOTE_BREAKPOINT_H + +typedef struct BREAKPOINT { + unsigned long long addr; + unsigned long len; + struct BREAKPOINT *next; +} breakpoint; + +enum breakpoint_type { + SOFTWARE_BP, + HARDWARE_BP, + WRITE_BP, + READ_BP, + ACCESS_BP +}; + +enum successcode { + FAIL = 0, + PASS = 1 +}; + +enum direction_code { + DIR_FORWARD = 0, + DIR_BACKWARD, + PLAY_O_PACKETS +}; + +extern enum successcode remote_remove_breakpoint (enum breakpoint_type, + unsigned long long, + unsigned long); + +extern enum successcode remote_set_breakpoint (enum breakpoint_type, + unsigned long long, + unsigned long); + +extern enum successcode remote_breakpoint_here_p (enum breakpoint_type, + unsigned long long); + +#endif + --------------070001090108080408040501--