]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - gdb/record.c
GDB copyright headers update after running GDB's copyright.py script.
[thirdparty/binutils-gdb.git] / gdb / record.c
index 8afca6bcf7521c5479bb4e1bbcaa447ed725999c..6190794492fd73f2b2ba67ab22a08e006b8cf154 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-2016 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
 
 #include "defs.h"
 #include "gdbcmd.h"
-#include "regcache.h"
-#include "gdbthread.h"
-#include "event-top.h"
-#include "exceptions.h"
+#include "completer.h"
 #include "record.h"
+#include "observer.h"
+#include "inferior.h"
+#include "common/common-utils.h"
+#include "cli/cli-utils.h"
+#include "disasm.h"
 
-#include <signal.h>
+#include <ctype.h>
 
-#define DEFAULT_RECORD_INSN_MAX_NUM    200000
+/* This is the debug switch for process record.  */
+unsigned int record_debug = 0;
+
+/* The number of instructions to print in "record instruction-history".  */
+static unsigned int record_insn_history_size = 10;
+
+/* The variable registered as control variable in the "record
+   instruction-history" command.  Necessary for extra input
+   validation.  */
+static unsigned int record_insn_history_size_setshow_var;
 
-#define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+/* The number of functions to print in "record function-call-history".  */
+static unsigned int record_call_history_size = 10;
 
-/* These are the core struct of record function.
+/* The variable registered as control variable in the "record
+   call-history" command.  Necessary for extra input validation.  */
+static unsigned int record_call_history_size_setshow_var;
 
-   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.
+struct cmd_list_element *record_cmdlist = NULL;
+struct cmd_list_element *record_goto_cmdlist = NULL;
+struct cmd_list_element *set_record_cmdlist = NULL;
+struct cmd_list_element *show_record_cmdlist = NULL;
+struct cmd_list_element *info_record_cmdlist = NULL;
 
-   Each struct record_entry is linked to "record_list" by "prev" and "next". */
+#define DEBUG(msg, args...)                                            \
+  if (record_debug)                                                    \
+    fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args)
 
-struct record_reg_entry
+/* See record.h.  */
+
+struct target_ops *
+find_record_target (void)
 {
-  int num;
-  gdb_byte *val;
-};
+  return find_target_at (record_stratum);
+}
+
+/* Check that recording is active.  Throw an error, if it isn't.  */
 
-struct record_mem_entry
+static struct target_ops *
+require_record_target (void)
 {
-  CORE_ADDR addr;
-  int len;
-  /* Set this flag if target memory for this entry
-     can no longer be accessed.  */
-  int mem_entry_not_accessible;
-  gdb_byte *val;
-};
-
-enum record_type
+  struct target_ops *t;
+
+  t = find_record_target ();
+  if (t == NULL)
+    error (_("No record target is currently active.\n"
+            "Use one of the \"target record-<tab><tab>\" commands first."));
+
+  return t;
+}
+
+/* See record.h.  */
+
+void
+record_preopen (void)
 {
-  record_end = 0,
-  record_reg,
-  record_mem
-};
+  /* Check if a record target is already running.  */
+  if (find_record_target () != NULL)
+    error (_("The process is already being recorded.  Use \"record stop\" to "
+            "stop recording first."));
+}
 
-struct record_entry
+/* See record.h.  */
+
+int
+record_read_memory (struct gdbarch *gdbarch,
+                   CORE_ADDR memaddr, gdb_byte *myaddr,
+                   ssize_t len)
 {
-  struct record_entry *prev;
-  struct record_entry *next;
-  enum record_type type;
-  union
-  {
-    /* reg */
-    struct record_reg_entry reg;
-    /* mem */
-    struct record_mem_entry mem;
-  } u;
-};
+  int ret = target_read_memory (memaddr, myaddr, len);
 
-/* This is the debug switch for process record.  */
-int record_debug = 0;
-
-/* These list is for 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;
-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;
-static int record_insn_num = 0;
-
-/* The target_ops of process record.  */
-static struct target_ops record_ops;
-
-/* The beneath function pointers.  */
-static struct target_ops *record_beneath_to_resume_ops;
-static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
-                                         enum target_signal);
-static struct target_ops *record_beneath_to_wait_ops;
-static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
-                                        struct target_waitstatus *,
-                                        int);
-static struct target_ops *record_beneath_to_store_registers_ops;
-static void (*record_beneath_to_store_registers) (struct target_ops *,
-                                                  struct regcache *,
-                                                 int regno);
-static struct target_ops *record_beneath_to_xfer_partial_ops;
-static LONGEST (*record_beneath_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 (*record_beneath_to_insert_breakpoint) (struct gdbarch *,
-                                                  struct bp_target_info *);
-static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
-                                                  struct bp_target_info *);
+  if (ret != 0)
+    DEBUG ("error reading memory at addr %s len = %ld.\n",
+          paddress (gdbarch, memaddr), (long) len);
+
+  return ret;
+}
+
+/* Stop recording.  */
 
 static void
-record_list_release (struct record_entry *rec)
+record_stop (struct target_ops *t)
 {
-  struct record_entry *tmp;
+  DEBUG ("stop %s", t->to_shortname);
 
-  if (!rec)
-    return;
+  t->to_stop_recording (t);
+}
 
-  while (rec->next)
-    {
-      rec = rec->next;
-    }
+/* Unpush the record target.  */
 
-  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);
-    }
+static void
+record_unpush (struct target_ops *t)
+{
+  DEBUG ("unpush %s", t->to_shortname);
 
-  if (rec != &record_first)
-    xfree (rec);
+  unpush_target (t);
 }
 
-static void
-record_list_release_next (void)
+/* See record.h.  */
+
+void
+record_disconnect (struct target_ops *t, const char *args, int from_tty)
 {
-  struct record_entry *rec = record_list;
-  struct record_entry *tmp = rec->next;
-  rec->next = NULL;
-  while (tmp)
-    {
-      rec = tmp->next;
-      if (tmp->type == record_reg)
-       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);
-      tmp = rec;
-    }
+  gdb_assert (t->to_stratum == record_stratum);
+
+  DEBUG ("disconnect %s", t->to_shortname);
+
+  record_stop (t);
+  record_unpush (t);
+
+  target_disconnect (args, from_tty);
 }
 
-static void
-record_list_release_first (void)
+/* See record.h.  */
+
+void
+record_detach (struct target_ops *t, const char *args, int from_tty)
 {
-  struct record_entry *tmp = NULL;
-  enum record_type type;
+  gdb_assert (t->to_stratum == record_stratum);
 
-  if (!record_first.next)
-    return;
+  DEBUG ("detach %s", t->to_shortname);
 
-  while (1)
-    {
-      type = record_first.next->type;
+  record_stop (t);
+  record_unpush (t);
 
-      if (type == record_reg)
-       xfree (record_first.next->u.reg.val);
-      else if (type == record_mem)
-       xfree (record_first.next->u.mem.val);
-      tmp = record_first.next;
-      record_first.next = tmp->next;
-      xfree (tmp);
+  target_detach (args, from_tty);
+}
 
-      if (!record_first.next)
-       {
-         gdb_assert (record_insn_num == 1);
-         break;
-       }
+/* See record.h.  */
+
+void
+record_mourn_inferior (struct target_ops *t)
+{
+  gdb_assert (t->to_stratum == record_stratum);
 
-      record_first.next->prev = &record_first;
+  DEBUG ("mourn inferior %s", t->to_shortname);
 
-      if (type == record_end)
-       break;
-    }
+  /* It is safer to not stop recording.  Resources will be freed when
+     threads are discarded.  */
+  record_unpush (t);
 
-  record_insn_num--;
+  target_mourn_inferior ();
 }
 
-/* Add a struct record_entry to record_arch_list.  */
+/* See record.h.  */
 
-static void
-record_arch_list_add (struct record_entry *rec)
+void
+record_kill (struct target_ops *t)
 {
-  if (record_debug > 1)
-    fprintf_unfiltered (gdb_stdlog,
-                       "Process record: record_arch_list_add %s.\n",
-                       host_address_to_string (rec));
+  gdb_assert (t->to_stratum == record_stratum);
 
-  if (record_arch_list_tail)
-    {
-      record_arch_list_tail->next = rec;
-      rec->prev = record_arch_list_tail;
-      record_arch_list_tail = rec;
-    }
-  else
-    {
-      record_arch_list_head = rec;
-      record_arch_list_tail = rec;
-    }
+  DEBUG ("kill %s", t->to_shortname);
+
+  /* It is safer to not stop recording.  Resources will be freed when
+     threads are discarded.  */
+  record_unpush (t);
+
+  target_kill ();
 }
 
-/* Record the value of a register NUM to record_arch_list.  */
+/* See record.h.  */
 
 int
-record_arch_list_add_reg (struct regcache *regcache, int num)
+record_check_stopped_by_breakpoint (struct address_space *aspace, CORE_ADDR pc,
+                                   enum target_stop_reason *reason)
 {
-  struct record_entry *rec;
+  if (breakpoint_inserted_here_p (aspace, pc))
+    {
+      if (hardware_breakpoint_inserted_here_p (aspace, pc))
+       *reason = TARGET_STOPPED_BY_HW_BREAKPOINT;
+      else
+       *reason = TARGET_STOPPED_BY_SW_BREAKPOINT;
+      return 1;
+    }
 
-  if (record_debug > 1)
-    fprintf_unfiltered (gdb_stdlog,
-                       "Process record: add register num = %d to "
-                       "record list.\n",
-                       num);
+  *reason = TARGET_STOPPED_BY_NO_REASON;
+  return 0;
+}
 
-  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;
+/* Implement "show record debug" command.  */
 
-  regcache_raw_read (regcache, num, rec->u.reg.val);
+static void
+show_record_debug (struct ui_file *file, int from_tty,
+                  struct cmd_list_element *c, const char *value)
+{
+  fprintf_filtered (file, _("Debugging of process record target is %s.\n"),
+                   value);
+}
 
-  record_arch_list_add (rec);
+/* Alias for "target record".  */
 
-  return 0;
+static void
+cmd_record_start (char *args, int from_tty)
+{
+  execute_command ("target record-full", from_tty);
 }
 
-/* Record the value of a region of memory whose address is ADDR and
-   length is LEN to record_arch_list.  */
+/* Truncate the record log from the present point
+   of replay until the end.  */
 
-int
-record_arch_list_add_mem (CORE_ADDR addr, int len)
+static void
+cmd_record_delete (char *args, int from_tty)
 {
-  struct record_entry *rec;
-
-  if (record_debug > 1)
-    fprintf_unfiltered (gdb_stdlog,
-                       "Process record: add mem addr = %s len = %d to "
-                       "record list.\n",
-                       paddress (target_gdbarch, addr), len);
-
-  if (!addr)
-    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;
-
-  if (target_read_memory (addr, rec->u.mem.val, len))
+  require_record_target ();
+
+  if (!target_record_is_replaying (inferior_ptid))
     {
-      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);
-      return -1;
+      printf_unfiltered (_("Already at end of record list.\n"));
+      return;
     }
 
-  record_arch_list_add (rec);
+  if (!target_supports_delete_record ())
+    {
+      printf_unfiltered (_("The current record target does not support "
+                          "this operation.\n"));
+      return;
+    }
 
-  return 0;
+  if (!from_tty || query (_("Delete the log from this point forward "
+                           "and begin to record the running message "
+                           "at current PC?")))
+    target_delete_record ();
 }
 
-/* Add a record_end type struct record_entry to record_arch_list.  */
+/* Implement the "stoprecord" or "record stop" command.  */
 
-int
-record_arch_list_add_end (void)
+static void
+cmd_record_stop (char *args, int from_tty)
 {
-  struct record_entry *rec;
+  struct target_ops *t;
 
-  if (record_debug > 1)
-    fprintf_unfiltered (gdb_stdlog,
-                       "Process record: add end to arch list.\n");
+  t = require_record_target ();
 
-  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->prev = NULL;
-  rec->next = NULL;
-  rec->type = record_end;
+  record_stop (t);
+  record_unpush (t);
 
-  record_arch_list_add (rec);
+  printf_unfiltered (_("Process record is stopped and all execution "
+                      "logs are deleted.\n"));
 
-  return 0;
+  observer_notify_record_changed (current_inferior (), 0);
 }
 
+/* The "set record" command.  */
+
 static void
-record_check_insn_num (int set_terminal)
+set_record_command (char *args, int from_tty)
 {
-  if (record_insn_max_num)
-    {
-      gdb_assert (record_insn_num <= record_insn_max_num);
-      if (record_insn_num == record_insn_max_num)
-       {
-         /* Ask user what to do.  */
-         if (record_stop_at_limit)
-           {
-             int q;
-             if (set_terminal)
-               target_terminal_ours ();
-             q = yquery (_("Do you want to auto delete previous execution "
-                           "log entries when record/replay buffer becomes "
-                           "full (record stop-at-limit)?"));
-             if (set_terminal)
-               target_terminal_inferior ();
-             if (q)
-               record_stop_at_limit = 0;
-             else
-               error (_("Process record: inferior program stopped."));
-           }
-       }
-    }
+  printf_unfiltered (_("\"set record\" must be followed "
+                      "by an apporpriate subcommand.\n"));
+  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
 }
 
-/* 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.  */
+/* The "show record" command.  */
 
 static void
-record_message_cleanups (void *ignore)
+show_record_command (char *args, int from_tty)
 {
-  record_list_release (record_arch_list_tail);
+  cmd_show_list (show_record_cmdlist, from_tty, "");
 }
 
-static int
-record_message (void *args)
+/* The "info record" command.  */
+
+static void
+info_record_command (char *args, int from_tty)
 {
-  int ret;
-  struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct target_ops *t;
 
-  record_arch_list_head = NULL;
-  record_arch_list_tail = NULL;
+  t = find_record_target ();
+  if (t == NULL)
+    {
+      printf_filtered (_("No record target is currently active.\n"));
+      return;
+    }
 
-  /* Check record_insn_num.  */
-  record_check_insn_num (1);
+  printf_filtered (_("Active record target: %s\n"), t->to_shortname);
+  t->to_info_record (t);
+}
 
-  ret = gdbarch_process_record (get_regcache_arch (regcache),
-                               regcache,
-                               regcache_read_pc (regcache));
-  if (ret > 0)
-    error (_("Process record: inferior program stopped."));
-  if (ret < 0)
-    error (_("Process record: failed to record execution log."));
+/* The "record save" command.  */
 
-  discard_cleanups (old_cleanups);
+static void
+cmd_record_save (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
 
-  record_list->next = record_arch_list_head;
-  record_arch_list_head->prev = record_list;
-  record_list = record_arch_list_tail;
+  require_record_target ();
 
-  if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+  if (args != NULL && *args != 0)
+    recfilename = args;
   else
-    record_insn_num++;
+    {
+      /* Default recfile name is "gdb_record.PID".  */
+      xsnprintf (recfilename_buffer, sizeof (recfilename_buffer),
+                "gdb_record.%d", ptid_get_pid (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
 
-  return 1;
+  target_save_record (recfilename);
 }
 
-static int
-do_record_message (struct regcache *regcache)
+/* See record.h.  */
+
+void
+record_goto (const char *arg)
 {
-  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
-}
+  ULONGEST insn;
 
-/* Set to 1 if record_store_registers and record_xfer_partial
-   doesn't need record.  */
+  if (arg == NULL || *arg == '\0')
+    error (_("Command requires an argument (insn number to go to)."));
 
-static int record_gdb_operation_disable = 0;
+  insn = parse_and_eval_long (arg);
 
-struct cleanup *
-record_gdb_operation_disable_set (void)
-{
-  struct cleanup *old_cleanups = NULL;
+  require_record_target ();
+  target_goto_record (insn);
+}
 
-  old_cleanups =
-    make_cleanup_restore_integer (&record_gdb_operation_disable);
-  record_gdb_operation_disable = 1;
+/* "record goto" command.  Argument is an instruction number,
+   as given by "info record".
 
-  return old_cleanups;
-}
+   Rewinds the recording (forward or backward) to the given instruction.  */
 
 static void
-record_open (char *name, int from_tty)
+cmd_record_goto (char *arg, int from_tty)
 {
-  struct target_ops *t;
+  record_goto (arg);
+}
 
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
-
-  /* check exec */
-  if (!target_has_execution)
-    error (_("Process record: the program is not being run."));
-  if (non_stop)
-    error (_("Process record target can't debug inferior in non-stop mode "
-            "(non-stop)."));
-  if (target_async_permitted)
-    error (_("Process record target can't debug inferior in asynchronous "
-            "mode (target-async)."));
-
-  if (!gdbarch_process_record_p (target_gdbarch))
-    error (_("Process record: the current architecture doesn't support "
-            "record function."));
-
-  /* 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;
-    }
+/* The "record goto begin" command.  */
 
-  /*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;
+static void
+cmd_record_goto_begin (char *arg, int from_tty)
+{
+  if (arg != NULL && *arg != '\0')
+    error (_("Junk after argument: %s."), arg);
 
-  /* Set the beneath function pointers.  */
-  for (t = current_target.beneath; t != NULL; t = t->beneath)
-    {
-      if (!record_beneath_to_resume)
-        {
-         record_beneath_to_resume = t->to_resume;
-         record_beneath_to_resume_ops = t;
-        }
-      if (!record_beneath_to_wait)
-        {
-         record_beneath_to_wait = t->to_wait;
-         record_beneath_to_wait_ops = t;
-        }
-      if (!record_beneath_to_store_registers)
-        {
-         record_beneath_to_store_registers = t->to_store_registers;
-         record_beneath_to_store_registers_ops = t;
-        }
-      if (!record_beneath_to_xfer_partial)
-        {
-         record_beneath_to_xfer_partial = t->to_xfer_partial;
-         record_beneath_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 (!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);
-
-  /* Reset */
-  record_insn_num = 0;
-  record_list = &record_first;
-  record_list->next = NULL;
+  require_record_target ();
+  target_goto_record_begin ();
 }
 
+/* The "record goto end" command.  */
+
 static void
-record_close (int quitting)
+cmd_record_goto_end (char *arg, int from_tty)
 {
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+  if (arg != NULL && *arg != '\0')
+    error (_("Junk after argument: %s."), arg);
 
-  record_list_release (record_list);
+  require_record_target ();
+  target_goto_record_end ();
 }
 
-static int record_resume_step = 0;
-static enum target_signal record_resume_siggnal;
-static int record_resume_error;
+/* Read an instruction number from an argument string.  */
 
-static void
-record_resume (struct target_ops *ops, ptid_t ptid, int step,
-               enum target_signal siggnal)
+static ULONGEST
+get_insn_number (char **arg)
 {
-  record_resume_step = step;
-  record_resume_siggnal = siggnal;
+  ULONGEST number;
+  const char *begin, *end, *pos;
 
-  if (!RECORD_IS_REPLAY)
-    {
-      if (do_record_message (get_current_regcache ()))
-        {
-          record_resume_error = 0;
-        }
-      else
-        {
-          record_resume_error = 1;
-          return;
-        }
-      record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
-                                siggnal);
-    }
+  begin = *arg;
+  pos = skip_spaces_const (begin);
+
+  if (!isdigit (*pos))
+    error (_("Expected positive number, got: %s."), pos);
+
+  number = strtoulst (pos, &end, 10);
+
+  *arg += (end - begin);
+
+  return number;
 }
 
-static int record_get_sig = 0;
+/* Read a context size from an argument string.  */
 
-static void
-record_sig_handler (int signo)
+static int
+get_context_size (char **arg)
 {
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+  char *pos;
+  int number;
 
-  /* It will break the running inferior in replay mode.  */
-  record_resume_step = 1;
+  pos = skip_spaces (*arg);
 
-  /* It will let record_wait set inferior status to get the signal
-     SIGINT.  */
-  record_get_sig = 1;
+  if (!isdigit (*pos))
+    error (_("Expected positive number, got: %s."), pos);
+
+  return strtol (pos, arg, 10);
 }
 
+/* Complain about junk at the end of an argument string.  */
+
 static void
-record_wait_cleanups (void *ignore)
+no_chunk (char *arg)
 {
-  if (execution_direction == EXEC_REVERSE)
-    {
-      if (record_list->next)
-       record_list = record_list->next;
-    }
-  else
-    record_list = record_list->prev;
+  if (*arg != 0)
+    error (_("Junk after argument: %s."), arg);
 }
 
-/* In replay mode, this function examines the recorded log and
-   determines where to stop.  */
+/* Read instruction-history modifiers from an argument string.  */
 
-static ptid_t
-record_wait (struct target_ops *ops,
-            ptid_t ptid, struct target_waitstatus *status,
-            int options)
+static int
+get_insn_history_modifiers (char **arg)
 {
-  struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
-
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog,
-                       "Process record: record_wait "
-                       "record_resume_step = %d\n",
-                       record_resume_step);
+  int modifiers;
+  char *args;
 
-  if (!RECORD_IS_REPLAY)
-    {
-      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.  */
-         return record_beneath_to_wait (record_beneath_to_wait_ops,
-                                        ptid, status, options);
-       }
-      else
-       {
-         /* This is not a single step.  */
-         ptid_t ret;
-         CORE_ADDR tmp_pc;
+  modifiers = 0;
+  args = *arg;
 
-         while (1)
-           {
-             ret = record_beneath_to_wait (record_beneath_to_wait_ops,
-                                           ptid, status, options);
-
-             if (status->kind == TARGET_WAITKIND_STOPPED
-                 && status->value.sig == TARGET_SIGNAL_TRAP)
-               {
-                 /* Check if there is a breakpoint.  */
-                 registers_changed ();
-                 tmp_pc = regcache_read_pc (get_current_regcache ());
-                 if (breakpoint_inserted_here_p (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)
-                       {
-                         regcache_write_pc (get_thread_regcache (ret),
-                                            tmp_pc + decr_pc_after_break);
-                       }
-                   }
-                 else
-                   {
-                     /* There is not a breakpoint.  */
-                     if (!do_record_message (get_current_regcache ()))
-                       {
-                          break;
-                       }
-                     record_beneath_to_resume (record_beneath_to_resume_ops,
-                                               ptid, 1,
-                                               record_resume_siggnal);
-                     continue;
-                   }
-               }
-
-             /* The inferior is broken by a breakpoint or a signal.  */
-             break;
-           }
+  if (args == NULL)
+    return modifiers;
 
-         return ret;
-       }
-    }
-  else
+  while (*args == '/')
     {
-      struct regcache *regcache = get_current_regcache ();
-      struct gdbarch *gdbarch = get_regcache_arch (regcache);
-      int continue_flag = 1;
-      int first_record_end = 1;
-      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
-      CORE_ADDR tmp_pc;
+      ++args;
 
-      status->kind = TARGET_WAITKIND_STOPPED;
+      if (*args == '\0')
+       error (_("Missing modifier."));
 
-      /* Check breakpoint when forward execute.  */
-      if (execution_direction == EXEC_FORWARD)
+      for (; *args; ++args)
        {
-         tmp_pc = regcache_read_pc (regcache);
-         if (breakpoint_inserted_here_p (tmp_pc))
-           {
-             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)
-               regcache_write_pc (regcache,
-                                  tmp_pc +
-                                  gdbarch_decr_pc_after_break (gdbarch));
-             goto replay_out;
-           }
-       }
+         if (isspace (*args))
+           break;
 
-      record_get_sig = 0;
-      signal (SIGINT, record_sig_handler);
-      /* If GDB is in terminal_inferior mode, it will not get the signal.
-         And in GDB replay mode, GDB doesn't need to be in terminal_inferior
-         mode, because inferior will not executed.
-         Then set it to terminal_ours to make GDB get the signal.  */
-      target_terminal_ours ();
-
-      /* In EXEC_FORWARD mode, record_list points to the tail of prev
-         instruction.  */
-      if (execution_direction == EXEC_FORWARD && record_list->next)
-       record_list = record_list->next;
-
-      /* Loop over the record_list, looking for the next place to
-        stop.  */
-      do
-       {
-         /* Check for beginning and end of log.  */
-         if (execution_direction == EXEC_REVERSE
-             && record_list == &record_first)
+         if (*args == '/')
+           continue;
+
+         switch (*args)
            {
-             /* Hit beginning of record log in reverse.  */
-             status->kind = TARGET_WAITKIND_NO_HISTORY;
+           case 'm':
+           case 's':
+             modifiers |= DISASSEMBLY_SOURCE;
+             modifiers |= DISASSEMBLY_FILENAME;
              break;
-           }
-         if (execution_direction != EXEC_REVERSE && !record_list->next)
-           {
-             /* Hit end of record log going forward.  */
-             status->kind = TARGET_WAITKIND_NO_HISTORY;
+           case 'r':
+             modifiers |= DISASSEMBLY_RAW_INSN;
              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
-           {
-             if (record_debug > 1)
-               fprintf_unfiltered (gdb_stdlog,
-                                   "Process record: record_end %s to "
-                                   "inferior.\n",
-                                   host_address_to_string (record_list));
-
-             if (first_record_end && execution_direction == EXEC_REVERSE)
-               {
-                 /* When reverse excute, the first record_end is the part of
-                    current instruction.  */
-                 first_record_end = 0;
-               }
-             else
-               {
-                 /* In EXEC_REVERSE mode, this is the record_end of prev
-                    instruction.
-                    In EXEC_FORWARD mode, this is the record_end of current
-                    instruction.  */
-                 /* step */
-                 if (record_resume_step)
-                   {
-                     if (record_debug > 1)
-                       fprintf_unfiltered (gdb_stdlog,
-                                           "Process record: step.\n");
-                     continue_flag = 0;
-                   }
-
-                 /* check breakpoint */
-                 tmp_pc = regcache_read_pc (regcache);
-                 if (breakpoint_inserted_here_p (tmp_pc))
-                   {
-                     if (record_debug)
-                       fprintf_unfiltered (gdb_stdlog,
-                                           "Process record: break "
-                                           "at %s.\n",
-                                           paddress (gdbarch, tmp_pc));
-                     if (gdbarch_decr_pc_after_break (gdbarch)
-                         && execution_direction == EXEC_FORWARD
-                         && !record_resume_step)
-                       regcache_write_pc (regcache,
-                                          tmp_pc +
-                                          gdbarch_decr_pc_after_break (gdbarch));
-                     continue_flag = 0;
-                   }
-               }
-           }
-
-         if (continue_flag)
-           {
-             if (execution_direction == EXEC_REVERSE)
-               {
-                 if (record_list->prev)
-                   record_list = record_list->prev;
-               }
-             else
-               {
-                 if (record_list->next)
-                   record_list = record_list->next;
-               }
+           case 'f':
+             modifiers |= DISASSEMBLY_OMIT_FNAME;
+             break;
+           case 'p':
+             modifiers |= DISASSEMBLY_OMIT_PC;
+             break;
+           default:
+             error (_("Invalid modifier: %c."), *args);
            }
        }
-      while (continue_flag);
 
-      signal (SIGINT, handle_sigint);
-
-replay_out:
-      if (record_get_sig)
-       status->value.sig = TARGET_SIGNAL_INT;
-      else
-       status->value.sig = TARGET_SIGNAL_TRAP;
-
-      discard_cleanups (old_cleanups);
+      args = skip_spaces (args);
     }
 
-  do_cleanups (set_cleanups);
-  return inferior_ptid;
-}
-
-static void
-record_disconnect (struct target_ops *target, char *args, int from_tty)
-{
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n");
+  /* Update the argument string.  */
+  *arg = args;
 
-  unpush_target (&record_ops);
-  target_disconnect (args, from_tty);
+  return modifiers;
 }
 
-static void
-record_detach (struct target_ops *ops, char *args, int from_tty)
-{
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n");
-
-  unpush_target (&record_ops);
-  target_detach (args, from_tty);
-}
+/* The "set record instruction-history-size / set record
+   function-call-history-size" commands are unsigned, with UINT_MAX
+   meaning unlimited.  The target interfaces works with signed int
+   though, to indicate direction, so map "unlimited" to INT_MAX, which
+   is about the same as unlimited in practice.  If the user does have
+   a log that huge, she can fetch it in chunks across several requests,
+   but she'll likely have other problems first...  */
 
-static void
-record_mourn_inferior (struct target_ops *ops)
+static int
+command_size_to_target_size (unsigned int size)
 {
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: "
-                                   "record_mourn_inferior\n");
+  gdb_assert (size <= INT_MAX || size == UINT_MAX);
 
-  unpush_target (&record_ops);
-  target_mourn_inferior ();
+  if (size == UINT_MAX)
+    return INT_MAX;
+  else
+    return size;
 }
 
-/* Close process record target before killing the inferior process.  */
+/* The "record instruction-history" command.  */
 
 static void
-record_kill (struct target_ops *ops)
+cmd_record_insn_history (char *arg, int from_tty)
 {
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+  int flags, size;
 
-  unpush_target (&record_ops);
-  target_kill ();
-}
-
-/* Record registers change (by user or by GDB) to list as an instruction.  */
+  require_record_target ();
 
-static void
-record_registers_change (struct regcache *regcache, int regnum)
-{
-  /* Check record_insn_num.  */
-  record_check_insn_num (0);
+  flags = get_insn_history_modifiers (&arg);
 
-  record_arch_list_head = NULL;
-  record_arch_list_tail = NULL;
+  size = command_size_to_target_size (record_insn_history_size);
 
-  if (regnum < 0)
+  if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0)
+    target_insn_history (size, flags);
+  else if (strcmp (arg, "-") == 0)
+    target_insn_history (-size, flags);
+  else
     {
-      int i;
-      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+      ULONGEST begin, end;
+
+      begin = get_insn_number (&arg);
+
+      if (*arg == ',')
        {
-         if (record_arch_list_add_reg (regcache, i))
+         arg = skip_spaces (++arg);
+
+         if (*arg == '+')
            {
-             record_list_release (record_arch_list_tail);
-             error (_("Process record: failed to record execution log."));
+             arg += 1;
+             size = get_context_size (&arg);
+
+             no_chunk (arg);
+
+             target_insn_history_from (begin, size, flags);
            }
-       }
-    }
-  else
-    {
-      if (record_arch_list_add_reg (regcache, regnum))
-       {
-         record_list_release (record_arch_list_tail);
-         error (_("Process record: failed to record execution log."));
-       }
-    }
-  if (record_arch_list_add_end ())
-    {
-      record_list_release (record_arch_list_tail);
-      error (_("Process record: failed to record execution log."));
-    }
-  record_list->next = record_arch_list_head;
-  record_arch_list_head->prev = record_list;
-  record_list = record_arch_list_tail;
+         else if (*arg == '-')
+           {
+             arg += 1;
+             size = get_context_size (&arg);
 
-  if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
-  else
-    record_insn_num++;
-}
+             no_chunk (arg);
 
-static void
-record_store_registers (struct target_ops *ops, struct regcache *regcache,
-                        int regno)
-{
-  if (!record_gdb_operation_disable)
-    {
-      if (RECORD_IS_REPLAY)
-       {
-         int n;
-
-         /* 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?"));
+             target_insn_history_from (begin, -size, flags);
+           }
          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?"),
-                     gdbarch_register_name (get_regcache_arch (regcache),
-                                              regno));
-
-         if (!n)
            {
-             /* Invalidate the value of regcache that was set in function
-                "regcache_raw_write".  */
-             if (regno < 0)
-               {
-                 int i;
-                 for (i = 0;
-                      i < gdbarch_num_regs (get_regcache_arch (regcache));
-                      i++)
-                   regcache_invalidate (regcache, i);
-               }
-             else
-               regcache_invalidate (regcache, regno);
-
-             error (_("Process record canceled the operation."));
+             end = get_insn_number (&arg);
+
+             no_chunk (arg);
+
+             target_insn_history_range (begin, end, flags);
            }
+       }
+      else
+       {
+         no_chunk (arg);
 
-         /* Destroy the record from here forward.  */
-         record_list_release_next ();
+         target_insn_history_from (begin, size, flags);
        }
 
-      record_registers_change (regcache, regno);
+      dont_repeat ();
     }
-  record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
-                                     regcache, regno);
 }
 
-/* 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.  */
+/* Read function-call-history modifiers from an argument string.  */
 
-static LONGEST
-record_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
+get_call_history_modifiers (char **arg)
 {
-  if (!record_gdb_operation_disable
-      && (object == TARGET_OBJECT_MEMORY
-         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+  int modifiers;
+  char *args;
+
+  modifiers = 0;
+  args = *arg;
+
+  if (args == NULL)
+    return modifiers;
+
+  while (*args == '/')
     {
-      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?"),
-                      paddress (target_gdbarch, offset)))
-           error (_("Process record canceled the operation."));
-
-         /* Destroy the record from here forward.  */
-         record_list_release_next ();
-       }
+      ++args;
 
-      /* Check record_insn_num */
-      record_check_insn_num (0);
+      if (*args == '\0')
+       error (_("Missing modifier."));
 
-      /* Record registers change to list as an instruction.  */
-      record_arch_list_head = NULL;
-      record_arch_list_tail = NULL;
-      if (record_arch_list_add_mem (offset, len))
-       {
-         record_list_release (record_arch_list_tail);
-         if (record_debug)
-           fprintf_unfiltered (gdb_stdlog,
-                               _("Process record: failed to record "
-                                 "execution log."));
-         return -1;
-       }
-      if (record_arch_list_add_end ())
+      for (; *args; ++args)
        {
-         record_list_release (record_arch_list_tail);
-         if (record_debug)
-           fprintf_unfiltered (gdb_stdlog,
-                               _("Process record: failed to record "
-                                 "execution log."));
-         return -1;
+         if (isspace (*args))
+           break;
+
+         if (*args == '/')
+           continue;
+
+         switch (*args)
+           {
+           case 'l':
+             modifiers |= RECORD_PRINT_SRC_LINE;
+             break;
+           case 'i':
+             modifiers |= RECORD_PRINT_INSN_RANGE;
+             break;
+           case 'c':
+             modifiers |= RECORD_PRINT_INDENT_CALLS;
+             break;
+           default:
+             error (_("Invalid modifier: %c."), *args);
+           }
        }
-      record_list->next = record_arch_list_head;
-      record_arch_list_head->prev = record_list;
-      record_list = record_arch_list_tail;
 
-      if (record_insn_num == record_insn_max_num && record_insn_max_num)
-       record_list_release_first ();
-      else
-       record_insn_num++;
+      args = skip_spaces (args);
     }
 
-  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
-                                         object, annex, readbuf, writebuf,
-                                         offset, len);
+  /* Update the argument string.  */
+  *arg = args;
+
+  return modifiers;
 }
 
-/* Behavior is conditional on RECORD_IS_REPLAY.
-   We will not actually insert or remove breakpoints when replaying,
-   nor when recording.  */
+/* The "record function-call-history" command.  */
 
-static int
-record_insert_breakpoint (struct gdbarch *gdbarch,
-                         struct bp_target_info *bp_tgt)
+static void
+cmd_record_call_history (char *arg, int from_tty)
 {
-  if (!RECORD_IS_REPLAY)
-    {
-      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
-      int ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
+  int flags, size;
 
-      do_cleanups (old_cleanups);
+  require_record_target ();
 
-      return ret;
-    }
+  flags = get_call_history_modifiers (&arg);
 
-  return 0;
-}
+  size = command_size_to_target_size (record_call_history_size);
 
-static int
-record_remove_breakpoint (struct gdbarch *gdbarch,
-                         struct bp_target_info *bp_tgt)
-{
-  if (!RECORD_IS_REPLAY)
+  if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0)
+    target_call_history (size, flags);
+  else if (strcmp (arg, "-") == 0)
+    target_call_history (-size, flags);
+  else
     {
-      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
-      int ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
-
-      do_cleanups (old_cleanups);
+      ULONGEST begin, end;
 
-      return ret;
-    }
+      begin = get_insn_number (&arg);
 
-  return 0;
-}
+      if (*arg == ',')
+       {
+         arg = skip_spaces (++arg);
 
-static int
-record_can_execute_reverse (void)
-{
-  return 1;
-}
+         if (*arg == '+')
+           {
+             arg += 1;
+             size = get_context_size (&arg);
 
-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.";
-  record_ops.to_open = record_open;
-  record_ops.to_close = record_close;
-  record_ops.to_resume = record_resume;
-  record_ops.to_wait = record_wait;
-  record_ops.to_disconnect = record_disconnect;
-  record_ops.to_detach = record_detach;
-  record_ops.to_mourn_inferior = record_mourn_inferior;
-  record_ops.to_kill = record_kill;
-  record_ops.to_create_inferior = find_default_create_inferior;
-  record_ops.to_store_registers = record_store_registers;
-  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_can_execute_reverse = record_can_execute_reverse;
-  record_ops.to_stratum = record_stratum;
-  record_ops.to_magic = OPS_MAGIC;
-}
+             no_chunk (arg);
 
-static void
-show_record_debug (struct ui_file *file, int from_tty,
-                  struct cmd_list_element *c, const char *value)
-{
-  fprintf_filtered (file, _("Debugging of process record target is %s.\n"),
-                   value);
-}
+             target_call_history_from (begin, size, flags);
+           }
+         else if (*arg == '-')
+           {
+             arg += 1;
+             size = get_context_size (&arg);
 
-/* Alias for "target record".  */
+             no_chunk (arg);
 
-static void
-cmd_record_start (char *args, int from_tty)
-{
-  execute_command ("target record", from_tty);
-}
+             target_call_history_from (begin, -size, flags);
+           }
+         else
+           {
+             end = get_insn_number (&arg);
 
-/* Truncate the record log from the present point
-   of replay until the end.  */
+             no_chunk (arg);
 
-static void
-cmd_record_delete (char *args, int from_tty)
-{
-  if (current_target.to_stratum == record_stratum)
-    {
-      if (RECORD_IS_REPLAY)
-       {
-         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 ();
+             target_call_history_range (begin, end, flags);
+           }
        }
       else
-         printf_unfiltered (_("Already at end of record list.\n"));
-
-    }
-  else
-    printf_unfiltered (_("Process record is not started.\n"));
-}
+       {
+         no_chunk (arg);
 
-/* Implement the "stoprecord" command.  */
+         target_call_history_from (begin, size, flags);
+       }
 
-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);
+      dont_repeat ();
     }
-  else
-    printf_unfiltered (_("Process record is not started.\n"));
 }
 
-/* Set upper limit of record log size.  */
+/* Helper for "set record instruction-history-size" and "set record
+   function-call-history-size" input validation.  COMMAND_VAR is the
+   variable registered in the command as control variable.  *SETTING
+   is the real setting the command allows changing.  */
 
 static void
-set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c)
+validate_history_size (unsigned int *command_var, unsigned int *setting)
 {
-  if (record_insn_num > record_insn_max_num && record_insn_max_num)
+  if (*command_var != UINT_MAX && *command_var > INT_MAX)
     {
-      printf_unfiltered (_("Record instructions number is bigger than "
-                          "record instructions max number.  Auto delete "
-                          "the first ones?\n"));
+      unsigned int new_value = *command_var;
 
-      while (record_insn_num > record_insn_max_num)
-       record_list_release_first ();
+      /* Restore previous value.  */
+      *command_var = *setting;
+      error (_("integer %u out of range"), new_value);
     }
-}
 
-/* 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);
+  /* Commit new value.  */
+  *setting = *command_var;
 }
 
-static struct cmd_list_element *record_cmdlist, *set_record_cmdlist,
-                              *show_record_cmdlist, *info_record_cmdlist;
+/* Called by do_setshow_command.  We only want values in the
+   [0..INT_MAX] range, while the command's machinery accepts
+   [0..UINT_MAX].  See command_size_to_target_size.  */
 
 static void
-set_record_command (char *args, int from_tty)
+set_record_insn_history_size (char *args, int from_tty,
+                             struct cmd_list_element *c)
 {
-  printf_unfiltered (_("\
-\"set record\" must be followed by an apporpriate subcommand.\n"));
-  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
+  validate_history_size (&record_insn_history_size_setshow_var,
+                        &record_insn_history_size);
 }
 
-static void
-show_record_command (char *args, int from_tty)
-{
-  cmd_show_list (show_record_cmdlist, from_tty, "");
-}
+/* Called by do_setshow_command.  We only want values in the
+   [0..INT_MAX] range, while the command's machinery accepts
+   [0..UINT_MAX].  See command_size_to_target_size.  */
 
 static void
-info_record_command (char *args, int from_tty)
+set_record_call_history_size (char *args, int from_tty,
+                             struct cmd_list_element *c)
 {
-  cmd_show_list (info_record_cmdlist, from_tty, "");
+  validate_history_size (&record_call_history_size_setshow_var,
+                        &record_call_history_size);
 }
 
+/* Provide a prototype to silence -Wmissing-prototypes.  */
+extern initialize_file_ftype _initialize_record;
+
 void
 _initialize_record (void)
 {
-  /* Init record_first.  */
-  record_first.prev = NULL;
-  record_first.next = NULL;
-  record_first.type = record_end;
-
-  init_record_ops ();
-  add_target (&record_ops);
-
-  add_setshow_zinteger_cmd ("record", no_class, &record_debug,
-                           _("Set debugging of record/replay feature."),
-                           _("Show debugging of record/replay feature."),
-                           _("When enabled, debugging output for "
-                             "record/replay feature is displayed."),
-                           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);
+  struct cmd_list_element *c;
+
+  add_setshow_zuinteger_cmd ("record", no_class, &record_debug,
+                            _("Set debugging of record/replay feature."),
+                            _("Show debugging of record/replay feature."),
+                            _("When enabled, debugging output for "
+                              "record/replay feature is displayed."),
+                            NULL, show_record_debug, &setdebuglist,
+                            &showdebuglist);
+
+  add_setshow_uinteger_cmd ("instruction-history-size", no_class,
+                           &record_insn_history_size_setshow_var, _("\
+Set number of instructions to print in \"record instruction-history\"."), _("\
+Show number of instructions to print in \"record instruction-history\"."), _("\
+A size of \"unlimited\" means unlimited instructions.  The default is 10."),
+                           set_record_insn_history_size, NULL,
+                           &set_record_cmdlist, &show_record_cmdlist);
+
+  add_setshow_uinteger_cmd ("function-call-history-size", no_class,
+                           &record_call_history_size_setshow_var, _("\
+Set number of function to print in \"record function-call-history\"."), _("\
+Show number of functions to print in \"record function-call-history\"."), _("\
+A size of \"unlimited\" means unlimited lines.  The default is 10."),
+                           set_record_call_history_size, NULL,
+                           &set_record_cmdlist, &show_record_cmdlist);
+
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+                     _("Start recording."),
+                     &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 +784,12 @@ _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);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
           _("Delete the rest of execution log and start recording it anew."),
@@ -1288,26 +802,61 @@ _initialize_record (void)
            &record_cmdlist);
   add_alias_cmd ("s", "stop", class_obscure, 1, &record_cmdlist);
 
-  /* Record instructions number limit command.  */
-  add_setshow_boolean_cmd ("stop-at-limit", no_class,
-                           &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,
-                           &record_insn_max_num,
-                           _("Set record/replay buffer limit."),
-                           _("Show record/replay buffer limit."), _("\
-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_prefix_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_goto_cmdlist, "record goto ", 1, &record_cmdlist);
+
+  add_cmd ("begin", class_obscure, cmd_record_goto_begin,
+          _("Go to the beginning of the execution log."),
+          &record_goto_cmdlist);
+  add_alias_cmd ("start", "begin", class_obscure, 1, &record_goto_cmdlist);
+
+  add_cmd ("end", class_obscure, cmd_record_goto_end,
+          _("Go to the end of the execution log."),
+          &record_goto_cmdlist);
+
+  add_cmd ("instruction-history", class_obscure, cmd_record_insn_history, _("\
+Print disassembled instructions stored in the execution log.\n\
+With a /m or /s modifier, source lines are included (if available).\n\
+With a /r modifier, raw instructions in hex are included.\n\
+With a /f modifier, function names are omitted.\n\
+With a /p modifier, current position markers are omitted.\n\
+With no argument, disassembles ten more instructions after the previous \
+disassembly.\n\
+\"record instruction-history -\" disassembles ten instructions before a \
+previous disassembly.\n\
+One argument specifies an instruction number as shown by 'info record', and \
+ten instructions are disassembled after that instruction.\n\
+Two arguments with comma between them specify starting and ending instruction \
+numbers to disassemble.\n\
+If the second argument is preceded by '+' or '-', it specifies the distance \
+from the first argument.\n\
+The number of instructions to disassemble can be defined with \"set record \
+instruction-history-size\"."),
+           &record_cmdlist);
+
+  add_cmd ("function-call-history", class_obscure, cmd_record_call_history, _("\
+Prints the execution history at function granularity.\n\
+It prints one line for each sequence of instructions that belong to the same \
+function.\n\
+Without modifiers, it prints the function name.\n\
+With a /l modifier, the source file and line number range is included.\n\
+With a /i modifier, the instruction number range is included.\n\
+With a /c modifier, the output is indented based on the call stack depth.\n\
+With no argument, prints ten more lines after the previous ten-line print.\n\
+\"record function-call-history -\" prints ten lines before a previous ten-line \
+print.\n\
+One argument specifies a function number as shown by 'info record', and \
+ten lines are printed after that function.\n\
+Two arguments with comma between them specify a range of functions to print.\n\
+If the second argument is preceded by '+' or '-', it specifies the distance \
+from the first argument.\n\
+The number of functions to print can be defined with \"set record \
+function-call-history-size\"."),
+           &record_cmdlist);
+
+  /* Sync command control variables.  */
+  record_insn_history_size_setshow_var = record_insn_history_size;
+  record_call_history_size_setshow_var = record_call_history_size;
 }