Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [RFA] Submit process record and replay fourth time, 3/8
@ 2009-03-21 16:02 Hui Zhu
  2009-03-25  7:19 ` Hui Zhu
  2009-04-15 17:02 ` Hui Zhu
  0 siblings, 2 replies; 10+ messages in thread
From: Hui Zhu @ 2009-03-21 16:02 UTC (permalink / raw)
  To: gdb-patches
  Cc: Pedro Alves, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982

[-- Attachment #1: Type: text/plain, Size: 2318 bytes --]

This patch add the process record and replay target.  This is the core
part of process record and replay.

This time, this patch was updated a lot of part according to the ideas of Pedro.

2009-03-21  Hui Zhu  <teawater@gmail.com>

	Process record and replay target.

	* Makefile.in (record.c): New file.
	* record.c, record.h: New file.

	2008-12-28  Michael Snyder  <msnyder@vmware.com>
	* Comments, spelling, white space clean-ups.

	2008-12-26  Michael Snyder  <msnyder@vmware.com>
	* record.h: Don't export record_not_record.
	* record.c (record_not_record): Rename to in_record_wait.
	(record_not_record_set): Rename to in_record_wait_set.
	(record_not_record_cleanup): Rename to in_record_wait_cleanup.
	(record_store_registers): Check in_record_wait flag.
	(record_xfer_partial): Ditto.

	2008-10-07  Michael Snyder  <msnyder@vmware.com>
	* record.h (record_exec_direction): Delete.
	(RECORD_IS_REPLAY): Consult infrun global direction variable.
	* record.c: (record_wait_cleanups): Use infrun state variable.
	(record_wait): Ditto.
	(record_get_exec_direction, record_set_exec_direction): Remove.
	(record_can_execute_reverse): New target method.

	2008-10-06  Michael Snyder  <msnyder@vmware.com>
	* record.c (displaced_step_fixup): Remove.
	(record_message_cleanups): Remove displaced step handling.
	(record_message): Remove displaced step handling.

	2008-10-06  Michael Snyder  <msnyder@vmware.com>
	* record.c (record_regcache_raw_write_regnum): Remove.

	2008-10-05  Michael Snyder  <msnyder@vmware.com>
	* record.c, record.h: Rename execdir to exec_direction.

	2008-10-02  Michael Snyder  <msnyder@vmware.com>
	* record.c (record_open): Call target_can_async_p() instead
	of relying on a global variable.
	* record.h (record_linux_async_permitted): Delete.

	2008-09-19  Michael Snyder  <msnyder@vmware.com>
	* record.c (trivial): Fix two commas in a comment.
	* record.c (record_wait): On end of record log, return
	TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do.

	2008-09-06  Michael Snyder  <msnyder@vmware.com>
	* record.c: Comment and message string cleanup.
	Add some function header comments.

	2008-08-01  Michael Snyder  <msnyder@specifix.com>
	* record.c (_initialize_record): Clarify language in help
	strings.
	Fix up comment format (period must be followed by two spaces).

[-- Attachment #2: 3-record_target.txt --]
[-- Type: text/plain, Size: 37660 bytes --]

---
 Makefile.in |    4 
 record.c    | 1214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 record.h    |   74 +++
 3 files changed, 1290 insertions(+), 2 deletions(-)

--- a/Makefile.in
+++ b/Makefile.in
@@ -666,7 +666,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
 	valarith.c valops.c valprint.c value.c varobj.c vec.c \
 	wrapper.c \
 	xml-tdesc.c xml-support.c \
-	inferior.c
+	inferior.c record.c
 
 LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
 
@@ -817,7 +817,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
 	solib.o solib-null.o \
 	prologue-value.o memory-map.o xml-support.o \
 	target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
-	inferior.o osdata.o
+	inferior.o osdata.o record.o
 
 TSOBS = inflow.o
 
--- /dev/null
+++ b/record.c
@@ -0,0 +1,1214 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "gdbthread.h"
+#include "event-top.h"
+#include "exceptions.h"
+#include "record.h"
+
+#include <signal.h>
+
+#define DEFAULT_RECORD_INSN_MAX_NUM	200000
+
+#define RECORD_IS_REPLAY \
+     (record_list->next || execution_direction == EXEC_REVERSE)
+
+/* This is the debug switch for process record.  */
+int record_debug = 0;
+
+/* This is regcache is used by record_message, record_arch_list_add_reg
+   and gdbarch_process_record to increase the speed that access to the
+   value of the registers.  */
+struct regcache *record_regcache = NULL;
+
+/* These list is for execution log.  */
+static record_t record_first;
+static record_t *record_list = &record_first;
+static record_t *record_arch_list_head = NULL;
+static record_t *record_arch_list_tail = NULL;
+
+/* 1 ask user. 0 auto delete the last record_t.  */
+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 *);
+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 bp_target_info *);
+static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+static void
+record_list_release (record_t * rec)
+{
+  record_t *tmp;
+
+  if (!rec)
+    return;
+
+  while (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);
+    }
+
+  if (rec != &record_first)
+    xfree (rec);
+}
+
+static void
+record_list_release_next (void)
+{
+  record_t *rec = record_list;
+  record_t *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;
+    }
+}
+
+static void
+record_list_release_first (void)
+{
+  record_t *tmp = NULL;
+  enum record_type type;
+
+  if (!record_first.next)
+    return;
+
+  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);
+      tmp = record_first.next;
+      record_first.next = tmp->next;
+      xfree (tmp);
+
+      if (!record_first.next)
+	{
+	  gdb_assert (record_insn_num == 1);
+	  break;
+	}
+
+      record_first.next->prev = &record_first;
+
+      if (type == record_end)
+	break;
+    }
+
+  record_insn_num--;
+}
+
+/* Add a record_t to record_arch_list.  */
+static void
+record_arch_list_add (record_t * rec)
+{
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: record_arch_list_add %s.\n",
+			host_address_to_string (rec));
+
+  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;
+    }
+}
+
+/* Record the value of a register NUM to record_arch_list.  */
+int
+record_arch_list_add_reg (int num)
+{
+  record_t *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add register num = %d to "
+			"record list.\n",
+			num);
+
+  rec = (record_t *) xmalloc (sizeof (record_t));
+  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;
+
+  regcache_raw_read (record_regcache, num, rec->u.reg.val);
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Record the value of a region of memory whose address is ADDR and
+   length is LEN to record_arch_list.  */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+  record_t *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add mem addr = 0x%s len = %d to "
+			"record list.\n",
+			paddr_nz (addr), len);
+
+  if (!addr)
+    return 0;
+
+  rec = (record_t *) xmalloc (sizeof (record_t));
+  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;
+
+  if (target_read_memory (addr, rec->u.mem.val, len))
+    {
+      if (record_debug)
+	fprintf_unfiltered (gdb_stdlog,
+			    "Process record: error reading memory at "
+			    "addr = 0x%s len = %d.\n",
+			    paddr_nz (addr), len);
+      xfree (rec->u.mem.val);
+      xfree (rec);
+      return -1;
+    }
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Add a record_end type record_t to record_arch_list.  */
+int
+record_arch_list_add_end (void)
+{
+  record_t *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add end to arch list.\n");
+
+  rec = (record_t *) xmalloc (sizeof (record_t));
+  rec->prev = NULL;
+  rec->next = NULL;
+  rec->type = record_end;
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+  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."));
+	    }
+	}
+    }
+}
+
+/* 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)
+{
+  int ret;
+  struct gdbarch *gdbarch = args;
+  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  /* Check record_insn_num.  */
+  record_check_insn_num (1);
+
+  record_regcache = get_current_regcache ();
+
+  ret = gdbarch_process_record (gdbarch,
+				regcache_read_pc (record_regcache));
+  if (ret > 0)
+    error (_("Process record: inferior program stopped."));
+  if (ret < 0)
+    error (_("Process record: failed to record execution log."));
+
+  discard_cleanups (old_cleanups);
+
+  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++;
+
+  return 1;
+}
+
+static int
+do_record_message (struct gdbarch *gdbarch)
+{
+  return catch_errors (record_message, gdbarch, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+   doesn't need record.  */
+
+static int record_gdb_operation_disable = 0;
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+  struct cleanup *old_cleanups;
+
+  record_gdb_operation_disable = 0;
+  old_cleanups =
+    make_cleanup_restore_integer (&record_gdb_operation_disable);
+  record_gdb_operation_disable = 1;
+
+  return old_cleanups;
+}
+
+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 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 (current_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;
+    }
+
+  /*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;
+
+  /* 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;
+}
+
+static void
+record_close (int quitting)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+  record_list_release (record_list);
+}
+
+static int record_resume_step = 0;
+static enum target_signal record_resume_siggnal;
+static int record_resume_error;
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+               enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+
+  if (!RECORD_IS_REPLAY)
+    {
+      if (do_record_message (current_gdbarch))
+        {
+          record_resume_error = 0;
+        }
+      else
+        {
+          record_resume_error = 1;
+          return;
+        }
+      record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
+                                siggnal);
+    }
+}
+
+static int record_get_sig = 0;
+
+static void
+record_sig_handler (int signo)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+
+  /* It will break the running inferior in replay mode.  */
+  record_resume_step = 1;
+
+  /* It will let record_wait set inferior status to get the signal
+     SIGINT.  */
+  record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+  if (execution_direction == EXEC_REVERSE)
+    {
+      if (record_list->next)
+	record_list = record_list->next;
+    }
+  else
+    record_list = record_list->prev;
+}
+
+/* In replay mode, this function examines the recorded log and
+   determines where to stop.  */
+
+static ptid_t
+record_wait (struct target_ops *ops,
+              ptid_t ptid, struct target_waitstatus *status)
+{
+  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);
+
+  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_TRAP;
+	  return inferior_ptid;
+	}
+
+      if (record_resume_step)
+	{
+	  /* This is a single step.  */
+	  return record_beneath_to_wait (record_beneath_to_wait_ops,
+                                                      ptid, status);
+	}
+      else
+	{
+	  /* This is not a single step.  */
+	  ptid_t ret;
+	  CORE_ADDR tmp_pc;
+
+	  while (1)
+	    {
+	      ret =
+		record_beneath_to_wait (record_beneath_to_wait_ops, ptid,
+					status);
+
+	      if (status->kind == TARGET_WAITKIND_STOPPED
+		  && status->value.sig == TARGET_SIGNAL_TRAP)
+		{
+		  /* Check if there is a breakpoint.  */
+		  registers_changed ();
+		  tmp_pc = read_pc ();
+		  if (breakpoint_inserted_here_p (tmp_pc))
+		    {
+		      /* There is a breakpoint.  */
+		      CORE_ADDR decr_pc_after_break =
+			gdbarch_decr_pc_after_break (current_gdbarch);
+		      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 (current_gdbarch))
+			{
+                          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;
+	    }
+
+	  return ret;
+	}
+    }
+  else
+    {
+      struct regcache *regcache = get_current_regcache ();
+      int continue_flag = 1;
+      int first_record_end = 1;
+      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+      CORE_ADDR tmp_pc;
+
+      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 (record_debug)
+		fprintf_unfiltered (gdb_stdlog,
+				    "Process record: break at 0x%s.\n",
+				    paddr_nz (tmp_pc));
+	      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+		  && !record_resume_step)
+		regcache_write_pc (regcache,
+				   tmp_pc +
+				   gdbarch_decr_pc_after_break
+				   (get_regcache_arch (regcache)));
+	      goto replay_out;
+	    }
+	}
+
+      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)
+	    {
+	      /* Hit beginning of record log in reverse.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      break;
+	    }
+	  if (execution_direction != EXEC_REVERSE && !record_list->next)
+	    {
+	      /* Hit end of record log going forward.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      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 */
+	      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 = 0x%s len = %d.\n",
+				    host_address_to_string (record_list),
+				    paddr_nz (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))
+		error (_("Process record: error reading memory at "
+			 "addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      if (target_write_memory
+		  (record_list->u.mem.addr, record_list->u.mem.val,
+		   record_list->u.mem.len))
+		error (_
+		       ("Process record: error writing memory at "
+			"addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      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 0x%s.\n",
+					    paddr_nz (tmp_pc));
+		      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+			  && execution_direction == EXEC_FORWARD
+			  && !record_resume_step)
+			regcache_write_pc (regcache,
+					   tmp_pc +
+					   gdbarch_decr_pc_after_break
+					   (get_regcache_arch (regcache)));
+		      continue_flag = 0;
+		    }
+		}
+	    }
+
+next:
+	  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;
+		}
+	    }
+	}
+      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);
+    }
+
+  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");
+
+  unpush_target (&record_ops);
+  target_disconnect (args, from_tty);
+}
+
+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);
+}
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: "
+			            "record_mourn_inferior\n");
+
+  unpush_target (&record_ops);
+  target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process.  */
+static void
+record_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+
+  unpush_target (&record_ops);
+  target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction.  */
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+  /* Check record_insn_num.  */
+  record_check_insn_num (0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  record_regcache = get_current_regcache ();
+
+  if (regnum < 0)
+    {
+      int i;
+      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+	{
+	  if (record_arch_list_add_reg (i))
+	    {
+	      record_list_release (record_arch_list_tail);
+	      error (_("Process record: failed to record execution log."));
+	    }
+	}
+    }
+  else
+    {
+      if (record_arch_list_add_reg (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;
+
+  if (record_insn_num == record_insn_max_num && record_insn_max_num)
+    record_list_release_first ();
+  else
+    record_insn_num++;
+}
+
+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;
+	  struct cleanup *old_cleanups;
+
+	  /* 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?"));
+	  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."));
+	    }
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      record_registers_change (regcache, regno);
+    }
+  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.  */
+
+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)
+{
+  if (!record_gdb_operation_disable
+      && (object == TARGET_OBJECT_MEMORY
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+    {
+      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 0x%s?"),
+		       paddr_nz (offset)))
+	    return -1;
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      /* Check record_insn_num */
+      record_check_insn_num (0);
+
+      /* 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 ())
+	{
+	  record_list_release (record_arch_list_tail);
+	  if (record_debug)
+	    fprintf_unfiltered (gdb_stdlog,
+				_("Process record: failed to record "
+				  "execution log."));
+	  return -1;
+	}
+      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++;
+    }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY.
+   We will not actually insert or remove breakpoints when replaying,
+   nor when recording.  */
+
+static int
+record_insert_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
+
+      do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_remove_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
+
+      do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_can_execute_reverse (void)
+{
+  return 1;
+}
+
+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;
+}
+
+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);
+}
+
+/* Alias for "target record".  */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+  execute_command ("target record", from_tty);
+}
+
+/* Truncate the record log from the present point
+   of replay until the end.  */
+
+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 ();
+	}
+      else
+	  printf_unfiltered (_("Already at end of record list.\n"));
+
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Implement the "stoprecord" 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);
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Set upper limit of record log size.  */
+
+static void
+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"));
+
+      while (record_insn_num > record_insn_max_num)
+	record_list_release_first ();
+    }
+}
+
+/* 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);
+}
+
+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_com ("record", class_obscure, cmd_record_start,
+	   _("Abbreviated form of \"target record\" command."));
+
+  add_com_alias ("rec", "record", class_obscure, 1);
+
+  /* XXX: I try to use some simple commands such as "disconnect" and
+     "detach" to support this functions.  But these commands all have
+     other affect to GDB such as call function "no_shared_libraries".
+     So I add special commands to GDB.  */
+  add_com ("delrecord", class_obscure, cmd_record_delete,
+	   _("Delete the rest of execution log and start recording it anew."));
+  add_com_alias ("dr", "delrecord", class_obscure, 1);
+  add_com ("stoprecord", class_obscure, cmd_record_stop,
+	   _("Stop the record/replay target."));
+  add_com_alias ("sr", "stoprecord", class_obscure, 1);
+
+  /* Record instructions number limit command.  */
+  add_setshow_boolean_cmd ("record-stop-at-limit", no_class,
+			    &record_stop_at_limit,
+			    _("Set whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+			    _("Show whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+	                    _("Enable is default value.\n"
+	                      "When enabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "ask user what to do.\n"
+                              "When disabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "delete it and start new recording."),
+			    NULL, NULL, &setlist, &showlist);
+  add_setshow_zinteger_cmd ("record-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 200000)."),
+			    set_record_insn_max_num,
+			    NULL, &setlist, &showlist);
+  add_info ("record-insn-number", show_record_insn_number,
+	    _("Show the current number of instructions in the "
+	      "record/replay buffer."));
+}
--- /dev/null
+++ b/record.h
@@ -0,0 +1,74 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RECORD_H_
+#define _RECORD_H_
+
+typedef struct record_reg_s
+{
+  int num;
+  gdb_byte *val;
+} record_reg_t;
+
+typedef struct record_mem_s
+{
+  CORE_ADDR addr;
+  int len;
+  gdb_byte *val;
+} record_mem_t;
+
+enum record_type
+{
+  record_end = 0,
+  record_reg,
+  record_mem
+};
+
+/* This is the core struct of record function.
+
+   An entity of record_t 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 record_t ("record_end") that points out this
+   is the last record_t of this instruction.
+
+   Each record_t is linked to "record_list" by "prev" and "next".
+ */
+typedef struct record_s
+{
+  struct record_s *prev;
+  struct record_s *next;
+  enum record_type type;
+  union
+  {
+    /* reg */
+    record_reg_t reg;
+    /* mem */
+    record_mem_t mem;
+  } u;
+} record_t;
+
+extern int record_debug;
+extern struct regcache *record_regcache;
+
+extern int record_arch_list_add_reg (int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup * record_gdb_operation_disable_set (void);
+
+#endif /* _RECORD_H_ */

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-03-21 16:02 [RFA] Submit process record and replay fourth time, 3/8 Hui Zhu
@ 2009-03-25  7:19 ` Hui Zhu
  2009-04-15 17:02 ` Hui Zhu
  1 sibling, 0 replies; 10+ messages in thread
From: Hui Zhu @ 2009-03-25  7:19 UTC (permalink / raw)
  To: gdb-patches
  Cc: Pedro Alves, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982

[-- Attachment #1: Type: text/plain, Size: 3314 bytes --]

New patch change "record_gdb_operation_disable_set" to:
struct cleanup *
record_gdb_operation_disable_set (void)
{
  struct cleanup *old_cleanups = NULL;

  if (!record_gdb_operation_disable)
    {
      old_cleanups =
        make_cleanup_restore_integer (&record_gdb_operation_disable);
      record_gdb_operation_disable = 1;
    }

  return old_cleanups;
}
Because the old one work not very well if call
record_gdb_operation_disable_set a lot of time before clean it.

Thanks,
Hui

On Sun, Mar 22, 2009 at 00:01, Hui Zhu <teawater@gmail.com> wrote:
>
> This patch add the process record and replay target.  This is the core
> part of process record and replay.
>
> This time, this patch was updated a lot of part according to the ideas of Pedro.
>
> 2009-03-21  Hui Zhu  <teawater@gmail.com>
>
>        Process record and replay target.
>
>        * Makefile.in (record.c): New file.
>        * record.c, record.h: New file.
>
>        2008-12-28  Michael Snyder  <msnyder@vmware.com>
>        * Comments, spelling, white space clean-ups.
>
>        2008-12-26  Michael Snyder  <msnyder@vmware.com>
>        * record.h: Don't export record_not_record.
>        * record.c (record_not_record): Rename to in_record_wait.
>        (record_not_record_set): Rename to in_record_wait_set.
>        (record_not_record_cleanup): Rename to in_record_wait_cleanup.
>        (record_store_registers): Check in_record_wait flag.
>        (record_xfer_partial): Ditto.
>
>        2008-10-07  Michael Snyder  <msnyder@vmware.com>
>        * record.h (record_exec_direction): Delete.
>        (RECORD_IS_REPLAY): Consult infrun global direction variable.
>        * record.c: (record_wait_cleanups): Use infrun state variable.
>        (record_wait): Ditto.
>        (record_get_exec_direction, record_set_exec_direction): Remove.
>        (record_can_execute_reverse): New target method.
>
>        2008-10-06  Michael Snyder  <msnyder@vmware.com>
>        * record.c (displaced_step_fixup): Remove.
>        (record_message_cleanups): Remove displaced step handling.
>        (record_message): Remove displaced step handling.
>
>        2008-10-06  Michael Snyder  <msnyder@vmware.com>
>        * record.c (record_regcache_raw_write_regnum): Remove.
>
>        2008-10-05  Michael Snyder  <msnyder@vmware.com>
>        * record.c, record.h: Rename execdir to exec_direction.
>
>        2008-10-02  Michael Snyder  <msnyder@vmware.com>
>        * record.c (record_open): Call target_can_async_p() instead
>        of relying on a global variable.
>        * record.h (record_linux_async_permitted): Delete.
>
>        2008-09-19  Michael Snyder  <msnyder@vmware.com>
>        * record.c (trivial): Fix two commas in a comment.
>        * record.c (record_wait): On end of record log, return
>        TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do.
>
>        2008-09-06  Michael Snyder  <msnyder@vmware.com>
>        * record.c: Comment and message string cleanup.
>        Add some function header comments.
>
>        2008-08-01  Michael Snyder  <msnyder@specifix.com>
>        * record.c (_initialize_record): Clarify language in help
>        strings.
>        Fix up comment format (period must be followed by two spaces).

[-- Attachment #2: 3-record_target.txt --]
[-- Type: text/plain, Size: 37827 bytes --]

---
 Makefile.in |    5 
 record.c    | 1219 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 record.h    |   74 +++
 3 files changed, 1296 insertions(+), 2 deletions(-)

--- a/Makefile.in
+++ b/Makefile.in
@@ -660,7 +660,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
 	valarith.c valops.c valprint.c value.c varobj.c vec.c \
 	wrapper.c \
 	xml-tdesc.c xml-support.c \
-	inferior.c gdb_usleep.c
+	inferior.c gdb_usleep.c \
+	record.c
 
 LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
 
@@ -812,7 +813,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
 	solib.o solib-null.o \
 	prologue-value.o memory-map.o xml-support.o \
 	target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
-	inferior.o osdata.o gdb_usleep.o
+	inferior.o osdata.o gdb_usleep.o record.o
 
 TSOBS = inflow.o
 
--- /dev/null
+++ b/record.c
@@ -0,0 +1,1219 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "gdbthread.h"
+#include "event-top.h"
+#include "exceptions.h"
+#include "record.h"
+
+#include <signal.h>
+
+#define DEFAULT_RECORD_INSN_MAX_NUM	200000
+
+#define RECORD_IS_REPLAY \
+     (record_list->next || execution_direction == EXEC_REVERSE)
+
+/* This is the debug switch for process record.  */
+int record_debug = 0;
+
+/* This is regcache is used by record_message, record_arch_list_add_reg
+   and gdbarch_process_record to increase the speed that access to the
+   value of the registers.  */
+struct regcache *record_regcache = NULL;
+
+/* These list is for execution log.  */
+static record_t record_first;
+static record_t *record_list = &record_first;
+static record_t *record_arch_list_head = NULL;
+static record_t *record_arch_list_tail = NULL;
+
+/* 1 ask user. 0 auto delete the last record_t.  */
+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 *);
+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 bp_target_info *);
+static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+static void
+record_list_release (record_t * rec)
+{
+  record_t *tmp;
+
+  if (!rec)
+    return;
+
+  while (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);
+    }
+
+  if (rec != &record_first)
+    xfree (rec);
+}
+
+static void
+record_list_release_next (void)
+{
+  record_t *rec = record_list;
+  record_t *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;
+    }
+}
+
+static void
+record_list_release_first (void)
+{
+  record_t *tmp = NULL;
+  enum record_type type;
+
+  if (!record_first.next)
+    return;
+
+  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);
+      tmp = record_first.next;
+      record_first.next = tmp->next;
+      xfree (tmp);
+
+      if (!record_first.next)
+	{
+	  gdb_assert (record_insn_num == 1);
+	  break;
+	}
+
+      record_first.next->prev = &record_first;
+
+      if (type == record_end)
+	break;
+    }
+
+  record_insn_num--;
+}
+
+/* Add a record_t to record_arch_list.  */
+static void
+record_arch_list_add (record_t * rec)
+{
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: record_arch_list_add %s.\n",
+			host_address_to_string (rec));
+
+  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;
+    }
+}
+
+/* Record the value of a register NUM to record_arch_list.  */
+int
+record_arch_list_add_reg (int num)
+{
+  record_t *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add register num = %d to "
+			"record list.\n",
+			num);
+
+  rec = (record_t *) xmalloc (sizeof (record_t));
+  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;
+
+  regcache_raw_read (record_regcache, num, rec->u.reg.val);
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Record the value of a region of memory whose address is ADDR and
+   length is LEN to record_arch_list.  */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+  record_t *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add mem addr = 0x%s len = %d to "
+			"record list.\n",
+			paddr_nz (addr), len);
+
+  if (!addr)
+    return 0;
+
+  rec = (record_t *) xmalloc (sizeof (record_t));
+  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;
+
+  if (target_read_memory (addr, rec->u.mem.val, len))
+    {
+      if (record_debug)
+	fprintf_unfiltered (gdb_stdlog,
+			    "Process record: error reading memory at "
+			    "addr = 0x%s len = %d.\n",
+			    paddr_nz (addr), len);
+      xfree (rec->u.mem.val);
+      xfree (rec);
+      return -1;
+    }
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Add a record_end type record_t to record_arch_list.  */
+int
+record_arch_list_add_end (void)
+{
+  record_t *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add end to arch list.\n");
+
+  rec = (record_t *) xmalloc (sizeof (record_t));
+  rec->prev = NULL;
+  rec->next = NULL;
+  rec->type = record_end;
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+  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."));
+	    }
+	}
+    }
+}
+
+/* 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)
+{
+  int ret;
+  struct gdbarch *gdbarch = args;
+  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  /* Check record_insn_num.  */
+  record_check_insn_num (1);
+
+  record_regcache = get_current_regcache ();
+
+  ret = gdbarch_process_record (gdbarch,
+				regcache_read_pc (record_regcache));
+  if (ret > 0)
+    error (_("Process record: inferior program stopped."));
+  if (ret < 0)
+    error (_("Process record: failed to record execution log."));
+
+  discard_cleanups (old_cleanups);
+
+  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++;
+
+  return 1;
+}
+
+static int
+do_record_message (struct gdbarch *gdbarch)
+{
+  return catch_errors (record_message, gdbarch, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+   doesn't need record.  */
+
+static int record_gdb_operation_disable = 0;
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+  struct cleanup *old_cleanups = NULL;
+
+  if (!record_gdb_operation_disable)
+    {
+      old_cleanups =
+        make_cleanup_restore_integer (&record_gdb_operation_disable);
+      record_gdb_operation_disable = 1;
+    }
+
+  return old_cleanups;
+}
+
+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 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 (current_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;
+    }
+
+  /*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;
+
+  /* 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;
+}
+
+static void
+record_close (int quitting)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+  record_list_release (record_list);
+}
+
+static int record_resume_step = 0;
+static enum target_signal record_resume_siggnal;
+static int record_resume_error;
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+               enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+
+  if (!RECORD_IS_REPLAY)
+    {
+      if (do_record_message (current_gdbarch))
+        {
+          record_resume_error = 0;
+        }
+      else
+        {
+          record_resume_error = 1;
+          return;
+        }
+      record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
+                                siggnal);
+    }
+}
+
+static int record_get_sig = 0;
+
+static void
+record_sig_handler (int signo)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+
+  /* It will break the running inferior in replay mode.  */
+  record_resume_step = 1;
+
+  /* It will let record_wait set inferior status to get the signal
+     SIGINT.  */
+  record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+  if (execution_direction == EXEC_REVERSE)
+    {
+      if (record_list->next)
+	record_list = record_list->next;
+    }
+  else
+    record_list = record_list->prev;
+}
+
+/* In replay mode, this function examines the recorded log and
+   determines where to stop.  */
+
+static ptid_t
+record_wait (struct target_ops *ops,
+              ptid_t ptid, struct target_waitstatus *status)
+{
+  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);
+
+  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_TRAP;
+	  return inferior_ptid;
+	}
+
+      if (record_resume_step)
+	{
+	  /* This is a single step.  */
+	  return record_beneath_to_wait (record_beneath_to_wait_ops,
+                                                      ptid, status);
+	}
+      else
+	{
+	  /* This is not a single step.  */
+	  ptid_t ret;
+	  CORE_ADDR tmp_pc;
+
+	  while (1)
+	    {
+	      ret =
+		record_beneath_to_wait (record_beneath_to_wait_ops, ptid,
+					status);
+
+	      if (status->kind == TARGET_WAITKIND_STOPPED
+		  && status->value.sig == TARGET_SIGNAL_TRAP)
+		{
+		  /* Check if there is a breakpoint.  */
+		  registers_changed ();
+		  tmp_pc = read_pc ();
+		  if (breakpoint_inserted_here_p (tmp_pc))
+		    {
+		      /* There is a breakpoint.  */
+		      CORE_ADDR decr_pc_after_break =
+			gdbarch_decr_pc_after_break (current_gdbarch);
+		      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 (current_gdbarch))
+			{
+                          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;
+	    }
+
+	  return ret;
+	}
+    }
+  else
+    {
+      struct regcache *regcache = get_current_regcache ();
+      int continue_flag = 1;
+      int first_record_end = 1;
+      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+      CORE_ADDR tmp_pc;
+
+      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 (record_debug)
+		fprintf_unfiltered (gdb_stdlog,
+				    "Process record: break at 0x%s.\n",
+				    paddr_nz (tmp_pc));
+	      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+		  && !record_resume_step)
+		regcache_write_pc (regcache,
+				   tmp_pc +
+				   gdbarch_decr_pc_after_break
+				   (get_regcache_arch (regcache)));
+	      goto replay_out;
+	    }
+	}
+
+      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)
+	    {
+	      /* Hit beginning of record log in reverse.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      break;
+	    }
+	  if (execution_direction != EXEC_REVERSE && !record_list->next)
+	    {
+	      /* Hit end of record log going forward.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      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 */
+	      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 = 0x%s len = %d.\n",
+				    host_address_to_string (record_list),
+				    paddr_nz (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))
+		error (_("Process record: error reading memory at "
+			 "addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      if (target_write_memory
+		  (record_list->u.mem.addr, record_list->u.mem.val,
+		   record_list->u.mem.len))
+		error (_
+		       ("Process record: error writing memory at "
+			"addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      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 0x%s.\n",
+					    paddr_nz (tmp_pc));
+		      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+			  && execution_direction == EXEC_FORWARD
+			  && !record_resume_step)
+			regcache_write_pc (regcache,
+					   tmp_pc +
+					   gdbarch_decr_pc_after_break
+					   (get_regcache_arch (regcache)));
+		      continue_flag = 0;
+		    }
+		}
+	    }
+
+next:
+	  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;
+		}
+	    }
+	}
+      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);
+    }
+
+  if (set_cleanups)
+    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");
+
+  unpush_target (&record_ops);
+  target_disconnect (args, from_tty);
+}
+
+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);
+}
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: "
+			            "record_mourn_inferior\n");
+
+  unpush_target (&record_ops);
+  target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process.  */
+static void
+record_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+
+  unpush_target (&record_ops);
+  target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction.  */
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+  /* Check record_insn_num.  */
+  record_check_insn_num (0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  record_regcache = get_current_regcache ();
+
+  if (regnum < 0)
+    {
+      int i;
+      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+	{
+	  if (record_arch_list_add_reg (i))
+	    {
+	      record_list_release (record_arch_list_tail);
+	      error (_("Process record: failed to record execution log."));
+	    }
+	}
+    }
+  else
+    {
+      if (record_arch_list_add_reg (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;
+
+  if (record_insn_num == record_insn_max_num && record_insn_max_num)
+    record_list_release_first ();
+  else
+    record_insn_num++;
+}
+
+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;
+	  struct cleanup *old_cleanups;
+
+	  /* 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?"));
+	  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."));
+	    }
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      record_registers_change (regcache, regno);
+    }
+  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.  */
+
+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)
+{
+  if (!record_gdb_operation_disable
+      && (object == TARGET_OBJECT_MEMORY
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+    {
+      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 0x%s?"),
+		       paddr_nz (offset)))
+	    return -1;
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      /* Check record_insn_num */
+      record_check_insn_num (0);
+
+      /* 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 ())
+	{
+	  record_list_release (record_arch_list_tail);
+	  if (record_debug)
+	    fprintf_unfiltered (gdb_stdlog,
+				_("Process record: failed to record "
+				  "execution log."));
+	  return -1;
+	}
+      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++;
+    }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY.
+   We will not actually insert or remove breakpoints when replaying,
+   nor when recording.  */
+
+static int
+record_insert_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
+
+      if (old_cleanups)
+        do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_remove_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
+
+      if (old_cleanups)
+        do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_can_execute_reverse (void)
+{
+  return 1;
+}
+
+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;
+}
+
+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);
+}
+
+/* Alias for "target record".  */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+  execute_command ("target record", from_tty);
+}
+
+/* Truncate the record log from the present point
+   of replay until the end.  */
+
+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 ();
+	}
+      else
+	  printf_unfiltered (_("Already at end of record list.\n"));
+
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Implement the "stoprecord" 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);
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Set upper limit of record log size.  */
+
+static void
+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"));
+
+      while (record_insn_num > record_insn_max_num)
+	record_list_release_first ();
+    }
+}
+
+/* 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);
+}
+
+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_com ("record", class_obscure, cmd_record_start,
+	   _("Abbreviated form of \"target record\" command."));
+
+  add_com_alias ("rec", "record", class_obscure, 1);
+
+  /* XXX: I try to use some simple commands such as "disconnect" and
+     "detach" to support this functions.  But these commands all have
+     other affect to GDB such as call function "no_shared_libraries".
+     So I add special commands to GDB.  */
+  add_com ("delrecord", class_obscure, cmd_record_delete,
+	   _("Delete the rest of execution log and start recording it anew."));
+  add_com_alias ("dr", "delrecord", class_obscure, 1);
+  add_com ("stoprecord", class_obscure, cmd_record_stop,
+	   _("Stop the record/replay target."));
+  add_com_alias ("sr", "stoprecord", class_obscure, 1);
+
+  /* Record instructions number limit command.  */
+  add_setshow_boolean_cmd ("record-stop-at-limit", no_class,
+			    &record_stop_at_limit,
+			    _("Set whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+			    _("Show whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+	                    _("Enable is default value.\n"
+	                      "When enabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "ask user what to do.\n"
+                              "When disabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "delete it and start new recording."),
+			    NULL, NULL, &setlist, &showlist);
+  add_setshow_zinteger_cmd ("record-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 200000)."),
+			    set_record_insn_max_num,
+			    NULL, &setlist, &showlist);
+  add_info ("record-insn-number", show_record_insn_number,
+	    _("Show the current number of instructions in the "
+	      "record/replay buffer."));
+}
--- /dev/null
+++ b/record.h
@@ -0,0 +1,74 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RECORD_H_
+#define _RECORD_H_
+
+typedef struct record_reg_s
+{
+  int num;
+  gdb_byte *val;
+} record_reg_t;
+
+typedef struct record_mem_s
+{
+  CORE_ADDR addr;
+  int len;
+  gdb_byte *val;
+} record_mem_t;
+
+enum record_type
+{
+  record_end = 0,
+  record_reg,
+  record_mem
+};
+
+/* This is the core struct of record function.
+
+   An entity of record_t 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 record_t ("record_end") that points out this
+   is the last record_t of this instruction.
+
+   Each record_t is linked to "record_list" by "prev" and "next".
+ */
+typedef struct record_s
+{
+  struct record_s *prev;
+  struct record_s *next;
+  enum record_type type;
+  union
+  {
+    /* reg */
+    record_reg_t reg;
+    /* mem */
+    record_mem_t mem;
+  } u;
+} record_t;
+
+extern int record_debug;
+extern struct regcache *record_regcache;
+
+extern int record_arch_list_add_reg (int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup * record_gdb_operation_disable_set (void);
+
+#endif /* _RECORD_H_ */

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-03-21 16:02 [RFA] Submit process record and replay fourth time, 3/8 Hui Zhu
  2009-03-25  7:19 ` Hui Zhu
@ 2009-04-15 17:02 ` Hui Zhu
  2009-04-22  9:06   ` Hui Zhu
  1 sibling, 1 reply; 10+ messages in thread
From: Hui Zhu @ 2009-04-15 17:02 UTC (permalink / raw)
  To: gdb-patches
  Cc: Pedro Alves, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982, Mark Kettenis

It's have a lot of big change.
---
 Makefile.in |    5
 record.c    | 1295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 record.h    |   32 +
 3 files changed, 1330 insertions(+), 2 deletions(-)

--- a/Makefile.in
+++ b/Makefile.in
@@ -662,7 +662,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
 	valarith.c valops.c valprint.c value.c varobj.c vec.c \
 	wrapper.c \
 	xml-tdesc.c xml-support.c \
-	inferior.c gdb_usleep.c
+	inferior.c gdb_usleep.c \
+	record.c

 LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c

@@ -814,7 +815,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
 	solib.o solib-null.o \
 	prologue-value.o memory-map.o xml-support.o \
 	target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
-	inferior.o osdata.o gdb_usleep.o
+	inferior.o osdata.o gdb_usleep.o record.o

 TSOBS = inflow.o

--- /dev/null
+++ b/record.c
@@ -0,0 +1,1295 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "gdbthread.h"
+#include "event-top.h"
+#include "exceptions.h"
+#include "record.h"
+
+#include <signal.h>
+
+#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.
+
+   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.
+
+   Each struct record_entry is linked to "record_list" by "prev" and "next". */
+
+struct record_reg_entry
+{
+  int num;
+  gdb_byte *val;
+};
+
+struct record_mem_entry
+{
+  CORE_ADDR addr;
+  int len;
+  gdb_byte *val;
+};
+
+enum record_type
+{
+  record_end = 0,
+  record_reg,
+  record_mem
+};
+
+struct record_entry
+{
+  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;
+};
+
+/* 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 *);
+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 bp_target_info *);
+static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+static void
+record_list_release (struct record_entry *rec)
+{
+  struct record_entry *tmp;
+
+  if (!rec)
+    return;
+
+  while (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);
+    }
+
+  if (rec != &record_first)
+    xfree (rec);
+}
+
+static void
+record_list_release_next (void)
+{
+  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;
+    }
+}
+
+static void
+record_list_release_first (void)
+{
+  struct record_entry *tmp = NULL;
+  enum record_type type;
+
+  if (!record_first.next)
+    return;
+
+  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);
+      tmp = record_first.next;
+      record_first.next = tmp->next;
+      xfree (tmp);
+
+      if (!record_first.next)
+	{
+	  gdb_assert (record_insn_num == 1);
+	  break;
+	}
+
+      record_first.next->prev = &record_first;
+
+      if (type == record_end)
+	break;
+    }
+
+  record_insn_num--;
+}
+
+/* Add a struct record_entry to record_arch_list.  */
+
+static void
+record_arch_list_add (struct record_entry *rec)
+{
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: record_arch_list_add %s.\n",
+			host_address_to_string (rec));
+
+  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;
+    }
+}
+
+/* Record the value of a register NUM to record_arch_list.  */
+
+int
+record_arch_list_add_reg (struct regcache *regcache, int num)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add register num = %d to "
+			"record list.\n",
+			num);
+
+  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;
+
+  regcache_raw_read (regcache, num, rec->u.reg.val);
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Record the value of a region of memory whose address is ADDR and
+   length is LEN to record_arch_list.  */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add mem addr = 0x%s len = %d to "
+			"record list.\n",
+			paddr_nz (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;
+
+  if (target_read_memory (addr, rec->u.mem.val, len))
+    {
+      if (record_debug)
+	fprintf_unfiltered (gdb_stdlog,
+			    "Process record: error reading memory at "
+			    "addr = 0x%s len = %d.\n",
+			    paddr_nz (addr), len);
+      xfree (rec->u.mem.val);
+      xfree (rec);
+      return -1;
+    }
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Add a record_end type struct record_entry to record_arch_list.  */
+
+int
+record_arch_list_add_end (void)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    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;
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+  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."));
+	    }
+	}
+    }
+}
+
+/* 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)
+{
+  int ret;
+  struct regcache *regcache = args;
+  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  /* Check record_insn_num.  */
+  record_check_insn_num (1);
+
+  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."));
+
+  discard_cleanups (old_cleanups);
+
+  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++;
+
+  return 1;
+}
+
+static int
+do_record_message (struct regcache *regcache)
+{
+  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+   doesn't need record.  */
+
+static int record_gdb_operation_disable = 0;
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+  struct cleanup *old_cleanups = NULL;
+
+  if (!record_gdb_operation_disable)
+    {
+      old_cleanups =
+        make_cleanup_restore_integer (&record_gdb_operation_disable);
+      record_gdb_operation_disable = 1;
+    }
+
+  return old_cleanups;
+}
+
+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 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 (current_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;
+    }
+
+  /*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;
+
+  /* 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;
+}
+
+static void
+record_close (int quitting)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+  record_list_release (record_list);
+}
+
+static int record_resume_step = 0;
+static enum target_signal record_resume_siggnal;
+static int record_resume_error;
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+               enum target_signal siggnal)
+{
+  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_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
+                                siggnal);
+    }
+}
+
+static int record_get_sig = 0;
+
+static void
+record_sig_handler (int signo)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+
+  /* It will break the running inferior in replay mode.  */
+  record_resume_step = 1;
+
+  /* It will let record_wait set inferior status to get the signal
+     SIGINT.  */
+  record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+  if (execution_direction == EXEC_REVERSE)
+    {
+      if (record_list->next)
+	record_list = record_list->next;
+    }
+  else
+    record_list = record_list->prev;
+}
+
+/* In replay mode, this function examines the recorded log and
+   determines where to stop.  */
+
+static ptid_t
+record_wait (struct target_ops *ops,
+              ptid_t ptid, struct target_waitstatus *status)
+{
+  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);
+
+  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_TRAP;
+	  return inferior_ptid;
+	}
+
+      if (record_resume_step)
+	{
+	  /* This is a single step.  */
+	  return record_beneath_to_wait (record_beneath_to_wait_ops,
+                                                      ptid, status);
+	}
+      else
+	{
+	  /* This is not a single step.  */
+	  ptid_t ret;
+	  CORE_ADDR tmp_pc;
+
+	  while (1)
+	    {
+	      ret = record_beneath_to_wait (record_beneath_to_wait_ops,
+					    ptid, status);
+
+	      if (status->kind == TARGET_WAITKIND_STOPPED
+		  && status->value.sig == TARGET_SIGNAL_TRAP)
+		{
+		  /* Check if there is a breakpoint.  */
+		  registers_changed ();
+		  tmp_pc = read_pc ();
+		  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;
+	    }
+
+	  return ret;
+	}
+    }
+  else
+    {
+      struct regcache *regcache = get_current_regcache ();
+      int continue_flag = 1;
+      int first_record_end = 1;
+      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+      CORE_ADDR tmp_pc;
+
+      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 (record_debug)
+		fprintf_unfiltered (gdb_stdlog,
+				    "Process record: break at 0x%s.\n",
+				    paddr_nz (tmp_pc));
+	      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+		  && !record_resume_step)
+		regcache_write_pc (regcache,
+				   tmp_pc +
+				   gdbarch_decr_pc_after_break
+				   (get_regcache_arch (regcache)));
+	      goto replay_out;
+	    }
+	}
+
+      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)
+	    {
+	      /* Hit beginning of record log in reverse.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      break;
+	    }
+	  if (execution_direction != EXEC_REVERSE && !record_list->next)
+	    {
+	      /* Hit end of record log going forward.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      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 */
+	      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 = 0x%s len = %d.\n",
+				    host_address_to_string (record_list),
+				    paddr_nz (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))
+		error (_("Process record: error reading memory at "
+			 "addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      if (target_write_memory
+		  (record_list->u.mem.addr, record_list->u.mem.val,
+		   record_list->u.mem.len))
+		error (_
+		       ("Process record: error writing memory at "
+			"addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      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 0x%s.\n",
+					    paddr_nz (tmp_pc));
+		      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+			  && execution_direction == EXEC_FORWARD
+			  && !record_resume_step)
+			regcache_write_pc (regcache,
+					   tmp_pc +
+					   gdbarch_decr_pc_after_break
+					   (get_regcache_arch (regcache)));
+		      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;
+		}
+	    }
+	}
+      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);
+    }
+
+  if (set_cleanups)
+    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");
+
+  unpush_target (&record_ops);
+  target_disconnect (args, from_tty);
+}
+
+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);
+}
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: "
+			            "record_mourn_inferior\n");
+
+  unpush_target (&record_ops);
+  target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process.  */
+
+static void
+record_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+
+  unpush_target (&record_ops);
+  target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction.  */
+
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+  /* Check record_insn_num.  */
+  record_check_insn_num (0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  if (regnum < 0)
+    {
+      int i;
+      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+	{
+	  if (record_arch_list_add_reg (regcache, i))
+	    {
+	      record_list_release (record_arch_list_tail);
+	      error (_("Process record: failed to record execution log."));
+	    }
+	}
+    }
+  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;
+
+  if (record_insn_num == record_insn_max_num && record_insn_max_num)
+    record_list_release_first ();
+  else
+    record_insn_num++;
+}
+
+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;
+	  struct cleanup *old_cleanups;
+
+	  /* 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?"));
+	  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."));
+	    }
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      record_registers_change (regcache, regno);
+    }
+  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.  */
+
+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)
+{
+  if (!record_gdb_operation_disable
+      && (object == TARGET_OBJECT_MEMORY
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+    {
+      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 0x%s?"),
+		       paddr_nz (offset)))
+	    return -1;
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      /* Check record_insn_num */
+      record_check_insn_num (0);
+
+      /* 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 ())
+	{
+	  record_list_release (record_arch_list_tail);
+	  if (record_debug)
+	    fprintf_unfiltered (gdb_stdlog,
+				_("Process record: failed to record "
+				  "execution log."));
+	  return -1;
+	}
+      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++;
+    }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY.
+   We will not actually insert or remove breakpoints when replaying,
+   nor when recording.  */
+
+static int
+record_insert_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
+
+      if (old_cleanups)
+        do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_remove_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
+
+      if (old_cleanups)
+        do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_can_execute_reverse (void)
+{
+  return 1;
+}
+
+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;
+}
+
+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);
+}
+
+/* Alias for "target record".  */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+  execute_command ("target record", from_tty);
+}
+
+/* Truncate the record log from the present point
+   of replay until the end.  */
+
+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 ();
+	}
+      else
+	  printf_unfiltered (_("Already at end of record list.\n"));
+
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Implement the "stoprecord" 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);
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Set upper limit of record log size.  */
+
+static void
+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"));
+
+      while (record_insn_num > record_insn_max_num)
+	record_list_release_first ();
+    }
+}
+
+/* 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;
+
+static void
+set_record_command (char *args, int from_tty)
+{
+  printf_unfiltered (_("\
+\"set record\" must be followed by an apporpriate subcommand.\n"));
+  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
+}
+
+static void
+show_record_command (char *args, int from_tty)
+{
+  cmd_show_list (show_record_cmdlist, from_tty, "");
+}
+
+static void
+info_record_command (char *args, int from_tty)
+{
+  cmd_show_list (info_record_cmdlist, from_tty, "");
+}
+
+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);
+  add_com_alias ("rec", "record", class_obscure, 1);
+  add_prefix_cmd ("record", class_support, set_record_command,
+		  _("Set record options"), &set_record_cmdlist,
+		  "set record ", 0, &setlist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &setlist);
+  add_prefix_cmd ("record", class_support, show_record_command,
+		  _("Show record options"), &show_record_cmdlist,
+		  "show record ", 0, &showlist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &showlist);
+  add_prefix_cmd ("record", class_support, info_record_command,
+		  _("Info record options"), &info_record_cmdlist,
+		  "info record ", 0, &infolist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
+
+
+  add_cmd ("delete", class_obscure, cmd_record_delete,
+	   _("Delete the rest of execution log and start recording it anew."),
+           &record_cmdlist);
+  add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist);
+  add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist);
+
+  add_cmd ("stop", class_obscure, cmd_record_stop,
+	   _("Stop the record/replay target."),
+           &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 stop when "
+			      "record/replay buffer becomes full."),
+			    _("Show whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+	                    _("Enable is default value.\n"
+	                      "When enabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "ask user what to do.\n"
+                              "When disabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "delete it and start new recording."),
+			    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 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);
+}
--- /dev/null
+++ b/record.h
@@ -0,0 +1,32 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RECORD_H_
+#define _RECORD_H_
+
+#define RECORD_IS_USED	(current_target.to_stratum == record_stratum)
+
+extern int record_debug;
+
+extern int record_arch_list_add_reg (struct regcache *regcache, int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup *record_gdb_operation_disable_set (void);
+
+#endif /* _RECORD_H_ */


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-15 17:02 ` Hui Zhu
@ 2009-04-22  9:06   ` Hui Zhu
  2009-04-27  6:01     ` Hui Zhu
  0 siblings, 1 reply; 10+ messages in thread
From: Hui Zhu @ 2009-04-22  9:06 UTC (permalink / raw)
  To: gdb-patches
  Cc: Pedro Alves, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982, Mark Kettenis

I fix the error in "record_gdb_operation_disable_set" in this version.

---
 Makefile.in |    5
 record.c    | 1289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 record.h    |   32 +
 3 files changed, 1324 insertions(+), 2 deletions(-)

--- a/Makefile.in
+++ b/Makefile.in
@@ -664,7 +664,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
 	valarith.c valops.c valprint.c value.c varobj.c vec.c \
 	wrapper.c \
 	xml-tdesc.c xml-support.c \
-	inferior.c gdb_usleep.c
+	inferior.c gdb_usleep.c \
+	record.c

 LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c

@@ -816,7 +817,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
 	solib.o solib-null.o \
 	prologue-value.o memory-map.o xml-support.o \
 	target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
-	inferior.o osdata.o gdb_usleep.o
+	inferior.o osdata.o gdb_usleep.o record.o

 TSOBS = inflow.o

--- /dev/null
+++ b/record.c
@@ -0,0 +1,1289 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "gdbthread.h"
+#include "event-top.h"
+#include "exceptions.h"
+#include "record.h"
+
+#include <signal.h>
+
+#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.
+
+   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.
+
+   Each struct record_entry is linked to "record_list" by "prev" and "next". */
+
+struct record_reg_entry
+{
+  int num;
+  gdb_byte *val;
+};
+
+struct record_mem_entry
+{
+  CORE_ADDR addr;
+  int len;
+  gdb_byte *val;
+};
+
+enum record_type
+{
+  record_end = 0,
+  record_reg,
+  record_mem
+};
+
+struct record_entry
+{
+  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;
+};
+
+/* 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 *);
+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 bp_target_info *);
+static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+static void
+record_list_release (struct record_entry *rec)
+{
+  struct record_entry *tmp;
+
+  if (!rec)
+    return;
+
+  while (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);
+    }
+
+  if (rec != &record_first)
+    xfree (rec);
+}
+
+static void
+record_list_release_next (void)
+{
+  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;
+    }
+}
+
+static void
+record_list_release_first (void)
+{
+  struct record_entry *tmp = NULL;
+  enum record_type type;
+
+  if (!record_first.next)
+    return;
+
+  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);
+      tmp = record_first.next;
+      record_first.next = tmp->next;
+      xfree (tmp);
+
+      if (!record_first.next)
+	{
+	  gdb_assert (record_insn_num == 1);
+	  break;
+	}
+
+      record_first.next->prev = &record_first;
+
+      if (type == record_end)
+	break;
+    }
+
+  record_insn_num--;
+}
+
+/* Add a struct record_entry to record_arch_list.  */
+
+static void
+record_arch_list_add (struct record_entry *rec)
+{
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: record_arch_list_add %s.\n",
+			host_address_to_string (rec));
+
+  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;
+    }
+}
+
+/* Record the value of a register NUM to record_arch_list.  */
+
+int
+record_arch_list_add_reg (struct regcache *regcache, int num)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add register num = %d to "
+			"record list.\n",
+			num);
+
+  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;
+
+  regcache_raw_read (regcache, num, rec->u.reg.val);
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Record the value of a region of memory whose address is ADDR and
+   length is LEN to record_arch_list.  */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add mem addr = 0x%s len = %d to "
+			"record list.\n",
+			paddr_nz (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;
+
+  if (target_read_memory (addr, rec->u.mem.val, len))
+    {
+      if (record_debug)
+	fprintf_unfiltered (gdb_stdlog,
+			    "Process record: error reading memory at "
+			    "addr = 0x%s len = %d.\n",
+			    paddr_nz (addr), len);
+      xfree (rec->u.mem.val);
+      xfree (rec);
+      return -1;
+    }
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Add a record_end type struct record_entry to record_arch_list.  */
+
+int
+record_arch_list_add_end (void)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    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;
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+  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."));
+	    }
+	}
+    }
+}
+
+/* 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)
+{
+  int ret;
+  struct regcache *regcache = args;
+  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  /* Check record_insn_num.  */
+  record_check_insn_num (1);
+
+  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."));
+
+  discard_cleanups (old_cleanups);
+
+  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++;
+
+  return 1;
+}
+
+static int
+do_record_message (struct regcache *regcache)
+{
+  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+   doesn't need record.  */
+
+static int record_gdb_operation_disable = 0;
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+  struct cleanup *old_cleanups = NULL;
+
+  old_cleanups =
+    make_cleanup_restore_integer (&record_gdb_operation_disable);
+  record_gdb_operation_disable = 1;
+
+  return old_cleanups;
+}
+
+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 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 (current_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;
+    }
+
+  /*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;
+
+  /* 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;
+}
+
+static void
+record_close (int quitting)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+  record_list_release (record_list);
+}
+
+static int record_resume_step = 0;
+static enum target_signal record_resume_siggnal;
+static int record_resume_error;
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+               enum target_signal siggnal)
+{
+  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_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
+                                siggnal);
+    }
+}
+
+static int record_get_sig = 0;
+
+static void
+record_sig_handler (int signo)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+
+  /* It will break the running inferior in replay mode.  */
+  record_resume_step = 1;
+
+  /* It will let record_wait set inferior status to get the signal
+     SIGINT.  */
+  record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+  if (execution_direction == EXEC_REVERSE)
+    {
+      if (record_list->next)
+	record_list = record_list->next;
+    }
+  else
+    record_list = record_list->prev;
+}
+
+/* In replay mode, this function examines the recorded log and
+   determines where to stop.  */
+
+static ptid_t
+record_wait (struct target_ops *ops,
+              ptid_t ptid, struct target_waitstatus *status)
+{
+  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);
+
+  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_TRAP;
+	  return inferior_ptid;
+	}
+
+      if (record_resume_step)
+	{
+	  /* This is a single step.  */
+	  return record_beneath_to_wait (record_beneath_to_wait_ops,
+                                                      ptid, status);
+	}
+      else
+	{
+	  /* This is not a single step.  */
+	  ptid_t ret;
+	  CORE_ADDR tmp_pc;
+
+	  while (1)
+	    {
+	      ret = record_beneath_to_wait (record_beneath_to_wait_ops,
+					    ptid, status);
+
+	      if (status->kind == TARGET_WAITKIND_STOPPED
+		  && status->value.sig == TARGET_SIGNAL_TRAP)
+		{
+		  /* Check if there is a breakpoint.  */
+		  registers_changed ();
+		  tmp_pc = read_pc ();
+		  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;
+	    }
+
+	  return ret;
+	}
+    }
+  else
+    {
+      struct regcache *regcache = get_current_regcache ();
+      int continue_flag = 1;
+      int first_record_end = 1;
+      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+      CORE_ADDR tmp_pc;
+
+      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 (record_debug)
+		fprintf_unfiltered (gdb_stdlog,
+				    "Process record: break at 0x%s.\n",
+				    paddr_nz (tmp_pc));
+	      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+		  && !record_resume_step)
+		regcache_write_pc (regcache,
+				   tmp_pc +
+				   gdbarch_decr_pc_after_break
+				   (get_regcache_arch (regcache)));
+	      goto replay_out;
+	    }
+	}
+
+      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)
+	    {
+	      /* Hit beginning of record log in reverse.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      break;
+	    }
+	  if (execution_direction != EXEC_REVERSE && !record_list->next)
+	    {
+	      /* Hit end of record log going forward.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      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 */
+	      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 = 0x%s len = %d.\n",
+				    host_address_to_string (record_list),
+				    paddr_nz (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))
+		error (_("Process record: error reading memory at "
+			 "addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      if (target_write_memory
+		  (record_list->u.mem.addr, record_list->u.mem.val,
+		   record_list->u.mem.len))
+		error (_
+		       ("Process record: error writing memory at "
+			"addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      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 0x%s.\n",
+					    paddr_nz (tmp_pc));
+		      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+			  && execution_direction == EXEC_FORWARD
+			  && !record_resume_step)
+			regcache_write_pc (regcache,
+					   tmp_pc +
+					   gdbarch_decr_pc_after_break
+					   (get_regcache_arch (regcache)));
+		      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;
+		}
+	    }
+	}
+      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);
+    }
+
+  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");
+
+  unpush_target (&record_ops);
+  target_disconnect (args, from_tty);
+}
+
+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);
+}
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: "
+			            "record_mourn_inferior\n");
+
+  unpush_target (&record_ops);
+  target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process.  */
+
+static void
+record_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+
+  unpush_target (&record_ops);
+  target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction.  */
+
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+  /* Check record_insn_num.  */
+  record_check_insn_num (0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  if (regnum < 0)
+    {
+      int i;
+      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+	{
+	  if (record_arch_list_add_reg (regcache, i))
+	    {
+	      record_list_release (record_arch_list_tail);
+	      error (_("Process record: failed to record execution log."));
+	    }
+	}
+    }
+  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;
+
+  if (record_insn_num == record_insn_max_num && record_insn_max_num)
+    record_list_release_first ();
+  else
+    record_insn_num++;
+}
+
+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;
+	  struct cleanup *old_cleanups;
+
+	  /* 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?"));
+	  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."));
+	    }
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      record_registers_change (regcache, regno);
+    }
+  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.  */
+
+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)
+{
+  if (!record_gdb_operation_disable
+      && (object == TARGET_OBJECT_MEMORY
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+    {
+      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 0x%s?"),
+		       paddr_nz (offset)))
+	    return -1;
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      /* Check record_insn_num */
+      record_check_insn_num (0);
+
+      /* 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 ())
+	{
+	  record_list_release (record_arch_list_tail);
+	  if (record_debug)
+	    fprintf_unfiltered (gdb_stdlog,
+				_("Process record: failed to record "
+				  "execution log."));
+	  return -1;
+	}
+      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++;
+    }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY.
+   We will not actually insert or remove breakpoints when replaying,
+   nor when recording.  */
+
+static int
+record_insert_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
+
+      do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_remove_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
+
+      do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_can_execute_reverse (void)
+{
+  return 1;
+}
+
+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;
+}
+
+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);
+}
+
+/* Alias for "target record".  */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+  execute_command ("target record", from_tty);
+}
+
+/* Truncate the record log from the present point
+   of replay until the end.  */
+
+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 ();
+	}
+      else
+	  printf_unfiltered (_("Already at end of record list.\n"));
+
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Implement the "stoprecord" 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);
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Set upper limit of record log size.  */
+
+static void
+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"));
+
+      while (record_insn_num > record_insn_max_num)
+	record_list_release_first ();
+    }
+}
+
+/* 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;
+
+static void
+set_record_command (char *args, int from_tty)
+{
+  printf_unfiltered (_("\
+\"set record\" must be followed by an apporpriate subcommand.\n"));
+  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
+}
+
+static void
+show_record_command (char *args, int from_tty)
+{
+  cmd_show_list (show_record_cmdlist, from_tty, "");
+}
+
+static void
+info_record_command (char *args, int from_tty)
+{
+  cmd_show_list (info_record_cmdlist, from_tty, "");
+}
+
+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);
+  add_com_alias ("rec", "record", class_obscure, 1);
+  add_prefix_cmd ("record", class_support, set_record_command,
+		  _("Set record options"), &set_record_cmdlist,
+		  "set record ", 0, &setlist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &setlist);
+  add_prefix_cmd ("record", class_support, show_record_command,
+		  _("Show record options"), &show_record_cmdlist,
+		  "show record ", 0, &showlist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &showlist);
+  add_prefix_cmd ("record", class_support, info_record_command,
+		  _("Info record options"), &info_record_cmdlist,
+		  "info record ", 0, &infolist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
+
+
+  add_cmd ("delete", class_obscure, cmd_record_delete,
+	   _("Delete the rest of execution log and start recording it anew."),
+           &record_cmdlist);
+  add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist);
+  add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist);
+
+  add_cmd ("stop", class_obscure, cmd_record_stop,
+	   _("Stop the record/replay target."),
+           &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 stop when "
+			      "record/replay buffer becomes full."),
+			    _("Show whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+	                    _("Enable is default value.\n"
+	                      "When enabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "ask user what to do.\n"
+                              "When disabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "delete it and start new recording."),
+			    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 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);
+}
--- /dev/null
+++ b/record.h
@@ -0,0 +1,32 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RECORD_H_
+#define _RECORD_H_
+
+#define RECORD_IS_USED	(current_target.to_stratum == record_stratum)
+
+extern int record_debug;
+
+extern int record_arch_list_add_reg (struct regcache *regcache, int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup *record_gdb_operation_disable_set (void);
+
+#endif /* _RECORD_H_ */


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-22  9:06   ` Hui Zhu
@ 2009-04-27  6:01     ` Hui Zhu
  2009-04-27 21:57       ` Pedro Alves
  0 siblings, 1 reply; 10+ messages in thread
From: Hui Zhu @ 2009-04-27  6:01 UTC (permalink / raw)
  To: gdb-patches
  Cc: Pedro Alves, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982, Mark Kettenis

Hi Pedro,

Could you please help me review this patch?

Thanks,
Hui

On Wed, Apr 22, 2009 at 17:05, Hui Zhu <teawater@gmail.com> wrote:
> I fix the error in "record_gdb_operation_disable_set" in this version.
>
> ---
>  Makefile.in |    5
>  record.c    | 1289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  record.h    |   32 +
>  3 files changed, 1324 insertions(+), 2 deletions(-)
>
> --- a/Makefile.in
> +++ b/Makefile.in
> @@ -664,7 +664,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
>        valarith.c valops.c valprint.c value.c varobj.c vec.c \
>        wrapper.c \
>        xml-tdesc.c xml-support.c \
> -       inferior.c gdb_usleep.c
> +       inferior.c gdb_usleep.c \
> +       record.c
>
>  LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
>
> @@ -816,7 +817,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
>        solib.o solib-null.o \
>        prologue-value.o memory-map.o xml-support.o \
>        target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
> -       inferior.o osdata.o gdb_usleep.o
> +       inferior.o osdata.o gdb_usleep.o record.o
>
>  TSOBS = inflow.o
>
> --- /dev/null
> +++ b/record.c
> @@ -0,0 +1,1289 @@
> +/* Process record and replay target for GDB, the GNU debugger.
> +
> +   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +#include "gdbcmd.h"
> +#include "regcache.h"
> +#include "gdbthread.h"
> +#include "event-top.h"
> +#include "exceptions.h"
> +#include "record.h"
> +
> +#include <signal.h>
> +
> +#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.
> +
> +   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.
> +
> +   Each struct record_entry is linked to "record_list" by "prev" and "next". */
> +
> +struct record_reg_entry
> +{
> +  int num;
> +  gdb_byte *val;
> +};
> +
> +struct record_mem_entry
> +{
> +  CORE_ADDR addr;
> +  int len;
> +  gdb_byte *val;
> +};
> +
> +enum record_type
> +{
> +  record_end = 0,
> +  record_reg,
> +  record_mem
> +};
> +
> +struct record_entry
> +{
> +  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;
> +};
> +
> +/* 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 *);
> +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 bp_target_info *);
> +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
> +
> +static void
> +record_list_release (struct record_entry *rec)
> +{
> +  struct record_entry *tmp;
> +
> +  if (!rec)
> +    return;
> +
> +  while (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);
> +    }
> +
> +  if (rec != &record_first)
> +    xfree (rec);
> +}
> +
> +static void
> +record_list_release_next (void)
> +{
> +  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;
> +    }
> +}
> +
> +static void
> +record_list_release_first (void)
> +{
> +  struct record_entry *tmp = NULL;
> +  enum record_type type;
> +
> +  if (!record_first.next)
> +    return;
> +
> +  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);
> +      tmp = record_first.next;
> +      record_first.next = tmp->next;
> +      xfree (tmp);
> +
> +      if (!record_first.next)
> +       {
> +         gdb_assert (record_insn_num == 1);
> +         break;
> +       }
> +
> +      record_first.next->prev = &record_first;
> +
> +      if (type == record_end)
> +       break;
> +    }
> +
> +  record_insn_num--;
> +}
> +
> +/* Add a struct record_entry to record_arch_list.  */
> +
> +static void
> +record_arch_list_add (struct record_entry *rec)
> +{
> +  if (record_debug > 1)
> +    fprintf_unfiltered (gdb_stdlog,
> +                       "Process record: record_arch_list_add %s.\n",
> +                       host_address_to_string (rec));
> +
> +  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;
> +    }
> +}
> +
> +/* Record the value of a register NUM to record_arch_list.  */
> +
> +int
> +record_arch_list_add_reg (struct regcache *regcache, int num)
> +{
> +  struct record_entry *rec;
> +
> +  if (record_debug > 1)
> +    fprintf_unfiltered (gdb_stdlog,
> +                       "Process record: add register num = %d to "
> +                       "record list.\n",
> +                       num);
> +
> +  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;
> +
> +  regcache_raw_read (regcache, num, rec->u.reg.val);
> +
> +  record_arch_list_add (rec);
> +
> +  return 0;
> +}
> +
> +/* Record the value of a region of memory whose address is ADDR and
> +   length is LEN to record_arch_list.  */
> +
> +int
> +record_arch_list_add_mem (CORE_ADDR addr, int len)
> +{
> +  struct record_entry *rec;
> +
> +  if (record_debug > 1)
> +    fprintf_unfiltered (gdb_stdlog,
> +                       "Process record: add mem addr = 0x%s len = %d to "
> +                       "record list.\n",
> +                       paddr_nz (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;
> +
> +  if (target_read_memory (addr, rec->u.mem.val, len))
> +    {
> +      if (record_debug)
> +       fprintf_unfiltered (gdb_stdlog,
> +                           "Process record: error reading memory at "
> +                           "addr = 0x%s len = %d.\n",
> +                           paddr_nz (addr), len);
> +      xfree (rec->u.mem.val);
> +      xfree (rec);
> +      return -1;
> +    }
> +
> +  record_arch_list_add (rec);
> +
> +  return 0;
> +}
> +
> +/* Add a record_end type struct record_entry to record_arch_list.  */
> +
> +int
> +record_arch_list_add_end (void)
> +{
> +  struct record_entry *rec;
> +
> +  if (record_debug > 1)
> +    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;
> +
> +  record_arch_list_add (rec);
> +
> +  return 0;
> +}
> +
> +static void
> +record_check_insn_num (int set_terminal)
> +{
> +  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."));
> +           }
> +       }
> +    }
> +}
> +
> +/* 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)
> +{
> +  int ret;
> +  struct regcache *regcache = args;
> +  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
> +
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +
> +  /* Check record_insn_num.  */
> +  record_check_insn_num (1);
> +
> +  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."));
> +
> +  discard_cleanups (old_cleanups);
> +
> +  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++;
> +
> +  return 1;
> +}
> +
> +static int
> +do_record_message (struct regcache *regcache)
> +{
> +  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
> +}
> +
> +/* Set to 1 if record_store_registers and record_xfer_partial
> +   doesn't need record.  */
> +
> +static int record_gdb_operation_disable = 0;
> +
> +struct cleanup *
> +record_gdb_operation_disable_set (void)
> +{
> +  struct cleanup *old_cleanups = NULL;
> +
> +  old_cleanups =
> +    make_cleanup_restore_integer (&record_gdb_operation_disable);
> +  record_gdb_operation_disable = 1;
> +
> +  return old_cleanups;
> +}
> +
> +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 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 (current_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;
> +    }
> +
> +  /*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;
> +
> +  /* 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;
> +}
> +
> +static void
> +record_close (int quitting)
> +{
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
> +
> +  record_list_release (record_list);
> +}
> +
> +static int record_resume_step = 0;
> +static enum target_signal record_resume_siggnal;
> +static int record_resume_error;
> +
> +static void
> +record_resume (struct target_ops *ops, ptid_t ptid, int step,
> +               enum target_signal siggnal)
> +{
> +  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_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
> +                                siggnal);
> +    }
> +}
> +
> +static int record_get_sig = 0;
> +
> +static void
> +record_sig_handler (int signo)
> +{
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
> +
> +  /* It will break the running inferior in replay mode.  */
> +  record_resume_step = 1;
> +
> +  /* It will let record_wait set inferior status to get the signal
> +     SIGINT.  */
> +  record_get_sig = 1;
> +}
> +
> +static void
> +record_wait_cleanups (void *ignore)
> +{
> +  if (execution_direction == EXEC_REVERSE)
> +    {
> +      if (record_list->next)
> +       record_list = record_list->next;
> +    }
> +  else
> +    record_list = record_list->prev;
> +}
> +
> +/* In replay mode, this function examines the recorded log and
> +   determines where to stop.  */
> +
> +static ptid_t
> +record_wait (struct target_ops *ops,
> +              ptid_t ptid, struct target_waitstatus *status)
> +{
> +  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);
> +
> +  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_TRAP;
> +         return inferior_ptid;
> +       }
> +
> +      if (record_resume_step)
> +       {
> +         /* This is a single step.  */
> +         return record_beneath_to_wait (record_beneath_to_wait_ops,
> +                                                      ptid, status);
> +       }
> +      else
> +       {
> +         /* This is not a single step.  */
> +         ptid_t ret;
> +         CORE_ADDR tmp_pc;
> +
> +         while (1)
> +           {
> +             ret = record_beneath_to_wait (record_beneath_to_wait_ops,
> +                                           ptid, status);
> +
> +             if (status->kind == TARGET_WAITKIND_STOPPED
> +                 && status->value.sig == TARGET_SIGNAL_TRAP)
> +               {
> +                 /* Check if there is a breakpoint.  */
> +                 registers_changed ();
> +                 tmp_pc = read_pc ();
> +                 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;
> +           }
> +
> +         return ret;
> +       }
> +    }
> +  else
> +    {
> +      struct regcache *regcache = get_current_regcache ();
> +      int continue_flag = 1;
> +      int first_record_end = 1;
> +      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
> +      CORE_ADDR tmp_pc;
> +
> +      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 (record_debug)
> +               fprintf_unfiltered (gdb_stdlog,
> +                                   "Process record: break at 0x%s.\n",
> +                                   paddr_nz (tmp_pc));
> +             if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
> +                 && !record_resume_step)
> +               regcache_write_pc (regcache,
> +                                  tmp_pc +
> +                                  gdbarch_decr_pc_after_break
> +                                  (get_regcache_arch (regcache)));
> +             goto replay_out;
> +           }
> +       }
> +
> +      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)
> +           {
> +             /* Hit beginning of record log in reverse.  */
> +             status->kind = TARGET_WAITKIND_NO_HISTORY;
> +             break;
> +           }
> +         if (execution_direction != EXEC_REVERSE && !record_list->next)
> +           {
> +             /* Hit end of record log going forward.  */
> +             status->kind = TARGET_WAITKIND_NO_HISTORY;
> +             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 */
> +             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 = 0x%s len = %d.\n",
> +                                   host_address_to_string (record_list),
> +                                   paddr_nz (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))
> +               error (_("Process record: error reading memory at "
> +                        "addr = 0x%s len = %d."),
> +                      paddr_nz (record_list->u.mem.addr),
> +                      record_list->u.mem.len);
> +
> +             if (target_write_memory
> +                 (record_list->u.mem.addr, record_list->u.mem.val,
> +                  record_list->u.mem.len))
> +               error (_
> +                      ("Process record: error writing memory at "
> +                       "addr = 0x%s len = %d."),
> +                      paddr_nz (record_list->u.mem.addr),
> +                      record_list->u.mem.len);
> +
> +             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 0x%s.\n",
> +                                           paddr_nz (tmp_pc));
> +                     if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
> +                         && execution_direction == EXEC_FORWARD
> +                         && !record_resume_step)
> +                       regcache_write_pc (regcache,
> +                                          tmp_pc +
> +                                          gdbarch_decr_pc_after_break
> +                                          (get_regcache_arch (regcache)));
> +                     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;
> +               }
> +           }
> +       }
> +      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);
> +    }
> +
> +  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");
> +
> +  unpush_target (&record_ops);
> +  target_disconnect (args, from_tty);
> +}
> +
> +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);
> +}
> +
> +static void
> +record_mourn_inferior (struct target_ops *ops)
> +{
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: "
> +                                   "record_mourn_inferior\n");
> +
> +  unpush_target (&record_ops);
> +  target_mourn_inferior ();
> +}
> +
> +/* Close process record target before killing the inferior process.  */
> +
> +static void
> +record_kill (struct target_ops *ops)
> +{
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
> +
> +  unpush_target (&record_ops);
> +  target_kill ();
> +}
> +
> +/* Record registers change (by user or by GDB) to list as an instruction.  */
> +
> +static void
> +record_registers_change (struct regcache *regcache, int regnum)
> +{
> +  /* Check record_insn_num.  */
> +  record_check_insn_num (0);
> +
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +
> +  if (regnum < 0)
> +    {
> +      int i;
> +      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
> +       {
> +         if (record_arch_list_add_reg (regcache, i))
> +           {
> +             record_list_release (record_arch_list_tail);
> +             error (_("Process record: failed to record execution log."));
> +           }
> +       }
> +    }
> +  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;
> +
> +  if (record_insn_num == record_insn_max_num && record_insn_max_num)
> +    record_list_release_first ();
> +  else
> +    record_insn_num++;
> +}
> +
> +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;
> +         struct cleanup *old_cleanups;
> +
> +         /* 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?"));
> +         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."));
> +           }
> +
> +         /* Destroy the record from here forward.  */
> +         record_list_release_next ();
> +       }
> +
> +      record_registers_change (regcache, regno);
> +    }
> +  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.  */
> +
> +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)
> +{
> +  if (!record_gdb_operation_disable
> +      && (object == TARGET_OBJECT_MEMORY
> +         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
> +    {
> +      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 0x%s?"),
> +                      paddr_nz (offset)))
> +           return -1;
> +
> +         /* Destroy the record from here forward.  */
> +         record_list_release_next ();
> +       }
> +
> +      /* Check record_insn_num */
> +      record_check_insn_num (0);
> +
> +      /* 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 ())
> +       {
> +         record_list_release (record_arch_list_tail);
> +         if (record_debug)
> +           fprintf_unfiltered (gdb_stdlog,
> +                               _("Process record: failed to record "
> +                                 "execution log."));
> +         return -1;
> +       }
> +      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++;
> +    }
> +
> +  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
> +                                         object, annex, readbuf, writebuf,
> +                                         offset, len);
> +}
> +
> +/* Behavior is conditional on RECORD_IS_REPLAY.
> +   We will not actually insert or remove breakpoints when replaying,
> +   nor when recording.  */
> +
> +static int
> +record_insert_breakpoint (struct bp_target_info *bp_tgt)
> +{
> +  if (!RECORD_IS_REPLAY)
> +    {
> +      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
> +      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
> +
> +      do_cleanups (old_cleanups);
> +
> +      return ret;
> +    }
> +
> +  return 0;
> +}
> +
> +static int
> +record_remove_breakpoint (struct bp_target_info *bp_tgt)
> +{
> +  if (!RECORD_IS_REPLAY)
> +    {
> +      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
> +      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
> +
> +      do_cleanups (old_cleanups);
> +
> +      return ret;
> +    }
> +
> +  return 0;
> +}
> +
> +static int
> +record_can_execute_reverse (void)
> +{
> +  return 1;
> +}
> +
> +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;
> +}
> +
> +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);
> +}
> +
> +/* Alias for "target record".  */
> +
> +static void
> +cmd_record_start (char *args, int from_tty)
> +{
> +  execute_command ("target record", from_tty);
> +}
> +
> +/* Truncate the record log from the present point
> +   of replay until the end.  */
> +
> +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 ();
> +       }
> +      else
> +         printf_unfiltered (_("Already at end of record list.\n"));
> +
> +    }
> +  else
> +    printf_unfiltered (_("Process record is not started.\n"));
> +}
> +
> +/* Implement the "stoprecord" 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);
> +    }
> +  else
> +    printf_unfiltered (_("Process record is not started.\n"));
> +}
> +
> +/* Set upper limit of record log size.  */
> +
> +static void
> +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"));
> +
> +      while (record_insn_num > record_insn_max_num)
> +       record_list_release_first ();
> +    }
> +}
> +
> +/* 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;
> +
> +static void
> +set_record_command (char *args, int from_tty)
> +{
> +  printf_unfiltered (_("\
> +\"set record\" must be followed by an apporpriate subcommand.\n"));
> +  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
> +}
> +
> +static void
> +show_record_command (char *args, int from_tty)
> +{
> +  cmd_show_list (show_record_cmdlist, from_tty, "");
> +}
> +
> +static void
> +info_record_command (char *args, int from_tty)
> +{
> +  cmd_show_list (info_record_cmdlist, from_tty, "");
> +}
> +
> +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);
> +  add_com_alias ("rec", "record", class_obscure, 1);
> +  add_prefix_cmd ("record", class_support, set_record_command,
> +                 _("Set record options"), &set_record_cmdlist,
> +                 "set record ", 0, &setlist);
> +  add_alias_cmd ("rec", "record", class_obscure, 1, &setlist);
> +  add_prefix_cmd ("record", class_support, show_record_command,
> +                 _("Show record options"), &show_record_cmdlist,
> +                 "show record ", 0, &showlist);
> +  add_alias_cmd ("rec", "record", class_obscure, 1, &showlist);
> +  add_prefix_cmd ("record", class_support, info_record_command,
> +                 _("Info record options"), &info_record_cmdlist,
> +                 "info record ", 0, &infolist);
> +  add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
> +
> +
> +  add_cmd ("delete", class_obscure, cmd_record_delete,
> +          _("Delete the rest of execution log and start recording it anew."),
> +           &record_cmdlist);
> +  add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist);
> +  add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist);
> +
> +  add_cmd ("stop", class_obscure, cmd_record_stop,
> +          _("Stop the record/replay target."),
> +           &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 stop when "
> +                             "record/replay buffer becomes full."),
> +                           _("Show whether record/replay stop when "
> +                             "record/replay buffer becomes full."),
> +                           _("Enable is default value.\n"
> +                             "When enabled, if the record/replay buffer "
> +                             "becomes full,\n"
> +                              "ask user what to do.\n"
> +                              "When disabled, if the record/replay buffer "
> +                             "becomes full,\n"
> +                              "delete it and start new recording."),
> +                           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 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);
> +}
> --- /dev/null
> +++ b/record.h
> @@ -0,0 +1,32 @@
> +/* Process record and replay target for GDB, the GNU debugger.
> +
> +   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#ifndef _RECORD_H_
> +#define _RECORD_H_
> +
> +#define RECORD_IS_USED (current_target.to_stratum == record_stratum)
> +
> +extern int record_debug;
> +
> +extern int record_arch_list_add_reg (struct regcache *regcache, int num);
> +extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
> +extern int record_arch_list_add_end (void);
> +extern struct cleanup *record_gdb_operation_disable_set (void);
> +
> +#endif /* _RECORD_H_ */
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-27  6:01     ` Hui Zhu
@ 2009-04-27 21:57       ` Pedro Alves
  2009-04-28  1:51         ` Hui Zhu
  0 siblings, 1 reply; 10+ messages in thread
From: Pedro Alves @ 2009-04-27 21:57 UTC (permalink / raw)
  To: gdb-patches
  Cc: Hui Zhu, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982, Mark Kettenis

On Monday 27 April 2009 07:00:48, Hui Zhu wrote:
> Hi Pedro,
> 
> Could you please help me review this patch?

Thank you very much.  This version is approved.  Please don't forget
a ChangeLog entry thought; I don't think I saw one.

> 
> Thanks,
> Hui
> 
> On Wed, Apr 22, 2009 at 17:05, Hui Zhu <teawater@gmail.com> wrote:
> > I fix the error in "record_gdb_operation_disable_set" in this version.
> >
> > ---
> >  Makefile.in |    5
> >  record.c    | 1289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  record.h    |   32 +
> >  3 files changed, 1324 insertions(+), 2 deletions(-)
> >
> > --- a/Makefile.in
> > +++ b/Makefile.in
> > @@ -664,7 +664,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
> >        valarith.c valops.c valprint.c value.c varobj.c vec.c \
> >        wrapper.c \
> >        xml-tdesc.c xml-support.c \
> > -       inferior.c gdb_usleep.c
> > +       inferior.c gdb_usleep.c \
> > +       record.c
> >
> >  LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
> >
> > @@ -816,7 +817,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
> >        solib.o solib-null.o \
> >        prologue-value.o memory-map.o xml-support.o \
> >        target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
> > -       inferior.o osdata.o gdb_usleep.o
> > +       inferior.o osdata.o gdb_usleep.o record.o
> >
> >  TSOBS = inflow.o
> >
> > --- /dev/null
> > +++ b/record.c
> > @@ -0,0 +1,1289 @@
> > +/* Process record and replay target for GDB, the GNU debugger.
> > +
> > +   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
> > +
> > +   This file is part of GDB.
> > +
> > +   This program is free software; you can redistribute it and/or modify
> > +   it under the terms of the GNU General Public License as published by
> > +   the Free Software Foundation; either version 3 of the License, or
> > +   (at your option) any later version.
> > +
> > +   This program is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +   GNU General Public License for more details.
> > +
> > +   You should have received a copy of the GNU General Public License
> > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "defs.h"
> > +#include "gdbcmd.h"
> > +#include "regcache.h"
> > +#include "gdbthread.h"
> > +#include "event-top.h"
> > +#include "exceptions.h"
> > +#include "record.h"
> > +
> > +#include <signal.h>
> > +
> > +#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.
> > +
> > +   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.
> > +
> > +   Each struct record_entry is linked to "record_list" by "prev" and "next". */
> > +
> > +struct record_reg_entry
> > +{
> > +  int num;
> > +  gdb_byte *val;
> > +};
> > +
> > +struct record_mem_entry
> > +{
> > +  CORE_ADDR addr;
> > +  int len;
> > +  gdb_byte *val;
> > +};
> > +
> > +enum record_type
> > +{
> > +  record_end = 0,
> > +  record_reg,
> > +  record_mem
> > +};
> > +
> > +struct record_entry
> > +{
> > +  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;
> > +};
> > +
> > +/* 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 *);
> > +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 bp_target_info *);
> > +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
> > +
> > +static void
> > +record_list_release (struct record_entry *rec)
> > +{
> > +  struct record_entry *tmp;
> > +
> > +  if (!rec)
> > +    return;
> > +
> > +  while (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);
> > +    }
> > +
> > +  if (rec != &record_first)
> > +    xfree (rec);
> > +}
> > +
> > +static void
> > +record_list_release_next (void)
> > +{
> > +  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;
> > +    }
> > +}
> > +
> > +static void
> > +record_list_release_first (void)
> > +{
> > +  struct record_entry *tmp = NULL;
> > +  enum record_type type;
> > +
> > +  if (!record_first.next)
> > +    return;
> > +
> > +  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);
> > +      tmp = record_first.next;
> > +      record_first.next = tmp->next;
> > +      xfree (tmp);
> > +
> > +      if (!record_first.next)
> > +       {
> > +         gdb_assert (record_insn_num == 1);
> > +         break;
> > +       }
> > +
> > +      record_first.next->prev = &record_first;
> > +
> > +      if (type == record_end)
> > +       break;
> > +    }
> > +
> > +  record_insn_num--;
> > +}
> > +
> > +/* Add a struct record_entry to record_arch_list.  */
> > +
> > +static void
> > +record_arch_list_add (struct record_entry *rec)
> > +{
> > +  if (record_debug > 1)
> > +    fprintf_unfiltered (gdb_stdlog,
> > +                       "Process record: record_arch_list_add %s.\n",
> > +                       host_address_to_string (rec));
> > +
> > +  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;
> > +    }
> > +}
> > +
> > +/* Record the value of a register NUM to record_arch_list.  */
> > +
> > +int
> > +record_arch_list_add_reg (struct regcache *regcache, int num)
> > +{
> > +  struct record_entry *rec;
> > +
> > +  if (record_debug > 1)
> > +    fprintf_unfiltered (gdb_stdlog,
> > +                       "Process record: add register num = %d to "
> > +                       "record list.\n",
> > +                       num);
> > +
> > +  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;
> > +
> > +  regcache_raw_read (regcache, num, rec->u.reg.val);
> > +
> > +  record_arch_list_add (rec);
> > +
> > +  return 0;
> > +}
> > +
> > +/* Record the value of a region of memory whose address is ADDR and
> > +   length is LEN to record_arch_list.  */
> > +
> > +int
> > +record_arch_list_add_mem (CORE_ADDR addr, int len)
> > +{
> > +  struct record_entry *rec;
> > +
> > +  if (record_debug > 1)
> > +    fprintf_unfiltered (gdb_stdlog,
> > +                       "Process record: add mem addr = 0x%s len = %d to "
> > +                       "record list.\n",
> > +                       paddr_nz (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;
> > +
> > +  if (target_read_memory (addr, rec->u.mem.val, len))
> > +    {
> > +      if (record_debug)
> > +       fprintf_unfiltered (gdb_stdlog,
> > +                           "Process record: error reading memory at "
> > +                           "addr = 0x%s len = %d.\n",
> > +                           paddr_nz (addr), len);
> > +      xfree (rec->u.mem.val);
> > +      xfree (rec);
> > +      return -1;
> > +    }
> > +
> > +  record_arch_list_add (rec);
> > +
> > +  return 0;
> > +}
> > +
> > +/* Add a record_end type struct record_entry to record_arch_list.  */
> > +
> > +int
> > +record_arch_list_add_end (void)
> > +{
> > +  struct record_entry *rec;
> > +
> > +  if (record_debug > 1)
> > +    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;
> > +
> > +  record_arch_list_add (rec);
> > +
> > +  return 0;
> > +}
> > +
> > +static void
> > +record_check_insn_num (int set_terminal)
> > +{
> > +  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."));
> > +           }
> > +       }
> > +    }
> > +}
> > +
> > +/* 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)
> > +{
> > +  int ret;
> > +  struct regcache *regcache = args;
> > +  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
> > +
> > +  record_arch_list_head = NULL;
> > +  record_arch_list_tail = NULL;
> > +
> > +  /* Check record_insn_num.  */
> > +  record_check_insn_num (1);
> > +
> > +  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."));
> > +
> > +  discard_cleanups (old_cleanups);
> > +
> > +  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++;
> > +
> > +  return 1;
> > +}
> > +
> > +static int
> > +do_record_message (struct regcache *regcache)
> > +{
> > +  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
> > +}
> > +
> > +/* Set to 1 if record_store_registers and record_xfer_partial
> > +   doesn't need record.  */
> > +
> > +static int record_gdb_operation_disable = 0;
> > +
> > +struct cleanup *
> > +record_gdb_operation_disable_set (void)
> > +{
> > +  struct cleanup *old_cleanups = NULL;
> > +
> > +  old_cleanups =
> > +    make_cleanup_restore_integer (&record_gdb_operation_disable);
> > +  record_gdb_operation_disable = 1;
> > +
> > +  return old_cleanups;
> > +}
> > +
> > +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 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 (current_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;
> > +    }
> > +
> > +  /*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;
> > +
> > +  /* 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;
> > +}
> > +
> > +static void
> > +record_close (int quitting)
> > +{
> > +  if (record_debug)
> > +    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
> > +
> > +  record_list_release (record_list);
> > +}
> > +
> > +static int record_resume_step = 0;
> > +static enum target_signal record_resume_siggnal;
> > +static int record_resume_error;
> > +
> > +static void
> > +record_resume (struct target_ops *ops, ptid_t ptid, int step,
> > +               enum target_signal siggnal)
> > +{
> > +  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_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
> > +                                siggnal);
> > +    }
> > +}
> > +
> > +static int record_get_sig = 0;
> > +
> > +static void
> > +record_sig_handler (int signo)
> > +{
> > +  if (record_debug)
> > +    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
> > +
> > +  /* It will break the running inferior in replay mode.  */
> > +  record_resume_step = 1;
> > +
> > +  /* It will let record_wait set inferior status to get the signal
> > +     SIGINT.  */
> > +  record_get_sig = 1;
> > +}
> > +
> > +static void
> > +record_wait_cleanups (void *ignore)
> > +{
> > +  if (execution_direction == EXEC_REVERSE)
> > +    {
> > +      if (record_list->next)
> > +       record_list = record_list->next;
> > +    }
> > +  else
> > +    record_list = record_list->prev;
> > +}
> > +
> > +/* In replay mode, this function examines the recorded log and
> > +   determines where to stop.  */
> > +
> > +static ptid_t
> > +record_wait (struct target_ops *ops,
> > +              ptid_t ptid, struct target_waitstatus *status)
> > +{
> > +  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);
> > +
> > +  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_TRAP;
> > +         return inferior_ptid;
> > +       }
> > +
> > +      if (record_resume_step)
> > +       {
> > +         /* This is a single step.  */
> > +         return record_beneath_to_wait (record_beneath_to_wait_ops,
> > +                                                      ptid, status);
> > +       }
> > +      else
> > +       {
> > +         /* This is not a single step.  */
> > +         ptid_t ret;
> > +         CORE_ADDR tmp_pc;
> > +
> > +         while (1)
> > +           {
> > +             ret = record_beneath_to_wait (record_beneath_to_wait_ops,
> > +                                           ptid, status);
> > +
> > +             if (status->kind == TARGET_WAITKIND_STOPPED
> > +                 && status->value.sig == TARGET_SIGNAL_TRAP)
> > +               {
> > +                 /* Check if there is a breakpoint.  */
> > +                 registers_changed ();
> > +                 tmp_pc = read_pc ();
> > +                 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;
> > +           }
> > +
> > +         return ret;
> > +       }
> > +    }
> > +  else
> > +    {
> > +      struct regcache *regcache = get_current_regcache ();
> > +      int continue_flag = 1;
> > +      int first_record_end = 1;
> > +      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
> > +      CORE_ADDR tmp_pc;
> > +
> > +      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 (record_debug)
> > +               fprintf_unfiltered (gdb_stdlog,
> > +                                   "Process record: break at 0x%s.\n",
> > +                                   paddr_nz (tmp_pc));
> > +             if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
> > +                 && !record_resume_step)
> > +               regcache_write_pc (regcache,
> > +                                  tmp_pc +
> > +                                  gdbarch_decr_pc_after_break
> > +                                  (get_regcache_arch (regcache)));
> > +             goto replay_out;
> > +           }
> > +       }
> > +
> > +      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)
> > +           {
> > +             /* Hit beginning of record log in reverse.  */
> > +             status->kind = TARGET_WAITKIND_NO_HISTORY;
> > +             break;
> > +           }
> > +         if (execution_direction != EXEC_REVERSE && !record_list->next)
> > +           {
> > +             /* Hit end of record log going forward.  */
> > +             status->kind = TARGET_WAITKIND_NO_HISTORY;
> > +             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 */
> > +             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 = 0x%s len = %d.\n",
> > +                                   host_address_to_string (record_list),
> > +                                   paddr_nz (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))
> > +               error (_("Process record: error reading memory at "
> > +                        "addr = 0x%s len = %d."),
> > +                      paddr_nz (record_list->u.mem.addr),
> > +                      record_list->u.mem.len);
> > +
> > +             if (target_write_memory
> > +                 (record_list->u.mem.addr, record_list->u.mem.val,
> > +                  record_list->u.mem.len))
> > +               error (_
> > +                      ("Process record: error writing memory at "
> > +                       "addr = 0x%s len = %d."),
> > +                      paddr_nz (record_list->u.mem.addr),
> > +                      record_list->u.mem.len);
> > +
> > +             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 0x%s.\n",
> > +                                           paddr_nz (tmp_pc));
> > +                     if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
> > +                         && execution_direction == EXEC_FORWARD
> > +                         && !record_resume_step)
> > +                       regcache_write_pc (regcache,
> > +                                          tmp_pc +
> > +                                          gdbarch_decr_pc_after_break
> > +                                          (get_regcache_arch (regcache)));
> > +                     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;
> > +               }
> > +           }
> > +       }
> > +      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);
> > +    }
> > +
> > +  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");
> > +
> > +  unpush_target (&record_ops);
> > +  target_disconnect (args, from_tty);
> > +}
> > +
> > +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);
> > +}
> > +
> > +static void
> > +record_mourn_inferior (struct target_ops *ops)
> > +{
> > +  if (record_debug)
> > +    fprintf_unfiltered (gdb_stdlog, "Process record: "
> > +                                   "record_mourn_inferior\n");
> > +
> > +  unpush_target (&record_ops);
> > +  target_mourn_inferior ();
> > +}
> > +
> > +/* Close process record target before killing the inferior process.  */
> > +
> > +static void
> > +record_kill (struct target_ops *ops)
> > +{
> > +  if (record_debug)
> > +    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
> > +
> > +  unpush_target (&record_ops);
> > +  target_kill ();
> > +}
> > +
> > +/* Record registers change (by user or by GDB) to list as an instruction.  */
> > +
> > +static void
> > +record_registers_change (struct regcache *regcache, int regnum)
> > +{
> > +  /* Check record_insn_num.  */
> > +  record_check_insn_num (0);
> > +
> > +  record_arch_list_head = NULL;
> > +  record_arch_list_tail = NULL;
> > +
> > +  if (regnum < 0)
> > +    {
> > +      int i;
> > +      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
> > +       {
> > +         if (record_arch_list_add_reg (regcache, i))
> > +           {
> > +             record_list_release (record_arch_list_tail);
> > +             error (_("Process record: failed to record execution log."));
> > +           }
> > +       }
> > +    }
> > +  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;
> > +
> > +  if (record_insn_num == record_insn_max_num && record_insn_max_num)
> > +    record_list_release_first ();
> > +  else
> > +    record_insn_num++;
> > +}
> > +
> > +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;
> > +         struct cleanup *old_cleanups;
> > +
> > +         /* 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?"));
> > +         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."));
> > +           }
> > +
> > +         /* Destroy the record from here forward.  */
> > +         record_list_release_next ();
> > +       }
> > +
> > +      record_registers_change (regcache, regno);
> > +    }
> > +  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.  */
> > +
> > +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)
> > +{
> > +  if (!record_gdb_operation_disable
> > +      && (object == TARGET_OBJECT_MEMORY
> > +         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
> > +    {
> > +      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 0x%s?"),
> > +                      paddr_nz (offset)))
> > +           return -1;
> > +
> > +         /* Destroy the record from here forward.  */
> > +         record_list_release_next ();
> > +       }
> > +
> > +      /* Check record_insn_num */
> > +      record_check_insn_num (0);
> > +
> > +      /* 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 ())
> > +       {
> > +         record_list_release (record_arch_list_tail);
> > +         if (record_debug)
> > +           fprintf_unfiltered (gdb_stdlog,
> > +                               _("Process record: failed to record "
> > +                                 "execution log."));
> > +         return -1;
> > +       }
> > +      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++;
> > +    }
> > +
> > +  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
> > +                                         object, annex, readbuf, writebuf,
> > +                                         offset, len);
> > +}
> > +
> > +/* Behavior is conditional on RECORD_IS_REPLAY.
> > +   We will not actually insert or remove breakpoints when replaying,
> > +   nor when recording.  */
> > +
> > +static int
> > +record_insert_breakpoint (struct bp_target_info *bp_tgt)
> > +{
> > +  if (!RECORD_IS_REPLAY)
> > +    {
> > +      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
> > +      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
> > +
> > +      do_cleanups (old_cleanups);
> > +
> > +      return ret;
> > +    }
> > +
> > +  return 0;
> > +}
> > +
> > +static int
> > +record_remove_breakpoint (struct bp_target_info *bp_tgt)
> > +{
> > +  if (!RECORD_IS_REPLAY)
> > +    {
> > +      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
> > +      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
> > +
> > +      do_cleanups (old_cleanups);
> > +
> > +      return ret;
> > +    }
> > +
> > +  return 0;
> > +}
> > +
> > +static int
> > +record_can_execute_reverse (void)
> > +{
> > +  return 1;
> > +}
> > +
> > +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;
> > +}
> > +
> > +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);
> > +}
> > +
> > +/* Alias for "target record".  */
> > +
> > +static void
> > +cmd_record_start (char *args, int from_tty)
> > +{
> > +  execute_command ("target record", from_tty);
> > +}
> > +
> > +/* Truncate the record log from the present point
> > +   of replay until the end.  */
> > +
> > +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 ();
> > +       }
> > +      else
> > +         printf_unfiltered (_("Already at end of record list.\n"));
> > +
> > +    }
> > +  else
> > +    printf_unfiltered (_("Process record is not started.\n"));
> > +}
> > +
> > +/* Implement the "stoprecord" 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);
> > +    }
> > +  else
> > +    printf_unfiltered (_("Process record is not started.\n"));
> > +}
> > +
> > +/* Set upper limit of record log size.  */
> > +
> > +static void
> > +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"));
> > +
> > +      while (record_insn_num > record_insn_max_num)
> > +       record_list_release_first ();
> > +    }
> > +}
> > +
> > +/* 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;
> > +
> > +static void
> > +set_record_command (char *args, int from_tty)
> > +{
> > +  printf_unfiltered (_("\
> > +\"set record\" must be followed by an apporpriate subcommand.\n"));
> > +  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
> > +}
> > +
> > +static void
> > +show_record_command (char *args, int from_tty)
> > +{
> > +  cmd_show_list (show_record_cmdlist, from_tty, "");
> > +}
> > +
> > +static void
> > +info_record_command (char *args, int from_tty)
> > +{
> > +  cmd_show_list (info_record_cmdlist, from_tty, "");
> > +}
> > +
> > +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);
> > +  add_com_alias ("rec", "record", class_obscure, 1);
> > +  add_prefix_cmd ("record", class_support, set_record_command,
> > +                 _("Set record options"), &set_record_cmdlist,
> > +                 "set record ", 0, &setlist);
> > +  add_alias_cmd ("rec", "record", class_obscure, 1, &setlist);
> > +  add_prefix_cmd ("record", class_support, show_record_command,
> > +                 _("Show record options"), &show_record_cmdlist,
> > +                 "show record ", 0, &showlist);
> > +  add_alias_cmd ("rec", "record", class_obscure, 1, &showlist);
> > +  add_prefix_cmd ("record", class_support, info_record_command,
> > +                 _("Info record options"), &info_record_cmdlist,
> > +                 "info record ", 0, &infolist);
> > +  add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
> > +
> > +
> > +  add_cmd ("delete", class_obscure, cmd_record_delete,
> > +          _("Delete the rest of execution log and start recording it anew."),
> > +           &record_cmdlist);
> > +  add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist);
> > +  add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist);
> > +
> > +  add_cmd ("stop", class_obscure, cmd_record_stop,
> > +          _("Stop the record/replay target."),
> > +           &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 stop when "
> > +                             "record/replay buffer becomes full."),
> > +                           _("Show whether record/replay stop when "
> > +                             "record/replay buffer becomes full."),
> > +                           _("Enable is default value.\n"
> > +                             "When enabled, if the record/replay buffer "
> > +                             "becomes full,\n"
> > +                              "ask user what to do.\n"
> > +                              "When disabled, if the record/replay buffer "
> > +                             "becomes full,\n"
> > +                              "delete it and start new recording."),
> > +                           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 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);
> > +}
> > --- /dev/null
> > +++ b/record.h
> > @@ -0,0 +1,32 @@
> > +/* Process record and replay target for GDB, the GNU debugger.
> > +
> > +   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
> > +
> > +   This file is part of GDB.
> > +
> > +   This program is free software; you can redistribute it and/or modify
> > +   it under the terms of the GNU General Public License as published by
> > +   the Free Software Foundation; either version 3 of the License, or
> > +   (at your option) any later version.
> > +
> > +   This program is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +   GNU General Public License for more details.
> > +
> > +   You should have received a copy of the GNU General Public License
> > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> > +
> > +#ifndef _RECORD_H_
> > +#define _RECORD_H_
> > +
> > +#define RECORD_IS_USED (current_target.to_stratum == record_stratum)
> > +
> > +extern int record_debug;
> > +
> > +extern int record_arch_list_add_reg (struct regcache *regcache, int num);
> > +extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
> > +extern int record_arch_list_add_end (void);
> > +extern struct cleanup *record_gdb_operation_disable_set (void);
> > +
> > +#endif /* _RECORD_H_ */
> >
> 



-- 
Pedro Alves


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-27 21:57       ` Pedro Alves
@ 2009-04-28  1:51         ` Hui Zhu
  2009-04-28 10:12           ` Hui Zhu
  0 siblings, 1 reply; 10+ messages in thread
From: Hui Zhu @ 2009-04-28  1:51 UTC (permalink / raw)
  To: Pedro Alves
  Cc: gdb-patches, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982, Mark Kettenis

This is the changelog:
2009-04-28  Hui Zhu  <teawater@gmail.com>

       Process record and replay target.

       * Makefile.in (record.c): New file.
       * record.c, record.h: New file.

       2008-12-28  Michael Snyder  <msnyder@vmware.com>
       * Comments, spelling, white space clean-ups.

       2008-12-26  Michael Snyder  <msnyder@vmware.com>
       * record.h: Don't export record_not_record.
       * record.c (record_not_record): Rename to in_record_wait.
       (record_not_record_set): Rename to in_record_wait_set.
       (record_not_record_cleanup): Rename to in_record_wait_cleanup.
       (record_store_registers): Check in_record_wait flag.
       (record_xfer_partial): Ditto.

       2008-10-07  Michael Snyder  <msnyder@vmware.com>
       * record.h (record_exec_direction): Delete.
       (RECORD_IS_REPLAY): Consult infrun global direction variable.
       * record.c: (record_wait_cleanups): Use infrun state variable.
       (record_wait): Ditto.
       (record_get_exec_direction, record_set_exec_direction): Remove.
       (record_can_execute_reverse): New target method.

       2008-10-06  Michael Snyder  <msnyder@vmware.com>
       * record.c (displaced_step_fixup): Remove.
       (record_message_cleanups): Remove displaced step handling.
       (record_message): Remove displaced step handling.

       2008-10-06  Michael Snyder  <msnyder@vmware.com>
       * record.c (record_regcache_raw_write_regnum): Remove.

       2008-10-05  Michael Snyder  <msnyder@vmware.com>
       * record.c, record.h: Rename execdir to exec_direction.

       2008-10-02  Michael Snyder  <msnyder@vmware.com>
       * record.c (record_open): Call target_can_async_p() instead
       of relying on a global variable.
       * record.h (record_linux_async_permitted): Delete.

       2008-09-19  Michael Snyder  <msnyder@vmware.com>
       * record.c (trivial): Fix two commas in a comment.
       * record.c (record_wait): On end of record log, return
       TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do.

       2008-09-06  Michael Snyder  <msnyder@vmware.com>
       * record.c: Comment and message string cleanup.
       Add some function header comments.

       2008-08-01  Michael Snyder  <msnyder@specifix.com>
       * record.c (_initialize_record): Clarify language in help
       strings.
       Fix up comment format (period must be followed by two spaces).

Thanks,
Hui


On Tue, Apr 28, 2009 at 05:54, Pedro Alves <pedro@codesourcery.com> wrote:
> On Monday 27 April 2009 07:00:48, Hui Zhu wrote:
>> Hi Pedro,
>>
>> Could you please help me review this patch?
>
> Thank you very much.  This version is approved.  Please don't forget
> a ChangeLog entry thought; I don't think I saw one.
>
>>
>> Thanks,
>> Hui
>>
>> On Wed, Apr 22, 2009 at 17:05, Hui Zhu <teawater@gmail.com> wrote:
>> > I fix the error in "record_gdb_operation_disable_set" in this version.
>> >
>> > ---
>> >  Makefile.in |    5
>> >  record.c    | 1289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> >  record.h    |   32 +
>> >  3 files changed, 1324 insertions(+), 2 deletions(-)
>> >
>> > --- a/Makefile.in
>> > +++ b/Makefile.in
>> > @@ -664,7 +664,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
>> >        valarith.c valops.c valprint.c value.c varobj.c vec.c \
>> >        wrapper.c \
>> >        xml-tdesc.c xml-support.c \
>> > -       inferior.c gdb_usleep.c
>> > +       inferior.c gdb_usleep.c \
>> > +       record.c
>> >
>> >  LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
>> >
>> > @@ -816,7 +817,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
>> >        solib.o solib-null.o \
>> >        prologue-value.o memory-map.o xml-support.o \
>> >        target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
>> > -       inferior.o osdata.o gdb_usleep.o
>> > +       inferior.o osdata.o gdb_usleep.o record.o
>> >
>> >  TSOBS = inflow.o
>> >
>> > --- /dev/null
>> > +++ b/record.c
>> > @@ -0,0 +1,1289 @@
>> > +/* Process record and replay target for GDB, the GNU debugger.
>> > +
>> > +   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
>> > +
>> > +   This file is part of GDB.
>> > +
>> > +   This program is free software; you can redistribute it and/or modify
>> > +   it under the terms of the GNU General Public License as published by
>> > +   the Free Software Foundation; either version 3 of the License, or
>> > +   (at your option) any later version.
>> > +
>> > +   This program is distributed in the hope that it will be useful,
>> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> > +   GNU General Public License for more details.
>> > +
>> > +   You should have received a copy of the GNU General Public License
>> > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> > +
>> > +#include "defs.h"
>> > +#include "gdbcmd.h"
>> > +#include "regcache.h"
>> > +#include "gdbthread.h"
>> > +#include "event-top.h"
>> > +#include "exceptions.h"
>> > +#include "record.h"
>> > +
>> > +#include <signal.h>
>> > +
>> > +#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.
>> > +
>> > +   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.
>> > +
>> > +   Each struct record_entry is linked to "record_list" by "prev" and "next". */
>> > +
>> > +struct record_reg_entry
>> > +{
>> > +  int num;
>> > +  gdb_byte *val;
>> > +};
>> > +
>> > +struct record_mem_entry
>> > +{
>> > +  CORE_ADDR addr;
>> > +  int len;
>> > +  gdb_byte *val;
>> > +};
>> > +
>> > +enum record_type
>> > +{
>> > +  record_end = 0,
>> > +  record_reg,
>> > +  record_mem
>> > +};
>> > +
>> > +struct record_entry
>> > +{
>> > +  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;
>> > +};
>> > +
>> > +/* 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 *);
>> > +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 bp_target_info *);
>> > +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
>> > +
>> > +static void
>> > +record_list_release (struct record_entry *rec)
>> > +{
>> > +  struct record_entry *tmp;
>> > +
>> > +  if (!rec)
>> > +    return;
>> > +
>> > +  while (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);
>> > +    }
>> > +
>> > +  if (rec != &record_first)
>> > +    xfree (rec);
>> > +}
>> > +
>> > +static void
>> > +record_list_release_next (void)
>> > +{
>> > +  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;
>> > +    }
>> > +}
>> > +
>> > +static void
>> > +record_list_release_first (void)
>> > +{
>> > +  struct record_entry *tmp = NULL;
>> > +  enum record_type type;
>> > +
>> > +  if (!record_first.next)
>> > +    return;
>> > +
>> > +  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);
>> > +      tmp = record_first.next;
>> > +      record_first.next = tmp->next;
>> > +      xfree (tmp);
>> > +
>> > +      if (!record_first.next)
>> > +       {
>> > +         gdb_assert (record_insn_num == 1);
>> > +         break;
>> > +       }
>> > +
>> > +      record_first.next->prev = &record_first;
>> > +
>> > +      if (type == record_end)
>> > +       break;
>> > +    }
>> > +
>> > +  record_insn_num--;
>> > +}
>> > +
>> > +/* Add a struct record_entry to record_arch_list.  */
>> > +
>> > +static void
>> > +record_arch_list_add (struct record_entry *rec)
>> > +{
>> > +  if (record_debug > 1)
>> > +    fprintf_unfiltered (gdb_stdlog,
>> > +                       "Process record: record_arch_list_add %s.\n",
>> > +                       host_address_to_string (rec));
>> > +
>> > +  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;
>> > +    }
>> > +}
>> > +
>> > +/* Record the value of a register NUM to record_arch_list.  */
>> > +
>> > +int
>> > +record_arch_list_add_reg (struct regcache *regcache, int num)
>> > +{
>> > +  struct record_entry *rec;
>> > +
>> > +  if (record_debug > 1)
>> > +    fprintf_unfiltered (gdb_stdlog,
>> > +                       "Process record: add register num = %d to "
>> > +                       "record list.\n",
>> > +                       num);
>> > +
>> > +  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;
>> > +
>> > +  regcache_raw_read (regcache, num, rec->u.reg.val);
>> > +
>> > +  record_arch_list_add (rec);
>> > +
>> > +  return 0;
>> > +}
>> > +
>> > +/* Record the value of a region of memory whose address is ADDR and
>> > +   length is LEN to record_arch_list.  */
>> > +
>> > +int
>> > +record_arch_list_add_mem (CORE_ADDR addr, int len)
>> > +{
>> > +  struct record_entry *rec;
>> > +
>> > +  if (record_debug > 1)
>> > +    fprintf_unfiltered (gdb_stdlog,
>> > +                       "Process record: add mem addr = 0x%s len = %d to "
>> > +                       "record list.\n",
>> > +                       paddr_nz (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;
>> > +
>> > +  if (target_read_memory (addr, rec->u.mem.val, len))
>> > +    {
>> > +      if (record_debug)
>> > +       fprintf_unfiltered (gdb_stdlog,
>> > +                           "Process record: error reading memory at "
>> > +                           "addr = 0x%s len = %d.\n",
>> > +                           paddr_nz (addr), len);
>> > +      xfree (rec->u.mem.val);
>> > +      xfree (rec);
>> > +      return -1;
>> > +    }
>> > +
>> > +  record_arch_list_add (rec);
>> > +
>> > +  return 0;
>> > +}
>> > +
>> > +/* Add a record_end type struct record_entry to record_arch_list.  */
>> > +
>> > +int
>> > +record_arch_list_add_end (void)
>> > +{
>> > +  struct record_entry *rec;
>> > +
>> > +  if (record_debug > 1)
>> > +    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;
>> > +
>> > +  record_arch_list_add (rec);
>> > +
>> > +  return 0;
>> > +}
>> > +
>> > +static void
>> > +record_check_insn_num (int set_terminal)
>> > +{
>> > +  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."));
>> > +           }
>> > +       }
>> > +    }
>> > +}
>> > +
>> > +/* 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)
>> > +{
>> > +  int ret;
>> > +  struct regcache *regcache = args;
>> > +  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
>> > +
>> > +  record_arch_list_head = NULL;
>> > +  record_arch_list_tail = NULL;
>> > +
>> > +  /* Check record_insn_num.  */
>> > +  record_check_insn_num (1);
>> > +
>> > +  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."));
>> > +
>> > +  discard_cleanups (old_cleanups);
>> > +
>> > +  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++;
>> > +
>> > +  return 1;
>> > +}
>> > +
>> > +static int
>> > +do_record_message (struct regcache *regcache)
>> > +{
>> > +  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
>> > +}
>> > +
>> > +/* Set to 1 if record_store_registers and record_xfer_partial
>> > +   doesn't need record.  */
>> > +
>> > +static int record_gdb_operation_disable = 0;
>> > +
>> > +struct cleanup *
>> > +record_gdb_operation_disable_set (void)
>> > +{
>> > +  struct cleanup *old_cleanups = NULL;
>> > +
>> > +  old_cleanups =
>> > +    make_cleanup_restore_integer (&record_gdb_operation_disable);
>> > +  record_gdb_operation_disable = 1;
>> > +
>> > +  return old_cleanups;
>> > +}
>> > +
>> > +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 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 (current_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;
>> > +    }
>> > +
>> > +  /*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;
>> > +
>> > +  /* 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;
>> > +}
>> > +
>> > +static void
>> > +record_close (int quitting)
>> > +{
>> > +  if (record_debug)
>> > +    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
>> > +
>> > +  record_list_release (record_list);
>> > +}
>> > +
>> > +static int record_resume_step = 0;
>> > +static enum target_signal record_resume_siggnal;
>> > +static int record_resume_error;
>> > +
>> > +static void
>> > +record_resume (struct target_ops *ops, ptid_t ptid, int step,
>> > +               enum target_signal siggnal)
>> > +{
>> > +  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_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
>> > +                                siggnal);
>> > +    }
>> > +}
>> > +
>> > +static int record_get_sig = 0;
>> > +
>> > +static void
>> > +record_sig_handler (int signo)
>> > +{
>> > +  if (record_debug)
>> > +    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
>> > +
>> > +  /* It will break the running inferior in replay mode.  */
>> > +  record_resume_step = 1;
>> > +
>> > +  /* It will let record_wait set inferior status to get the signal
>> > +     SIGINT.  */
>> > +  record_get_sig = 1;
>> > +}
>> > +
>> > +static void
>> > +record_wait_cleanups (void *ignore)
>> > +{
>> > +  if (execution_direction == EXEC_REVERSE)
>> > +    {
>> > +      if (record_list->next)
>> > +       record_list = record_list->next;
>> > +    }
>> > +  else
>> > +    record_list = record_list->prev;
>> > +}
>> > +
>> > +/* In replay mode, this function examines the recorded log and
>> > +   determines where to stop.  */
>> > +
>> > +static ptid_t
>> > +record_wait (struct target_ops *ops,
>> > +              ptid_t ptid, struct target_waitstatus *status)
>> > +{
>> > +  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);
>> > +
>> > +  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_TRAP;
>> > +         return inferior_ptid;
>> > +       }
>> > +
>> > +      if (record_resume_step)
>> > +       {
>> > +         /* This is a single step.  */
>> > +         return record_beneath_to_wait (record_beneath_to_wait_ops,
>> > +                                                      ptid, status);
>> > +       }
>> > +      else
>> > +       {
>> > +         /* This is not a single step.  */
>> > +         ptid_t ret;
>> > +         CORE_ADDR tmp_pc;
>> > +
>> > +         while (1)
>> > +           {
>> > +             ret = record_beneath_to_wait (record_beneath_to_wait_ops,
>> > +                                           ptid, status);
>> > +
>> > +             if (status->kind == TARGET_WAITKIND_STOPPED
>> > +                 && status->value.sig == TARGET_SIGNAL_TRAP)
>> > +               {
>> > +                 /* Check if there is a breakpoint.  */
>> > +                 registers_changed ();
>> > +                 tmp_pc = read_pc ();
>> > +                 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;
>> > +           }
>> > +
>> > +         return ret;
>> > +       }
>> > +    }
>> > +  else
>> > +    {
>> > +      struct regcache *regcache = get_current_regcache ();
>> > +      int continue_flag = 1;
>> > +      int first_record_end = 1;
>> > +      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
>> > +      CORE_ADDR tmp_pc;
>> > +
>> > +      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 (record_debug)
>> > +               fprintf_unfiltered (gdb_stdlog,
>> > +                                   "Process record: break at 0x%s.\n",
>> > +                                   paddr_nz (tmp_pc));
>> > +             if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
>> > +                 && !record_resume_step)
>> > +               regcache_write_pc (regcache,
>> > +                                  tmp_pc +
>> > +                                  gdbarch_decr_pc_after_break
>> > +                                  (get_regcache_arch (regcache)));
>> > +             goto replay_out;
>> > +           }
>> > +       }
>> > +
>> > +      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)
>> > +           {
>> > +             /* Hit beginning of record log in reverse.  */
>> > +             status->kind = TARGET_WAITKIND_NO_HISTORY;
>> > +             break;
>> > +           }
>> > +         if (execution_direction != EXEC_REVERSE && !record_list->next)
>> > +           {
>> > +             /* Hit end of record log going forward.  */
>> > +             status->kind = TARGET_WAITKIND_NO_HISTORY;
>> > +             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 */
>> > +             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 = 0x%s len = %d.\n",
>> > +                                   host_address_to_string (record_list),
>> > +                                   paddr_nz (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))
>> > +               error (_("Process record: error reading memory at "
>> > +                        "addr = 0x%s len = %d."),
>> > +                      paddr_nz (record_list->u.mem.addr),
>> > +                      record_list->u.mem.len);
>> > +
>> > +             if (target_write_memory
>> > +                 (record_list->u.mem.addr, record_list->u.mem.val,
>> > +                  record_list->u.mem.len))
>> > +               error (_
>> > +                      ("Process record: error writing memory at "
>> > +                       "addr = 0x%s len = %d."),
>> > +                      paddr_nz (record_list->u.mem.addr),
>> > +                      record_list->u.mem.len);
>> > +
>> > +             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 0x%s.\n",
>> > +                                           paddr_nz (tmp_pc));
>> > +                     if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
>> > +                         && execution_direction == EXEC_FORWARD
>> > +                         && !record_resume_step)
>> > +                       regcache_write_pc (regcache,
>> > +                                          tmp_pc +
>> > +                                          gdbarch_decr_pc_after_break
>> > +                                          (get_regcache_arch (regcache)));
>> > +                     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;
>> > +               }
>> > +           }
>> > +       }
>> > +      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);
>> > +    }
>> > +
>> > +  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");
>> > +
>> > +  unpush_target (&record_ops);
>> > +  target_disconnect (args, from_tty);
>> > +}
>> > +
>> > +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);
>> > +}
>> > +
>> > +static void
>> > +record_mourn_inferior (struct target_ops *ops)
>> > +{
>> > +  if (record_debug)
>> > +    fprintf_unfiltered (gdb_stdlog, "Process record: "
>> > +                                   "record_mourn_inferior\n");
>> > +
>> > +  unpush_target (&record_ops);
>> > +  target_mourn_inferior ();
>> > +}
>> > +
>> > +/* Close process record target before killing the inferior process.  */
>> > +
>> > +static void
>> > +record_kill (struct target_ops *ops)
>> > +{
>> > +  if (record_debug)
>> > +    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
>> > +
>> > +  unpush_target (&record_ops);
>> > +  target_kill ();
>> > +}
>> > +
>> > +/* Record registers change (by user or by GDB) to list as an instruction.  */
>> > +
>> > +static void
>> > +record_registers_change (struct regcache *regcache, int regnum)
>> > +{
>> > +  /* Check record_insn_num.  */
>> > +  record_check_insn_num (0);
>> > +
>> > +  record_arch_list_head = NULL;
>> > +  record_arch_list_tail = NULL;
>> > +
>> > +  if (regnum < 0)
>> > +    {
>> > +      int i;
>> > +      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
>> > +       {
>> > +         if (record_arch_list_add_reg (regcache, i))
>> > +           {
>> > +             record_list_release (record_arch_list_tail);
>> > +             error (_("Process record: failed to record execution log."));
>> > +           }
>> > +       }
>> > +    }
>> > +  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;
>> > +
>> > +  if (record_insn_num == record_insn_max_num && record_insn_max_num)
>> > +    record_list_release_first ();
>> > +  else
>> > +    record_insn_num++;
>> > +}
>> > +
>> > +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;
>> > +         struct cleanup *old_cleanups;
>> > +
>> > +         /* 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?"));
>> > +         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."));
>> > +           }
>> > +
>> > +         /* Destroy the record from here forward.  */
>> > +         record_list_release_next ();
>> > +       }
>> > +
>> > +      record_registers_change (regcache, regno);
>> > +    }
>> > +  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.  */
>> > +
>> > +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)
>> > +{
>> > +  if (!record_gdb_operation_disable
>> > +      && (object == TARGET_OBJECT_MEMORY
>> > +         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
>> > +    {
>> > +      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 0x%s?"),
>> > +                      paddr_nz (offset)))
>> > +           return -1;
>> > +
>> > +         /* Destroy the record from here forward.  */
>> > +         record_list_release_next ();
>> > +       }
>> > +
>> > +      /* Check record_insn_num */
>> > +      record_check_insn_num (0);
>> > +
>> > +      /* 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 ())
>> > +       {
>> > +         record_list_release (record_arch_list_tail);
>> > +         if (record_debug)
>> > +           fprintf_unfiltered (gdb_stdlog,
>> > +                               _("Process record: failed to record "
>> > +                                 "execution log."));
>> > +         return -1;
>> > +       }
>> > +      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++;
>> > +    }
>> > +
>> > +  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
>> > +                                         object, annex, readbuf, writebuf,
>> > +                                         offset, len);
>> > +}
>> > +
>> > +/* Behavior is conditional on RECORD_IS_REPLAY.
>> > +   We will not actually insert or remove breakpoints when replaying,
>> > +   nor when recording.  */
>> > +
>> > +static int
>> > +record_insert_breakpoint (struct bp_target_info *bp_tgt)
>> > +{
>> > +  if (!RECORD_IS_REPLAY)
>> > +    {
>> > +      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
>> > +      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
>> > +
>> > +      do_cleanups (old_cleanups);
>> > +
>> > +      return ret;
>> > +    }
>> > +
>> > +  return 0;
>> > +}
>> > +
>> > +static int
>> > +record_remove_breakpoint (struct bp_target_info *bp_tgt)
>> > +{
>> > +  if (!RECORD_IS_REPLAY)
>> > +    {
>> > +      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
>> > +      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
>> > +
>> > +      do_cleanups (old_cleanups);
>> > +
>> > +      return ret;
>> > +    }
>> > +
>> > +  return 0;
>> > +}
>> > +
>> > +static int
>> > +record_can_execute_reverse (void)
>> > +{
>> > +  return 1;
>> > +}
>> > +
>> > +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;
>> > +}
>> > +
>> > +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);
>> > +}
>> > +
>> > +/* Alias for "target record".  */
>> > +
>> > +static void
>> > +cmd_record_start (char *args, int from_tty)
>> > +{
>> > +  execute_command ("target record", from_tty);
>> > +}
>> > +
>> > +/* Truncate the record log from the present point
>> > +   of replay until the end.  */
>> > +
>> > +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 ();
>> > +       }
>> > +      else
>> > +         printf_unfiltered (_("Already at end of record list.\n"));
>> > +
>> > +    }
>> > +  else
>> > +    printf_unfiltered (_("Process record is not started.\n"));
>> > +}
>> > +
>> > +/* Implement the "stoprecord" 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);
>> > +    }
>> > +  else
>> > +    printf_unfiltered (_("Process record is not started.\n"));
>> > +}
>> > +
>> > +/* Set upper limit of record log size.  */
>> > +
>> > +static void
>> > +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"));
>> > +
>> > +      while (record_insn_num > record_insn_max_num)
>> > +       record_list_release_first ();
>> > +    }
>> > +}
>> > +
>> > +/* 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;
>> > +
>> > +static void
>> > +set_record_command (char *args, int from_tty)
>> > +{
>> > +  printf_unfiltered (_("\
>> > +\"set record\" must be followed by an apporpriate subcommand.\n"));
>> > +  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
>> > +}
>> > +
>> > +static void
>> > +show_record_command (char *args, int from_tty)
>> > +{
>> > +  cmd_show_list (show_record_cmdlist, from_tty, "");
>> > +}
>> > +
>> > +static void
>> > +info_record_command (char *args, int from_tty)
>> > +{
>> > +  cmd_show_list (info_record_cmdlist, from_tty, "");
>> > +}
>> > +
>> > +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);
>> > +  add_com_alias ("rec", "record", class_obscure, 1);
>> > +  add_prefix_cmd ("record", class_support, set_record_command,
>> > +                 _("Set record options"), &set_record_cmdlist,
>> > +                 "set record ", 0, &setlist);
>> > +  add_alias_cmd ("rec", "record", class_obscure, 1, &setlist);
>> > +  add_prefix_cmd ("record", class_support, show_record_command,
>> > +                 _("Show record options"), &show_record_cmdlist,
>> > +                 "show record ", 0, &showlist);
>> > +  add_alias_cmd ("rec", "record", class_obscure, 1, &showlist);
>> > +  add_prefix_cmd ("record", class_support, info_record_command,
>> > +                 _("Info record options"), &info_record_cmdlist,
>> > +                 "info record ", 0, &infolist);
>> > +  add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
>> > +
>> > +
>> > +  add_cmd ("delete", class_obscure, cmd_record_delete,
>> > +          _("Delete the rest of execution log and start recording it anew."),
>> > +           &record_cmdlist);
>> > +  add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist);
>> > +  add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist);
>> > +
>> > +  add_cmd ("stop", class_obscure, cmd_record_stop,
>> > +          _("Stop the record/replay target."),
>> > +           &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 stop when "
>> > +                             "record/replay buffer becomes full."),
>> > +                           _("Show whether record/replay stop when "
>> > +                             "record/replay buffer becomes full."),
>> > +                           _("Enable is default value.\n"
>> > +                             "When enabled, if the record/replay buffer "
>> > +                             "becomes full,\n"
>> > +                              "ask user what to do.\n"
>> > +                              "When disabled, if the record/replay buffer "
>> > +                             "becomes full,\n"
>> > +                              "delete it and start new recording."),
>> > +                           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 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);
>> > +}
>> > --- /dev/null
>> > +++ b/record.h
>> > @@ -0,0 +1,32 @@
>> > +/* Process record and replay target for GDB, the GNU debugger.
>> > +
>> > +   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
>> > +
>> > +   This file is part of GDB.
>> > +
>> > +   This program is free software; you can redistribute it and/or modify
>> > +   it under the terms of the GNU General Public License as published by
>> > +   the Free Software Foundation; either version 3 of the License, or
>> > +   (at your option) any later version.
>> > +
>> > +   This program is distributed in the hope that it will be useful,
>> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> > +   GNU General Public License for more details.
>> > +
>> > +   You should have received a copy of the GNU General Public License
>> > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> > +
>> > +#ifndef _RECORD_H_
>> > +#define _RECORD_H_
>> > +
>> > +#define RECORD_IS_USED (current_target.to_stratum == record_stratum)
>> > +
>> > +extern int record_debug;
>> > +
>> > +extern int record_arch_list_add_reg (struct regcache *regcache, int num);
>> > +extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
>> > +extern int record_arch_list_add_end (void);
>> > +extern struct cleanup *record_gdb_operation_disable_set (void);
>> > +
>> > +#endif /* _RECORD_H_ */
>> >
>>
>
>
>
> --
> Pedro Alves
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-28  1:51         ` Hui Zhu
@ 2009-04-28 10:12           ` Hui Zhu
  2009-04-28 21:55             ` Pedro Alves
  0 siblings, 1 reply; 10+ messages in thread
From: Hui Zhu @ 2009-04-28 10:12 UTC (permalink / raw)
  To: Pedro Alves
  Cc: gdb-patches, Marc Khouzam, Michael Snyder, Thiago Jung Bauermann,
	Eli Zaretskii, paawan1982, Mark Kettenis

Change TARGET_SIGNAL_TRAP to TARGET_SIGNAL_ABRT:
+  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;
+	}
Because sometime when record_resume get error, gdb will call
record_wait in cycle.

2009-03-21  Hui Zhu  <teawater@gmail.com>

       Process record and replay target.

       * Makefile.in (record.c): New file.
       * record.c, record.h: New file.

       2008-12-28  Michael Snyder  <msnyder@vmware.com>
       * Comments, spelling, white space clean-ups.

       2008-12-26  Michael Snyder  <msnyder@vmware.com>
       * record.h: Don't export record_not_record.
       * record.c (record_not_record): Rename to in_record_wait.
       (record_not_record_set): Rename to in_record_wait_set.
       (record_not_record_cleanup): Rename to in_record_wait_cleanup.
       (record_store_registers): Check in_record_wait flag.
       (record_xfer_partial): Ditto.

       2008-10-07  Michael Snyder  <msnyder@vmware.com>
       * record.h (record_exec_direction): Delete.
       (RECORD_IS_REPLAY): Consult infrun global direction variable.
       * record.c: (record_wait_cleanups): Use infrun state variable.
       (record_wait): Ditto.
       (record_get_exec_direction, record_set_exec_direction): Remove.
       (record_can_execute_reverse): New target method.

       2008-10-06  Michael Snyder  <msnyder@vmware.com>
       * record.c (displaced_step_fixup): Remove.
       (record_message_cleanups): Remove displaced step handling.
       (record_message): Remove displaced step handling.

       2008-10-06  Michael Snyder  <msnyder@vmware.com>
       * record.c (record_regcache_raw_write_regnum): Remove.

       2008-10-05  Michael Snyder  <msnyder@vmware.com>
       * record.c, record.h: Rename execdir to exec_direction.

       2008-10-02  Michael Snyder  <msnyder@vmware.com>
       * record.c (record_open): Call target_can_async_p() instead
       of relying on a global variable.
       * record.h (record_linux_async_permitted): Delete.

       2008-09-19  Michael Snyder  <msnyder@vmware.com>
       * record.c (trivial): Fix two commas in a comment.
       * record.c (record_wait): On end of record log, return
       TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do.

       2008-09-06  Michael Snyder  <msnyder@vmware.com>
       * record.c: Comment and message string cleanup.
       Add some function header comments.

       2008-08-01  Michael Snyder  <msnyder@specifix.com>
       * record.c (_initialize_record): Clarify language in help
       strings.
       Fix up comment format (period must be followed by two spaces).

Thanks,
Hui

---
 Makefile.in |    5
 record.c    | 1289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 record.h    |   32 +
 3 files changed, 1324 insertions(+), 2 deletions(-)

--- a/Makefile.in
+++ b/Makefile.in
@@ -664,7 +664,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
 	valarith.c valops.c valprint.c value.c varobj.c vec.c \
 	wrapper.c \
 	xml-tdesc.c xml-support.c \
-	inferior.c gdb_usleep.c
+	inferior.c gdb_usleep.c \
+	record.c

 LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c

@@ -816,7 +817,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
 	solib.o solib-null.o \
 	prologue-value.o memory-map.o xml-support.o \
 	target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
-	inferior.o osdata.o gdb_usleep.o
+	inferior.o osdata.o gdb_usleep.o record.o

 TSOBS = inflow.o

--- /dev/null
+++ b/record.c
@@ -0,0 +1,1289 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "gdbthread.h"
+#include "event-top.h"
+#include "exceptions.h"
+#include "record.h"
+
+#include <signal.h>
+
+#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.
+
+   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.
+
+   Each struct record_entry is linked to "record_list" by "prev" and "next". */
+
+struct record_reg_entry
+{
+  int num;
+  gdb_byte *val;
+};
+
+struct record_mem_entry
+{
+  CORE_ADDR addr;
+  int len;
+  gdb_byte *val;
+};
+
+enum record_type
+{
+  record_end = 0,
+  record_reg,
+  record_mem
+};
+
+struct record_entry
+{
+  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;
+};
+
+/* 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 *);
+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 bp_target_info *);
+static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+static void
+record_list_release (struct record_entry *rec)
+{
+  struct record_entry *tmp;
+
+  if (!rec)
+    return;
+
+  while (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);
+    }
+
+  if (rec != &record_first)
+    xfree (rec);
+}
+
+static void
+record_list_release_next (void)
+{
+  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;
+    }
+}
+
+static void
+record_list_release_first (void)
+{
+  struct record_entry *tmp = NULL;
+  enum record_type type;
+
+  if (!record_first.next)
+    return;
+
+  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);
+      tmp = record_first.next;
+      record_first.next = tmp->next;
+      xfree (tmp);
+
+      if (!record_first.next)
+	{
+	  gdb_assert (record_insn_num == 1);
+	  break;
+	}
+
+      record_first.next->prev = &record_first;
+
+      if (type == record_end)
+	break;
+    }
+
+  record_insn_num--;
+}
+
+/* Add a struct record_entry to record_arch_list.  */
+
+static void
+record_arch_list_add (struct record_entry *rec)
+{
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: record_arch_list_add %s.\n",
+			host_address_to_string (rec));
+
+  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;
+    }
+}
+
+/* Record the value of a register NUM to record_arch_list.  */
+
+int
+record_arch_list_add_reg (struct regcache *regcache, int num)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add register num = %d to "
+			"record list.\n",
+			num);
+
+  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;
+
+  regcache_raw_read (regcache, num, rec->u.reg.val);
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Record the value of a region of memory whose address is ADDR and
+   length is LEN to record_arch_list.  */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    fprintf_unfiltered (gdb_stdlog,
+			"Process record: add mem addr = 0x%s len = %d to "
+			"record list.\n",
+			paddr_nz (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;
+
+  if (target_read_memory (addr, rec->u.mem.val, len))
+    {
+      if (record_debug)
+	fprintf_unfiltered (gdb_stdlog,
+			    "Process record: error reading memory at "
+			    "addr = 0x%s len = %d.\n",
+			    paddr_nz (addr), len);
+      xfree (rec->u.mem.val);
+      xfree (rec);
+      return -1;
+    }
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+/* Add a record_end type struct record_entry to record_arch_list.  */
+
+int
+record_arch_list_add_end (void)
+{
+  struct record_entry *rec;
+
+  if (record_debug > 1)
+    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;
+
+  record_arch_list_add (rec);
+
+  return 0;
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+  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."));
+	    }
+	}
+    }
+}
+
+/* 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)
+{
+  int ret;
+  struct regcache *regcache = args;
+  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  /* Check record_insn_num.  */
+  record_check_insn_num (1);
+
+  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."));
+
+  discard_cleanups (old_cleanups);
+
+  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++;
+
+  return 1;
+}
+
+static int
+do_record_message (struct regcache *regcache)
+{
+  return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+   doesn't need record.  */
+
+static int record_gdb_operation_disable = 0;
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+  struct cleanup *old_cleanups = NULL;
+
+  old_cleanups =
+    make_cleanup_restore_integer (&record_gdb_operation_disable);
+  record_gdb_operation_disable = 1;
+
+  return old_cleanups;
+}
+
+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 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 (current_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;
+    }
+
+  /*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;
+
+  /* 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;
+}
+
+static void
+record_close (int quitting)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+  record_list_release (record_list);
+}
+
+static int record_resume_step = 0;
+static enum target_signal record_resume_siggnal;
+static int record_resume_error;
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+               enum target_signal siggnal)
+{
+  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_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
+                                siggnal);
+    }
+}
+
+static int record_get_sig = 0;
+
+static void
+record_sig_handler (int signo)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+
+  /* It will break the running inferior in replay mode.  */
+  record_resume_step = 1;
+
+  /* It will let record_wait set inferior status to get the signal
+     SIGINT.  */
+  record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+  if (execution_direction == EXEC_REVERSE)
+    {
+      if (record_list->next)
+	record_list = record_list->next;
+    }
+  else
+    record_list = record_list->prev;
+}
+
+/* In replay mode, this function examines the recorded log and
+   determines where to stop.  */
+
+static ptid_t
+record_wait (struct target_ops *ops,
+              ptid_t ptid, struct target_waitstatus *status)
+{
+  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);
+
+  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);
+	}
+      else
+	{
+	  /* This is not a single step.  */
+	  ptid_t ret;
+	  CORE_ADDR tmp_pc;
+
+	  while (1)
+	    {
+	      ret = record_beneath_to_wait (record_beneath_to_wait_ops,
+					    ptid, status);
+
+	      if (status->kind == TARGET_WAITKIND_STOPPED
+		  && status->value.sig == TARGET_SIGNAL_TRAP)
+		{
+		  /* Check if there is a breakpoint.  */
+		  registers_changed ();
+		  tmp_pc = read_pc ();
+		  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;
+	    }
+
+	  return ret;
+	}
+    }
+  else
+    {
+      struct regcache *regcache = get_current_regcache ();
+      int continue_flag = 1;
+      int first_record_end = 1;
+      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+      CORE_ADDR tmp_pc;
+
+      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 (record_debug)
+		fprintf_unfiltered (gdb_stdlog,
+				    "Process record: break at 0x%s.\n",
+				    paddr_nz (tmp_pc));
+	      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+		  && !record_resume_step)
+		regcache_write_pc (regcache,
+				   tmp_pc +
+				   gdbarch_decr_pc_after_break
+				   (get_regcache_arch (regcache)));
+	      goto replay_out;
+	    }
+	}
+
+      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)
+	    {
+	      /* Hit beginning of record log in reverse.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      break;
+	    }
+	  if (execution_direction != EXEC_REVERSE && !record_list->next)
+	    {
+	      /* Hit end of record log going forward.  */
+	      status->kind = TARGET_WAITKIND_NO_HISTORY;
+	      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 */
+	      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 = 0x%s len = %d.\n",
+				    host_address_to_string (record_list),
+				    paddr_nz (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))
+		error (_("Process record: error reading memory at "
+			 "addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      if (target_write_memory
+		  (record_list->u.mem.addr, record_list->u.mem.val,
+		   record_list->u.mem.len))
+		error (_
+		       ("Process record: error writing memory at "
+			"addr = 0x%s len = %d."),
+		       paddr_nz (record_list->u.mem.addr),
+		       record_list->u.mem.len);
+
+	      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 0x%s.\n",
+					    paddr_nz (tmp_pc));
+		      if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+			  && execution_direction == EXEC_FORWARD
+			  && !record_resume_step)
+			regcache_write_pc (regcache,
+					   tmp_pc +
+					   gdbarch_decr_pc_after_break
+					   (get_regcache_arch (regcache)));
+		      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;
+		}
+	    }
+	}
+      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);
+    }
+
+  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");
+
+  unpush_target (&record_ops);
+  target_disconnect (args, from_tty);
+}
+
+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);
+}
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: "
+			            "record_mourn_inferior\n");
+
+  unpush_target (&record_ops);
+  target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process.  */
+
+static void
+record_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+
+  unpush_target (&record_ops);
+  target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction.  */
+
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+  /* Check record_insn_num.  */
+  record_check_insn_num (0);
+
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+
+  if (regnum < 0)
+    {
+      int i;
+      for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+	{
+	  if (record_arch_list_add_reg (regcache, i))
+	    {
+	      record_list_release (record_arch_list_tail);
+	      error (_("Process record: failed to record execution log."));
+	    }
+	}
+    }
+  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;
+
+  if (record_insn_num == record_insn_max_num && record_insn_max_num)
+    record_list_release_first ();
+  else
+    record_insn_num++;
+}
+
+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;
+	  struct cleanup *old_cleanups;
+
+	  /* 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?"));
+	  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."));
+	    }
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      record_registers_change (regcache, regno);
+    }
+  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.  */
+
+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)
+{
+  if (!record_gdb_operation_disable
+      && (object == TARGET_OBJECT_MEMORY
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+    {
+      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 0x%s?"),
+		       paddr_nz (offset)))
+	    return -1;
+
+	  /* Destroy the record from here forward.  */
+	  record_list_release_next ();
+	}
+
+      /* Check record_insn_num */
+      record_check_insn_num (0);
+
+      /* 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 ())
+	{
+	  record_list_release (record_arch_list_tail);
+	  if (record_debug)
+	    fprintf_unfiltered (gdb_stdlog,
+				_("Process record: failed to record "
+				  "execution log."));
+	  return -1;
+	}
+      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++;
+    }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY.
+   We will not actually insert or remove breakpoints when replaying,
+   nor when recording.  */
+
+static int
+record_insert_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_insert_breakpoint (bp_tgt);
+
+      do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_remove_breakpoint (struct bp_target_info *bp_tgt)
+{
+  if (!RECORD_IS_REPLAY)
+    {
+      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+      int ret = record_beneath_to_remove_breakpoint (bp_tgt);
+
+      do_cleanups (old_cleanups);
+
+      return ret;
+    }
+
+  return 0;
+}
+
+static int
+record_can_execute_reverse (void)
+{
+  return 1;
+}
+
+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;
+}
+
+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);
+}
+
+/* Alias for "target record".  */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+  execute_command ("target record", from_tty);
+}
+
+/* Truncate the record log from the present point
+   of replay until the end.  */
+
+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 ();
+	}
+      else
+	  printf_unfiltered (_("Already at end of record list.\n"));
+
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Implement the "stoprecord" 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);
+    }
+  else
+    printf_unfiltered (_("Process record is not started.\n"));
+}
+
+/* Set upper limit of record log size.  */
+
+static void
+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"));
+
+      while (record_insn_num > record_insn_max_num)
+	record_list_release_first ();
+    }
+}
+
+/* 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;
+
+static void
+set_record_command (char *args, int from_tty)
+{
+  printf_unfiltered (_("\
+\"set record\" must be followed by an apporpriate subcommand.\n"));
+  help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
+}
+
+static void
+show_record_command (char *args, int from_tty)
+{
+  cmd_show_list (show_record_cmdlist, from_tty, "");
+}
+
+static void
+info_record_command (char *args, int from_tty)
+{
+  cmd_show_list (info_record_cmdlist, from_tty, "");
+}
+
+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);
+  add_com_alias ("rec", "record", class_obscure, 1);
+  add_prefix_cmd ("record", class_support, set_record_command,
+		  _("Set record options"), &set_record_cmdlist,
+		  "set record ", 0, &setlist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &setlist);
+  add_prefix_cmd ("record", class_support, show_record_command,
+		  _("Show record options"), &show_record_cmdlist,
+		  "show record ", 0, &showlist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &showlist);
+  add_prefix_cmd ("record", class_support, info_record_command,
+		  _("Info record options"), &info_record_cmdlist,
+		  "info record ", 0, &infolist);
+  add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
+
+
+  add_cmd ("delete", class_obscure, cmd_record_delete,
+	   _("Delete the rest of execution log and start recording it anew."),
+           &record_cmdlist);
+  add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist);
+  add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist);
+
+  add_cmd ("stop", class_obscure, cmd_record_stop,
+	   _("Stop the record/replay target."),
+           &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 stop when "
+			      "record/replay buffer becomes full."),
+			    _("Show whether record/replay stop when "
+			      "record/replay buffer becomes full."),
+	                    _("Enable is default value.\n"
+	                      "When enabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "ask user what to do.\n"
+                              "When disabled, if the record/replay buffer "
+			      "becomes full,\n"
+                              "delete it and start new recording."),
+			    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 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);
+}
--- /dev/null
+++ b/record.h
@@ -0,0 +1,32 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+   Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RECORD_H_
+#define _RECORD_H_
+
+#define RECORD_IS_USED	(current_target.to_stratum == record_stratum)
+
+extern int record_debug;
+
+extern int record_arch_list_add_reg (struct regcache *regcache, int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup *record_gdb_operation_disable_set (void);
+
+#endif /* _RECORD_H_ */


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-28 10:12           ` Hui Zhu
@ 2009-04-28 21:55             ` Pedro Alves
  2009-04-29  1:43               ` Hui Zhu
  0 siblings, 1 reply; 10+ messages in thread
From: Pedro Alves @ 2009-04-28 21:55 UTC (permalink / raw)
  To: gdb-patches; +Cc: Hui Zhu, Michael Snyder

On Tuesday 28 April 2009 11:12:03, Hui Zhu wrote:

> 2009-03-21  Hui Zhu  <teawater@gmail.com>
> 
>        Process record and replay target.
> 
>        * Makefile.in (record.c): New file.
>        * record.c, record.h: New file.
> 
>        2008-12-28  Michael Snyder  <msnyder@vmware.com>
>        * Comments, spelling, white space clean-ups.
> 
>        2008-12-26  Michael Snyder  <msnyder@vmware.com>
>        * record.h: Don't export record_not_record.
>        * record.c (record_not_record): Rename to in_record_wait.
>        (record_not_record_set): Rename to in_record_wait_set.
>        (record_not_record_cleanup): Rename to in_record_wait_cleanup.
>        (record_store_registers): Check in_record_wait flag.
>        (record_xfer_partial): Ditto.
> 
...

Sorry that I have to be the nick-picky-guy again, but, as far as
I know and can see, we don't use that style of change log
entry covering the history of the changes that led to the current
form of the patch.  I know I've seen it before in *branches*,
but never on mainline's ChangeLog file.

The standard form of entry when more than one person did work on a
patch, is to append the name of the person in the header, like so:

2009-03-21  Hui Zhu  <teawater@gmail.com>
            Michael Snyder  <msnyder@vmware.com>

	* Makefile.in (SFILES): Add record.c.
	(COMMON_OBS): Add record.o.
	* record.c, record.h: New files.

You'll find plenty examples of entries like that in the
ChangeLog files.

Would you mind making that change?  Thanks in advance.

BTW, notice that I've tweaked the Makefile.in entry a bit, to
more accurately describe the change being made.

-- 
Pedro Alves


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFA] Submit process record and replay fourth time, 3/8
  2009-04-28 21:55             ` Pedro Alves
@ 2009-04-29  1:43               ` Hui Zhu
  0 siblings, 0 replies; 10+ messages in thread
From: Hui Zhu @ 2009-04-29  1:43 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches, Michael Snyder

Hi Pedro,

On Wed, Apr 29, 2009 at 05:55, Pedro Alves <pedro@codesourcery.com> wrote:
> On Tuesday 28 April 2009 11:12:03, Hui Zhu wrote:
>
>> 2009-03-21  Hui Zhu  <teawater@gmail.com>
>>
>>        Process record and replay target.
>>
>>        * Makefile.in (record.c): New file.
>>        * record.c, record.h: New file.
>>
>>        2008-12-28  Michael Snyder  <msnyder@vmware.com>
>>        * Comments, spelling, white space clean-ups.
>>
>>        2008-12-26  Michael Snyder  <msnyder@vmware.com>
>>        * record.h: Don't export record_not_record.
>>        * record.c (record_not_record): Rename to in_record_wait.
>>        (record_not_record_set): Rename to in_record_wait_set.
>>        (record_not_record_cleanup): Rename to in_record_wait_cleanup.
>>        (record_store_registers): Check in_record_wait flag.
>>        (record_xfer_partial): Ditto.
>>
> ...
>
> Sorry that I have to be the nick-picky-guy again, but, as far as
> I know and can see, we don't use that style of change log
> entry covering the history of the changes that led to the current
> form of the patch.  I know I've seen it before in *branches*,
> but never on mainline's ChangeLog file.
>
> The standard form of entry when more than one person did work on a
> patch, is to append the name of the person in the header, like so:
>
> 2009-03-21  Hui Zhu  <teawater@gmail.com>
>            Michael Snyder  <msnyder@vmware.com>
>
>        * Makefile.in (SFILES): Add record.c.
>        (COMMON_OBS): Add record.o.
>        * record.c, record.h: New files.
>
> You'll find plenty examples of entries like that in the
> ChangeLog files.
>
> Would you mind making that change?  Thanks in advance.

Of course you are not the nick-picky-guy.  You give me a lot of help
to make process record better.  :)

Your comment about the changelog is very well.  I will change all
changelog like it.

2009-04-29  Hui Zhu  <teawater@gmail.com>
            Michael Snyder  <msnyder@vmware.com>

	Process record and replay target.

	* Makefile.in (SFILES): Add record.c.
        (COMMON_OBS): Add record.o.
	* record.c, record.h: New file.


Thanks,
Hui


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2009-04-29  1:43 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-03-21 16:02 [RFA] Submit process record and replay fourth time, 3/8 Hui Zhu
2009-03-25  7:19 ` Hui Zhu
2009-04-15 17:02 ` Hui Zhu
2009-04-22  9:06   ` Hui Zhu
2009-04-27  6:01     ` Hui Zhu
2009-04-27 21:57       ` Pedro Alves
2009-04-28  1:51         ` Hui Zhu
2009-04-28 10:12           ` Hui Zhu
2009-04-28 21:55             ` Pedro Alves
2009-04-29  1:43               ` Hui Zhu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox