Update copyright year in most headers.
[deliverable/binutils-gdb.git] / gdb / record.c
index 26983953a9a5b0de72e119dfb0109f2cec2d53a2..1f68d0496f55d36f1bc3e90b79a12de66bbe2ec1 100644 (file)
@@ -1,6 +1,6 @@
 /* Process record and replay target for GDB, the GNU debugger.
 
-   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+   Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
+#include "elf-bfd.h"
+#include "gcore.h"
 
 #include <signal.h>
 
+/* This module implements "target record", also known as "process
+   record and replay".  This target sits on top of a "normal" target
+   (a target that "has execution"), and provides a record and replay
+   functionality, including reverse debugging.
+
+   Target record has two modes: recording, and replaying.
+
+   In record mode, we intercept the to_resume and to_wait methods.
+   Whenever gdb resumes the target, we run the target in single step
+   mode, and we build up an execution log in which, for each executed
+   instruction, we record all changes in memory and register state.
+   This is invisible to the user, to whom it just looks like an
+   ordinary debugging session (except for performance degredation).
+
+   In replay mode, instead of actually letting the inferior run as a
+   process, we simulate its execution by playing back the recorded
+   execution log.  For each instruction in the log, we simulate the
+   instruction's side effects by duplicating the changes that it would
+   have made on memory and registers.  */
+
 #define DEFAULT_RECORD_INSN_MAX_NUM    200000
 
 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)
 
-/* These are the core struct of record function.
+#define RECORD_FILE_MAGIC      netorder32(0x20091016)
 
-   An record_entry is a record of the value change of a register
-   ("record_reg") or a part of memory ("record_mem").  And each
-   instruction must has a struct record_entry ("record_end") that points out this
-   is the last struct record_entry of this instruction.
+/* These are the core structs of the process record functionality.
 
-   Each struct record_entry is linked to "record_list" by "prev" and "next". */
+   A record_entry is a record of the value change of a register
+   ("record_reg") or a part of memory ("record_mem").  And each
+   instruction must have a struct record_entry ("record_end") that
+   indicates that this is the last struct record_entry of this
+   instruction.
 
-struct record_reg_entry
-{
-  int num;
-  gdb_byte *val;
-};
+   Each struct record_entry is linked to "record_list" by "prev" and
+   "next" pointers.  */
 
 struct record_mem_entry
 {
@@ -54,7 +78,28 @@ struct record_mem_entry
   /* Set this flag if target memory for this entry
      can no longer be accessed.  */
   int mem_entry_not_accessible;
-  gdb_byte *val;
+  union
+  {
+    gdb_byte *ptr;
+    gdb_byte buf[sizeof (gdb_byte *)];
+  } u;
+};
+
+struct record_reg_entry
+{
+  unsigned short num;
+  unsigned short len;
+  union 
+  {
+    gdb_byte *ptr;
+    gdb_byte buf[2 * sizeof (gdb_byte *)];
+  } u;
+};
+
+struct record_end_entry
+{
+  enum target_signal sigval;
+  ULONGEST insn_num;
 };
 
 enum record_type
@@ -64,6 +109,30 @@ enum record_type
   record_mem
 };
 
+/* This is the data structure that makes up the execution log.
+
+   The execution log consists of a single linked list of entries
+   of type "struct record_entry".  It is doubly linked so that it
+   can be traversed in either direction.
+
+   The start of the list is anchored by a struct called
+   "record_first".  The pointer "record_list" either points to the
+   last entry that was added to the list (in record mode), or to the
+   next entry in the list that will be executed (in replay mode).
+
+   Each list element (struct record_entry), in addition to next and
+   prev pointers, consists of a union of three entry types: mem, reg,
+   and end.  A field called "type" determines which entry type is
+   represented by a given list element.
+
+   Each instruction that is added to the execution log is represented
+   by a variable number of list elements ('entries').  The instruction
+   will have one "reg" entry for each register that is changed by 
+   executing the instruction (including the PC in every case).  It 
+   will also have one "mem" entry for each memory change.  Finally,
+   each instruction will have an "end" entry that separates it from
+   the changes associated with the next instruction.  */
+
 struct record_entry
 {
   struct record_entry *prev;
@@ -75,13 +144,43 @@ struct record_entry
     struct record_reg_entry reg;
     /* mem */
     struct record_mem_entry mem;
+    /* end */
+    struct record_end_entry end;
   } u;
 };
 
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
-/* These list is for execution log.  */
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
+/* The following variables are used for managing the linked list that
+   represents the execution log.
+
+   record_first is the anchor that holds down the beginning of the list.
+
+   record_list serves two functions:
+     1) In record mode, it anchors the end of the list.
+     2) In replay mode, it traverses the list and points to
+        the next instruction that must be emulated.
+
+   record_arch_list_head and record_arch_list_tail are used to manage
+   a separate list, which is used to build up the change elements of
+   the currently executing instruction during record mode.  When this
+   instruction has been completely annotated in the "arch list", it 
+   will be appended to the main execution log.  */
+
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
 static struct record_entry *record_arch_list_head = NULL;
@@ -89,11 +188,17 @@ static struct record_entry *record_arch_list_tail = NULL;
 
 /* 1 ask user. 0 auto delete the last struct record_entry.  */
 static int record_stop_at_limit = 1;
-static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
+/* Maximum allowed number of insns in execution log.  */
+static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
+/* Actual count of insns presently in execution log.  */
 static int record_insn_num = 0;
+/* Count of insns logged so far (may be larger
+   than count of insns presently in execution log).  */
+static ULONGEST record_insn_count;
 
 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;
 
 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -119,89 +224,190 @@ static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *,
                                                   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
                                                   struct bp_target_info *);
+static int (*record_beneath_to_stopped_by_watchpoint) (void);
+static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
+                                                     CORE_ADDR *);
+
+/* Alloc and free functions for record_reg, record_mem, and record_end 
+   entries.  */
+
+/* Alloc a record_reg record entry.  */
+
+static inline struct record_entry *
+record_reg_alloc (struct regcache *regcache, int regnum)
+{
+  struct record_entry *rec;
+  struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+  rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+  rec->type = record_reg;
+  rec->u.reg.num = regnum;
+  rec->u.reg.len = register_size (gdbarch, regnum);
+  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+    rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
+
+  return rec;
+}
+
+/* Free a record_reg record entry.  */
+
+static inline void
+record_reg_release (struct record_entry *rec)
+{
+  gdb_assert (rec->type == record_reg);
+  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+    xfree (rec->u.reg.u.ptr);
+  xfree (rec);
+}
+
+/* Alloc a record_mem record entry.  */
+
+static inline struct record_entry *
+record_mem_alloc (CORE_ADDR addr, int len)
+{
+  struct record_entry *rec;
+
+  rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+  rec->type = record_mem;
+  rec->u.mem.addr = addr;
+  rec->u.mem.len = len;
+  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+    rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
+
+  return rec;
+}
+
+/* Free a record_mem record entry.  */
+
+static inline void
+record_mem_release (struct record_entry *rec)
+{
+  gdb_assert (rec->type == record_mem);
+  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+    xfree (rec->u.mem.u.ptr);
+  xfree (rec);
+}
+
+/* Alloc a record_end record entry.  */
+
+static inline struct record_entry *
+record_end_alloc (void)
+{
+  struct record_entry *rec;
+
+  rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+  rec->type = record_end;
+
+  return rec;
+}
+
+/* Free a record_end record entry.  */
+
+static inline void
+record_end_release (struct record_entry *rec)
+{
+  xfree (rec);
+}
+
+/* Free one record entry, any type.
+   Return entry->type, in case caller wants to know.  */
+
+static inline enum record_type
+record_entry_release (struct record_entry *rec)
+{
+  enum record_type type = rec->type;
+
+  switch (type) {
+  case record_reg:
+    record_reg_release (rec);
+    break;
+  case record_mem:
+    record_mem_release (rec);
+    break;
+  case record_end:
+    record_end_release (rec);
+    break;
+  }
+  return type;
+}
+
+/* Free all record entries in list pointed to by REC.  */
 
 static void
 record_list_release (struct record_entry *rec)
 {
-  struct record_entry *tmp;
-
   if (!rec)
     return;
 
   while (rec->next)
-    {
-      rec = rec->next;
-    }
+    rec = rec->next;
 
   while (rec->prev)
     {
-      tmp = rec;
       rec = rec->prev;
-      if (tmp->type == record_reg)
-       xfree (tmp->u.reg.val);
-      else if (tmp->type == record_mem)
-       xfree (tmp->u.mem.val);
-      xfree (tmp);
+      record_entry_release (rec->next);
     }
 
-  if (rec != &record_first)
-    xfree (rec);
+  if (rec == &record_first)
+    {
+      record_insn_num = 0;
+      record_first.next = NULL;
+    }
+  else
+    record_entry_release (rec);
 }
 
+/* Free all record entries forward of the given list position.  */
+
 static void
-record_list_release_next (void)
+record_list_release_following (struct record_entry *rec)
 {
-  struct record_entry *rec = record_list;
   struct record_entry *tmp = rec->next;
+
   rec->next = NULL;
   while (tmp)
     {
       rec = tmp->next;
-      if (tmp->type == record_end)
-       record_insn_num--;
-      else if (tmp->type == record_reg)
-       xfree (tmp->u.reg.val);
-      else if (tmp->type == record_mem)
-       xfree (tmp->u.mem.val);
-      xfree (tmp);
+      if (record_entry_release (tmp) == record_end)
+       {
+         record_insn_num--;
+         record_insn_count--;
+       }
       tmp = rec;
     }
 }
 
+/* Delete the first instruction from the beginning of the log, to make
+   room for adding a new instruction at the end of the log.
+
+   Note -- this function does not modify record_insn_num.  */
+
 static void
 record_list_release_first (void)
 {
-  struct record_entry *tmp = NULL;
-  enum record_type type;
+  struct record_entry *tmp;
 
   if (!record_first.next)
     return;
 
+  /* Loop until a record_end.  */
   while (1)
     {
-      type = record_first.next->type;
-
-      if (type == record_reg)
-       xfree (record_first.next->u.reg.val);
-      else if (type == record_mem)
-       xfree (record_first.next->u.mem.val);
+      /* Cut record_first.next out of the linked list.  */
       tmp = record_first.next;
       record_first.next = tmp->next;
-      xfree (tmp);
+      tmp->next->prev = &record_first;
+
+      /* tmp is now isolated, and can be deleted.  */
+      if (record_entry_release (tmp) == record_end)
+       break;  /* End loop at first record_end.  */
 
       if (!record_first.next)
        {
          gdb_assert (record_insn_num == 1);
-         break;
+         break;        /* End loop when list is empty.  */
        }
-
-      record_first.next->prev = &record_first;
-
-      if (type == record_end)
-       break;
     }
-
-  record_insn_num--;
 }
 
 /* Add a struct record_entry to record_arch_list.  */
@@ -227,10 +433,32 @@ record_arch_list_add (struct record_entry *rec)
     }
 }
 
+/* Return the value storage location of a record entry.  */
+static inline gdb_byte *
+record_get_loc (struct record_entry *rec)
+{
+  switch (rec->type) {
+  case record_mem:
+    if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+      return rec->u.mem.u.ptr;
+    else
+      return rec->u.mem.u.buf;
+  case record_reg:
+    if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+      return rec->u.reg.u.ptr;
+    else
+      return rec->u.reg.u.buf;
+  case record_end:
+  default:
+    gdb_assert (0);
+    return NULL;
+  }
+}
+
 /* Record the value of a register NUM to record_arch_list.  */
 
 int
-record_arch_list_add_reg (struct regcache *regcache, int num)
+record_arch_list_add_reg (struct regcache *regcache, int regnum)
 {
   struct record_entry *rec;
 
@@ -238,16 +466,11 @@ record_arch_list_add_reg (struct regcache *regcache, int num)
     fprintf_unfiltered (gdb_stdlog,
                        "Process record: add register num = %d to "
                        "record list.\n",
-                       num);
+                       regnum);
 
-  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
-  rec->prev = NULL;
-  rec->next = NULL;
-  rec->type = record_reg;
-  rec->u.reg.num = num;
+  rec = record_reg_alloc (regcache, regnum);
 
-  regcache_raw_read (regcache, num, rec->u.reg.val);
+  regcache_raw_read (regcache, regnum, record_get_loc (rec));
 
   record_arch_list_add (rec);
 
@@ -268,27 +491,19 @@ record_arch_list_add_mem (CORE_ADDR addr, int len)
                        "record list.\n",
                        paddress (target_gdbarch, addr), len);
 
-  if (!addr)
+  if (!addr)   /* FIXME: Why?  Some arch must permit it... */
     return 0;
 
-  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->u.mem.val = (gdb_byte *) xmalloc (len);
-  rec->prev = NULL;
-  rec->next = NULL;
-  rec->type = record_mem;
-  rec->u.mem.addr = addr;
-  rec->u.mem.len = len;
-  rec->u.mem.mem_entry_not_accessible = 0;
+  rec = record_mem_alloc (addr, len);
 
-  if (target_read_memory (addr, rec->u.mem.val, len))
+  if (target_read_memory (addr, record_get_loc (rec), len))
     {
       if (record_debug)
        fprintf_unfiltered (gdb_stdlog,
                            "Process record: error reading memory at "
                            "addr = %s len = %d.\n",
                            paddress (target_gdbarch, addr), len);
-      xfree (rec->u.mem.val);
-      xfree (rec);
+      record_mem_release (rec);
       return -1;
     }
 
@@ -308,10 +523,9 @@ record_arch_list_add_end (void)
     fprintf_unfiltered (gdb_stdlog,
                        "Process record: add end to arch list.\n");
 
-  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->prev = NULL;
-  rec->next = NULL;
-  rec->type = record_end;
+  rec = record_end_alloc ();
+  rec->u.end.sigval = TARGET_SIGNAL_0;
+  rec->u.end.insn_num = ++record_insn_count;
 
   record_arch_list_add (rec);
 
@@ -340,30 +554,30 @@ record_check_insn_num (int set_terminal)
              if (q)
                record_stop_at_limit = 0;
              else
-               error (_("Process record: inferior program stopped."));
+               error (_("Process record: stopped by user."));
            }
        }
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
-record_message (void *args)
+record_message (struct regcache *regcache, enum target_signal signal)
 {
   int ret;
-  struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct gdbarch *gdbarch = get_regcache_arch (regcache);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -371,9 +585,44 @@ record_message (void *args)
   /* Check record_insn_num.  */
   record_check_insn_num (1);
 
-  ret = gdbarch_process_record (get_regcache_arch (regcache),
-                               regcache,
-                               regcache_read_pc (regcache));
+  /* If gdb sends a signal value to target_resume,
+     save it in the 'end' field of the previous instruction.
+
+     Maybe process record should record what really happened,
+     rather than what gdb pretends has happened.
+
+     So if Linux delivered the signal to the child process during
+     the record mode, we will record it and deliver it again in
+     the replay mode.
+
+     If user says "ignore this signal" during the record mode, then
+     it will be ignored again during the replay mode (no matter if
+     the user says something different, like "deliver this signal"
+     during the replay mode).
+
+     User should understand that nothing he does during the replay
+     mode will change the behavior of the child.  If he tries,
+     then that is a user error.
+
+     But we should still deliver the signal to gdb during the replay,
+     if we delivered it during the recording.  Therefore we should
+     record the signal during record_wait, not record_resume.  */
+  if (record_list != &record_first)    /* FIXME better way to check */
+    {
+      gdb_assert (record_list->type == record_end);
+      record_list->u.end.sigval = signal;
+    }
+
+  if (signal == TARGET_SIGNAL_0
+      || !gdbarch_process_record_signal_p (gdbarch))
+    ret = gdbarch_process_record (gdbarch,
+                                 regcache,
+                                 regcache_read_pc (regcache));
+  else
+    ret = gdbarch_process_record_signal (gdbarch,
+                                        regcache,
+                                        signal);
+
   if (ret > 0)
     error (_("Process record: inferior program stopped."));
   if (ret < 0)
@@ -393,10 +642,29 @@ record_message (void *args)
   return 1;
 }
 
+struct record_message_args {
+  struct regcache *regcache;
+  enum target_signal signal;
+};
+
+static int
+record_message_wrapper (void *args)
+{
+  struct record_message_args *record_args = args;
+
+  return record_message (record_args->regcache, record_args->signal);
+}
+
 static int
-do_record_message (struct regcache *regcache)
+record_message_wrapper_safe (struct regcache *regcache,
+                             enum target_signal signal)
 {
-  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
+  struct record_message_args args;
+
+  args.regcache = regcache;
+  args.signal = signal;
+
+  return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL);
 }
 
 /* Set to 1 if record_store_registers and record_xfer_partial
@@ -416,8 +684,159 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+/* Flag set to TRUE for target_stopped_by_watchpoint.  */
+static int record_hw_watchpoint = 0;
+
+/* Execute one instruction from the record log.  Each instruction in
+   the log will be represented by an arbitrary sequence of register
+   entries and memory entries, followed by an 'end' entry.  */
+
+static inline void
+record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
+                 struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, 
+                              record_get_loc (entry));
+        memcpy (record_get_loc (entry), reg, entry->u.reg.len);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+       /* Nothing to do if the entry is flagged not_accessible.  */
+        if (!entry->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  entry->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                entry->u.mem.mem_entry_not_accessible = 1;
+                if (record_debug)
+                  warning ("Process record: error reading memory at "
+                          "addr = %s len = %d.",
+                           paddress (gdbarch, entry->u.mem.addr),
+                           entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, 
+                                        record_get_loc (entry),
+                                        entry->u.mem.len))
+                  {
+                    entry->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning ("Process record: error writing memory at "
+                              "addr = %s len = %d.",
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                 {
+                   memcpy (record_get_loc (entry), mem, entry->u.mem.len);
+
+                   /* We've changed memory --- check if a hardware
+                      watchpoint should trap.  Note that this
+                      presently assumes the target beneath supports
+                      continuable watchpoints.  On non-continuable
+                      watchpoints target, we'll want to check this
+                      _before_ actually doing the memory change, and
+                      not doing the change at all if the watchpoint
+                      traps.  */
+                   if (hardware_watchpoint_inserted_in_range
+                       (get_regcache_aspace (regcache),
+                        entry->u.mem.addr, entry->u.mem.len))
+                     record_hw_watchpoint = 1;
+                 }
+              }
+          }
+      }
+      break;
+    }
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                             enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                             struct target_waitstatus *,
+                             int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                      struct regcache *,
+                                      int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                      enum target_object object,
+                                      const char *annex,
+                                      gdb_byte *readbuf,
+                                      const gdb_byte *writebuf,
+                                      ULONGEST offset,
+                                      LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                       struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                       struct bp_target_info *);
+static int (*tmp_to_stopped_by_watchpoint) (void);
+static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+
+static void record_restore (void);
+
+/* Open the process record target.  */
+
 static void
-record_open (char *name, int from_tty)
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                         record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+            bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+  record_restore ();
+}
+
+/* "to_open" target method for 'live' processes.  */
+
+static void
+record_open_1 (char *name, int from_tty)
 {
   struct target_ops *t;
 
@@ -438,110 +857,161 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
             "record function."));
 
+  if (!tmp_to_resume)
+    error (_("Could not find 'to_resume' method on the target stack."));
+  if (!tmp_to_wait)
+    error (_("Could not find 'to_wait' method on the target stack."));
+  if (!tmp_to_store_registers)
+    error (_("Could not find 'to_store_registers' method on the target stack."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Could not find 'to_insert_breakpoint' method on the target stack."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Could not find 'to_remove_breakpoint' method on the target stack."));
+
+  push_target (&record_ops);
+}
+
+/* "to_open" target method.  Open the process record target.  */
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
-    {
-      if (!nquery
-         (_("Process record target already running, do you want to delete "
-            "the old record log?")))
-       return;
-    }
-
-  /*Reset the beneath function pointers.  */
-  record_beneath_to_resume = NULL;
-  record_beneath_to_wait = NULL;
-  record_beneath_to_store_registers = NULL;
-  record_beneath_to_xfer_partial = NULL;
-  record_beneath_to_insert_breakpoint = NULL;
-  record_beneath_to_remove_breakpoint = NULL;
+    error (_("Process record target already running.  Use \"record stop\" to "
+             "stop record target first."));
+
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-         record_beneath_to_resume = t->to_resume;
-         record_beneath_to_resume_ops = t;
+         tmp_to_resume = t->to_resume;
+         tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-         record_beneath_to_wait = t->to_wait;
-         record_beneath_to_wait_ops = t;
+         tmp_to_wait = t->to_wait;
+         tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-         record_beneath_to_store_registers = t->to_store_registers;
-         record_beneath_to_store_registers_ops = t;
+         tmp_to_store_registers = t->to_store_registers;
+         tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-         record_beneath_to_xfer_partial = t->to_xfer_partial;
-         record_beneath_to_xfer_partial_ops = t;
+         tmp_to_xfer_partial = t->to_xfer_partial;
+         tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-       record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-       record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+       tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+       tmp_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_stopped_by_watchpoint)
+       tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint;
+      if (!tmp_to_stopped_data_address)
+       tmp_to_stopped_data_address = t->to_stopped_data_address;
     }
-  if (!record_beneath_to_resume)
-    error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
-    error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
-    error (_("Process record can't get to_store_registers."));
-  if (!record_beneath_to_xfer_partial)
-    error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));
-
-  push_target (&record_ops);
+  if (!tmp_to_xfer_partial)
+    error (_("Could not find 'to_xfer_partial' method on the target stack."));
 
   /* Reset */
   record_insn_num = 0;
+  record_insn_count = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+  record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint;
+  record_beneath_to_stopped_data_address = tmp_to_stopped_data_address;
+
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);
 }
 
+/* "to_close" target method.  Close the process record target.  */
+
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+       {
+         xfree (record_core_buf_list);
+         record_core_buf_list = entry;
+       }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
-static enum target_signal record_resume_siggnal;
-static int record_resume_error;
+
+/* "to_resume" target method.  Resume the process record target.  */
 
 static void
 record_resume (struct target_ops *ops, ptid_t ptid, int step,
-               enum target_signal siggnal)
+               enum target_signal signal)
 {
   record_resume_step = step;
-  record_resume_siggnal = siggnal;
 
   if (!RECORD_IS_REPLAY)
     {
-      if (do_record_message (get_current_regcache ()))
-        {
-          record_resume_error = 0;
-        }
-      else
-        {
-          record_resume_error = 1;
-          return;
-        }
+      record_message (get_current_regcache (), signal);
       record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
-                                siggnal);
+                                signal);
     }
 }
 
 static int record_get_sig = 0;
 
+/* SIGINT signal handler, registered by "to_wait" method.  */
+
 static void
 record_sig_handler (int signo)
 {
@@ -568,8 +1038,18 @@ record_wait_cleanups (void *ignore)
     record_list = record_list->prev;
 }
 
-/* In replay mode, this function examines the recorded log and
-   determines where to stop.  */
+/* "to_wait" target method for process record target.
+
+   In record mode, the target is always run in singlestep mode
+   (even when gdb says to continue).  The to_wait method intercepts
+   the stop events and determines which ones are to be passed on to
+   gdb.  Most stop events are just singlestep events that gdb is not
+   to know about, so the to_wait method just records them and keeps
+   singlestepping.
+
+   In replay mode, this function emulates the recorded execution log, 
+   one instruction at a time (forward or backward), and determines 
+   where to stop.  */
 
 static ptid_t
 record_wait (struct target_ops *ops,
@@ -584,16 +1064,8 @@ record_wait (struct target_ops *ops,
                        "record_resume_step = %d\n",
                        record_resume_step);
 
-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
-      if (record_resume_error)
-       {
-         /* If record_resume get error, return directly.  */
-         status->kind = TARGET_WAITKIND_STOPPED;
-         status->value.sig = TARGET_SIGNAL_ABRT;
-         return inferior_ptid;
-       }
-
       if (record_resume_step)
        {
          /* This is a single step.  */
@@ -611,34 +1083,55 @@ record_wait (struct target_ops *ops,
              ret = record_beneath_to_wait (record_beneath_to_wait_ops,
                                            ptid, status, options);
 
+             /* Is this a SIGTRAP?  */
              if (status->kind == TARGET_WAITKIND_STOPPED
                  && status->value.sig == TARGET_SIGNAL_TRAP)
                {
-                 /* Check if there is a breakpoint.  */
+                 struct regcache *regcache;
+                 struct address_space *aspace;
+
+                 /* Yes -- this is likely our single-step finishing,
+                    but check if there's any reason the core would be
+                    interested in the event.  */
+
                  registers_changed ();
-                 tmp_pc = regcache_read_pc (get_current_regcache ());
-                 if (breakpoint_inserted_here_p (tmp_pc))
+                 regcache = get_current_regcache ();
+                 tmp_pc = regcache_read_pc (regcache);
+                 aspace = get_regcache_aspace (regcache);
+
+                 if (target_stopped_by_watchpoint ())
+                   {
+                     /* Always interested in watchpoints.  */
+                   }
+                 else if (breakpoint_inserted_here_p (aspace, tmp_pc))
                    {
-                     /* There is a breakpoint.  */
-                     CORE_ADDR decr_pc_after_break =
-                       gdbarch_decr_pc_after_break
-                       (get_regcache_arch (get_current_regcache ()));
-                     if (decr_pc_after_break)
+                     /* There is a breakpoint here.  Let the core
+                        handle it.  */
+                     if (software_breakpoint_inserted_here_p (aspace, tmp_pc))
                        {
-                         regcache_write_pc (get_thread_regcache (ret),
-                                            tmp_pc + decr_pc_after_break);
+                         struct gdbarch *gdbarch = get_regcache_arch (regcache);
+                         CORE_ADDR decr_pc_after_break
+                           = gdbarch_decr_pc_after_break (gdbarch);
+                         if (decr_pc_after_break)
+                           regcache_write_pc (regcache,
+                                              tmp_pc + decr_pc_after_break);
                        }
                    }
                  else
                    {
-                     /* There is not a breakpoint.  */
-                     if (!do_record_message (get_current_regcache ()))
-                       {
-                          break;
-                       }
+                     /* This must be a single-step trap.  Record the
+                        insn and issue another step.  */
+                     if (!record_message_wrapper_safe (regcache,
+                                                        TARGET_SIGNAL_0))
+                       {
+                           status->kind = TARGET_WAITKIND_STOPPED;
+                           status->value.sig = TARGET_SIGNAL_0;
+                           break;
+                       }
+
                      record_beneath_to_resume (record_beneath_to_resume_ops,
                                                ptid, 1,
-                                               record_resume_siggnal);
+                                               TARGET_SIGNAL_0);
                      continue;
                    }
                }
@@ -654,28 +1147,33 @@ record_wait (struct target_ops *ops,
     {
       struct regcache *regcache = get_current_regcache ();
       struct gdbarch *gdbarch = get_regcache_arch (regcache);
+      struct address_space *aspace = get_regcache_aspace (regcache);
       int continue_flag = 1;
       int first_record_end = 1;
       struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
       CORE_ADDR tmp_pc;
 
+      record_hw_watchpoint = 0;
       status->kind = TARGET_WAITKIND_STOPPED;
 
       /* Check breakpoint when forward execute.  */
       if (execution_direction == EXEC_FORWARD)
        {
          tmp_pc = regcache_read_pc (regcache);
-         if (breakpoint_inserted_here_p (tmp_pc))
+         if (breakpoint_inserted_here_p (aspace, tmp_pc))
            {
+             int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
+
              if (record_debug)
                fprintf_unfiltered (gdb_stdlog,
                                    "Process record: break at %s.\n",
                                    paddress (gdbarch, tmp_pc));
-             if (gdbarch_decr_pc_after_break (gdbarch)
-                 && !record_resume_step)
+
+             if (decr_pc_after_break
+                 && !record_resume_step
+                 && software_breakpoint_inserted_here_p (aspace, tmp_pc))
                regcache_write_pc (regcache,
-                                  tmp_pc +
-                                  gdbarch_decr_pc_after_break (gdbarch));
+                                  tmp_pc + decr_pc_after_break);
              goto replay_out;
            }
        }
@@ -712,76 +1210,9 @@ record_wait (struct target_ops *ops,
              break;
            }
 
-         /* Set ptid, register and memory according to record_list.  */
-         if (record_list->type == record_reg)
-           {
-             /* reg */
-             gdb_byte reg[MAX_REGISTER_SIZE];
-             if (record_debug > 1)
-               fprintf_unfiltered (gdb_stdlog,
-                                   "Process record: record_reg %s to "
-                                   "inferior num = %d.\n",
-                                   host_address_to_string (record_list),
-                                   record_list->u.reg.num);
-             regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-             regcache_cooked_write (regcache, record_list->u.reg.num,
-                                    record_list->u.reg.val);
-             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-           }
-         else if (record_list->type == record_mem)
-           {
-             /* mem */
-             /* Nothing to do if the entry is flagged not_accessible.  */
-             if (!record_list->u.mem.mem_entry_not_accessible)
-               {
-                 gdb_byte *mem = alloca (record_list->u.mem.len);
-                 if (record_debug > 1)
-                   fprintf_unfiltered (gdb_stdlog,
-                                       "Process record: record_mem %s to "
-                                       "inferior addr = %s len = %d.\n",
-                                       host_address_to_string (record_list),
-                                       paddress (gdbarch,
-                                                 record_list->u.mem.addr),
-                                       record_list->u.mem.len);
-
-                 if (target_read_memory (record_list->u.mem.addr, mem,
-                                         record_list->u.mem.len))
-                   {
-                     if (execution_direction != EXEC_REVERSE)
-                       error (_("Process record: error reading memory at "
-                                "addr = %s len = %d."),
-                              paddress (gdbarch, record_list->u.mem.addr),
-                              record_list->u.mem.len);
-                     else
-                       /* Read failed -- 
-                          flag entry as not_accessible.  */
-                       record_list->u.mem.mem_entry_not_accessible = 1;
-                   }
-                 else
-                   {
-                     if (target_write_memory (record_list->u.mem.addr,
-                                              record_list->u.mem.val,
-                                              record_list->u.mem.len))
-                       {
-                         if (execution_direction != EXEC_REVERSE)
-                           error (_("Process record: error writing memory at "
-                                    "addr = %s len = %d."),
-                                  paddress (gdbarch, record_list->u.mem.addr),
-                                  record_list->u.mem.len);
-                         else
-                           /* Write failed -- 
-                              flag entry as not_accessible.  */
-                           record_list->u.mem.mem_entry_not_accessible = 1;
-                       }
-                     else
-                       {
-                         memcpy (record_list->u.mem.val, mem,
-                                 record_list->u.mem.len);
-                       }
-                   }
-               }
-           }
-         else
+          record_exec_insn (regcache, gdbarch, record_list);
+
+         if (record_list->type == record_end)
            {
              if (record_debug > 1)
                fprintf_unfiltered (gdb_stdlog,
@@ -812,21 +1243,37 @@ record_wait (struct target_ops *ops,
 
                  /* check breakpoint */
                  tmp_pc = regcache_read_pc (regcache);
-                 if (breakpoint_inserted_here_p (tmp_pc))
+                 if (breakpoint_inserted_here_p (aspace, tmp_pc))
                    {
+                     int decr_pc_after_break
+                       = gdbarch_decr_pc_after_break (gdbarch);
+
                      if (record_debug)
                        fprintf_unfiltered (gdb_stdlog,
                                            "Process record: break "
                                            "at %s.\n",
                                            paddress (gdbarch, tmp_pc));
-                     if (gdbarch_decr_pc_after_break (gdbarch)
+                     if (decr_pc_after_break
                          && execution_direction == EXEC_FORWARD
-                         && !record_resume_step)
+                         && !record_resume_step
+                         && software_breakpoint_inserted_here_p (aspace,
+                                                                 tmp_pc))
                        regcache_write_pc (regcache,
-                                          tmp_pc +
-                                          gdbarch_decr_pc_after_break (gdbarch));
+                                          tmp_pc + decr_pc_after_break);
+                     continue_flag = 0;
+                   }
+
+                 if (record_hw_watchpoint)
+                   {
+                     if (record_debug)
+                       fprintf_unfiltered (gdb_stdlog, "\
+Process record: hit hw watchpoint.\n");
                      continue_flag = 0;
                    }
+                 /* Check target signal */
+                 if (record_list->u.end.sigval != TARGET_SIGNAL_0)
+                   /* FIXME: better way to check */
+                   continue_flag = 0;
                }
            }
 
@@ -851,6 +1298,9 @@ record_wait (struct target_ops *ops,
 replay_out:
       if (record_get_sig)
        status->value.sig = TARGET_SIGNAL_INT;
+      else if (record_list->u.end.sigval != TARGET_SIGNAL_0)
+       /* FIXME: better way to check */
+       status->value.sig = record_list->u.end.sigval;
       else
        status->value.sig = TARGET_SIGNAL_TRAP;
 
@@ -861,6 +1311,26 @@ replay_out:
   return inferior_ptid;
 }
 
+static int
+record_stopped_by_watchpoint (void)
+{
+  if (RECORD_IS_REPLAY)
+    return record_hw_watchpoint;
+  else
+    return record_beneath_to_stopped_by_watchpoint ();
+}
+
+static int
+record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
+{
+  if (RECORD_IS_REPLAY)
+    return 0;
+  else
+    return record_beneath_to_stopped_data_address (ops, addr_p);
+}
+
+/* "to_disconnect" method for process record target.  */
+
 static void
 record_disconnect (struct target_ops *target, char *args, int from_tty)
 {
@@ -871,6 +1341,8 @@ record_disconnect (struct target_ops *target, char *args, int from_tty)
   target_disconnect (args, from_tty);
 }
 
+/* "to_detach" method for process record target.  */
+
 static void
 record_detach (struct target_ops *ops, char *args, int from_tty)
 {
@@ -881,6 +1353,8 @@ record_detach (struct target_ops *ops, char *args, int from_tty)
   target_detach (args, from_tty);
 }
 
+/* "to_mourn_inferior" method for process record target.  */
+
 static void
 record_mourn_inferior (struct target_ops *ops)
 {
@@ -950,6 +1424,8 @@ record_registers_change (struct regcache *regcache, int regnum)
     record_insn_num++;
 }
 
+/* "to_store_registers" method for process record target.  */
+
 static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
@@ -963,15 +1439,15 @@ record_store_registers (struct target_ops *ops, struct regcache *regcache,
          /* Let user choose if he wants to write register or not.  */
          if (regno < 0)
            n =
-             nquery (_("Because GDB is in replay mode, changing the "
-                       "value of a register will make the execution "
-                       "log unusable from this point onward.  "
-                       "Change all registers?"));
+             query (_("Because GDB is in replay mode, changing the "
+                      "value of a register will make the execution "
+                      "log unusable from this point onward.  "
+                      "Change all registers?"));
          else
            n =
-             nquery (_("Because GDB is in replay mode, changing the value "
-                       "of a register will make the execution log unusable "
-                       "from this point onward.  Change register %s?"),
+             query (_("Because GDB is in replay mode, changing the value "
+                      "of a register will make the execution log unusable "
+                      "from this point onward.  Change register %s?"),
                      gdbarch_register_name (get_regcache_arch (regcache),
                                               regno));
 
@@ -994,7 +1470,7 @@ record_store_registers (struct target_ops *ops, struct regcache *regcache,
            }
 
          /* Destroy the record from here forward.  */
-         record_list_release_next ();
+         record_list_release_following (record_list);
        }
 
       record_registers_change (regcache, regno);
@@ -1003,7 +1479,7 @@ record_store_registers (struct target_ops *ops, struct regcache *regcache,
                                      regcache, regno);
 }
 
-/* Behavior is conditional on RECORD_IS_REPLAY.
+/* "to_xfer_partial" method.  Behavior is conditional on RECORD_IS_REPLAY.
    In replay mode, we cannot write memory unles we are willing to
    invalidate the record/replay log from this point forward.  */
 
@@ -1019,14 +1495,14 @@ record_xfer_partial (struct target_ops *ops, enum target_object object,
       if (RECORD_IS_REPLAY)
        {
          /* Let user choose if he wants to write memory or not.  */
-         if (!nquery (_("Because GDB is in replay mode, writing to memory "
-                        "will make the execution log unusable from this "
-                        "point onward.  Write memory at address %s?"),
+         if (!query (_("Because GDB is in replay mode, writing to memory "
+                       "will make the execution log unusable from this "
+                       "point onward.  Write memory at address %s?"),
                       paddress (target_gdbarch, offset)))
            error (_("Process record canceled the operation."));
 
          /* Destroy the record from here forward.  */
-         record_list_release_next ();
+         record_list_release_following (record_list);
        }
 
       /* Check record_insn_num */
@@ -1040,8 +1516,8 @@ record_xfer_partial (struct target_ops *ops, enum target_object object,
          record_list_release (record_arch_list_tail);
          if (record_debug)
            fprintf_unfiltered (gdb_stdlog,
-                               _("Process record: failed to record "
-                                 "execution log."));
+                               "Process record: failed to record "
+                               "execution log.");
          return -1;
        }
       if (record_arch_list_add_end ())
@@ -1049,8 +1525,8 @@ record_xfer_partial (struct target_ops *ops, enum target_object object,
          record_list_release (record_arch_list_tail);
          if (record_debug)
            fprintf_unfiltered (gdb_stdlog,
-                               _("Process record: failed to record "
-                                 "execution log."));
+                               "Process record: failed to record "
+                               "execution log.");
          return -1;
        }
       record_list->next = record_arch_list_head;
@@ -1089,6 +1565,8 @@ record_insert_breakpoint (struct gdbarch *gdbarch,
   return 0;
 }
 
+/* "to_remove_breakpoint" method for process record target.  */
+
 static int
 record_remove_breakpoint (struct gdbarch *gdbarch,
                          struct bp_target_info *bp_tgt)
@@ -1106,16 +1584,69 @@ record_remove_breakpoint (struct gdbarch *gdbarch,
   return 0;
 }
 
+/* "to_can_execute_reverse" method for process record target.  */
+
 static int
 record_can_execute_reverse (void)
 {
   return 1;
 }
 
-static void
-init_record_ops (void)
+/* "to_get_bookmark" method for process record and prec over core.  */
+
+static gdb_byte *
+record_get_bookmark (char *args, int from_tty)
 {
-  record_ops.to_shortname = "record";
+  gdb_byte *ret = NULL;
+
+  /* Return stringified form of instruction count.  */
+  if (record_list && record_list->type == record_end)
+    ret = xstrdup (pulongest (record_list->u.end.insn_num));
+
+  if (record_debug)
+    {
+      if (ret)
+       fprintf_unfiltered (gdb_stdlog,
+                           "record_get_bookmark returns %s\n", ret);
+      else
+       fprintf_unfiltered (gdb_stdlog,
+                           "record_get_bookmark returns NULL\n");
+    }
+  return ret;
+}
+
+/* The implementation of the command "record goto".  */
+static void cmd_record_goto (char *, int);
+
+/* "to_goto_bookmark" method for process record and prec over core.  */
+
+static void
+record_goto_bookmark (gdb_byte *bookmark, int from_tty)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                       "record_goto_bookmark receives %s\n", bookmark);
+
+  if (bookmark[0] == '\'' || bookmark[0] == '\"')
+    {
+      if (bookmark[strlen (bookmark) - 1] != bookmark[0])
+       error (_("Unbalanced quotes: %s"), bookmark);
+
+      /* Strip trailing quote.  */
+      bookmark[strlen (bookmark) - 1] = '\0';
+      /* Strip leading quote.  */
+      bookmark++;
+      /* Pass along to cmd_record_goto.  */
+    }
+
+  cmd_record_goto ((char *) bookmark, from_tty);
+  return;
+}
+
+static void
+init_record_ops (void)
+{
+  record_ops.to_shortname = "record";
   record_ops.to_longname = "Process record and replay target";
   record_ops.to_doc =
     "Log program while executing and replay execution from log.";
@@ -1132,11 +1663,228 @@ init_record_ops (void)
   record_ops.to_xfer_partial = record_xfer_partial;
   record_ops.to_insert_breakpoint = record_insert_breakpoint;
   record_ops.to_remove_breakpoint = record_remove_breakpoint;
+  record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
+  record_ops.to_stopped_data_address = record_stopped_data_address;
   record_ops.to_can_execute_reverse = record_can_execute_reverse;
   record_ops.to_stratum = record_stratum;
+  /* Add bookmark target methods.  */
+  record_ops.to_get_bookmark = record_get_bookmark;
+  record_ops.to_goto_bookmark = record_goto_bookmark;
   record_ops.to_magic = OPS_MAGIC;
 }
 
+/* "to_resume" method for prec over corefile.  */
+
+static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal signal)
+{
+  record_resume_step = step;
+}
+
+/* "to_kill" method for prec over corefile.  */
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+/* "to_fetch_registers" method for prec over corefile.  */
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+/* "to_prepare_to_store" method for prec over corefile.  */
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+/* "to_store_registers" method for prec over corefile.  */
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+/* "to_xfer_partial" method for prec over corefile.  */
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+                         const char *annex, gdb_byte *readbuf,
+                         const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+                  ULONGEST sec_offset;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   sec_offset = offset - p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + sec_offset, writebuf,
+                               (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + sec_offset,
+                              (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+/* "to_insert_breakpoint" method for prec over corefile.  */
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+                              struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+/* "to_remove_breakpoint" method for prec over corefile.  */
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+                              struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+/* "to_has_execution" method for prec over corefile.  */
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
+  record_core_ops.to_stopped_data_address = record_stopped_data_address;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  /* Add bookmark target methods.  */
+  record_core_ops.to_get_bookmark = record_get_bookmark;
+  record_core_ops.to_goto_bookmark = record_goto_bookmark;
+  record_core_ops.to_magic = OPS_MAGIC;
+}
+
+/* Implement "show record debug" command.  */
+
 static void
 show_record_debug (struct ui_file *file, int from_tty,
                   struct cmd_list_element *c, const char *value)
@@ -1166,7 +1914,7 @@ cmd_record_delete (char *args, int from_tty)
          if (!from_tty || query (_("Delete the log from this point forward "
                                    "and begin to record the running message "
                                    "at current PC?")))
-           record_list_release_next ();
+           record_list_release_following (record_list);
        }
       else
          printf_unfiltered (_("Already at end of record list.\n"));
@@ -1176,16 +1924,16 @@ cmd_record_delete (char *args, int from_tty)
     printf_unfiltered (_("Process record is not started.\n"));
 }
 
-/* Implement the "stoprecord" command.  */
+/* Implement the "stoprecord" or "record stop" command.  */
 
 static void
 cmd_record_stop (char *args, int from_tty)
 {
   if (current_target.to_stratum == record_stratum)
     {
-      if (!record_list || !from_tty || query (_("Delete recorded log and "
-                                               "stop recording?")))
-       unpush_target (&record_ops);
+      unpush_target (&record_ops);
+      printf_unfiltered (_("Process record is stopped and all execution "
+                           "logs are deleted.\n"));
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1198,25 +1946,15 @@ set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c)
 {
   if (record_insn_num > record_insn_max_num && record_insn_max_num)
     {
-      printf_unfiltered (_("Record instructions number is bigger than "
-                          "record instructions max number.  Auto delete "
-                          "the first ones?\n"));
-
+      /* Count down record_insn_num while releasing records from list.  */
       while (record_insn_num > record_insn_max_num)
-       record_list_release_first ();
+       {
+         record_list_release_first ();
+         record_insn_num--;
+       }
     }
 }
 
-/* Print the current index into the record log (number of insns recorded
-   so far).  */
-
-static void
-show_record_insn_number (char *ignore, int from_tty)
-{
-  printf_unfiltered (_("Record instruction number is %d.\n"),
-                    record_insn_num);
-}
-
 static struct cmd_list_element *record_cmdlist, *set_record_cmdlist,
                               *show_record_cmdlist, *info_record_cmdlist;
 
@@ -1234,15 +1972,670 @@ show_record_command (char *args, int from_tty)
   cmd_show_list (show_record_cmdlist, from_tty, "");
 }
 
+/* Display some statistics about the execution log.  */
+
 static void
 info_record_command (char *args, int from_tty)
 {
-  cmd_show_list (info_record_cmdlist, from_tty, "");
+  struct record_entry *p;
+
+  if (current_target.to_stratum == record_stratum)
+    {
+      if (RECORD_IS_REPLAY)
+       printf_filtered (_("Replay mode:\n"));
+      else
+       printf_filtered (_("Record mode:\n"));
+
+      /* Find entry for first actual instruction in the log.  */
+      for (p = record_first.next;
+          p != NULL && p->type != record_end;
+          p = p->next)
+       ;
+
+      /* Do we have a log at all?  */
+      if (p != NULL && p->type == record_end)
+       {
+         /* Display instruction number for first instruction in the log.  */
+         printf_filtered (_("Lowest recorded instruction number is %s.\n"),
+                          pulongest (p->u.end.insn_num));
+
+         /* If in replay mode, display where we are in the log.  */
+         if (RECORD_IS_REPLAY)
+           printf_filtered (_("Current instruction number is %s.\n"),
+                            pulongest (record_list->u.end.insn_num));
+
+         /* Display instruction number for last instruction in the log.  */
+         printf_filtered (_("Highest recorded instruction number is %s.\n"), 
+                          pulongest (record_insn_count));
+
+         /* Display log count.  */
+         printf_filtered (_("Log contains %d instructions.\n"), 
+                          record_insn_num);
+       }
+      else
+       {
+         printf_filtered (_("No instructions have been logged.\n"));
+       }
+    }
+  else
+    {
+      printf_filtered (_("target record is not active.\n"));
+    }
+
+  /* Display max log size.  */
+  printf_filtered (_("Max logged instructions is %d.\n"),
+                  record_insn_max_num);
+}
+
+/* Record log save-file format
+   Version 1 (never released)
+
+   Header:
+     4 bytes: magic number htonl(0x20090829).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end, see enum record_type).
+     record_reg:
+       1 byte:  record type (record_reg, see enum record_type).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem, see enum record_type).
+       8 bytes: memory length (network byte order).
+       8 bytes: memory address (network byte order).
+       n bytes: memory value (n == memory length).
+
+   Version 2
+     4 bytes: magic number netorder32(0x20091016).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end, see enum record_type).
+       4 bytes: signal
+       4 bytes: instruction count
+     record_reg:
+       1 byte:  record type (record_reg, see enum record_type).
+       4 bytes: register id (network byte order).
+       n bytes: register value (n == actual register size).
+                (eg. 4 bytes for x86 general registers).
+     record_mem:
+       1 byte:  record type (record_mem, see enum record_type).
+       4 bytes: memory length (network byte order).
+       8 bytes: memory address (network byte order).
+       n bytes: memory value (n == memory length).
+
+*/
+
+/* bfdcore_read -- read bytes from a core file section.  */
+
+static inline void
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  else
+    error (_("Failed to read %d bytes from core file %s ('%s').\n"),
+          len, bfd_get_filename (obfd),
+          bfd_errmsg (bfd_get_error ()));
+}
+
+static inline uint64_t
+netorder64 (uint64_t input)
+{
+  uint64_t ret;
+
+  store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), 
+                         BFD_ENDIAN_BIG, input);
+  return ret;
+}
+
+static inline uint32_t
+netorder32 (uint32_t input)
+{
+  uint32_t ret;
+
+  store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), 
+                         BFD_ENDIAN_BIG, input);
+  return ret;
+}
+
+static inline uint16_t
+netorder16 (uint16_t input)
+{
+  uint16_t ret;
+
+  store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), 
+                         BFD_ENDIAN_BIG, input);
+  return ret;
+}
+
+/* Restore the execution log from a core_bfd file.  */
+static void
+record_restore (void)
+{
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct record_entry *rec;
+  asection *osec;
+  uint32_t osec_size;
+  int bfd_offset = 0;
+  struct regcache *regcache;
+
+  /* We restore the execution log from the open core bfd,
+     if there is one.  */
+  if (core_bfd == NULL)
+    return;
+
+  /* "record_restore" can only be called when record list is empty.  */
+  gdb_assert (record_first.next == NULL);
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n");
+
+  /* Now need to find our special note section.  */
+  osec = bfd_get_section_by_name (core_bfd, "null0");
+  osec_size = bfd_section_size (core_bfd, osec);
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n",
+                       osec ? "succeeded" : "failed");
+  if (osec == NULL)
+    return;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec));
+
+  /* Check the magic code.  */
+  bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("Version mis-match or file format error in core file %s."),
+          bfd_get_filename (core_bfd));
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "\
+  Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%s)\n",
+                       phex_nz (netorder32 (magic), 4));
+
+  /* Restore the entries in recfd into record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  record_insn_num = 0;
+  old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+  regcache = get_current_regcache ();
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint32_t regnum, len, signal, count;
+      uint64_t addr;
+
+      /* We are finished when offset reaches osec_size.  */
+      if (bfd_offset >= osec_size)
+       break;
+      bfdcore_read (core_bfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset);
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          /* Get register number to regnum.  */
+          bfdcore_read (core_bfd, osec, &regnum,
+                       sizeof (regnum), &bfd_offset);
+         regnum = netorder32 (regnum);
+
+          rec = record_reg_alloc (regcache, regnum);
+
+          /* Get val.  */
+          bfdcore_read (core_bfd, osec, record_get_loc (rec),
+                       rec->u.reg.len, &bfd_offset);
+
+         if (record_debug)
+           fprintf_unfiltered (gdb_stdlog, "\
+  Reading register %d (1 plus %lu plus %d bytes)\n",
+                               rec->u.reg.num,
+                               (unsigned long) sizeof (regnum),
+                               rec->u.reg.len);
+          break;
+
+        case record_mem: /* mem */
+          /* Get len.  */
+          bfdcore_read (core_bfd, osec, &len, 
+                       sizeof (len), &bfd_offset);
+         len = netorder32 (len);
+
+          /* Get addr.  */
+          bfdcore_read (core_bfd, osec, &addr,
+                       sizeof (addr), &bfd_offset);
+         addr = netorder64 (addr);
+
+          rec = record_mem_alloc (addr, len);
+
+          /* Get val.  */
+          bfdcore_read (core_bfd, osec, record_get_loc (rec),
+                       rec->u.mem.len, &bfd_offset);
+
+         if (record_debug)
+           fprintf_unfiltered (gdb_stdlog, "\
+  Reading memory %s (1 plus %lu plus %lu plus %d bytes)\n",
+                               paddress (get_current_arch (),
+                                         rec->u.mem.addr),
+                               (unsigned long) sizeof (addr),
+                               (unsigned long) sizeof (len),
+                               rec->u.mem.len);
+          break;
+
+        case record_end: /* end */
+          rec = record_end_alloc ();
+          record_insn_num ++;
+
+         /* Get signal value.  */
+         bfdcore_read (core_bfd, osec, &signal, 
+                       sizeof (signal), &bfd_offset);
+         signal = netorder32 (signal);
+         rec->u.end.sigval = signal;
+
+         /* Get insn count.  */
+         bfdcore_read (core_bfd, osec, &count, 
+                       sizeof (count), &bfd_offset);
+         count = netorder32 (count);
+         rec->u.end.insn_num = count;
+         record_insn_count = count + 1;
+         if (record_debug)
+           fprintf_unfiltered (gdb_stdlog, "\
+  Reading record_end (1 + %lu + %lu bytes), offset == %s\n",
+                               (unsigned long) sizeof (signal),
+                               (unsigned long) sizeof (count),
+                               paddress (get_current_arch (),
+                                         bfd_offset));
+          break;
+
+        default:
+          error (_("Bad entry type in core file %s."),
+                bfd_get_filename (core_bfd));
+          break;
+        }
+
+      /* Add rec to record arch list.  */
+      record_arch_list_add (rec);
+    }
+
+  discard_cleanups (old_cleanups);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  record_first.next = record_arch_list_head;
+  record_arch_list_head->prev = &record_first;
+  record_arch_list_tail->next = NULL;
+  record_list = &record_first;
+
+  /* Update record_insn_max_num.  */
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  /* Succeeded.  */
+  printf_filtered (_("Restored records from core file %s.\n"),
+                  bfd_get_filename (core_bfd));
+
+  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+/* bfdcore_write -- write bytes into a core file section.  */
+
+static inline void
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  else
+    error (_("Failed to write %d bytes to core file %s ('%s').\n"),
+          len, bfd_get_filename (obfd),
+          bfd_errmsg (bfd_get_error ()));
+}
+
+/* Restore the execution log from a file.  We use a modified elf
+   corefile format, with an extra section for our data.  */
+
+static void
+cmd_record_restore (char *args, int from_tty)
+{
+  core_file_command (args, from_tty);
+  record_open (args, from_tty);
+}
+
+static void
+record_save_cleanups (void *data)
+{
+  bfd *obfd = data;
+  char *pathname = xstrdup (bfd_get_filename (obfd));
+  bfd_close (obfd);
+  unlink (pathname);
+  xfree (pathname);
+}
+
+/* Save the execution log to a file.  We use a modified elf corefile
+   format, with an extra section for our data.  */
+
+static void
+cmd_record_save (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+  bfd *obfd;
+  int save_size = 0;
+  asection *osec = NULL;
+  int bfd_offset = 0;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("This command can only be used with target 'record'.\n"
+            "Use 'target record' first.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default recfile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, sizeof (recfilename_buffer),
+                "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the save file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n",
+                       recfilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (recfilename);
+  old_cleanups = make_cleanup (record_save_cleanups, obfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_insn (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Compute the size needed for the extra bfd section.  */
+  save_size = 4;       /* magic cookie */
+  for (record_list = record_first.next; record_list;
+       record_list = record_list->next)
+    switch (record_list->type)
+      {
+      case record_end:
+       save_size += 1 + 4 + 4;
+       break;
+      case record_reg:
+       save_size += 1 + 4 + record_list->u.reg.len;
+       break;
+      case record_mem:
+       save_size += 1 + 4 + 8 + record_list->u.mem.len;
+       break;
+      }
+
+  /* Make the new bfd section.  */
+  osec = bfd_make_section_anyway_with_flags (obfd, "precord",
+                                             SEC_HAS_CONTENTS
+                                             | SEC_READONLY);
+  if (osec == NULL)
+    error (_("Failed to create 'precord' section for corefile %s: %s"),
+          recfilename,
+           bfd_errmsg (bfd_get_error ()));
+  bfd_set_section_size (obfd, osec, save_size);
+  bfd_set_section_vma (obfd, osec, 0);
+  bfd_set_section_alignment (obfd, osec, 0);
+  bfd_section_lma (obfd, osec) = 0;
+
+  /* Save corefile state.  */
+  write_gcore_file (obfd);
+
+  /* Write out the record log.  */
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "\
+  Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%s)\n",
+                     phex_nz (magic, 4));
+  bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset);
+
+  /* Save the entries to recfd and forward execute to the end of
+     record list.  */
+  record_list = &record_first;
+  while (1)
+    {
+      /* Save entry.  */
+      if (record_list != &record_first)
+        {
+         uint8_t type;
+         uint32_t regnum, len, signal, count;
+          uint64_t addr;
+
+         type = record_list->type;
+          bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+             if (record_debug)
+               fprintf_unfiltered (gdb_stdlog, "\
+  Writing register %d (1 plus %lu plus %d bytes)\n",
+                                   record_list->u.reg.num,
+                                   (unsigned long) sizeof (regnum),
+                                   record_list->u.reg.len);
+
+              /* Write regnum.  */
+              regnum = netorder32 (record_list->u.reg.num);
+              bfdcore_write (obfd, osec, &regnum,
+                            sizeof (regnum), &bfd_offset);
+
+              /* Write regval.  */
+              bfdcore_write (obfd, osec, record_get_loc (record_list),
+                            record_list->u.reg.len, &bfd_offset);
+              break;
+
+            case record_mem: /* mem */
+             if (record_debug)
+               fprintf_unfiltered (gdb_stdlog, "\
+  Writing memory %s (1 plus %lu plus %lu plus %d bytes)\n",
+                                   paddress (gdbarch,
+                                             record_list->u.mem.addr),
+                                   (unsigned long) sizeof (addr),
+                                   (unsigned long) sizeof (len),
+                                   record_list->u.mem.len);
+
+             /* Write memlen.  */
+             len = netorder32 (record_list->u.mem.len);
+             bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset);
+
+             /* Write memaddr.  */
+             addr = netorder64 (record_list->u.mem.addr);
+             bfdcore_write (obfd, osec, &addr, 
+                            sizeof (addr), &bfd_offset);
+
+             /* Write memval.  */
+             bfdcore_write (obfd, osec, record_get_loc (record_list),
+                            record_list->u.mem.len, &bfd_offset);
+              break;
+
+              case record_end:
+               if (record_debug)
+                 fprintf_unfiltered (gdb_stdlog, "\
+  Writing record_end (1 + %lu + %lu bytes)\n", 
+                                     (unsigned long) sizeof (signal),
+                                     (unsigned long) sizeof (count));
+               /* Write signal value.  */
+               signal = netorder32 (record_list->u.end.sigval);
+               bfdcore_write (obfd, osec, &signal,
+                              sizeof (signal), &bfd_offset);
+
+               /* Write insn count.  */
+               count = netorder32 (record_list->u.end.insn_num);
+               bfdcore_write (obfd, osec, &count,
+                              sizeof (count), &bfd_offset);
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_insn (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_insn (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  bfd_close (obfd);
+  discard_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  printf_filtered (_("Saved core file %s with execution log.\n"),
+                  recfilename);
+}
+
+/* record_goto_insn -- rewind the record log (forward or backward,
+   depending on DIR) to the given entry, changing the program state
+   correspondingly.  */
+
+static void
+record_goto_insn (struct record_entry *entry,
+                 enum exec_direction_kind dir)
+{
+  struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
+  struct regcache *regcache = get_current_regcache ();
+  struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+  /* Assume everything is valid: we will hit the entry,
+     and we will not hit the end of the recording.  */
+
+  if (dir == EXEC_FORWARD)
+    record_list = record_list->next;
+
+  do
+    {
+      record_exec_insn (regcache, gdbarch, record_list);
+      if (dir == EXEC_REVERSE)
+       record_list = record_list->prev;
+      else
+       record_list = record_list->next;
+    } while (record_list != entry);
+  do_cleanups (set_cleanups);
+}
+
+/* "record goto" command.  Argument is an instruction number,
+   as given by "info record".
+
+   Rewinds the recording (forward or backward) to the given instruction.  */
+
+static void
+cmd_record_goto (char *arg, int from_tty)
+{
+  struct record_entry *p = NULL;
+  ULONGEST target_insn = 0;
+
+  if (arg == NULL || *arg == '\0')
+    error (_("Command requires an argument (insn number to go to)."));
+
+  if (strncmp (arg, "start", strlen ("start")) == 0
+      || strncmp (arg, "begin", strlen ("begin")) == 0)
+    {
+      /* Special case.  Find first insn.  */
+      for (p = &record_first; p != NULL; p = p->next)
+       if (p->type == record_end)
+         break;
+      if (p)
+       target_insn = p->u.end.insn_num;
+    }
+  else if (strncmp (arg, "end", strlen ("end")) == 0)
+    {
+      /* Special case.  Find last insn.  */
+      for (p = record_list; p->next != NULL; p = p->next)
+       ;
+      for (; p!= NULL; p = p->prev)
+       if (p->type == record_end)
+         break;
+      if (p)
+       target_insn = p->u.end.insn_num;
+    }
+  else
+    {
+      /* General case.  Find designated insn.  */
+      target_insn = parse_and_eval_long (arg);
+
+      for (p = &record_first; p != NULL; p = p->next)
+       if (p->type == record_end && p->u.end.insn_num == target_insn)
+         break;
+    }
+
+  if (p == NULL)
+    error (_("Target insn '%s' not found."), arg);
+  else if (p == record_list)
+    error (_("Already at insn '%s'."), arg);
+  else if (p->u.end.insn_num > record_list->u.end.insn_num)
+    {
+      printf_filtered (_("Go forward to insn number %s\n"),
+                      pulongest (target_insn));
+      record_goto_insn (p, EXEC_FORWARD);
+    }
+  else
+    {
+      printf_filtered (_("Go backward to insn number %s\n"),
+                      pulongest (target_insn));
+      record_goto_insn (p, EXEC_REVERSE);
+    }
+  registers_changed ();
+  reinit_frame_cache ();
+  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
 }
 
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +2643,8 @@ _initialize_record (void)
 
   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);
 
   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
                            _("Set debugging of record/replay feature."),
@@ -1259,9 +2654,11 @@ _initialize_record (void)
                            NULL, show_record_debug, &setdebuglist,
                            &showdebuglist);
 
-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-                 _("Abbreviated form of \"target record\" command."),
-                 &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+                     _("Abbreviated form of \"target record\" command."),
+                     &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
+
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
                  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +2673,18 @@ _initialize_record (void)
                  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("save", class_obscure, cmd_record_save,
+              _("Save the execution log to a file.\n\
+Argument is optional filename.\n\
+Default filename is 'gdb_record.<process_id>'."),
+              &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+
+  c = add_cmd ("restore", class_obscure, cmd_record_restore,
+              _("Restore the execution log from a file.\n\
+Argument is filename.  File must be created with 'record save'."),
+              &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
           _("Delete the rest of execution log and start recording it anew."),
@@ -1290,16 +2699,16 @@ _initialize_record (void)
 
   /* Record instructions number limit command.  */
   add_setshow_boolean_cmd ("stop-at-limit", no_class,
-                           &record_stop_at_limit, _("\
+                          &record_stop_at_limit, _("\
 Set whether record/replay stops when record/replay buffer becomes full."), _("\
 Show whether record/replay stops when record/replay buffer becomes full."), _("\
 Default is ON.\n\
 When ON, if the record/replay buffer becomes full, ask user what to do.\n\
 When OFF, if the record/replay buffer becomes full,\n\
 delete the oldest recorded instruction to make room for each new one."),
-                           NULL, NULL,
-                            &set_record_cmdlist, &show_record_cmdlist);
-  add_setshow_zinteger_cmd ("insn-number-max", no_class,
+                          NULL, NULL,
+                          &set_record_cmdlist, &show_record_cmdlist);
+  add_setshow_uinteger_cmd ("insn-number-max", no_class,
                            &record_insn_max_num,
                            _("Set record/replay buffer limit."),
                            _("Show record/replay buffer limit."), _("\
@@ -1307,7 +2716,9 @@ Set the maximum number of instructions to be stored in the\n\
 record/replay buffer.  Zero means unlimited.  Default is 200000."),
                            set_record_insn_max_num,
                            NULL, &set_record_cmdlist, &show_record_cmdlist);
-  add_cmd ("insn-number", class_obscure, show_record_insn_number,
-           _("Show the current number of instructions in the "
-             "record/replay buffer."), &info_record_cmdlist);
+
+  add_cmd ("goto", class_obscure, cmd_record_goto, _("\
+Restore the program to its state at instruction number N.\n\
+Argument is instruction number, as shown by 'info record'."),
+          &record_cmdlist);
 }
This page took 0.050694 seconds and 4 git commands to generate.