Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [RFA/RFC] Add dump and load command to process record and replay
@ 2009-08-01  7:31 Hui Zhu
  2009-08-01  9:57 ` Eli Zaretskii
  2009-08-01 19:20 ` Michael Snyder
  0 siblings, 2 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-01  7:31 UTC (permalink / raw)
  To: gdb-patches ml; +Cc: Michael Snyder, Eli Zaretskii

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

Hi,

This patch add command record dump and record load to make prec can
dump execution log to a file and load it later.  And load can work
with core file.

And I include "mem_entry_not_accessible"(not_replay) patch to this
patch because when replay with core file, some address in lib cannot
be read and write.

For example:
./gdb ./a.out
GNU gdb (GDB) 6.8.50.20090801-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Setting up the environment for debugging gdb.
Function "internal_error" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
Function "info_command" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
/home/teawater/gdb/bgdbno/gdb/.gdbinit:8: Error in sourced command file:
No breakpoint number 0.
(gdb) start
Temporary breakpoint 1 at 0x80483c1: file 1.c, line 20.
Starting program: /home/teawater/gdb/bgdbno/gdb/a.out

Temporary breakpoint 1, main () at 1.c:20
20	       int     b = 0;
(gdb) gcore
Saved corefile core.11945
(gdb) record
(gdb) n
During symbol reading, incomplete CFI data; unspecified registers
(e.g., eax) at 0x80483be.
21	       int     c = 1;
(gdb)
24		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 0 b = 0 c = 1
25	       b = cool ();
(gdb)
a = 3
26		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 3 b = 3 c = 1
29	       c += 1;
(gdb)
30		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 3 b = 3 c = 2
31	       a -= 2;
(gdb)
32		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 1 b = 3 c = 2
35	return (0);
(gdb)
36	}
(gdb) rc
Continuing.

No more reverse-execution history.
main () at 1.c:20
20	       int     b = 0;
(gdb) record dump
Saved recfile rec.11945.
(gdb) quit


After that, you get core.11945 and rec.11945.
Then you can replay 11945 execution log with them.


./gdb ./a.out ./core.11945
GNU gdb (GDB) 6.8.50.20090801-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `/home/teawater/gdb/bgdbno/gdb/a.out'.
Program terminated with signal 5, Trace/breakpoint trap.
#0  main () at 1.c:20
20	       int     b = 0;
Setting up the environment for debugging gdb.
Function "internal_error" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
Function "info_command" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
/home/teawater/gdb/bgdbno/gdb/.gdbinit:8: Error in sourced command file:
No breakpoint number 0.
(gdb) record load ./rec.11945
Auto start process record.
Loaded recfile ./rec.11945.
(gdb) p b
During symbol reading, incomplete CFI data; unspecified registers
(e.g., eax) at 0x80483be.
$1 = -1208017488
(gdb) n
21	       int     c = 1;
(gdb) p b
$2 = 0
(gdb) n
24		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
25	       b = cool ();
(gdb) p b
$3 = 0
(gdb) n
26		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
29	       c += 1;
(gdb) p c
$4 = 1
(gdb) n
30		printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb) p c
$5 = 2
(gdb) c
Continuing.

No more reverse-execution history.
main () at 1.c:36
36	}
(gdb) rc
Continuing.

No more reverse-execution history.
main () at 1.c:20
20	       int     b = 0;
(gdb) p c
$6 = 134513849
(gdb) quit
The program is running.  Quit anyway (and kill it)? (y or n) y


Please send your comments about it.  Thanks a lot.

Hui

2009-08-01  Hui Zhu  <teawater@gmail.com>

	Add dump and load command to process record and replay.

	* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
        byteswap.h, netinet/in.h): Include files.
	(RECORD_IS_REPLAY): Return true if record_core is true.
	(RECORD_FILE_MAGIC): New macro.
	(record_mem_entry): New field 'mem_entry_not_accessible'.
	(record_core_buf_entry): New struct.
	(record_core, record_core_regbuf, record_core_start,
	record_core_end, record_core_buf_list,
	record_beneath_to_fetch_registers_ops,
	record_beneath_to_fetch_registers,
	record_beneath_to_store_registers_ops,
	record_beneath_to_store_registers,
	record_beneath_to_has_execution_ops,
	record_beneath_to_has_execution,
	record_beneath_to_prepare_to_store): New variables.
	(record_list_release_first_insn): Change function
	record_list_release_first to this name.
	(record_arch_list_add_mem): Initialize
	mem_entry_not_accessible to 0.
	(record_arch_list_cleanups): New function.
	(record_message_cleanups): Removed.
	(record_message): Change to call record_arch_list_cleanups
	and record_list_release_first_insn.
	(record_exec_entry): New function.
	(record_open): Add support for target core.
	(record_close): Add support for target core.
	(record_wait): Call record_exec_entry.
	(record_kill): Add support for target core.
	(record_registers_change): Call record_list_release_first_insn.
	(record_fetch_registers): New function.
	(record_prepare_to_store): New function.
	(record_store_registers): Add support for target core.
	(record_xfer_partial): Add support for target core.
	(record_has_execution): New function.
	(init_record_ops): Set record_ops.to_fetch_registers,
	record_ops.to_prepare_to_store
	and record_ops.to_has_execution.
	(cmd_record_fd_cleanups): New function.
	(cmd_record_dump): New function.
	(cmd_record_load): New function.
	(set_record_insn_max_num): Call record_list_release_first_insn.
	(_initialize_record): Add commands "record dump"
	and "record load".

---
 record.c |  709 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 641 insertions(+), 68 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)

 /* These are the core struct of record function.

@@ -51,6 +59,7 @@ struct record_mem_entry
 {
   CORE_ADDR addr;
   int len;
+  int mem_entry_not_accessible;
   gdb_byte *val;
 };

@@ -75,9 +84,23 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -100,6 +123,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -116,6 +147,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);

 static void
 record_list_release (struct record_entry *rec)
@@ -166,7 +200,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -275,6 +309,7 @@ record_arch_list_add_mem (CORE_ADDR addr
   rec->type = record_mem;
   rec->u.mem.addr = addr;
   rec->u.mem.len = len;
+  rec->u.mem.mem_entry_not_accessible = 0;

   if (target_read_memory (addr, rec->u.mem.val, len))
     {
@@ -336,30 +371,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -382,7 +417,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -412,6 +447,92 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }

+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+    case record_mem: /* mem */
+      {
+        if (record_list->u.mem.mem_entry_not_accessible)
+          record_list->u.mem.mem_entry_not_accessible = 0;
+        else
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                if ((execution_direction == EXEC_REVERSE && !record_core)
+                    || (execution_direction != EXEC_REVERSE && record_core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    if ((execution_direction == EXEC_REVERSE && !record_core)
+                        || (execution_direction != EXEC_REVERSE &&
record_core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -420,8 +541,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");

+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -450,6 +576,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -464,6 +592,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -478,19 +611,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }

   push_target (&record_ops);

@@ -503,10 +668,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -708,53 +889,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }

-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      gdb_byte *mem = alloca (record_list->u.mem.len);
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_mem %s to "
-				    "inferior addr = %s len = %d.\n",
-				    host_address_to_string (record_list),
-				    paddress (gdbarch, record_list->u.mem.addr),
-				    record_list->u.mem.len);
-
-	      if (target_read_memory
-		  (record_list->u.mem.addr, mem, record_list->u.mem.len))
-		error (_("Process record: error reading memory at "
-			 "addr = %s len = %d."),
-		       paddress (gdbarch, 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 = %s len = %d."),
-		       paddress (gdbarch, record_list->u.mem.addr),
-		       record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);

-	      memcpy (record_list->u.mem.val, mem, record_list->u.mem.len);
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -874,7 +1011,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }

 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -918,15 +1057,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }

 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -973,6 +1155,7 @@ record_store_registers (struct target_op

       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -988,7 +1171,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1032,11 +1215,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }

+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1086,6 +1349,15 @@ record_can_execute_reverse (void)
   return 1;
 }

+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1102,11 +1374,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1127,6 +1402,293 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "rec.PID".  */
+      sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump."), recfilename);
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (write (recfd, &magic, 4) != 4)
+    error (_("Failed to write '%s' for dump."), recfilename);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          if (write (recfd, &tmpu8, 1) != 1)
+            error (_("Failed to write '%s' for dump."), recfilename);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              tmpu64 = record_list->u.reg.num;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+              tmpu64 = bswap_64 (tmpu64);
+#endif
+              if (write (recfd, &tmpu64, 8) != 8)
+                error (_("Failed to write '%s' for dump."), recfilename);
+
+              if (write (recfd, record_list->u.reg.val,
+                         MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+                error (_("Failed to write '%s' for dump."), recfilename);
+              break;
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  tmpu64 = record_list->u.mem.addr;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  if (write (recfd, &tmpu64, 8) != 8)
+                    error (_("Failed to write '%s' for dump."), recfilename);
+
+                  tmpu64 = record_list->u.mem.len;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  if (write (recfd, &tmpu64, 8) != 8)
+                    error (_("Failed to write '%s' for dump."), recfilename);
+
+                  if (write (recfd, record_list->u.mem.val,
+                             record_list->u.mem.len) != record_list->u.mem.len)
+                    error (_("Failed to write '%s' for dump."), recfilename);
+                }
+              break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for load."), args);
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  if (read (recfd, &magic, 4) != 4)
+    error (_("Failed to read '%s' for load."), args);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a record dump."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read '%s' for load."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+          /* Get num.  */
+          if (read (recfd, &tmpu64, 8) != 8)
+            error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.reg.num = tmpu64;
+          /* Get val.  */
+          if (read (recfd, rec->u.reg.val,
+                    MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+            error (_("Failed to read '%s' for load."), args);
+          record_arch_list_add (rec);
+          break;
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+          /* Get addr.  */
+          if (read (recfd, &tmpu64, 8) != 8)
+            error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.addr = tmpu64;
+          /* Get len.  */
+          if (read (recfd, &tmpu64, 8) != 8)
+            error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+          /* Get val.  */
+          if (read (recfd, rec->u.mem.val,
+                    rec->u.mem.len) != rec->u.mem.len)
+            error (_("Failed to read '%s' for load."), args);
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_end;
+          record_arch_list_add (rec);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1177,7 +1739,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1217,6 +1779,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +1814,15 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution log to a file.\n\
+Argument is optional filename.  Default filename is 'rec.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load the execution log from a file.  Argument is filename."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),


2009-08-01  Hui Zhu  <teawater@gmail.com>

	* gdb.texinfo (Process Record and Replay): Document the
	"record dump" and "record dump" commands.

---
 doc/gdb.texinfo |   16 ++++++++++++++++
 1 file changed, 16 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5190,6 +5190,22 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Produce a record execution log of the inferior process.  The optional
+argument @var{file} specifies the file name where to put the record dump.
+If not specified, the file name defaults to @file{rec.@var{pid}}, where
+@var{pid} is the inferior process ID.
+
+@kindex record load
+@kindex rec load
+@item record load @var{file}
+@itemx rec dump @var{file}
+Load process record execution log from @var{file}.
+It can work with @code{core-file}.
 @end table

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 32602 bytes --]

---
 record.c |  709 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 641 insertions(+), 68 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)
 
 /* These are the core struct of record function.
 
@@ -51,6 +59,7 @@ struct record_mem_entry
 {
   CORE_ADDR addr;
   int len;
+  int mem_entry_not_accessible;
   gdb_byte *val;
 };
 
@@ -75,9 +84,23 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -100,6 +123,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -116,6 +147,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
 
 static void
 record_list_release (struct record_entry *rec)
@@ -166,7 +200,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -275,6 +309,7 @@ record_arch_list_add_mem (CORE_ADDR addr
   rec->type = record_mem;
   rec->u.mem.addr = addr;
   rec->u.mem.len = len;
+  rec->u.mem.mem_entry_not_accessible = 0;
 
   if (target_read_memory (addr, rec->u.mem.val, len))
     {
@@ -336,30 +371,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -382,7 +417,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -412,6 +447,92 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+    case record_mem: /* mem */
+      {
+        if (record_list->u.mem.mem_entry_not_accessible)
+          record_list->u.mem.mem_entry_not_accessible = 0;
+        else
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                if ((execution_direction == EXEC_REVERSE && !record_core)
+                    || (execution_direction != EXEC_REVERSE && record_core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    if ((execution_direction == EXEC_REVERSE && !record_core)
+                        || (execution_direction != EXEC_REVERSE && record_core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -420,8 +541,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
 
+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -450,6 +576,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -464,6 +592,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -478,19 +611,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
 
   push_target (&record_ops);
 
@@ -503,10 +668,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -708,53 +889,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      gdb_byte *mem = alloca (record_list->u.mem.len);
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_mem %s to "
-				    "inferior addr = %s len = %d.\n",
-				    host_address_to_string (record_list),
-				    paddress (gdbarch, record_list->u.mem.addr),
-				    record_list->u.mem.len);
-
-	      if (target_read_memory
-		  (record_list->u.mem.addr, mem, record_list->u.mem.len))
-		error (_("Process record: error reading memory at "
-			 "addr = %s len = %d."),
-		       paddress (gdbarch, 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 = %s len = %d."),
-		       paddress (gdbarch, record_list->u.mem.addr),
-		       record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);
 
-	      memcpy (record_list->u.mem.val, mem, record_list->u.mem.len);
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -874,7 +1011,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }
 
 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -918,15 +1057,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
 
 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -973,6 +1155,7 @@ record_store_registers (struct target_op
 
       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -988,7 +1171,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1032,11 +1215,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
 
+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1086,6 +1349,15 @@ record_can_execute_reverse (void)
   return 1;
 }
 
+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1102,11 +1374,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1127,6 +1402,293 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "rec.PID".  */
+      sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump."), recfilename);
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (write (recfd, &magic, 4) != 4)
+    error (_("Failed to write '%s' for dump."), recfilename);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          if (write (recfd, &tmpu8, 1) != 1)
+            error (_("Failed to write '%s' for dump."), recfilename);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              tmpu64 = record_list->u.reg.num;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+              tmpu64 = bswap_64 (tmpu64);
+#endif
+              if (write (recfd, &tmpu64, 8) != 8)
+                error (_("Failed to write '%s' for dump."), recfilename);
+
+              if (write (recfd, record_list->u.reg.val,
+                         MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+                error (_("Failed to write '%s' for dump."), recfilename);
+              break;
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  tmpu64 = record_list->u.mem.addr;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  if (write (recfd, &tmpu64, 8) != 8)
+                    error (_("Failed to write '%s' for dump."), recfilename);
+
+                  tmpu64 = record_list->u.mem.len;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  if (write (recfd, &tmpu64, 8) != 8)
+                    error (_("Failed to write '%s' for dump."), recfilename);
+
+                  if (write (recfd, record_list->u.mem.val,
+                             record_list->u.mem.len) != record_list->u.mem.len)
+                    error (_("Failed to write '%s' for dump."), recfilename);
+                }
+              break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for load."), args);
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  if (read (recfd, &magic, 4) != 4)
+    error (_("Failed to read '%s' for load."), args);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a record dump."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read '%s' for load."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+          /* Get num.  */
+          if (read (recfd, &tmpu64, 8) != 8)
+            error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.reg.num = tmpu64;
+          /* Get val.  */
+          if (read (recfd, rec->u.reg.val,
+                    MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+            error (_("Failed to read '%s' for load."), args);
+          record_arch_list_add (rec);
+          break;
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+          /* Get addr.  */
+          if (read (recfd, &tmpu64, 8) != 8)
+            error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.addr = tmpu64;
+          /* Get len.  */
+          if (read (recfd, &tmpu64, 8) != 8)
+            error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+          /* Get val.  */
+          if (read (recfd, rec->u.mem.val,
+                    rec->u.mem.len) != rec->u.mem.len)
+            error (_("Failed to read '%s' for load."), args);
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_end;
+          record_arch_list_add (rec);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1177,7 +1739,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1217,6 +1779,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +1814,15 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution log to a file.\n\
+Argument is optional filename.  Default filename is 'rec.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load the execution log from a file.  Argument is filename."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #3: prec-dump-doc.txt --]
[-- Type: text/plain, Size: 957 bytes --]

---
 doc/gdb.texinfo |   16 ++++++++++++++++
 1 file changed, 16 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5190,6 +5190,22 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Produce a record execution log of the inferior process.  The optional
+argument @var{file} specifies the file name where to put the record dump.
+If not specified, the file name defaults to @file{rec.@var{pid}}, where
+@var{pid} is the inferior process ID.
+
+@kindex record load
+@kindex rec load
+@item record load @var{file}
+@itemx rec dump @var{file}
+Load process record execution log from @var{file}.
+It can work with @code{core-file}.
 @end table
 
 

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-01  7:31 [RFA/RFC] Add dump and load command to process record and replay Hui Zhu
@ 2009-08-01  9:57 ` Eli Zaretskii
  2009-08-01 19:20 ` Michael Snyder
  1 sibling, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-01  9:57 UTC (permalink / raw)
  To: Hui Zhu; +Cc: gdb-patches, msnyder

> From: Hui Zhu <teawater@gmail.com>
> Date: Sat, 1 Aug 2009 15:30:38 +0800
> Cc: Michael Snyder <msnyder@vmware.com>, Eli Zaretskii <eliz@gnu.org>
> 
> This patch add command record dump and record load to make prec can
> dump execution log to a file and load it later.  And load can work
> with core file.

Thanks.

> +      /* Default corefile name is "rec.PID".  */

I suggest the default name to be "gdb_record.PID".

> +  if (write (recfd, &magic, 4) != 4)
> +    error (_("Failed to write '%s' for dump."), recfilename);

I suggest to change the text (here and elsewhere in your patch) to:

  "Failed to write dump of execution records to `%s'."

I also suggest to replace the numerous fragments like this:

> +          if (write (recfd, &tmpu8, 1) != 1)
> +            error (_("Failed to write '%s' for dump."), recfilename);

with calls to a function which tries to write and errors out of it
cannot.  That would leave only one "Failed to write ..." string,
instead of having gobs of them.

> +  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);

I suggest to replace the message text with this:

  "Saved dump of execution records to `%s'."

> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for load."), args);

A better text would be

  "Failed to open `%s' for loading execution records: %s"

The second %s should get the result of strerror (errno), to describe
the reason of the failure

> +  if (read (recfd, &magic, 4) != 4)
> +    error (_("Failed to read '%s' for load."), args);

  "Failed reading dump of execution records in `%s'"

Again, I suggest to have a single function that reads and errors out
on error.

> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a record dump."), args);

  "`%s' is not a valid dump of execution records."

> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> +	       _("Dump the execution log to a file.\n\

 "Dump the execution records to a file."

> +Argument is optional filename.  Default filename is 'rec.<process_id>'."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> +  c = add_cmd ("load", class_obscure, cmd_record_load,
> +	       _("Load the execution log from a file.  Argument is filename."),

  "Load previously dumped execution records from a file given as argument."

Note that you cannot have more than one period on the first line,
because the help commands assume there's only one there.

> +@kindex record dump
> +@kindex rec dump
> +@item record dump [@var{file}]
> +@itemx rec dump [@var{file}]
> +Produce a record execution log of the inferior process.  The optional
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 Dump the execution records of the inferior process to a file.

> +argument @var{file} specifies the file name where to put the record dump.
> +If not specified, the file name defaults to @file{rec.@var{pid}}, where
> +@var{pid} is the inferior process ID.
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
 "is the PID of the inferior process."

> +Load process record execution log from @var{file}.

 Load previously-dumped execution records from @var{file}.

> +It can work with @code{core-file}.

This needs further explanation.  Or maybe just delete it, if it
silently does The Right Thing.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-01  7:31 [RFA/RFC] Add dump and load command to process record and replay Hui Zhu
  2009-08-01  9:57 ` Eli Zaretskii
@ 2009-08-01 19:20 ` Michael Snyder
  2009-08-02  3:18   ` Michael Snyder
  1 sibling, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-01 19:20 UTC (permalink / raw)
  To: Hui Zhu; +Cc: gdb-patches ml, Eli Zaretskii

Hui Zhu wrote:
> Hi,
> 
> This patch add command record dump and record load to make prec can
> dump execution log to a file and load it later.  And load can work
> with core file.
> 
> And I include "mem_entry_not_accessible"(not_replay) patch to this
> patch because when replay with core file, some address in lib cannot
> be read and write.

Augh!  No -- please, this is lumping too much together.
Smaller, more discrete patches are much easier to review.

1) You already have a patch thread for the memory-not-accessible
change -- let's finish that idea there, not lump it in here.

2) I think the idea of saving and restoring the recording
to a file is GREAT -- but can you please give us a patch
that adds that, by itself.

3) I don't really understand how core files fit into this,
but I'd love to discuss that idea in a separate patch thread.
It really adds a lot of complexity to this patch, which it seems
to me could be much simpler and easier to review if it added
only one thing at a time.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-01 19:20 ` Michael Snyder
@ 2009-08-02  3:18   ` Michael Snyder
  2009-08-02  5:58     ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-02  3:18 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Hui Zhu, gdb-patches ml

Michael Snyder wrote:

> 3) I don't really understand how core files fit into this,
> but I'd love to discuss that idea in a separate patch thread.

Oh, sorry -- I see how they're related now.
Very clever, actually.

I'd still like to see them submitted separately, though,
because:
1) the dump/restore part of the patch is cleaner and
closer to being acceptable, and can really be used on
its own, while
2) the corefile part of the patch is kind of messy and
prototype-ish, and I think needs much more discussion and
cleaning up before it will be ready.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-02  3:18   ` Michael Snyder
@ 2009-08-02  5:58     ` Hui Zhu
  2009-08-03  4:12       ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-02  5:58 UTC (permalink / raw)
  To: Michael Snyder; +Cc: gdb-patches ml

OK.  I will post new patches for them, memory and dump.

Thanks,
Hui

On Sun, Aug 2, 2009 at 11:17, Michael Snyder<msnyder@vmware.com> wrote:
> Michael Snyder wrote:
>
>> 3) I don't really understand how core files fit into this,
>> but I'd love to discuss that idea in a separate patch thread.
>
> Oh, sorry -- I see how they're related now.
> Very clever, actually.
>
> I'd still like to see them submitted separately, though,
> because:
> 1) the dump/restore part of the patch is cleaner and
> closer to being acceptable, and can really be used on
> its own, while
> 2) the corefile part of the patch is kind of messy and
> prototype-ish, and I think needs much more discussion and
> cleaning up before it will be ready.
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-02  5:58     ` Hui Zhu
@ 2009-08-03  4:12       ` Hui Zhu
  2009-08-03 18:29         ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-03  4:12 UTC (permalink / raw)
  To: Michael Snyder, Eli Zaretskii; +Cc: gdb-patches ml

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

On Sun, Aug 2, 2009 at 13:58, Hui Zhu<teawater@gmail.com> wrote:
> OK.  I will post new patches for them, memory and dump.
>
> Thanks,
> Hui
>
> On Sun, Aug 2, 2009 at 11:17, Michael Snyder<msnyder@vmware.com> wrote:
>> Michael Snyder wrote:
>>
>>> 3) I don't really understand how core files fit into this,
>>> but I'd love to discuss that idea in a separate patch thread.
>>
>> Oh, sorry -- I see how they're related now.
>> Very clever, actually.
>>
>> I'd still like to see them submitted separately, though,
>> because:
>> 1) the dump/restore part of the patch is cleaner and
>> closer to being acceptable, and can really be used on
>> its own, while
>> 2) the corefile part of the patch is kind of messy and
>> prototype-ish, and I think needs much more discussion and
>> cleaning up before it will be ready.
>>
>

Hi Eli and Michael,

I make a new patch according to your mail.  It depend on patch in
http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html

Please help me review it.

Thanks,
Hui


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

	Add dump and load command to process record and replay.

	* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
        byteswap.h, netinet/in.h): Include files.
	(RECORD_IS_REPLAY): Return true if record_core is true.
	(RECORD_FILE_MAGIC): New macro.
	(record_core_buf_entry): New struct.
	(record_core, record_core_regbuf, record_core_start,
	record_core_end, record_core_buf_list,
	record_beneath_to_fetch_registers_ops,
	record_beneath_to_fetch_registers,
	record_beneath_to_store_registers_ops,
	record_beneath_to_store_registers,
	record_beneath_to_has_execution_ops,
	record_beneath_to_has_execution,
	record_beneath_to_prepare_to_store): New variables.
	(record_list_release_first_insn): Change function
	record_list_release_first to this name.
	(record_arch_list_cleanups): New function.
	(record_message_cleanups): Removed.
	(record_message): Change to call record_arch_list_cleanups
	and record_list_release_first_insn.
	(record_exec_entry): Add support for target core.
	(record_open): Add support for target core.
	(record_close): Add support for target core.
	(record_kill): Add support for target core.
	(record_registers_change): Call record_list_release_first_insn.
	(record_fetch_registers): New function.
	(record_prepare_to_store): New function.
	(record_store_registers): Add support for target core.
	(record_xfer_partial): Add support for target core.
	(record_has_execution): New function.
	(init_record_ops): Set record_ops.to_fetch_registers,
	record_ops.to_prepare_to_store
	and record_ops.to_has_execution.
	(cmd_record_fd_cleanups): New function.
	(cmd_record_dump): New function.
	(cmd_record_load): New function.
	(set_record_insn_max_num): Call record_list_release_first_insn.
	(_initialize_record): Add commands "record dump"
	and "record load".

---
 record.c |  587 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 563 insertions(+), 24 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)

 /* These are the core struct of record function.

@@ -76,9 +84,23 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -101,6 +123,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -117,6 +147,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);

 static void
 record_list_release (struct record_entry *rec)
@@ -167,7 +200,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -338,30 +371,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -384,7 +417,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -453,7 +486,8 @@ record_exec_entry (struct regcache *regc

             if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
               {
-                if (execution_direction == EXEC_REVERSE)
+                if ((execution_direction == EXEC_REVERSE && !record_core)
+                    || (execution_direction != EXEC_REVERSE && record_core))
                   {
                     record_list->u.mem.mem_entry_not_accessible = 1;
                     if (record_debug)
@@ -473,7 +507,8 @@ record_exec_entry (struct regcache *regc
                 if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
                                          entry->u.mem.len))
                   {
-                    if (execution_direction == EXEC_REVERSE)
+                    if ((execution_direction == EXEC_REVERSE && !record_core)
+                        || (execution_direction != EXEC_REVERSE &&
record_core))
                       {
                         record_list->u.mem.mem_entry_not_accessible = 1;
                         if (record_debug)
@@ -505,8 +540,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");

+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -535,6 +575,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -549,6 +591,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -563,19 +610,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }

   push_target (&record_ops);

@@ -588,10 +667,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -915,7 +1010,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }

 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -959,15 +1056,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }

 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -1014,6 +1154,7 @@ record_store_registers (struct target_op

       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1029,7 +1170,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1073,11 +1214,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }

+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1127,6 +1348,15 @@ record_can_execute_reverse (void)
   return 1;
 }

+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1143,11 +1373,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1168,6 +1401,300 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              tmpu64 = record_list->u.reg.num;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+              tmpu64 = bswap_64 (tmpu64);
+#endif
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  tmpu64 = record_list->u.mem.addr;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           args, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (args, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+          /* Get num.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.reg.num = tmpu64;
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+          record_arch_list_add (rec);
+          break;
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+          /* Get addr.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.addr = tmpu64;
+          /* Get len.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_end;
+          record_arch_list_add (rec);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1218,7 +1745,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1258,6 +1785,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1291,6 +1820,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is
'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),


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

	* gdb.texinfo (Process Record and Replay): Document the
	"record dump" and "record dump" commands.

---
 doc/gdb.texinfo |   15 +++++++++++++++
 1 file changed, 15 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5190,6 +5190,21 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Dump the execution records of the inferior process to a file.  The optional
+argument @var{file} specifies the file name where to put the record dump.
+If not specified, the file name defaults to @file{gdb_record.@var{pid}}, where
+@var{pid} is is the PID of the inferior process.
+
+@kindex record load
+@kindex rec load
+@item record load @var{file}
+@itemx rec dump @var{file}
+Load previously-dumped execution records from @var{file}.
 @end table

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 27569 bytes --]

---
 record.c |  587 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 563 insertions(+), 24 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)
 
 /* These are the core struct of record function.
 
@@ -76,9 +84,23 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -101,6 +123,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -117,6 +147,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
 
 static void
 record_list_release (struct record_entry *rec)
@@ -167,7 +200,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -338,30 +371,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -384,7 +417,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -453,7 +486,8 @@ record_exec_entry (struct regcache *regc
 
             if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
               {
-                if (execution_direction == EXEC_REVERSE)
+                if ((execution_direction == EXEC_REVERSE && !record_core)
+                    || (execution_direction != EXEC_REVERSE && record_core))
                   {
                     record_list->u.mem.mem_entry_not_accessible = 1;
                     if (record_debug)
@@ -473,7 +507,8 @@ record_exec_entry (struct regcache *regc
                 if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
                                          entry->u.mem.len))
                   {
-                    if (execution_direction == EXEC_REVERSE)
+                    if ((execution_direction == EXEC_REVERSE && !record_core)
+                        || (execution_direction != EXEC_REVERSE && record_core))
                       {
                         record_list->u.mem.mem_entry_not_accessible = 1;
                         if (record_debug)
@@ -505,8 +540,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
 
+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -535,6 +575,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -549,6 +591,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -563,19 +610,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
 
   push_target (&record_ops);
 
@@ -588,10 +667,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -915,7 +1010,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }
 
 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -959,15 +1056,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
 
 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -1014,6 +1154,7 @@ record_store_registers (struct target_op
 
       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1029,7 +1170,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1073,11 +1214,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
 
+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1127,6 +1348,15 @@ record_can_execute_reverse (void)
   return 1;
 }
 
+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1143,11 +1373,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1168,6 +1401,300 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              tmpu64 = record_list->u.reg.num;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+              tmpu64 = bswap_64 (tmpu64);
+#endif
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  tmpu64 = record_list->u.mem.addr;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+                  tmpu64 = bswap_64 (tmpu64);
+#endif
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           args, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (args, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+          /* Get num.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.reg.num = tmpu64;
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+          record_arch_list_add (rec);
+          break;
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+          /* Get addr.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.addr = tmpu64;
+          /* Get len.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+          tmpu64 = bswap_64 (tmpu64);
+#endif
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_end;
+          record_arch_list_add (rec);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1218,7 +1745,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1258,6 +1785,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1291,6 +1820,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #3: prec-dump-doc.txt --]
[-- Type: text/plain, Size: 951 bytes --]

---
 doc/gdb.texinfo |   15 +++++++++++++++
 1 file changed, 15 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5190,6 +5190,21 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Dump the execution records of the inferior process to a file.  The optional
+argument @var{file} specifies the file name where to put the record dump.
+If not specified, the file name defaults to @file{gdb_record.@var{pid}}, where
+@var{pid} is is the PID of the inferior process.
+
+@kindex record load
+@kindex rec load
+@item record load @var{file}
+@itemx rec dump @var{file}
+Load previously-dumped execution records from @var{file}.
 @end table
 
 

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-03  4:12       ` Hui Zhu
@ 2009-08-03 18:29         ` Eli Zaretskii
  2009-08-04  1:58           ` Hui Zhu
  2009-08-04  2:07           ` Hui Zhu
  0 siblings, 2 replies; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-03 18:29 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Mon, 3 Aug 2009 12:11:46 +0800
> Cc: gdb-patches ml <gdb-patches@sourceware.org>
> 
> I make a new patch according to your mail.  It depend on patch in
> http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html

Thanks.

> +@var{pid} is is the PID of the inferior process.
             ^^^^^
Two "is" in a row; one is redundant.

Otherwise, I'm happy now.

By the way, does it make sense to make the dump file portable, so that
another host that supports debugging the same target could then use
it?


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-03 18:29         ` Eli Zaretskii
@ 2009-08-04  1:58           ` Hui Zhu
  2009-08-04  2:07           ` Hui Zhu
  1 sibling, 0 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-04  1:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: msnyder, gdb-patches

On Tue, Aug 4, 2009 at 02:29, Eli Zaretskii<eliz@gnu.org> wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Mon, 3 Aug 2009 12:11:46 +0800
>> Cc: gdb-patches ml <gdb-patches@sourceware.org>
>>
>> I make a new patch according to your mail.  It depend on patch in
>> http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html
>
> Thanks.
>
>> +@var{pid} is is the PID of the inferior process.
>             ^^^^^
> Two "is" in a row; one is redundant.
>
> Otherwise, I'm happy now.
>
> By the way, does it make sense to make the dump file portable, so that
> another host that supports debugging the same target could then use
> it?
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-03 18:29         ` Eli Zaretskii
  2009-08-04  1:58           ` Hui Zhu
@ 2009-08-04  2:07           ` Hui Zhu
  2009-08-04 18:26             ` Eli Zaretskii
  1 sibling, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-04  2:07 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: msnyder, gdb-patches

On Tue, Aug 4, 2009 at 02:29, Eli Zaretskii<eliz@gnu.org> wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Mon, 3 Aug 2009 12:11:46 +0800
>> Cc: gdb-patches ml <gdb-patches@sourceware.org>
>>
>> I make a new patch according to your mail.  It depend on patch in
>> http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html
>
> Thanks.
>
>> +@var{pid} is is the PID of the inferior process.
>             ^^^^^
> Two "is" in a row; one is redundant.

Oops, I will fix it.

>
> Otherwise, I'm happy now.
>
> By the way, does it make sense to make the dump file portable, so that
> another host that supports debugging the same target could then use
> it?
>

Yes, the dump file is portable like core file.

Thanks,
Hui


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-04  2:07           ` Hui Zhu
@ 2009-08-04 18:26             ` Eli Zaretskii
  2009-08-04 20:01               ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-04 18:26 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Tue, 4 Aug 2009 10:07:11 +0800
> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
> 
> > By the way, does it make sense to make the dump file portable, so that
> > another host that supports debugging the same target could then use
> > it?
> >
> 
> Yes, the dump file is portable like core file.

Well, maybe I'm missing something important, but isn't the dump file a
series of struct's defined as this:

    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;
    };

?  If so, then the dump file uses host's native pointers, so it is not
portable to a different host.  Right?


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-04 18:26             ` Eli Zaretskii
@ 2009-08-04 20:01               ` Michael Snyder
  2009-08-05  9:21                 ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-04 20:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches

Eli Zaretskii wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Tue, 4 Aug 2009 10:07:11 +0800
>> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
>>
>>> By the way, does it make sense to make the dump file portable, so that
>>> another host that supports debugging the same target could then use
>>> it?
>>>
>> Yes, the dump file is portable like core file.
> 
> Well, maybe I'm missing something important, but isn't the dump file a
> series of struct's defined as this:
> 
>     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;
>     };
> 
> ?  If so, then the dump file uses host's native pointers, so it is not
> portable to a different host.  Right?

No.  If you look at the code that dumps the file (and I had
to add a bunch of printfs and stuff to figure it out), you'll
see that the dump file looks like this:

4 byte magic number
Series of the following:
1) 1 byte tag (record_end), or
2) 1 byte tag (record_rec) followed by
    8 byte number (register id), followed by
    MAX_REGISTER_SIZE byte value (register value): or
3) 1 byte tag (record_mem) followed by
    8 byte number (memory address) followed by
    8 byte number (memory length) followed by
    N byte buffer (memory value).

If you look for the #if (BYTE_ORDER == BIG_ENDIAN), this is
where Teawater is making the byte orders host-independent.

I was going to mention the ifdefs eventually.   ;-)



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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-04 20:01               ` Michael Snyder
@ 2009-08-05  9:21                 ` Hui Zhu
  2009-08-05 20:19                   ` [RFA/RFC] Add dump and load command to process record (file format etc) Michael Snyder
  2009-08-05 21:23                   ` [RFA/RFC] Add dump and load command to process record and replay Michael Snyder
  0 siblings, 2 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-05  9:21 UTC (permalink / raw)
  To: Michael Snyder, Eli Zaretskii; +Cc: gdb-patches

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

On Wed, Aug 5, 2009 at 04:00, Michael Snyder<msnyder@vmware.com> wrote:
> Eli Zaretskii wrote:
>>>
>>> From: Hui Zhu <teawater@gmail.com>
>>> Date: Tue, 4 Aug 2009 10:07:11 +0800
>>> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
>>>
>>>> By the way, does it make sense to make the dump file portable, so that
>>>> another host that supports debugging the same target could then use
>>>> it?
>>>>
>>> Yes, the dump file is portable like core file.
>>
>> Well, maybe I'm missing something important, but isn't the dump file a
>> series of struct's defined as this:
>>
>>    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;
>>    };
>>
>> ?  If so, then the dump file uses host's native pointers, so it is not
>> portable to a different host.  Right?
>
> No.  If you look at the code that dumps the file (and I had
> to add a bunch of printfs and stuff to figure it out), you'll
> see that the dump file looks like this:
>
> 4 byte magic number
> Series of the following:
> 1) 1 byte tag (record_end), or
> 2) 1 byte tag (record_rec) followed by
>   8 byte number (register id), followed by
>   MAX_REGISTER_SIZE byte value (register value): or
> 3) 1 byte tag (record_mem) followed by
>   8 byte number (memory address) followed by
>   8 byte number (memory length) followed by
>   N byte buffer (memory value).
>
> If you look for the #if (BYTE_ORDER == BIG_ENDIAN), this is
> where Teawater is making the byte orders host-independent.
>
> I was going to mention the ifdefs eventually.   ;-)
>

Cool.  It's very clear.  :)
Thanks Michael.

I make a new version patch to follow the cvs head.  And removed the ifdef.
Please help me review it.

Thanks,
Hui

2009-08-05  Hui Zhu  <teawater@gmail.com>

	Add dump and load command to process record and replay.

	* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
        byteswap.h, netinet/in.h): Include files.
	(RECORD_IS_REPLAY): Return true if record_core is true.
	(RECORD_FILE_MAGIC): New macro.
	(record_core_buf_entry): New struct.
	(record_core, record_core_regbuf, record_core_start,
	record_core_end, record_core_buf_list,
	record_beneath_to_fetch_registers_ops,
	record_beneath_to_fetch_registers,
	record_beneath_to_store_registers_ops,
	record_beneath_to_store_registers,
	record_beneath_to_has_execution_ops,
	record_beneath_to_has_execution,
	record_beneath_to_prepare_to_store): New variables.
	(record_list_release_first_insn): Change function
	record_list_release_first to this name.
	(record_arch_list_cleanups): New function.
	(record_message_cleanups): Removed.
	(record_message): Change to call record_arch_list_cleanups
	and record_list_release_first_insn.
	(record_exec_entry): New function.
	(record_open): Add support for target core.
	(record_close): Add support for target core.
	(record_wait): Call function 'record_exec_entry'.
	(record_kill): Add support for target core.
	(record_registers_change): Call record_list_release_first_insn.
	(record_fetch_registers): New function.
	(record_prepare_to_store): New function.
	(record_store_registers): Add support for target core.
	(record_xfer_partial): Add support for target core.
	(record_has_execution): New function.
	(init_record_ops): Set record_ops.to_fetch_registers,
	record_ops.to_prepare_to_store
	and record_ops.to_has_execution.
	(cmd_record_fd_cleanups): New function.
	(cmd_record_dump): New function.
	(cmd_record_load): New function.
	(set_record_insn_max_num): Call record_list_release_first_insn.
	(_initialize_record): Add commands "record dump"
	and "record load".

---
 record.c |  731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 640 insertions(+), 91 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)

 /* These are the core struct of record function.

@@ -78,9 +86,23 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -103,6 +125,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);

 static void
 record_list_release (struct record_entry *rec)
@@ -169,7 +202,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +419,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }

+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                 if ((execution_direction == EXEC_REVERSE && !record_core)
+                     || (execution_direction != EXEC_REVERSE && record_core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                     if ((execution_direction == EXEC_REVERSE && !record_core)
+                         || (execution_direction != EXEC_REVERSE &&
record_core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");

+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }

   push_target (&record_ops);

@@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }

-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);

-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed --
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed --
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }

 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -945,15 +1058,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }

 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -1000,6 +1156,7 @@ record_store_registers (struct target_op

       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }

+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
   return 1;
 }

+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1129,11 +1375,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           args, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (args, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+          /* Get num.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+          record_arch_list_add (rec);
+          break;
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+          /* Get addr.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+          /* Get len.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_end;
+          record_arch_list_add (rec);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1277,6 +1816,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is
'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 32990 bytes --]

---
 record.c |  731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 640 insertions(+), 91 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)
 
 /* These are the core struct of record function.
 
@@ -78,9 +86,23 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -103,6 +125,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
 
 static void
 record_list_release (struct record_entry *rec)
@@ -169,7 +202,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +419,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                 if ((execution_direction == EXEC_REVERSE && !record_core)
+                     || (execution_direction != EXEC_REVERSE && record_core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                     if ((execution_direction == EXEC_REVERSE && !record_core)
+                         || (execution_direction != EXEC_REVERSE && record_core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
 
+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
 
   push_target (&record_ops);
 
@@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);
 
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }
 
 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -945,15 +1058,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
 
 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -1000,6 +1156,7 @@ record_store_registers (struct target_op
 
       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
 
+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
   return 1;
 }
 
+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1129,11 +1375,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           args, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (args, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+          /* Get num.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+          record_arch_list_add (rec);
+          break;
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+          /* Get addr.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+          /* Get len.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_end;
+          record_arch_list_add (rec);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1277,6 +1816,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

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

* Re: [RFA/RFC] Add dump and load command to process record (file format  etc)
  2009-08-05  9:21                 ` Hui Zhu
@ 2009-08-05 20:19                   ` Michael Snyder
  2009-08-06  2:17                     ` Hui Zhu
  2009-08-05 21:23                   ` [RFA/RFC] Add dump and load command to process record and replay Michael Snyder
  1 sibling, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-05 20:19 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches


msnyder:
>> If you look at the code that dumps the file (and I had
>> to add a bunch of printfs and stuff to figure it out), you'll
>> see that the dump file looks like this:
>>
>> 4 byte magic number
>> Series of the following:
>> 1) 1 byte tag (record_end), or
>> 2) 1 byte tag (record_rec) followed by
>>   8 byte number (register id), followed by
>>   MAX_REGISTER_SIZE byte value (register value): or
>> 3) 1 byte tag (record_mem) followed by
>>   8 byte number (memory address) followed by
>>   8 byte number (memory length) followed by
>>   N byte buffer (memory value).
>>
>> If you look for the #if (BYTE_ORDER == BIG_ENDIAN), this is
>> where Teawater is making the byte orders host-independent.
>>
>> I was going to mention the ifdefs eventually.   ;-)

Hui Zhu:
> Cool.  It's very clear.  :)
> Thanks Michael.
> 
> I make a new version patch to follow the cvs head.  And removed the ifdef.
> Please help me review it.

OK, well, let me start out with some thoughts about the
idea and the architecture.

It's a great idea to save and reload a recording log.
We need to document the file format, as I've informally
done above, maybe by placing a comment in the file.
I'll post a suggested comment soon.

Once that's done, it becomes obvious that we are only saving
state changes -- no snapshot of the starting state.  Then it
becomes obvious why you need to save and load a corefile as well --
that gives you the starting state snapshot, to go with your
saved state changes.

The trouble that I see now is that there is no insurance that
the starting state (core file) and the state changes (record file)
correspond to one another.  A user could get into trouble by
loading one starting state and a DIFFERENT set of state changes.
And he would have no way of knowing why his debugging session
was producing nonsense results.

This is a little bit like the problem of storing separate
symbol files (separate from the executable file).  We need
a way to confirm that the two files correspond with one another.

I've given it a little thought, and I would like to hear
what others think.  Here are some of my ideas:

1) Store a pointer to the corefile in the record log file.
2) Store a checksum, hash, or CRC of the corefile in the log file.
3) Store a CRC of the loadable sections, plus a snapshot of the
    register set, in the log file.
4) Store both corefile and log file together in the same file.

Of course, every time we change the file format, we should
change the magic number to insure that a new-format gdb will
not attempt to read an old-format file.

Discussion?


> 2009-08-05  Hui Zhu  <teawater@gmail.com>
> 
>         Add dump and load command to process record and replay.
> 
>         * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
>         byteswap.h, netinet/in.h): Include files.
>         (RECORD_IS_REPLAY): Return true if record_core is true.
>         (RECORD_FILE_MAGIC): New macro.
>         (record_core_buf_entry): New struct.
>         (record_core, record_core_regbuf, record_core_start,
>         record_core_end, record_core_buf_list,
>         record_beneath_to_fetch_registers_ops,
>         record_beneath_to_fetch_registers,
>         record_beneath_to_store_registers_ops,
>         record_beneath_to_store_registers,
>         record_beneath_to_has_execution_ops,
>         record_beneath_to_has_execution,
>         record_beneath_to_prepare_to_store): New variables.
>         (record_list_release_first_insn): Change function
>         record_list_release_first to this name.
>         (record_arch_list_cleanups): New function.
>         (record_message_cleanups): Removed.
>         (record_message): Change to call record_arch_list_cleanups
>         and record_list_release_first_insn.
>         (record_exec_entry): New function.
>         (record_open): Add support for target core.
>         (record_close): Add support for target core.
>         (record_wait): Call function 'record_exec_entry'.
>         (record_kill): Add support for target core.
>         (record_registers_change): Call record_list_release_first_insn.
>         (record_fetch_registers): New function.
>         (record_prepare_to_store): New function.
>         (record_store_registers): Add support for target core.
>         (record_xfer_partial): Add support for target core.
>         (record_has_execution): New function.
>         (init_record_ops): Set record_ops.to_fetch_registers,
>         record_ops.to_prepare_to_store
>         and record_ops.to_has_execution.
>         (cmd_record_fd_cleanups): New function.
>         (cmd_record_dump): New function.
>         (cmd_record_load): New function.
>         (set_record_insn_max_num): Call record_list_release_first_insn.
>         (_initialize_record): Add commands "record dump"
>         and "record load".
> 
> ---
>  record.c |  731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 640 insertions(+), 91 deletions(-)
> 
> --- a/record.c
> +++ b/record.c
> @@ -23,14 +23,22 @@
>  #include "gdbthread.h"
>  #include "event-top.h"
>  #include "exceptions.h"
> +#include "completer.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "exec.h"
>  #include "record.h"
> 
> +#include <byteswap.h>
>  #include <signal.h>
> +#include <netinet/in.h>
> 
>  #define DEFAULT_RECORD_INSN_MAX_NUM    200000
> 
>  #define RECORD_IS_REPLAY \
> -     (record_list->next || execution_direction == EXEC_REVERSE)
> +     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
> +
> +#define RECORD_FILE_MAGIC      htonl(0x20090726)
> 
>  /* These are the core struct of record function.
> 
> @@ -78,9 +86,23 @@ struct record_entry
>    } u;
>  };
> 
> +struct record_core_buf_entry
> +{
> +  struct record_core_buf_entry *prev;
> +  struct target_section *p;
> +  bfd_byte *buf;
> +};
> +
>  /* This is the debug switch for process record.  */
>  int record_debug = 0;
> 
> +/* Record with core target.  */
> +static int record_core = 0;
> +static gdb_byte *record_core_regbuf;
> +static struct target_section *record_core_start;
> +static struct target_section *record_core_end;
> +static struct record_core_buf_entry *record_core_buf_list = NULL;
> +
>  /* These list is for execution log.  */
>  static struct record_entry record_first;
>  static struct record_entry *record_list = &record_first;
> @@ -103,6 +125,14 @@ static struct target_ops *record_beneath
>  static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
>                                          struct target_waitstatus *,
>                                          int);
> +static struct target_ops *record_beneath_to_fetch_registers_ops;
> +static void (*record_beneath_to_fetch_registers) (struct target_ops *,
> +                                                  struct regcache *,
> +                                                 int regno);
> +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_store_registers_ops;
>  static void (*record_beneath_to_store_registers) (struct target_ops *,
>                                                    struct regcache *,
> @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
>                                                    struct bp_target_info *);
>  static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
>                                                    struct bp_target_info *);
> +static struct target_ops *record_beneath_to_has_execution_ops;
> +static int (*record_beneath_to_has_execution) (struct target_ops *ops);
> +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
> 
>  static void
>  record_list_release (struct record_entry *rec)
> @@ -169,7 +202,7 @@ record_list_release_next (void)
>  }
> 
>  static void
> -record_list_release_first (void)
> +record_list_release_first_insn (void)
>  {
>    struct record_entry *tmp = NULL;
>    enum record_type type;
> @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
>               if (q)
>                 record_stop_at_limit = 0;
>               else
> -               error (_("Process record: inferior program stopped."));
> +               error (_("Process record: stoped by user."));
>             }
>         }
>      }
>  }
> 
> +static void
> +record_arch_list_cleanups (void *ignore)
> +{
> +  record_list_release (record_arch_list_tail);
> +}
> +
>  /* Before inferior step (when GDB record the running message, inferior
>     only can step), GDB will call this function to record the values to
>     record_list.  This function will call gdbarch_process_record to
>     record the running message of inferior and set them to
>     record_arch_list, and add it to record_list.  */
> 
> -static void
> -record_message_cleanups (void *ignore)
> -{
> -  record_list_release (record_arch_list_tail);
> -}
> -
>  static int
>  record_message (void *args)
>  {
>    int ret;
>    struct regcache *regcache = args;
> -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
> +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
> 
>    record_arch_list_head = NULL;
>    record_arch_list_tail = NULL;
> @@ -386,7 +419,7 @@ record_message (void *args)
>    record_list = record_arch_list_tail;
> 
>    if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>    else
>      record_insn_num++;
> 
> @@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void)
>    return old_cleanups;
>  }
> 
> +static inline void
> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> +                   struct record_entry *entry)
> +{
> +  switch (entry->type)
> +    {
> +    case record_reg: /* reg */
> +      {
> +        gdb_byte reg[MAX_REGISTER_SIZE];
> +
> +        if (record_debug > 1)
> +          fprintf_unfiltered (gdb_stdlog,
> +                              "Process record: record_reg %s to "
> +                              "inferior num = %d.\n",
> +                              host_address_to_string (entry),
> +                              entry->u.reg.num);
> +
> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> +        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> +      }
> +      break;
> +
> +    case record_mem: /* mem */
> +      {
> +        if (!record_list->u.mem.mem_entry_not_accessible)
> +          {
> +            gdb_byte *mem = alloca (entry->u.mem.len);
> +
> +            if (record_debug > 1)
> +              fprintf_unfiltered (gdb_stdlog,
> +                                  "Process record: record_mem %s to "
> +                                  "inferior addr = %s len = %d.\n",
> +                                  host_address_to_string (entry),
> +                                  paddress (gdbarch, entry->u.mem.addr),
> +                                  record_list->u.mem.len);
> +
> +            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
> +              {
> +                 if ((execution_direction == EXEC_REVERSE && !record_core)
> +                     || (execution_direction != EXEC_REVERSE && record_core))
> +                  {
> +                    record_list->u.mem.mem_entry_not_accessible = 1;
> +                    if (record_debug)
> +                      warning (_("Process record: error reading memory at "
> +                                 "addr = %s len = %d."),
> +                               paddress (gdbarch, entry->u.mem.addr),
> +                               entry->u.mem.len);
> +                  }
> +                else
> +                  error (_("Process record: error reading memory at "
> +                           "addr = %s len = %d."),
> +                         paddress (gdbarch, entry->u.mem.addr),
> +                        entry->u.mem.len);
> +              }
> +            else
> +              {
> +                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
> +                                         entry->u.mem.len))
> +                  {
> +                     if ((execution_direction == EXEC_REVERSE && !record_core)
> +                         || (execution_direction != EXEC_REVERSE &&
> record_core))
> +                      {
> +                        record_list->u.mem.mem_entry_not_accessible = 1;
> +                        if (record_debug)
> +                          warning (_("Process record: error writing memory at "
> +                                     "addr = %s len = %d."),
> +                                   paddress (gdbarch, entry->u.mem.addr),
> +                                   entry->u.mem.len);
> +                      }
> +                    else
> +                      error (_("Process record: error writing memory at "
> +                               "addr = %s len = %d."),
> +                             paddress (gdbarch, entry->u.mem.addr),
> +                            entry->u.mem.len);
> +                  }
> +              }
> +
> +            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> +          }
> +      }
> +      break;
> +    }
> +}
> +
>  static void
>  record_open (char *name, int from_tty)
>  {
> @@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
>    if (record_debug)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> 
> +  if (!strcmp (current_target.to_shortname, "core"))
> +    record_core = 1;
> +  else
> +    record_core = 0;
> +
>    /* check exec */
> -  if (!target_has_execution)
> +  if (!target_has_execution && !record_core)
>      error (_("Process record: the program is not being run."));
>    if (non_stop)
>      error (_("Process record target can't debug inferior in non-stop mode "
> @@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
>    record_beneath_to_xfer_partial = NULL;
>    record_beneath_to_insert_breakpoint = NULL;
>    record_beneath_to_remove_breakpoint = NULL;
> +  record_beneath_to_has_execution = NULL;
> +  record_beneath_to_prepare_to_store = NULL;
> 
>    /* Set the beneath function pointers.  */
>    for (t = current_target.beneath; t != NULL; t = t->beneath)
> @@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
>           record_beneath_to_wait = t->to_wait;
>           record_beneath_to_wait_ops = t;
>          }
> +      if (!record_beneath_to_fetch_registers)
> +        {
> +         record_beneath_to_fetch_registers = t->to_fetch_registers;
> +         record_beneath_to_fetch_registers_ops = t;
> +        }
>        if (!record_beneath_to_store_registers)
>          {
>           record_beneath_to_store_registers = t->to_store_registers;
> @@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
>         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_has_execution)
> +        {
> +          record_beneath_to_has_execution_ops = t;
> +         record_beneath_to_has_execution = t->to_has_execution;
> +        }
> +      if (!record_beneath_to_prepare_to_store)
> +       record_beneath_to_prepare_to_store = t->to_prepare_to_store;
>      }
> -  if (!record_beneath_to_resume)
> +  if (!record_beneath_to_resume && !record_core)
>      error (_("Process record can't get to_resume."));
> -  if (!record_beneath_to_wait)
> +  if (!record_beneath_to_wait && !record_core)
>      error (_("Process record can't get to_wait."));
> -  if (!record_beneath_to_store_registers)
> +  if (!record_beneath_to_fetch_registers)
> +    error (_("Process record can't get to_fetch_registers."));
> +  if (!record_beneath_to_store_registers && !record_core)
>      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)
> +  if (!record_beneath_to_insert_breakpoint && !record_core)
>      error (_("Process record can't get to_insert_breakpoint."));
> -  if (!record_beneath_to_remove_breakpoint)
> +  if (!record_beneath_to_remove_breakpoint && !record_core)
>      error (_("Process record can't get to_remove_breakpoint."));
> +  if (!record_beneath_to_has_execution && !record_core)
> +    error (_("Process record can't get to_has_execution."));
> +  if (!record_beneath_to_prepare_to_store && !record_core)
> +    error (_("Process record can't get to_prepare_to_store."));
> +
> +  if (record_core)
> +    {
> +      /* Get record_core_regbuf.  */
> +      struct regcache *regcache = get_current_regcache ();
> +      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
> +      int i;
> +
> +      target_fetch_registers (regcache, -1);
> +      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
> +      for (i = 0; i < regnum; i ++)
> +        regcache_raw_collect (regcache, i,
> +                              record_core_regbuf + MAX_REGISTER_SIZE * i);
> +
> +      /* Get record_core_start and record_core_end.  */
> +      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
> +        error (_("\"%s\": Can't find sections: %s"),
> +               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
> +    }
> 
>    push_target (&record_ops);
> 
> @@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
>  static void
>  record_close (int quitting)
>  {
> +  struct record_core_buf_entry *entry;
> +
>    if (record_debug)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
> 
>    record_list_release (record_list);
> +
> +  /* Release record_core_regbuf.  */
> +  xfree (record_core_regbuf);
> +
> +  /* Release record_core_buf_list.  */
> +  if (record_core_buf_list)
> +    {
> +      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
> +        {
> +          xfree (record_core_buf_list);
> +          record_core_buf_list = entry;
> +        }
> +      record_core_buf_list = NULL;
> +    }
>  }
> 
>  static int record_resume_step = 0;
> @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
>               break;
>             }
> 
> -         /* Set ptid, register and memory according to record_list.  */
> -         if (record_list->type == record_reg)
> -           {
> -             /* reg */
> -             gdb_byte reg[MAX_REGISTER_SIZE];
> -             if (record_debug > 1)
> -               fprintf_unfiltered (gdb_stdlog,
> -                                   "Process record: record_reg %s to "
> -                                   "inferior num = %d.\n",
> -                                   host_address_to_string (record_list),
> -                                   record_list->u.reg.num);
> -             regcache_cooked_read (regcache, record_list->u.reg.num, reg);
> -             regcache_cooked_write (regcache, record_list->u.reg.num,
> -                                    record_list->u.reg.val);
> -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> -           }
> -         else if (record_list->type == record_mem)
> -           {
> -             /* mem */
> -             /* Nothing to do if the entry is flagged not_accessible.  */
> -             if (!record_list->u.mem.mem_entry_not_accessible)
> -               {
> -                 gdb_byte *mem = alloca (record_list->u.mem.len);
> -                 if (record_debug > 1)
> -                   fprintf_unfiltered (gdb_stdlog,
> -                                       "Process record: record_mem %s to "
> -                                       "inferior addr = %s len = %d.\n",
> -                                       host_address_to_string (record_list),
> -                                       paddress (gdbarch,
> -                                                 record_list->u.mem.addr),
> -                                       record_list->u.mem.len);
> +          record_exec_entry (regcache, gdbarch, record_list);
> 
> -                 if (target_read_memory (record_list->u.mem.addr, mem,
> -                                         record_list->u.mem.len))
> -                   {
> -                     if (execution_direction != EXEC_REVERSE)
> -                       error (_("Process record: error reading memory at "
> -                                "addr = %s len = %d."),
> -                              paddress (gdbarch, record_list->u.mem.addr),
> -                              record_list->u.mem.len);
> -                     else
> -                       /* Read failed --
> -                          flag entry as not_accessible.  */
> -                       record_list->u.mem.mem_entry_not_accessible = 1;
> -                   }
> -                 else
> -                   {
> -                     if (target_write_memory (record_list->u.mem.addr,
> -                                              record_list->u.mem.val,
> -                                              record_list->u.mem.len))
> -                       {
> -                         if (execution_direction != EXEC_REVERSE)
> -                           error (_("Process record: error writing memory at "
> -                                    "addr = %s len = %d."),
> -                                  paddress (gdbarch, record_list->u.mem.addr),
> -                                  record_list->u.mem.len);
> -                         else
> -                           /* Write failed --
> -                              flag entry as not_accessible.  */
> -                           record_list->u.mem.mem_entry_not_accessible = 1;
> -                       }
> -                     else
> -                       {
> -                         memcpy (record_list->u.mem.val, mem,
> -                                 record_list->u.mem.len);
> -                       }
> -                   }
> -               }
> -           }
> -         else
> +         if (record_list->type == record_end)
>             {
>               if (record_debug > 1)
>                 fprintf_unfiltered (gdb_stdlog,
> @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
> 
>    unpush_target (&record_ops);
> -  target_kill ();
> +
> +  if (!record_core)
> +    target_kill ();
>  }
> 
>  /* Record registers change (by user or by GDB) to list as an instruction.  */
> @@ -945,15 +1058,58 @@ record_registers_change (struct regcache
>    record_list = record_arch_list_tail;
> 
>    if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>    else
>      record_insn_num++;
>  }
> 
>  static void
> +record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
> +                        int regno)
> +{
> +  if (record_core)
> +    {
> +      if (regno < 0)
> +        {
> +          int num = gdbarch_num_regs (get_regcache_arch (regcache));
> +          int i;
> +
> +          for (i = 0; i < num; i ++)
> +            regcache_raw_supply (regcache, i,
> +                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
> +        }
> +      else
> +        regcache_raw_supply (regcache, regno,
> +                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +    }
> +  else
> +    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
> +                                       regcache, regno);
> +}
> +
> +static void
> +record_prepare_to_store (struct regcache *regcache)
> +{
> +  if (!record_core)
> +    record_beneath_to_prepare_to_store (regcache);
> +}
> +
> +static void
>  record_store_registers (struct target_ops *ops, struct regcache *regcache,
>                          int regno)
>  {
> +  if (record_core)
> +    {
> +      /* Debug with core.  */
> +      if (record_gdb_operation_disable)
> +        regcache_raw_collect (regcache, regno,
> +                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +      else
> +        error (_("You can't do that without a process to debug."));
> +
> +      return;
> +    }
> +
>    if (!record_gdb_operation_disable)
>      {
>        if (RECORD_IS_REPLAY)
> @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op
> 
>        record_registers_change (regcache, regno);
>      }
> +
>    record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
>                                       regcache, regno);
>  }
> @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
>  {
>    if (!record_gdb_operation_disable
>        && (object == TARGET_OBJECT_MEMORY
> -         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
> +         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
>      {
>        if (RECORD_IS_REPLAY)
>         {
> @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
>        record_list = record_arch_list_tail;
> 
>        if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -       record_list_release_first ();
> +       record_list_release_first_insn ();
>        else
>         record_insn_num++;
>      }
> 
> +   if (record_core && object == TARGET_OBJECT_MEMORY)
> +     {
> +       /* Debug with core.  */
> +       if (record_gdb_operation_disable || !writebuf)
> +         {
> +           struct target_section *p;
> +           for (p = record_core_start; p < record_core_end; p++)
> +             {
> +               if (offset >= p->addr)
> +                 {
> +                   struct record_core_buf_entry *entry;
> +
> +                   if (offset >= p->endaddr)
> +                     continue;
> +
> +                   if (offset + len > p->endaddr)
> +                     len = p->endaddr - offset;
> +
> +                   offset -= p->addr;
> +
> +                   /* Read readbuf or write writebuf p, offset, len.  */
> +                   /* Check flags.  */
> +                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
> +                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
> +                     {
> +                       if (readbuf)
> +                         memset (readbuf, 0, len);
> +                       return len;
> +                     }
> +                   /* Get record_core_buf_entry.  */
> +                   for (entry = record_core_buf_list; entry;
> +                        entry = entry->prev)
> +                     if (entry->p == p)
> +                       break;
> +                   if (writebuf)
> +                     {
> +                       if (!entry)
> +                         {
> +                           /* Add a new entry.  */
> +                           entry
> +                             = (struct record_core_buf_entry *)
> +                                 xmalloc
> +                                   (sizeof (struct record_core_buf_entry));
> +                           entry->p = p;
> +                           if (!bfd_malloc_and_get_section (p->bfd,
> +                                                            p->the_bfd_section,
> +                                                            &entry->buf))
> +                             {
> +                               xfree (entry);
> +                               return 0;
> +                             }
> +                           entry->prev = record_core_buf_list;
> +                           record_core_buf_list = entry;
> +                         }
> +
> +                        memcpy (entry->buf + offset, writebuf, (size_t) len);
> +                     }
> +                   else
> +                     {
> +                       if (!entry)
> +                         return record_beneath_to_xfer_partial
> +                                  (record_beneath_to_xfer_partial_ops,
> +                                   object, annex, readbuf, writebuf,
> +                                   offset, len);
> +
> +                       memcpy (readbuf, entry->buf + offset, (size_t) len);
> +                     }
> +
> +                   return len;
> +                 }
> +             }
> +
> +           return 0;
> +         }
> +       else
> +         error (_("You can't do that without a process to debug."));
> +
> +       return 0;
> +     }
> +
>    return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
>                                           object, annex, readbuf, writebuf,
>                                           offset, len);
> @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
>    return 1;
>  }
> 
> +int
> +record_has_execution (struct target_ops *ops)
> +{
> +  if (record_core)
> +    return 1;
> +
> +  return record_beneath_to_has_execution (ops);
> +}
> +
>  static void
>  init_record_ops (void)
>  {
> @@ -1129,11 +1375,14 @@ init_record_ops (void)
>    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_fetch_registers = record_fetch_registers;
> +  record_ops.to_prepare_to_store = record_prepare_to_store;
>    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_has_execution = record_has_execution;
>    record_ops.to_stratum = record_stratum;
>    record_ops.to_magic = OPS_MAGIC;
>  }
> @@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t
>    execute_command ("target record", from_tty);
>  }
> 
> +static void
> +cmd_record_fd_cleanups (void *recfdp)
> +{
> +  int recfd = *(int *) recfdp;
> +  close (recfd);
> +}
> +
> +static inline void
> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
> +{
> +  if (read (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to read dump of execution records in '%s'."),
> +           recfilename);
> +}
> +
> +static inline void
> +record_write_dump (char *recfilename, int fildes, const void *buf,
> +                   size_t nbyte)
> +{
> +  if (write (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to write dump of execution records to '%s'."),
> +           recfilename);
> +}
> +
> +/* Dump the execution log to a file.  */
> +
> +static void
> +cmd_record_dump (char *args, int from_tty)
> +{
> +  char *recfilename, recfilename_buffer[40];
> +  int recfd;
> +  struct record_entry *cur_record_list;
> +  uint32_t magic;
> +  struct regcache *regcache;
> +  struct gdbarch *gdbarch;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *set_cleanups;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    error (_("Process record is not started.\n"));
> +
> +  if (args && *args)
> +    recfilename = args;
> +  else
> +    {
> +      /* Default corefile name is "gdb_record.PID".  */
> +      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
> +      recfilename = recfilename_buffer;
> +    }
> +
> +  /* Open the dump file.  */
> +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> +                S_IRUSR | S_IWUSR);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for dump execution records: %s"),
> +           recfilename, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Save the current record entry to "cur_record_list".  */
> +  cur_record_list = record_list;
> +
> +  /* Get the values of regcache and gdbarch.  */
> +  regcache = get_current_regcache ();
> +  gdbarch = get_regcache_arch (regcache);
> +
> +  /* Disable the GDB operation record.  */
> +  set_cleanups = record_gdb_operation_disable_set ();
> +
> +  /* Write the magic code.  */
> +  magic = RECORD_FILE_MAGIC;
> +  record_write_dump (recfilename, recfd, &magic, 4);
> +
> +  /* Reverse execute to the begin of record list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == &record_first)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  /* Dump the entries to recfd and forward execute to the end of
> +     record list.  */
> +  while (1)
> +    {
> +      /* Dump entry.  */
> +      if (record_list != &record_first)
> +        {
> +          uint8_t tmpu8;
> +          uint64_t tmpu64;
> +
> +          tmpu8 = record_list->type;
> +          record_write_dump (recfilename, recfd, &tmpu8, 1);
> +
> +          switch (record_list->type)
> +            {
> +            case record_reg: /* reg */
> +              tmpu64 = record_list->u.reg.num;
> +              if (BYTE_ORDER == LITTLE_ENDIAN)
> +                tmpu64 = bswap_64 (tmpu64);
> +              record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +              record_write_dump (recfilename, recfd, record_list->u.reg.val,
> +                                 MAX_REGISTER_SIZE);
> +              break;
> +            case record_mem: /* mem */
> +              if (!record_list->u.mem.mem_entry_not_accessible)
> +                {
> +                  tmpu64 = record_list->u.mem.addr;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  tmpu64 = record_list->u.mem.len;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  record_write_dump (recfilename, recfd,
> +                                     record_list->u.mem.val,
> +                                     record_list->u.mem.len);
> +                }
> +              break;
> +            }
> +        }
> +
> +      /* Execute entry.  */
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->next)
> +        record_list = record_list->next;
> +      else
> +        break;
> +    }
> +
> +  /* Reverse execute to cur_record_list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == cur_record_list)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  do_cleanups (set_cleanups);
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
> +                                  "records to `%s'.\n"),
> +                    recfilename);
> +}
> +
> +/* Load the execution log from a file.  */
> +
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    {
> +      cmd_record_start (NULL, from_tty);
> +      printf_unfiltered (_("Auto start process record.\n"));
> +    }
> +
> +  if (!args || (args && !*args))
> +    error (_("Argument for filename required.\n"));
> +
> +  /* Open the load file.  */
> +  recfd = open (args, O_RDONLY | O_BINARY);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for loading execution records: %s"),
> +           args, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Check the magic code.  */
> +  record_read_dump (args, recfd, &magic, 4);
> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a valid dump of execution records."), args);
> +
> +  /* Load the entries in recfd to the record_arch_list_head and
> +     record_arch_list_tail.  */
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
> +
> +  while (1)
> +    {
> +      int ret;
> +      uint8_t tmpu8;
> +      uint64_t tmpu64;
> +
> +      ret = read (recfd, &tmpu8, 1);
> +      if (ret < 0)
> +        error (_("Failed to read dump of execution records in '%s'."), args);
> +      if (ret == 0)
> +        break;
> +
> +      switch (tmpu8)
> +        {
> +        case record_reg: /* reg */
> +          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;
> +          /* Get num.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.reg.num = tmpu64;
> +          /* Get val.  */
> +          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
> +          record_arch_list_add (rec);
> +          break;
> +        case record_mem: /* mem */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_mem;
> +          /* Get addr.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.addr = tmpu64;
> +          /* Get len.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.len = tmpu64;
> +          rec->u.mem.mem_entry_not_accessible = 0;
> +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> +          /* Get val.  */
> +          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_end: /* end */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_end;
> +          record_arch_list_add (rec);
> +          insn_number ++;
> +          break;
> +
> +        default:
> +          error (_("Format of '%s' is not right."), args);
> +          break;
> +        }
> +    }
> +
> +  discard_cleanups (old_cleanups2);
> +
> +  /* Add record_arch_list_head to the end of record list.  */
> +  for (rec = record_list; rec->next; rec = rec->next);
> +  rec->next = record_arch_list_head;
> +  record_arch_list_head->prev = rec;
> +
> +  /* Update record_insn_num and record_insn_max_num.  */
> +  record_insn_num += insn_number;
> +  if (record_insn_num > record_insn_max_num)
> +    {
> +      record_insn_max_num = record_insn_num;
> +      warning (_("Auto increase record/replay buffer limit to %d."),
> +               record_insn_max_num);
> +    }
> +
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
> +}
> +
>  /* Truncate the record log from the present point
>     of replay until the end.  */
> 
> @@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int
>                            "the first ones?\n"));
> 
>        while (record_insn_num > record_insn_max_num)
> -       record_list_release_first ();
> +       record_list_release_first_insn ();
>      }
>  }
> 
> @@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro
>  void
>  _initialize_record (void)
>  {
> +  struct cmd_list_element *c;
> +
>    /* Init record_first.  */
>    record_first.prev = NULL;
>    record_first.next = NULL;
> @@ -1277,6 +1816,16 @@ _initialize_record (void)
>                   "info record ", 0, &infolist);
>    add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
> 
> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> +              _("Dump the execution records to a file.\n\
> +Argument is optional filename.  Default filename is
> 'gdb_record.<process_id>'."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> +  c = add_cmd ("load", class_obscure, cmd_record_load,
> +              _("Load previously dumped execution records from \
> +a file given as argument."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> 
>    add_cmd ("delete", class_obscure, cmd_record_delete,
>            _("Delete the rest of execution log and start recording it anew."),
> 
> 
> ------------------------------------------------------------------------
> 
> ---
>  record.c |  731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 640 insertions(+), 91 deletions(-)
> 
> --- a/record.c
> +++ b/record.c
> @@ -23,14 +23,22 @@
>  #include "gdbthread.h"
>  #include "event-top.h"
>  #include "exceptions.h"
> +#include "completer.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "exec.h"
>  #include "record.h"
>  
> +#include <byteswap.h>
>  #include <signal.h>
> +#include <netinet/in.h>
>  
>  #define DEFAULT_RECORD_INSN_MAX_NUM	200000
>  
>  #define RECORD_IS_REPLAY \
> -     (record_list->next || execution_direction == EXEC_REVERSE)
> +     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
> +
> +#define RECORD_FILE_MAGIC	htonl(0x20090726)
>  
>  /* These are the core struct of record function.
>  
> @@ -78,9 +86,23 @@ struct record_entry
>    } u;
>  };
>  
> +struct record_core_buf_entry
> +{
> +  struct record_core_buf_entry *prev;
> +  struct target_section *p;
> +  bfd_byte *buf;
> +};
> +
>  /* This is the debug switch for process record.  */
>  int record_debug = 0;
>  
> +/* Record with core target.  */
> +static int record_core = 0;
> +static gdb_byte *record_core_regbuf;
> +static struct target_section *record_core_start;
> +static struct target_section *record_core_end;
> +static struct record_core_buf_entry *record_core_buf_list = NULL;
> +
>  /* These list is for execution log.  */
>  static struct record_entry record_first;
>  static struct record_entry *record_list = &record_first;
> @@ -103,6 +125,14 @@ static struct target_ops *record_beneath
>  static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
>  					 struct target_waitstatus *,
>  					 int);
> +static struct target_ops *record_beneath_to_fetch_registers_ops;
> +static void (*record_beneath_to_fetch_registers) (struct target_ops *,
> +                                                  struct regcache *,
> +						  int regno);
> +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_store_registers_ops;
>  static void (*record_beneath_to_store_registers) (struct target_ops *,
>                                                    struct regcache *,
> @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
>  						   struct bp_target_info *);
>  static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
>  						   struct bp_target_info *);
> +static struct target_ops *record_beneath_to_has_execution_ops;
> +static int (*record_beneath_to_has_execution) (struct target_ops *ops);
> +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
>  
>  static void
>  record_list_release (struct record_entry *rec)
> @@ -169,7 +202,7 @@ record_list_release_next (void)
>  }
>  
>  static void
> -record_list_release_first (void)
> +record_list_release_first_insn (void)
>  {
>    struct record_entry *tmp = NULL;
>    enum record_type type;
> @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
>  	      if (q)
>  		record_stop_at_limit = 0;
>  	      else
> -		error (_("Process record: inferior program stopped."));
> +		error (_("Process record: stoped by user."));
>  	    }
>  	}
>      }
>  }
>  
> +static void
> +record_arch_list_cleanups (void *ignore)
> +{
> +  record_list_release (record_arch_list_tail);
> +}
> +
>  /* Before inferior step (when GDB record the running message, inferior
>     only can step), GDB will call this function to record the values to
>     record_list.  This function will call gdbarch_process_record to
>     record the running message of inferior and set them to
>     record_arch_list, and add it to record_list.  */
>  
> -static void
> -record_message_cleanups (void *ignore)
> -{
> -  record_list_release (record_arch_list_tail);
> -}
> -
>  static int
>  record_message (void *args)
>  {
>    int ret;
>    struct regcache *regcache = args;
> -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
> +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
>  
>    record_arch_list_head = NULL;
>    record_arch_list_tail = NULL;
> @@ -386,7 +419,7 @@ record_message (void *args)
>    record_list = record_arch_list_tail;
>  
>    if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>    else
>      record_insn_num++;
>  
> @@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void)
>    return old_cleanups;
>  }
>  
> +static inline void
> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> +                   struct record_entry *entry)
> +{
> +  switch (entry->type)
> +    {
> +    case record_reg: /* reg */
> +      {
> +        gdb_byte reg[MAX_REGISTER_SIZE];
> +
> +        if (record_debug > 1)
> +          fprintf_unfiltered (gdb_stdlog,
> +                              "Process record: record_reg %s to "
> +                              "inferior num = %d.\n",
> +                              host_address_to_string (entry),
> +                              entry->u.reg.num);
> +
> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> +        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> +      }
> +      break;
> +
> +    case record_mem: /* mem */
> +      {
> +        if (!record_list->u.mem.mem_entry_not_accessible)
> +          {
> +            gdb_byte *mem = alloca (entry->u.mem.len);
> +
> +            if (record_debug > 1)
> +              fprintf_unfiltered (gdb_stdlog,
> +                                  "Process record: record_mem %s to "
> +                                  "inferior addr = %s len = %d.\n",
> +                                  host_address_to_string (entry),
> +                                  paddress (gdbarch, entry->u.mem.addr),
> +                                  record_list->u.mem.len);
> +
> +            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
> +              {
> +                 if ((execution_direction == EXEC_REVERSE && !record_core)
> +                     || (execution_direction != EXEC_REVERSE && record_core))
> +                  {
> +                    record_list->u.mem.mem_entry_not_accessible = 1;
> +                    if (record_debug)
> +                      warning (_("Process record: error reading memory at "
> +                                 "addr = %s len = %d."),
> +                               paddress (gdbarch, entry->u.mem.addr),
> +                               entry->u.mem.len);
> +                  }
> +                else
> +                  error (_("Process record: error reading memory at "
> +                           "addr = %s len = %d."),
> +                         paddress (gdbarch, entry->u.mem.addr),
> +                        entry->u.mem.len);
> +              }
> +            else
> +              {
> +                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
> +                                         entry->u.mem.len))
> +                  {
> +                     if ((execution_direction == EXEC_REVERSE && !record_core)
> +                         || (execution_direction != EXEC_REVERSE && record_core))
> +                      {
> +                        record_list->u.mem.mem_entry_not_accessible = 1;
> +                        if (record_debug)
> +                          warning (_("Process record: error writing memory at "
> +                                     "addr = %s len = %d."),
> +                                   paddress (gdbarch, entry->u.mem.addr),
> +                                   entry->u.mem.len);
> +                      }
> +                    else
> +                      error (_("Process record: error writing memory at "
> +                               "addr = %s len = %d."),
> +                             paddress (gdbarch, entry->u.mem.addr),
> +                            entry->u.mem.len);
> +                  }
> +              }
> +
> +            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> +          }
> +      }
> +      break;
> +    }
> +}
> +
>  static void
>  record_open (char *name, int from_tty)
>  {
> @@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
>    if (record_debug)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
>  
> +  if (!strcmp (current_target.to_shortname, "core"))
> +    record_core = 1;
> +  else
> +    record_core = 0;
> +
>    /* check exec */
> -  if (!target_has_execution)
> +  if (!target_has_execution && !record_core)
>      error (_("Process record: the program is not being run."));
>    if (non_stop)
>      error (_("Process record target can't debug inferior in non-stop mode "
> @@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
>    record_beneath_to_xfer_partial = NULL;
>    record_beneath_to_insert_breakpoint = NULL;
>    record_beneath_to_remove_breakpoint = NULL;
> +  record_beneath_to_has_execution = NULL;
> +  record_beneath_to_prepare_to_store = NULL;
>  
>    /* Set the beneath function pointers.  */
>    for (t = current_target.beneath; t != NULL; t = t->beneath)
> @@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
>  	  record_beneath_to_wait = t->to_wait;
>  	  record_beneath_to_wait_ops = t;
>          }
> +      if (!record_beneath_to_fetch_registers)
> +        {
> +	  record_beneath_to_fetch_registers = t->to_fetch_registers;
> +	  record_beneath_to_fetch_registers_ops = t;
> +        }
>        if (!record_beneath_to_store_registers)
>          {
>  	  record_beneath_to_store_registers = t->to_store_registers;
> @@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
>  	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_has_execution)
> +        {
> +          record_beneath_to_has_execution_ops = t;
> +	  record_beneath_to_has_execution = t->to_has_execution;
> +        }
> +      if (!record_beneath_to_prepare_to_store)
> +	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
>      }
> -  if (!record_beneath_to_resume)
> +  if (!record_beneath_to_resume && !record_core)
>      error (_("Process record can't get to_resume."));
> -  if (!record_beneath_to_wait)
> +  if (!record_beneath_to_wait && !record_core)
>      error (_("Process record can't get to_wait."));
> -  if (!record_beneath_to_store_registers)
> +  if (!record_beneath_to_fetch_registers)
> +    error (_("Process record can't get to_fetch_registers."));
> +  if (!record_beneath_to_store_registers && !record_core)
>      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)
> +  if (!record_beneath_to_insert_breakpoint && !record_core)
>      error (_("Process record can't get to_insert_breakpoint."));
> -  if (!record_beneath_to_remove_breakpoint)
> +  if (!record_beneath_to_remove_breakpoint && !record_core)
>      error (_("Process record can't get to_remove_breakpoint."));
> +  if (!record_beneath_to_has_execution && !record_core)
> +    error (_("Process record can't get to_has_execution."));
> +  if (!record_beneath_to_prepare_to_store && !record_core)
> +    error (_("Process record can't get to_prepare_to_store."));
> +
> +  if (record_core)
> +    {
> +      /* Get record_core_regbuf.  */
> +      struct regcache *regcache = get_current_regcache ();
> +      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
> +      int i;
> +
> +      target_fetch_registers (regcache, -1);
> +      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
> +      for (i = 0; i < regnum; i ++)
> +        regcache_raw_collect (regcache, i,
> +                              record_core_regbuf + MAX_REGISTER_SIZE * i);
> +
> +      /* Get record_core_start and record_core_end.  */
> +      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
> +        error (_("\"%s\": Can't find sections: %s"),
> +               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
> +    }
>  
>    push_target (&record_ops);
>  
> @@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
>  static void
>  record_close (int quitting)
>  {
> +  struct record_core_buf_entry *entry;
> +
>    if (record_debug)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
>  
>    record_list_release (record_list);
> +
> +  /* Release record_core_regbuf.  */
> +  xfree (record_core_regbuf);
> +
> +  /* Release record_core_buf_list.  */
> +  if (record_core_buf_list)
> +    {
> +      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
> +        {
> +          xfree (record_core_buf_list);
> +          record_core_buf_list = entry;
> +        }
> +      record_core_buf_list = NULL;
> +    }
>  }
>  
>  static int record_resume_step = 0;
> @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
>  	      break;
>  	    }
>  
> -	  /* Set ptid, register and memory according to record_list.  */
> -	  if (record_list->type == record_reg)
> -	    {
> -	      /* reg */
> -	      gdb_byte reg[MAX_REGISTER_SIZE];
> -	      if (record_debug > 1)
> -		fprintf_unfiltered (gdb_stdlog,
> -				    "Process record: record_reg %s to "
> -				    "inferior num = %d.\n",
> -				    host_address_to_string (record_list),
> -				    record_list->u.reg.num);
> -	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
> -	      regcache_cooked_write (regcache, record_list->u.reg.num,
> -				     record_list->u.reg.val);
> -	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> -	    }
> -	  else if (record_list->type == record_mem)
> -	    {
> -	      /* mem */
> -	      /* Nothing to do if the entry is flagged not_accessible.  */
> -	      if (!record_list->u.mem.mem_entry_not_accessible)
> -		{
> -		  gdb_byte *mem = alloca (record_list->u.mem.len);
> -		  if (record_debug > 1)
> -		    fprintf_unfiltered (gdb_stdlog,
> -				        "Process record: record_mem %s to "
> -				        "inferior addr = %s len = %d.\n",
> -				        host_address_to_string (record_list),
> -				        paddress (gdbarch,
> -					          record_list->u.mem.addr),
> -				        record_list->u.mem.len);
> +          record_exec_entry (regcache, gdbarch, record_list);
>  
> -		  if (target_read_memory (record_list->u.mem.addr, mem,
> -		                          record_list->u.mem.len))
> -	            {
> -		      if (execution_direction != EXEC_REVERSE)
> -		        error (_("Process record: error reading memory at "
> -			         "addr = %s len = %d."),
> -		               paddress (gdbarch, record_list->u.mem.addr),
> -		               record_list->u.mem.len);
> -		      else
> -			/* Read failed -- 
> -			   flag entry as not_accessible.  */
> -		        record_list->u.mem.mem_entry_not_accessible = 1;
> -		    }
> -		  else
> -		    {
> -		      if (target_write_memory (record_list->u.mem.addr,
> -			                       record_list->u.mem.val,
> -		                               record_list->u.mem.len))
> -	                {
> -			  if (execution_direction != EXEC_REVERSE)
> -			    error (_("Process record: error writing memory at "
> -			             "addr = %s len = %d."),
> -		                   paddress (gdbarch, record_list->u.mem.addr),
> -		                   record_list->u.mem.len);
> -			  else
> -			    /* Write failed -- 
> -			       flag entry as not_accessible.  */
> -			    record_list->u.mem.mem_entry_not_accessible = 1;
> -			}
> -		      else
> -		        {
> -			  memcpy (record_list->u.mem.val, mem,
> -				  record_list->u.mem.len);
> -			}
> -		    }
> -		}
> -	    }
> -	  else
> +	  if (record_list->type == record_end)
>  	    {
>  	      if (record_debug > 1)
>  		fprintf_unfiltered (gdb_stdlog,
> @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
>  
>    unpush_target (&record_ops);
> -  target_kill ();
> +
> +  if (!record_core)
> +    target_kill ();
>  }
>  
>  /* Record registers change (by user or by GDB) to list as an instruction.  */
> @@ -945,15 +1058,58 @@ record_registers_change (struct regcache
>    record_list = record_arch_list_tail;
>  
>    if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>    else
>      record_insn_num++;
>  }
>  
>  static void
> +record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
> +                        int regno)
> +{
> +  if (record_core)
> +    {
> +      if (regno < 0)
> +        {
> +          int num = gdbarch_num_regs (get_regcache_arch (regcache));
> +          int i;
> +
> +          for (i = 0; i < num; i ++)
> +            regcache_raw_supply (regcache, i,
> +                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
> +        }
> +      else
> +        regcache_raw_supply (regcache, regno,
> +                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +    }
> +  else
> +    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
> +                                       regcache, regno);
> +}
> +
> +static void
> +record_prepare_to_store (struct regcache *regcache)
> +{
> +  if (!record_core)
> +    record_beneath_to_prepare_to_store (regcache);
> +}
> +
> +static void
>  record_store_registers (struct target_ops *ops, struct regcache *regcache,
>                          int regno)
>  {
> +  if (record_core)
> +    {
> +      /* Debug with core.  */
> +      if (record_gdb_operation_disable)
> +        regcache_raw_collect (regcache, regno,
> +                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +      else
> +        error (_("You can't do that without a process to debug."));
> +
> +      return;
> +    }
> +
>    if (!record_gdb_operation_disable)
>      {
>        if (RECORD_IS_REPLAY)
> @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op
>  
>        record_registers_change (regcache, regno);
>      }
> +
>    record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
>                                       regcache, regno);
>  }
> @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
>  {
>    if (!record_gdb_operation_disable
>        && (object == TARGET_OBJECT_MEMORY
> -	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
> +	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
>      {
>        if (RECORD_IS_REPLAY)
>  	{
> @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
>        record_list = record_arch_list_tail;
>  
>        if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -	record_list_release_first ();
> +	record_list_release_first_insn ();
>        else
>  	record_insn_num++;
>      }
>  
> +   if (record_core && object == TARGET_OBJECT_MEMORY)
> +     {
> +       /* Debug with core.  */
> +       if (record_gdb_operation_disable || !writebuf)
> +         {
> +           struct target_section *p;
> +           for (p = record_core_start; p < record_core_end; p++)
> +             {
> +               if (offset >= p->addr)
> +                 {
> +                   struct record_core_buf_entry *entry;
> +
> +                   if (offset >= p->endaddr)
> +                     continue;
> +
> +                   if (offset + len > p->endaddr)
> +                     len = p->endaddr - offset;
> +
> +                   offset -= p->addr;
> +
> +                   /* Read readbuf or write writebuf p, offset, len.  */
> +                   /* Check flags.  */
> +                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
> +                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
> +                     {
> +                       if (readbuf)
> +                         memset (readbuf, 0, len);
> +                       return len;
> +                     }
> +                   /* Get record_core_buf_entry.  */
> +                   for (entry = record_core_buf_list; entry;
> +                        entry = entry->prev)
> +                     if (entry->p == p)
> +                       break;
> +                   if (writebuf)
> +                     {
> +                       if (!entry)
> +                         {
> +                           /* Add a new entry.  */
> +                           entry
> +                             = (struct record_core_buf_entry *)
> +                                 xmalloc
> +                                   (sizeof (struct record_core_buf_entry));
> +                           entry->p = p;
> +                           if (!bfd_malloc_and_get_section (p->bfd,
> +                                                            p->the_bfd_section,
> +                                                            &entry->buf))
> +                             {
> +                               xfree (entry);
> +                               return 0;
> +                             }
> +                           entry->prev = record_core_buf_list;
> +                           record_core_buf_list = entry;
> +                         }
> +
> +                        memcpy (entry->buf + offset, writebuf, (size_t) len);
> +                     }
> +                   else
> +                     {
> +                       if (!entry)
> +                         return record_beneath_to_xfer_partial
> +                                  (record_beneath_to_xfer_partial_ops,
> +                                   object, annex, readbuf, writebuf,
> +                                   offset, len);
> +
> +                       memcpy (readbuf, entry->buf + offset, (size_t) len);
> +                     }
> +
> +                   return len;
> +                 }
> +             }
> +
> +           return 0;
> +         }
> +       else
> +         error (_("You can't do that without a process to debug."));
> +
> +       return 0;
> +     }
> +
>    return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
>                                           object, annex, readbuf, writebuf,
>                                           offset, len);
> @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
>    return 1;
>  }
>  
> +int
> +record_has_execution (struct target_ops *ops)
> +{
> +  if (record_core)
> +    return 1;
> +
> +  return record_beneath_to_has_execution (ops);
> +}
> +
>  static void
>  init_record_ops (void)
>  {
> @@ -1129,11 +1375,14 @@ init_record_ops (void)
>    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_fetch_registers = record_fetch_registers;
> +  record_ops.to_prepare_to_store = record_prepare_to_store;
>    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_has_execution = record_has_execution;
>    record_ops.to_stratum = record_stratum;
>    record_ops.to_magic = OPS_MAGIC;
>  }
> @@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t
>    execute_command ("target record", from_tty);
>  }
>  
> +static void
> +cmd_record_fd_cleanups (void *recfdp)
> +{
> +  int recfd = *(int *) recfdp;
> +  close (recfd);
> +}
> +
> +static inline void
> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
> +{
> +  if (read (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to read dump of execution records in '%s'."),
> +           recfilename);
> +}
> +
> +static inline void
> +record_write_dump (char *recfilename, int fildes, const void *buf,
> +                   size_t nbyte)
> +{
> +  if (write (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to write dump of execution records to '%s'."),
> +           recfilename);
> +}
> +
> +/* Dump the execution log to a file.  */
> +
> +static void
> +cmd_record_dump (char *args, int from_tty)
> +{
> +  char *recfilename, recfilename_buffer[40];
> +  int recfd;
> +  struct record_entry *cur_record_list;
> +  uint32_t magic;
> +  struct regcache *regcache;
> +  struct gdbarch *gdbarch;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *set_cleanups;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    error (_("Process record is not started.\n"));
> +
> +  if (args && *args)
> +    recfilename = args;
> +  else
> +    {
> +      /* Default corefile name is "gdb_record.PID".  */
> +      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
> +      recfilename = recfilename_buffer;
> +    }
> +
> +  /* Open the dump file.  */
> +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> +                S_IRUSR | S_IWUSR);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for dump execution records: %s"),
> +           recfilename, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Save the current record entry to "cur_record_list".  */
> +  cur_record_list = record_list;
> +
> +  /* Get the values of regcache and gdbarch.  */
> +  regcache = get_current_regcache ();
> +  gdbarch = get_regcache_arch (regcache);
> +
> +  /* Disable the GDB operation record.  */
> +  set_cleanups = record_gdb_operation_disable_set ();
> +
> +  /* Write the magic code.  */
> +  magic = RECORD_FILE_MAGIC;
> +  record_write_dump (recfilename, recfd, &magic, 4);
> +
> +  /* Reverse execute to the begin of record list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == &record_first)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  /* Dump the entries to recfd and forward execute to the end of
> +     record list.  */
> +  while (1)
> +    {
> +      /* Dump entry.  */
> +      if (record_list != &record_first)
> +        {
> +          uint8_t tmpu8;
> +          uint64_t tmpu64;
> +
> +          tmpu8 = record_list->type;
> +          record_write_dump (recfilename, recfd, &tmpu8, 1);
> +
> +          switch (record_list->type)
> +            {
> +            case record_reg: /* reg */
> +              tmpu64 = record_list->u.reg.num;
> +              if (BYTE_ORDER == LITTLE_ENDIAN)
> +                tmpu64 = bswap_64 (tmpu64);
> +              record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +              record_write_dump (recfilename, recfd, record_list->u.reg.val,
> +                                 MAX_REGISTER_SIZE);
> +              break;
> +            case record_mem: /* mem */
> +              if (!record_list->u.mem.mem_entry_not_accessible)
> +                {
> +                  tmpu64 = record_list->u.mem.addr;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  tmpu64 = record_list->u.mem.len;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  record_write_dump (recfilename, recfd,
> +                                     record_list->u.mem.val,
> +                                     record_list->u.mem.len);
> +                }
> +              break;
> +            }
> +        }
> +
> +      /* Execute entry.  */
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->next)
> +        record_list = record_list->next;
> +      else
> +        break;
> +    }
> +
> +  /* Reverse execute to cur_record_list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == cur_record_list)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  do_cleanups (set_cleanups);
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
> +                                  "records to `%s'.\n"),
> +                    recfilename);
> +}
> +
> +/* Load the execution log from a file.  */
> +
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    {
> +      cmd_record_start (NULL, from_tty);
> +      printf_unfiltered (_("Auto start process record.\n"));
> +    }
> +
> +  if (!args || (args && !*args))
> +    error (_("Argument for filename required.\n"));
> +
> +  /* Open the load file.  */
> +  recfd = open (args, O_RDONLY | O_BINARY);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for loading execution records: %s"),
> +           args, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Check the magic code.  */
> +  record_read_dump (args, recfd, &magic, 4);
> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a valid dump of execution records."), args);
> +
> +  /* Load the entries in recfd to the record_arch_list_head and
> +     record_arch_list_tail.  */
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
> +
> +  while (1)
> +    {
> +      int ret;
> +      uint8_t tmpu8;
> +      uint64_t tmpu64;
> +
> +      ret = read (recfd, &tmpu8, 1);
> +      if (ret < 0)
> +        error (_("Failed to read dump of execution records in '%s'."), args);
> +      if (ret == 0)
> +        break;
> +
> +      switch (tmpu8)
> +        {
> +        case record_reg: /* reg */
> +          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;
> +          /* Get num.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.reg.num = tmpu64;
> +          /* Get val.  */
> +          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
> +          record_arch_list_add (rec);
> +          break;
> +        case record_mem: /* mem */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_mem;
> +          /* Get addr.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.addr = tmpu64;
> +          /* Get len.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.len = tmpu64;
> +          rec->u.mem.mem_entry_not_accessible = 0;
> +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> +          /* Get val.  */
> +          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_end: /* end */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_end;
> +          record_arch_list_add (rec);
> +          insn_number ++;
> +          break;
> +
> +        default:
> +          error (_("Format of '%s' is not right."), args);
> +          break;
> +        }
> +    }
> +
> +  discard_cleanups (old_cleanups2);
> +
> +  /* Add record_arch_list_head to the end of record list.  */
> +  for (rec = record_list; rec->next; rec = rec->next);
> +  rec->next = record_arch_list_head;
> +  record_arch_list_head->prev = rec;
> +
> +  /* Update record_insn_num and record_insn_max_num.  */
> +  record_insn_num += insn_number;
> +  if (record_insn_num > record_insn_max_num)
> +    {
> +      record_insn_max_num = record_insn_num;
> +      warning (_("Auto increase record/replay buffer limit to %d."),
> +               record_insn_max_num);
> +    }
> +
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
> +}
> +
>  /* Truncate the record log from the present point
>     of replay until the end.  */
>  
> @@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int
>  		           "the first ones?\n"));
>  
>        while (record_insn_num > record_insn_max_num)
> -	record_list_release_first ();
> +	record_list_release_first_insn ();
>      }
>  }
>  
> @@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro
>  void
>  _initialize_record (void)
>  {
> +  struct cmd_list_element *c;
> +
>    /* Init record_first.  */
>    record_first.prev = NULL;
>    record_first.next = NULL;
> @@ -1277,6 +1816,16 @@ _initialize_record (void)
>  		  "info record ", 0, &infolist);
>    add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
>  
> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> +	       _("Dump the execution records to a file.\n\
> +Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> +  c = add_cmd ("load", class_obscure, cmd_record_load,
> +	       _("Load previously dumped execution records from \
> +a file given as argument."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
>  
>    add_cmd ("delete", class_obscure, cmd_record_delete,
>  	   _("Delete the rest of execution log and start recording it anew."),


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-05  9:21                 ` Hui Zhu
  2009-08-05 20:19                   ` [RFA/RFC] Add dump and load command to process record (file format etc) Michael Snyder
@ 2009-08-05 21:23                   ` Michael Snyder
  2009-08-06  3:14                     ` Eli Zaretskii
  1 sibling, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-05 21:23 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

Hui Zhu wrote:

> +static void
> +cmd_record_fd_cleanups (void *recfdp)
> +{
> +  int recfd = *(int *) recfdp;
> +  close (recfd);
> +}

Here's a suggested comment to start documenting the file format:

/* Record log save-file format
    Version 1

      Header:
        4 bytes: magic number RECORD_FILE_MAGIC.
          NOTE: be sure to change whenever this file format changes!

      Records:
       record_end:
        1 byte:  record type (record_end)
       record_reg:
        1 byte:  record type (record_reg)
        8 bytes: register id
       16 bytes: register value
       record_mem:
        1 byte:  record type (record_mem)
        8 bytes: memory address
        8 bytes: memory length
        n bytes: memory value (n == memory length)

    Version 2 (proposed)
    [...] */

Below I'll suggest some possible debugging printfs.
Feel free to modify them to your own liking.

> +static inline void
> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
> +{
> +  if (read (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to read dump of execution records in '%s'."),
> +           recfilename);
> +}
> +
> +static inline void
> +record_write_dump (char *recfilename, int fildes, const void *buf,
> +                   size_t nbyte)
> +{
> +  if (write (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to write dump of execution records to '%s'."),
> +           recfilename);
> +}
> +
> +/* Dump the execution log to a file.  */
> +
> +static void
> +cmd_record_dump (char *args, int from_tty)
> +{
> +  char *recfilename, recfilename_buffer[40];
> +  int recfd;
> +  struct record_entry *cur_record_list;
> +  uint32_t magic;
> +  struct regcache *regcache;
> +  struct gdbarch *gdbarch;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *set_cleanups;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    error (_("Process record is not started.\n"));
> +
> +  if (args && *args)
> +    recfilename = args;
> +  else
> +    {
> +      /* Default corefile name is "gdb_record.PID".  */
> +      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
> +      recfilename = recfilename_buffer;
> +    }
> +
> +  /* Open the dump file.  */
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog,
                         _("Saving recording to file '%s'\n"),
                         recfilename);

> +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> +                S_IRUSR | S_IWUSR);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for dump execution records: %s"),
> +           recfilename, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Save the current record entry to "cur_record_list".  */
> +  cur_record_list = record_list;
> +
> +  /* Get the values of regcache and gdbarch.  */
> +  regcache = get_current_regcache ();
> +  gdbarch = get_regcache_arch (regcache);
> +
> +  /* Disable the GDB operation record.  */
> +  set_cleanups = record_gdb_operation_disable_set ();
> +
> +  /* Write the magic code.  */
> +  magic = RECORD_FILE_MAGIC;
     if (record_debug)
       fprintf_unfiltered (gdb_stdlog, _("\
   Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
                           magic);

> +  record_write_dump (recfilename, recfd, &magic, 4);
> +
> +  /* Reverse execute to the begin of record list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == &record_first)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  /* Dump the entries to recfd and forward execute to the end of
> +     record list.  */
> +  while (1)
> +    {
> +      /* Dump entry.  */
> +      if (record_list != &record_first)
> +        {
> +          uint8_t tmpu8;
> +          uint64_t tmpu64;
> +
> +          tmpu8 = record_list->type;
> +          record_write_dump (recfilename, recfd, &tmpu8, 1);
> +
> +          switch (record_list->type)
> +            {
> +            case record_reg: /* reg */
> +              tmpu64 = record_list->u.reg.num;
> +              if (BYTE_ORDER == LITTLE_ENDIAN)
> +                tmpu64 = bswap_64 (tmpu64);
             if (record_debug)
               fprintf_unfiltered (gdb_stdlog, _("\
   Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
                                   p->u.reg.num,
                                   *(ULONGEST *) p->u.reg.val,
                                   MAX_REGISTER_SIZE);

> +              record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +              record_write_dump (recfilename, recfd, record_list->u.reg.val,
> +                                 MAX_REGISTER_SIZE);
> +              break;
> +            case record_mem: /* mem */
> +              if (!record_list->u.mem.mem_entry_not_accessible)
> +                {
> +                  tmpu64 = record_list->u.mem.addr;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
             if (record_debug)
               fprintf_unfiltered (gdb_stdlog, _("\
   Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"),
                                   (unsigned int) p->u.mem.addr,
                                   p->u.mem.len);

> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  tmpu64 = record_list->u.mem.len;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  record_write_dump (recfilename, recfd,
> +                                     record_list->u.mem.val,
> +                                     record_list->u.mem.len);
> +                }
> +              break;
           case record_end:
               /* FIXME: record the contents of record_end rec.  */
               if (record_debug)
                 fprintf_unfiltered (gdb_stdlog, _("\
   Writing record_end (1 byte)\n"));
             break;

> +            }
> +        }
> +
> +      /* Execute entry.  */
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->next)
> +        record_list = record_list->next;
> +      else
> +        break;
> +    }
> +
> +  /* Reverse execute to cur_record_list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == cur_record_list)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  do_cleanups (set_cleanups);
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
> +                                  "records to `%s'.\n"),
> +                    recfilename);
> +}
> +
> +/* Load the execution log from a file.  */
> +
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    {
> +      cmd_record_start (NULL, from_tty);
> +      printf_unfiltered (_("Auto start process record.\n"));
> +    }
> +
> +  if (!args || (args && !*args))
> +    error (_("Argument for filename required.\n"));
> +
   /* Open the load file.  */
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog,
                         _("Restoring recording from file '%s'\n"), args);

> +  /* Open the load file.  */
> +  recfd = open (args, O_RDONLY | O_BINARY);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for loading execution records: %s"),
> +           args, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Check the magic code.  */
> +  record_read_dump (args, recfd, &magic, 4);
> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a valid dump of execution records."), args);
> +
> +  /* Load the entries in recfd to the record_arch_list_head and
> +     record_arch_list_tail.  */
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
> +
> +  while (1)
> +    {
> +      int ret;
> +      uint8_t tmpu8;
> +      uint64_t tmpu64;
> +
> +      ret = read (recfd, &tmpu8, 1);
> +      if (ret < 0)
> +        error (_("Failed to read dump of execution records in '%s'."), args);
> +      if (ret == 0)
> +        break;
> +
> +      switch (tmpu8)
> +        {
> +        case record_reg: /* reg */
> +          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;
> +          /* Get num.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.reg.num = tmpu64;
> +          /* Get val.  */
> +          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
                if (record_debug)
                  fprintf_unfiltered (gdb_stdlog, _("\
   Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
                                      rec->u.reg.num,
                                      *(ULONGEST *) rec->u.reg.val,
                                      MAX_REGISTER_SIZE);

> +          record_arch_list_add (rec);
> +          break;
> +        case record_mem: /* mem */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_mem;
> +          /* Get addr.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.addr = tmpu64;
> +          /* Get len.  */
> +          record_read_dump (args, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.len = tmpu64;
> +          rec->u.mem.mem_entry_not_accessible = 0;
> +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> +          /* Get val.  */
> +          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
                if (record_debug)
                  fprintf_unfiltered (gdb_stdlog, _("\
   Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"),
                                      (unsigned int) rec->u.mem.addr,
                                      rec->u.mem.len);

> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_end: /* end */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_end;
                if (record_debug)
                  fprintf_unfiltered (gdb_stdlog, _("\
   Reading record_end (one byte)\n"));

> +          record_arch_list_add (rec);
> +          insn_number ++;
> +          break;
> +
> +        default:
> +          error (_("Format of '%s' is not right."), args);
> +          break;
> +        }
> +    }
> +
> +  discard_cleanups (old_cleanups2);
> +
> +  /* Add record_arch_list_head to the end of record list.  */
> +  for (rec = record_list; rec->next; rec = rec->next);
> +  rec->next = record_arch_list_head;
> +  record_arch_list_head->prev = rec;
> +
> +  /* Update record_insn_num and record_insn_max_num.  */
> +  record_insn_num += insn_number;
> +  if (record_insn_num > record_insn_max_num)
> +    {
> +      record_insn_max_num = record_insn_num;
> +      warning (_("Auto increase record/replay buffer limit to %d."),
> +               record_insn_max_num);
> +    }
> +
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
> +}
> +


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

* Re: [RFA/RFC] Add dump and load command to process record (file   format etc)
  2009-08-05 20:19                   ` [RFA/RFC] Add dump and load command to process record (file format etc) Michael Snyder
@ 2009-08-06  2:17                     ` Hui Zhu
  2009-08-12 14:11                       ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-06  2:17 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

On 2009-08-06, Michael Snyder <msnyder@vmware.com> wrote:
>
>  msnyder:
>
> >
> > > If you look at the code that dumps the file (and I had
> > > to add a bunch of printfs and stuff to figure it out), you'll
> > > see that the dump file looks like this:
> > >
> > > 4 byte magic number
> > > Series of the following:
> > > 1) 1 byte tag (record_end), or
> > > 2) 1 byte tag (record_rec) followed by
> > >  8 byte number (register id), followed by
> > >  MAX_REGISTER_SIZE byte value (register value): or
> > > 3) 1 byte tag (record_mem) followed by
> > >  8 byte number (memory address) followed by
> > >  8 byte number (memory length) followed by
> > >  N byte buffer (memory value).
> > >
> > > If you look for the #if (BYTE_ORDER == BIG_ENDIAN), this is
> > > where Teawater is making the byte orders host-independent.
> > >
> > > I was going to mention the ifdefs eventually.   ;-)
> > >
> >
>
>  Hui Zhu:
>
> > Cool.  It's very clear.  :)
> > Thanks Michael.
> >
> > I make a new version patch to follow the cvs head.  And removed the ifdef.
> > Please help me review it.
> >
>
>  OK, well, let me start out with some thoughts about the
>  idea and the architecture.
>
>  It's a great idea to save and reload a recording log.
>  We need to document the file format, as I've informally
>  done above, maybe by placing a comment in the file.
>  I'll post a suggested comment soon.
>
>  Once that's done, it becomes obvious that we are only saving
>  state changes -- no snapshot of the starting state.  Then it
>  becomes obvious why you need to save and load a corefile as well --
>  that gives you the starting state snapshot, to go with your
>  saved state changes.
>
>  The trouble that I see now is that there is no insurance that
>  the starting state (core file) and the state changes (record file)
>  correspond to one another.  A user could get into trouble by
>  loading one starting state and a DIFFERENT set of state changes.
>  And he would have no way of knowing why his debugging session
>  was producing nonsense results.
>
>  This is a little bit like the problem of storing separate
>  symbol files (separate from the executable file).  We need
>  a way to confirm that the two files correspond with one another.
>
>  I've given it a little thought, and I would like to hear
>  what others think.  Here are some of my ideas:
>
>  1) Store a pointer to the corefile in the record log file.
>  2) Store a checksum, hash, or CRC of the corefile in the log file.

I think this way is easy to implement.

>  3) Store a CRC of the loadable sections, plus a snapshot of the
>    register set, in the log file.
>  4) Store both corefile and log file together in the same file.

For all of this way, I think we have a problem is when we dump the
log, we don't know where is the core file.
Maybe we can extend the record dump command, to let it point out where
is the  core file or let it call gcore too.


>
>  Of course, every time we change the file format, we should
>  change the magic number to insure that a new-format gdb will
>  not attempt to read an old-format file.

Agree with it.


Thanks,
Hui

>
>  Discussion?
>
>
>
> > 2009-08-05  Hui Zhu  <teawater@gmail.com>
> >
> >        Add dump and load command to process record and replay.
> >
> >        * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
> >        byteswap.h, netinet/in.h): Include files.
> >        (RECORD_IS_REPLAY): Return true if record_core is true.
> >        (RECORD_FILE_MAGIC): New macro.
> >        (record_core_buf_entry): New struct.
> >        (record_core, record_core_regbuf, record_core_start,
> >        record_core_end, record_core_buf_list,
> >        record_beneath_to_fetch_registers_ops,
> >        record_beneath_to_fetch_registers,
> >        record_beneath_to_store_registers_ops,
> >        record_beneath_to_store_registers,
> >        record_beneath_to_has_execution_ops,
> >        record_beneath_to_has_execution,
> >        record_beneath_to_prepare_to_store): New
> variables.
> >        (record_list_release_first_insn): Change function
> >        record_list_release_first to this name.
> >        (record_arch_list_cleanups): New function.
> >        (record_message_cleanups): Removed.
> >        (record_message): Change to call record_arch_list_cleanups
> >        and record_list_release_first_insn.
> >        (record_exec_entry): New function.
> >        (record_open): Add support for target core.
> >        (record_close): Add support for target core.
> >        (record_wait): Call function 'record_exec_entry'.
> >        (record_kill): Add support for target core.
> >        (record_registers_change): Call
> record_list_release_first_insn.
> >        (record_fetch_registers): New function.
> >        (record_prepare_to_store): New function.
> >        (record_store_registers): Add support for target core.
> >        (record_xfer_partial): Add support for target core.
> >        (record_has_execution): New function.
> >        (init_record_ops): Set record_ops.to_fetch_registers,
> >        record_ops.to_prepare_to_store
> >        and record_ops.to_has_execution.
> >        (cmd_record_fd_cleanups): New function.
> >        (cmd_record_dump): New function.
> >        (cmd_record_load): New function.
> >        (set_record_insn_max_num): Call
> record_list_release_first_insn.
> >        (_initialize_record): Add commands "record dump"
> >        and "record load".
> >
> > ---
> >  record.c |  731
> +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
> >  1 file changed, 640 insertions(+), 91 deletions(-)
> >
> > --- a/record.c
> > +++ b/record.c
> > @@ -23,14 +23,22 @@
> >  #include "gdbthread.h"
> >  #include "event-top.h"
> >  #include "exceptions.h"
> > +#include "completer.h"
> > +#include "arch-utils.h"
> > +#include "gdbcore.h"
> > +#include "exec.h"
> >  #include "record.h"
> >
> > +#include <byteswap.h>
> >  #include <signal.h>
> > +#include <netinet/in.h>
> >
> >  #define DEFAULT_RECORD_INSN_MAX_NUM    200000
> >
> >  #define RECORD_IS_REPLAY \
> > -     (record_list->next || execution_direction == EXEC_REVERSE)
> > +     (record_list->next || execution_direction == EXEC_REVERSE ||
> record_core)
> > +
> > +#define RECORD_FILE_MAGIC      htonl(0x20090726)
> >
> >  /* These are the core struct of record function.
> >
> > @@ -78,9 +86,23 @@ struct record_entry
> >   } u;
> >  };
> >
> > +struct record_core_buf_entry
> > +{
> > +  struct record_core_buf_entry *prev;
> > +  struct target_section *p;
> > +  bfd_byte *buf;
> > +};
> > +
> >  /* This is the debug switch for process record.  */
> >  int record_debug = 0;
> >
> > +/* Record with core target.  */
> > +static int record_core = 0;
> > +static gdb_byte *record_core_regbuf;
> > +static struct target_section *record_core_start;
> > +static struct target_section *record_core_end;
> > +static struct record_core_buf_entry *record_core_buf_list = NULL;
> > +
> >  /* These list is for execution log.  */
> >  static struct record_entry record_first;
> >  static struct record_entry *record_list = &record_first;
> > @@ -103,6 +125,14 @@ static struct target_ops *record_beneath
> >  static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
> >                                         struct target_waitstatus *,
> >                                         int);
> > +static struct target_ops
> *record_beneath_to_fetch_registers_ops;
> > +static void (*record_beneath_to_fetch_registers) (struct
> target_ops *,
> > +                                                  struct regcache *,
> > +                                                 int regno);
> > +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_store_registers_ops;
> >  static void (*record_beneath_to_store_registers) (struct
> target_ops *,
> >                                                   struct regcache *,
> > @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
> >                                                   struct bp_target_info
> *);
> >  static int (*record_beneath_to_remove_breakpoint)
> (struct gdbarch *,
> >                                                   struct bp_target_info
> *);
> > +static struct target_ops
> *record_beneath_to_has_execution_ops;
> > +static int (*record_beneath_to_has_execution) (struct
> target_ops *ops);
> > +static void (*record_beneath_to_prepare_to_store)
> (struct regcache *regcache);
> >
> >  static void
> >  record_list_release (struct record_entry *rec)
> > @@ -169,7 +202,7 @@ record_list_release_next (void)
> >  }
> >
> >  static void
> > -record_list_release_first (void)
> > +record_list_release_first_insn (void)
> >  {
> >   struct record_entry *tmp = NULL;
> >   enum record_type type;
> > @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
> >              if (q)
> >                record_stop_at_limit = 0;
> >              else
> > -               error (_("Process record: inferior program stopped."));
> > +               error (_("Process record: stoped by user."));
> >            }
> >        }
> >     }
> >  }
> >
> > +static void
> > +record_arch_list_cleanups (void *ignore)
> > +{
> > +  record_list_release (record_arch_list_tail);
> > +}
> > +
> >  /* Before inferior step (when GDB record the running message, inferior
> >    only can step), GDB will call this function to record the values to
> >    record_list.  This function will call gdbarch_process_record to
> >    record the running message of inferior and set them to
> >    record_arch_list, and add it to record_list.  */
> >
> > -static void
> > -record_message_cleanups (void *ignore)
> > -{
> > -  record_list_release (record_arch_list_tail);
> > -}
> > -
> >  static int
> >  record_message (void *args)
> >  {
> >   int ret;
> >   struct regcache *regcache = args;
> > -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups,
> 0);
> > +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups,
> 0);
> >
> >   record_arch_list_head = NULL;
> >   record_arch_list_tail = NULL;
> > @@ -386,7 +419,7 @@ record_message (void *args)
> >   record_list = record_arch_list_tail;
> >
> >   if (record_insn_num == record_insn_max_num && record_insn_max_num)
> > -    record_list_release_first ();
> > +    record_list_release_first_insn ();
> >   else
> >     record_insn_num++;
> >
> > @@ -416,6 +449,91 @@ record_gdb_operation_disable_set
> (void)
> >   return old_cleanups;
> >  }
> >
> > +static inline void
> > +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> > +                   struct record_entry *entry)
> > +{
> > +  switch (entry->type)
> > +    {
> > +    case record_reg: /* reg */
> > +      {
> > +        gdb_byte reg[MAX_REGISTER_SIZE];
> > +
> > +        if (record_debug > 1)
> > +          fprintf_unfiltered (gdb_stdlog,
> > +                              "Process record: record_reg %s to "
> > +                              "inferior num = %d.\n",
> > +                              host_address_to_string (entry),
> > +                              entry->u.reg.num);
> > +
> > +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> > +        regcache_cooked_write (regcache, entry->u.reg.num,
> entry->u.reg.val);
> > +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> > +      }
> > +      break;
> > +
> > +    case record_mem: /* mem */
> > +      {
> > +        if
> (!record_list->u.mem.mem_entry_not_accessible)
> > +          {
> > +            gdb_byte *mem = alloca (entry->u.mem.len);
> > +
> > +            if (record_debug > 1)
> > +              fprintf_unfiltered (gdb_stdlog,
> > +                                  "Process record: record_mem %s to "
> > +                                  "inferior addr = %s len = %d.\n",
> > +                                  host_address_to_string (entry),
> > +                                  paddress (gdbarch, entry->u.mem.addr),
> > +                                  record_list->u.mem.len);
> > +
> > +            if (target_read_memory (entry->u.mem.addr, mem,
> entry->u.mem.len))
> > +              {
> > +                 if ((execution_direction == EXEC_REVERSE &&
> !record_core)
> > +                     || (execution_direction != EXEC_REVERSE &&
> record_core))
> > +                  {
> > +
> record_list->u.mem.mem_entry_not_accessible = 1;
> > +                    if (record_debug)
> > +                      warning (_("Process record: error reading memory at
> "
> > +                                 "addr = %s len = %d."),
> > +                               paddress (gdbarch, entry->u.mem.addr),
> > +                               entry->u.mem.len);
> > +                  }
> > +                else
> > +                  error (_("Process record: error reading memory at "
> > +                           "addr = %s len = %d."),
> > +                         paddress (gdbarch, entry->u.mem.addr),
> > +                        entry->u.mem.len);
> > +              }
> > +            else
> > +              {
> > +                if (target_write_memory (entry->u.mem.addr,
> entry->u.mem.val,
> > +                                         entry->u.mem.len))
> > +                  {
> > +                     if ((execution_direction == EXEC_REVERSE &&
> !record_core)
> > +                         || (execution_direction != EXEC_REVERSE &&
> > record_core))
> > +                      {
> > +
> record_list->u.mem.mem_entry_not_accessible = 1;
> > +                        if (record_debug)
> > +                          warning (_("Process record: error writing
> memory at "
> > +                                     "addr = %s len = %d."),
> > +                                   paddress (gdbarch, entry->u.mem.addr),
> > +                                   entry->u.mem.len);
> > +                      }
> > +                    else
> > +                      error (_("Process record: error writing memory at "
> > +                               "addr = %s len = %d."),
> > +                             paddress (gdbarch, entry->u.mem.addr),
> > +                            entry->u.mem.len);
> > +                  }
> > +              }
> > +
> > +            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> > +          }
> > +      }
> > +      break;
> > +    }
> > +}
> > +
> >  static void
> >  record_open (char *name, int from_tty)
> >  {
> > @@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
> >   if (record_debug)
> >     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> >
> > +  if (!strcmp (current_target.to_shortname, "core"))
> > +    record_core = 1;
> > +  else
> > +    record_core = 0;
> > +
> >   /* check exec */
> > -  if (!target_has_execution)
> > +  if (!target_has_execution && !record_core)
> >     error (_("Process record: the program is not being run."));
> >   if (non_stop)
> >     error (_("Process record target can't debug inferior in non-stop mode
> "
> > @@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
> >   record_beneath_to_xfer_partial = NULL;
> >   record_beneath_to_insert_breakpoint = NULL;
> >   record_beneath_to_remove_breakpoint = NULL;
> > +  record_beneath_to_has_execution = NULL;
> > +  record_beneath_to_prepare_to_store = NULL;
> >
> >   /* Set the beneath function pointers.  */
> >   for (t = current_target.beneath; t != NULL; t = t->beneath)
> > @@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
> >          record_beneath_to_wait = t->to_wait;
> >          record_beneath_to_wait_ops = t;
> >         }
> > +      if (!record_beneath_to_fetch_registers)
> > +        {
> > +         record_beneath_to_fetch_registers =
> t->to_fetch_registers;
> > +         record_beneath_to_fetch_registers_ops = t;
> > +        }
> >       if (!record_beneath_to_store_registers)
> >         {
> >          record_beneath_to_store_registers =
> t->to_store_registers;
> > @@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
> >        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_has_execution)
> > +        {
> > +          record_beneath_to_has_execution_ops = t;
> > +         record_beneath_to_has_execution =
> t->to_has_execution;
> > +        }
> > +      if (!record_beneath_to_prepare_to_store)
> > +       record_beneath_to_prepare_to_store =
> t->to_prepare_to_store;
> >     }
> > -  if (!record_beneath_to_resume)
> > +  if (!record_beneath_to_resume && !record_core)
> >     error (_("Process record can't get to_resume."));
> > -  if (!record_beneath_to_wait)
> > +  if (!record_beneath_to_wait && !record_core)
> >     error (_("Process record can't get to_wait."));
> > -  if (!record_beneath_to_store_registers)
> > +  if (!record_beneath_to_fetch_registers)
> > +    error (_("Process record can't get to_fetch_registers."));
> > +  if (!record_beneath_to_store_registers &&
> !record_core)
> >     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)
> > +  if (!record_beneath_to_insert_breakpoint &&
> !record_core)
> >     error (_("Process record can't get to_insert_breakpoint."));
> > -  if (!record_beneath_to_remove_breakpoint)
> > +  if (!record_beneath_to_remove_breakpoint &&
> !record_core)
> >     error (_("Process record can't get to_remove_breakpoint."));
> > +  if (!record_beneath_to_has_execution && !record_core)
> > +    error (_("Process record can't get to_has_execution."));
> > +  if (!record_beneath_to_prepare_to_store &&
> !record_core)
> > +    error (_("Process record can't get to_prepare_to_store."));
> > +
> > +  if (record_core)
> > +    {
> > +      /* Get record_core_regbuf.  */
> > +      struct regcache *regcache = get_current_regcache ();
> > +      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
> > +      int i;
> > +
> > +      target_fetch_registers (regcache, -1);
> > +      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
> > +      for (i = 0; i < regnum; i ++)
> > +        regcache_raw_collect (regcache, i,
> > +                              record_core_regbuf + MAX_REGISTER_SIZE *
> i);
> > +
> > +      /* Get record_core_start and record_core_end.  */
> > +      if (build_section_table (core_bfd, &record_core_start,
> &record_core_end))
> > +        error (_("\"%s\": Can't find sections: %s"),
> > +               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error
> ()));
> > +    }
> >
> >   push_target (&record_ops);
> >
> > @@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
> >  static void
> >  record_close (int quitting)
> >  {
> > +  struct record_core_buf_entry *entry;
> > +
> >   if (record_debug)
> >     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
> >
> >   record_list_release (record_list);
> > +
> > +  /* Release record_core_regbuf.  */
> > +  xfree (record_core_regbuf);
> > +
> > +  /* Release record_core_buf_list.  */
> > +  if (record_core_buf_list)
> > +    {
> > +      for (entry = record_core_buf_list->prev; entry; entry =
> entry->prev)
> > +        {
> > +          xfree (record_core_buf_list);
> > +          record_core_buf_list = entry;
> > +        }
> > +      record_core_buf_list = NULL;
> > +    }
> >  }
> >
> >  static int record_resume_step = 0;
> > @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
> >              break;
> >            }
> >
> > -         /* Set ptid, register and memory according to record_list.  */
> > -         if (record_list->type == record_reg)
> > -           {
> > -             /* reg */
> > -             gdb_byte reg[MAX_REGISTER_SIZE];
> > -             if (record_debug > 1)
> > -               fprintf_unfiltered (gdb_stdlog,
> > -                                   "Process record: record_reg %s to "
> > -                                   "inferior num = %d.\n",
> > -                                   host_address_to_string (record_list),
> > -                                   record_list->u.reg.num);
> > -             regcache_cooked_read (regcache, record_list->u.reg.num,
> reg);
> > -             regcache_cooked_write (regcache, record_list->u.reg.num,
> > -                                    record_list->u.reg.val);
> > -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> > -           }
> > -         else if (record_list->type == record_mem)
> > -           {
> > -             /* mem */
> > -             /* Nothing to do if the entry is flagged not_accessible.  */
> > -             if
> (!record_list->u.mem.mem_entry_not_accessible)
> > -               {
> > -                 gdb_byte *mem = alloca (record_list->u.mem.len);
> > -                 if (record_debug > 1)
> > -                   fprintf_unfiltered (gdb_stdlog,
> > -                                       "Process record: record_mem %s to
> "
> > -                                       "inferior addr = %s len = %d.\n",
> > -                                       host_address_to_string
> (record_list),
> > -                                       paddress (gdbarch,
> > -
> record_list->u.mem.addr),
> > -                                       record_list->u.mem.len);
> > +          record_exec_entry (regcache, gdbarch, record_list);
> >
> > -                 if (target_read_memory (record_list->u.mem.addr, mem,
> > -                                         record_list->u.mem.len))
> > -                   {
> > -                     if (execution_direction != EXEC_REVERSE)
> > -                       error (_("Process record: error reading memory at
> "
> > -                                "addr = %s len = %d."),
> > -                              paddress (gdbarch,
> record_list->u.mem.addr),
> > -                              record_list->u.mem.len);
> > -                     else
> > -                       /* Read failed --
> > -                          flag entry as not_accessible.  */
> > -
> record_list->u.mem.mem_entry_not_accessible = 1;
> > -                   }
> > -                 else
> > -                   {
> > -                     if (target_write_memory (record_list->u.mem.addr,
> > -                                              record_list->u.mem.val,
> > -                                              record_list->u.mem.len))
> > -                       {
> > -                         if (execution_direction != EXEC_REVERSE)
> > -                           error (_("Process record: error writing memory
> at "
> > -                                    "addr = %s len = %d."),
> > -                                  paddress (gdbarch,
> record_list->u.mem.addr),
> > -                                  record_list->u.mem.len);
> > -                         else
> > -                           /* Write failed --
> > -                              flag entry as not_accessible.  */
> > -
> record_list->u.mem.mem_entry_not_accessible = 1;
> > -                       }
> > -                     else
> > -                       {
> > -                         memcpy (record_list->u.mem.val, mem,
> > -                                 record_list->u.mem.len);
> > -                       }
> > -                   }
> > -               }
> > -           }
> > -         else
> > +         if (record_list->type == record_end)
> >            {
> >              if (record_debug > 1)
> >                fprintf_unfiltered (gdb_stdlog,
> > @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
> >     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
> >
> >   unpush_target (&record_ops);
> > -  target_kill ();
> > +
> > +  if (!record_core)
> > +    target_kill ();
> >  }
> >
> >  /* Record registers change (by user or by GDB) to list as an instruction.
>  */
> > @@ -945,15 +1058,58 @@ record_registers_change (struct regcache
> >   record_list = record_arch_list_tail;
> >
> >   if (record_insn_num == record_insn_max_num && record_insn_max_num)
> > -    record_list_release_first ();
> > +    record_list_release_first_insn ();
> >   else
> >     record_insn_num++;
> >  }
> >
> >  static void
> > +record_fetch_registers (struct target_ops *ops, struct regcache
> *regcache,
> > +                        int regno)
> > +{
> > +  if (record_core)
> > +    {
> > +      if (regno < 0)
> > +        {
> > +          int num = gdbarch_num_regs (get_regcache_arch (regcache));
> > +          int i;
> > +
> > +          for (i = 0; i < num; i ++)
> > +            regcache_raw_supply (regcache, i,
> > +                                 record_core_regbuf + MAX_REGISTER_SIZE *
> i);
> > +        }
> > +      else
> > +        regcache_raw_supply (regcache, regno,
> > +                             record_core_regbuf + MAX_REGISTER_SIZE *
> regno);
> > +    }
> > +  else
> > +    record_beneath_to_fetch_registers
> (record_beneath_to_store_registers_ops,
> > +                                       regcache, regno);
> > +}
> > +
> > +static void
> > +record_prepare_to_store (struct regcache *regcache)
> > +{
> > +  if (!record_core)
> > +    record_beneath_to_prepare_to_store (regcache);
> > +}
> > +
> > +static void
> >  record_store_registers (struct target_ops *ops, struct regcache
> *regcache,
> >                         int regno)
> >  {
> > +  if (record_core)
> > +    {
> > +      /* Debug with core.  */
> > +      if (record_gdb_operation_disable)
> > +        regcache_raw_collect (regcache, regno,
> > +                              record_core_regbuf + MAX_REGISTER_SIZE *
> regno);
> > +      else
> > +        error (_("You can't do that without a process to debug."));
> > +
> > +      return;
> > +    }
> > +
> >   if (!record_gdb_operation_disable)
> >     {
> >       if (RECORD_IS_REPLAY)
> > @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op
> >
> >       record_registers_change (regcache, regno);
> >     }
> > +
> >   record_beneath_to_store_registers
> (record_beneath_to_store_registers_ops,
> >                                      regcache, regno);
> >  }
> > @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
> >  {
> >   if (!record_gdb_operation_disable
> >       && (object == TARGET_OBJECT_MEMORY
> > -         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
> > +         || object == TARGET_OBJECT_RAW_MEMORY) && writebuf &&
> !record_core)
> >     {
> >       if (RECORD_IS_REPLAY)
> >        {
> > @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
> >       record_list = record_arch_list_tail;
> >
> >       if (record_insn_num == record_insn_max_num && record_insn_max_num)
> > -       record_list_release_first ();
> > +       record_list_release_first_insn ();
> >       else
> >        record_insn_num++;
> >     }
> >
> > +   if (record_core && object == TARGET_OBJECT_MEMORY)
> > +     {
> > +       /* Debug with core.  */
> > +       if (record_gdb_operation_disable || !writebuf)
> > +         {
> > +           struct target_section *p;
> > +           for (p = record_core_start; p < record_core_end; p++)
> > +             {
> > +               if (offset >= p->addr)
> > +                 {
> > +                   struct record_core_buf_entry *entry;
> > +
> > +                   if (offset >= p->endaddr)
> > +                     continue;
> > +
> > +                   if (offset + len > p->endaddr)
> > +                     len = p->endaddr - offset;
> > +
> > +                   offset -= p->addr;
> > +
> > +                   /* Read readbuf or write writebuf p, offset, len.  */
> > +                   /* Check flags.  */
> > +                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
> > +                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS)
> == 0)
> > +                     {
> > +                       if (readbuf)
> > +                         memset (readbuf, 0, len);
> > +                       return len;
> > +                     }
> > +                   /* Get record_core_buf_entry.  */
> > +                   for (entry = record_core_buf_list; entry;
> > +                        entry = entry->prev)
> > +                     if (entry->p == p)
> > +                       break;
> > +                   if (writebuf)
> > +                     {
> > +                       if (!entry)
> > +                         {
> > +                           /* Add a new entry.  */
> > +                           entry
> > +                             = (struct record_core_buf_entry *)
> > +                                 xmalloc
> > +                                   (sizeof (struct
> record_core_buf_entry));
> > +                           entry->p = p;
> > +                           if (!bfd_malloc_and_get_section (p->bfd,
> > +
> p->the_bfd_section,
> > +                                                            &entry->buf))
> > +                             {
> > +                               xfree (entry);
> > +                               return 0;
> > +                             }
> > +                           entry->prev = record_core_buf_list;
> > +                           record_core_buf_list = entry;
> > +                         }
> > +
> > +                        memcpy (entry->buf + offset, writebuf, (size_t)
> len);
> > +                     }
> > +                   else
> > +                     {
> > +                       if (!entry)
> > +                         return record_beneath_to_xfer_partial
> > +
> (record_beneath_to_xfer_partial_ops,
> > +                                   object, annex, readbuf, writebuf,
> > +                                   offset, len);
> > +
> > +                       memcpy (readbuf, entry->buf + offset, (size_t)
> len);
> > +                     }
> > +
> > +                   return len;
> > +                 }
> > +             }
> > +
> > +           return 0;
> > +         }
> > +       else
> > +         error (_("You can't do that without a process to debug."));
> > +
> > +       return 0;
> > +     }
> > +
> >   return record_beneath_to_xfer_partial
> (record_beneath_to_xfer_partial_ops,
> >                                          object, annex, readbuf, writebuf,
> >                                          offset, len);
> > @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
> >   return 1;
> >  }
> >
> > +int
> > +record_has_execution (struct target_ops *ops)
> > +{
> > +  if (record_core)
> > +    return 1;
> > +
> > +  return record_beneath_to_has_execution (ops);
> > +}
> > +
> >  static void
> >  init_record_ops (void)
> >  {
> > @@ -1129,11 +1375,14 @@ init_record_ops (void)
> >   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_fetch_registers = record_fetch_registers;
> > +  record_ops.to_prepare_to_store =
> record_prepare_to_store;
> >   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_has_execution = record_has_execution;
> >   record_ops.to_stratum = record_stratum;
> >   record_ops.to_magic = OPS_MAGIC;
> >  }
> > @@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t
> >   execute_command ("target record", from_tty);
> >  }
> >
> > +static void
> > +cmd_record_fd_cleanups (void *recfdp)
> > +{
> > +  int recfd = *(int *) recfdp;
> > +  close (recfd);
> > +}
> > +
> > +static inline void
> > +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
> > +{
> > +  if (read (fildes, buf, nbyte) != nbyte)
> > +    error (_("Failed to read dump of execution records in '%s'."),
> > +           recfilename);
> > +}
> > +
> > +static inline void
> > +record_write_dump (char *recfilename, int fildes, const void *buf,
> > +                   size_t nbyte)
> > +{
> > +  if (write (fildes, buf, nbyte) != nbyte)
> > +    error (_("Failed to write dump of execution records to '%s'."),
> > +           recfilename);
> > +}
> > +
> > +/* Dump the execution log to a file.  */
> > +
> > +static void
> > +cmd_record_dump (char *args, int from_tty)
> > +{
> > +  char *recfilename, recfilename_buffer[40];
> > +  int recfd;
> > +  struct record_entry *cur_record_list;
> > +  uint32_t magic;
> > +  struct regcache *regcache;
> > +  struct gdbarch *gdbarch;
> > +  struct cleanup *old_cleanups;
> > +  struct cleanup *set_cleanups;
> > +
> > +  if (current_target.to_stratum != record_stratum)
> > +    error (_("Process record is not started.\n"));
> > +
> > +  if (args && *args)
> > +    recfilename = args;
> > +  else
> > +    {
> > +      /* Default corefile name is "gdb_record.PID".  */
> > +      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET
> (inferior_ptid));
> > +      recfilename = recfilename_buffer;
> > +    }
> > +
> > +  /* Open the dump file.  */
> > +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> > +                S_IRUSR | S_IWUSR);
> > +  if (recfd < 0)
> > +    error (_("Failed to open '%s' for dump execution records: %s"),
> > +           recfilename, strerror (errno));
> > +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> > +
> > +  /* Save the current record entry to "cur_record_list".  */
> > +  cur_record_list = record_list;
> > +
> > +  /* Get the values of regcache and gdbarch.  */
> > +  regcache = get_current_regcache ();
> > +  gdbarch = get_regcache_arch (regcache);
> > +
> > +  /* Disable the GDB operation record.  */
> > +  set_cleanups = record_gdb_operation_disable_set ();
> > +
> > +  /* Write the magic code.  */
> > +  magic = RECORD_FILE_MAGIC;
> > +  record_write_dump (recfilename, recfd, &magic, 4);
> > +
> > +  /* Reverse execute to the begin of record list.  */
> > +  while (1)
> > +    {
> > +      /* Check for beginning and end of log.  */
> > +      if (record_list == &record_first)
> > +        break;
> > +
> > +      record_exec_entry (regcache, gdbarch, record_list);
> > +
> > +      if (record_list->prev)
> > +        record_list = record_list->prev;
> > +    }
> > +
> > +  /* Dump the entries to recfd and forward execute to the end of
> > +     record list.  */
> > +  while (1)
> > +    {
> > +      /* Dump entry.  */
> > +      if (record_list != &record_first)
> > +        {
> > +          uint8_t tmpu8;
> > +          uint64_t tmpu64;
> > +
> > +          tmpu8 = record_list->type;
> > +          record_write_dump (recfilename, recfd, &tmpu8, 1);
> > +
> > +          switch (record_list->type)
> > +            {
> > +            case record_reg: /* reg */
> > +              tmpu64 = record_list->u.reg.num;
> > +              if (BYTE_ORDER == LITTLE_ENDIAN)
> > +                tmpu64 = bswap_64 (tmpu64);
> > +              record_write_dump (recfilename, recfd, &tmpu64, 8);
> > +
> > +              record_write_dump (recfilename, recfd,
> record_list->u.reg.val,
> > +                                 MAX_REGISTER_SIZE);
> > +              break;
> > +            case record_mem: /* mem */
> > +              if
> (!record_list->u.mem.mem_entry_not_accessible)
> > +                {
> > +                  tmpu64 = record_list->u.mem.addr;
> > +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> > +                    tmpu64 = bswap_64 (tmpu64);
> > +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> > +
> > +                  tmpu64 = record_list->u.mem.len;
> > +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> > +                    tmpu64 = bswap_64 (tmpu64);
> > +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> > +
> > +                  record_write_dump (recfilename, recfd,
> > +                                     record_list->u.mem.val,
> > +                                     record_list->u.mem.len);
> > +                }
> > +              break;
> > +            }
> > +        }
> > +
> > +      /* Execute entry.  */
> > +      record_exec_entry (regcache, gdbarch, record_list);
> > +
> > +      if (record_list->next)
> > +        record_list = record_list->next;
> > +      else
> > +        break;
> > +    }
> > +
> > +  /* Reverse execute to cur_record_list.  */
> > +  while (1)
> > +    {
> > +      /* Check for beginning and end of log.  */
> > +      if (record_list == cur_record_list)
> > +        break;
> > +
> > +      record_exec_entry (regcache, gdbarch, record_list);
> > +
> > +      if (record_list->prev)
> > +        record_list = record_list->prev;
> > +    }
> > +
> > +  do_cleanups (set_cleanups);
> > +  do_cleanups (old_cleanups);
> > +
> > +  /* Succeeded.  */
> > +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
> > +                                  "records to `%s'.\n"),
> > +                    recfilename);
> > +}
> > +
> > +/* Load the execution log from a file.  */
> > +
> > +static void
> > +cmd_record_load (char *args, int from_tty)
> > +{
> > +  int recfd;
> > +  uint32_t magic;
> > +  struct cleanup *old_cleanups;
> > +  struct cleanup *old_cleanups2;
> > +  struct record_entry *rec;
> > +  int insn_number = 0;
> > +
> > +  if (current_target.to_stratum != record_stratum)
> > +    {
> > +      cmd_record_start (NULL, from_tty);
> > +      printf_unfiltered (_("Auto start process record.\n"));
> > +    }
> > +
> > +  if (!args || (args && !*args))
> > +    error (_("Argument for filename required.\n"));
> > +
> > +  /* Open the load file.  */
> > +  recfd = open (args, O_RDONLY | O_BINARY);
> > +  if (recfd < 0)
> > +    error (_("Failed to open '%s' for loading execution records: %s"),
> > +           args, strerror (errno));
> > +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> > +
> > +  /* Check the magic code.  */
> > +  record_read_dump (args, recfd, &magic, 4);
> > +  if (magic != RECORD_FILE_MAGIC)
> > +    error (_("'%s' is not a valid dump of execution records."), args);
> > +
> > +  /* Load the entries in recfd to the record_arch_list_head and
> > +     record_arch_list_tail.  */
> > +  record_arch_list_head = NULL;
> > +  record_arch_list_tail = NULL;
> > +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
> > +
> > +  while (1)
> > +    {
> > +      int ret;
> > +      uint8_t tmpu8;
> > +      uint64_t tmpu64;
> > +
> > +      ret = read (recfd, &tmpu8, 1);
> > +      if (ret < 0)
> > +        error (_("Failed to read dump of execution records in '%s'."),
> args);
> > +      if (ret == 0)
> > +        break;
> > +
> > +      switch (tmpu8)
> > +        {
> > +        case record_reg: /* reg */
> > +          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;
> > +          /* Get num.  */
> > +          record_read_dump (args, recfd, &tmpu64, 8);
> > +          if (BYTE_ORDER == LITTLE_ENDIAN)
> > +            tmpu64 = bswap_64 (tmpu64);
> > +          rec->u.reg.num = tmpu64;
> > +          /* Get val.  */
> > +          record_read_dump (args, recfd, rec->u.reg.val,
> MAX_REGISTER_SIZE);
> > +          record_arch_list_add (rec);
> > +          break;
> > +        case record_mem: /* mem */
> > +          rec = (struct record_entry *) xmalloc (sizeof (struct
> record_entry));
> > +          rec->prev = NULL;
> > +          rec->next = NULL;
> > +          rec->type = record_mem;
> > +          /* Get addr.  */
> > +          record_read_dump (args, recfd, &tmpu64, 8);
> > +          if (BYTE_ORDER == LITTLE_ENDIAN)
> > +            tmpu64 = bswap_64 (tmpu64);
> > +          rec->u.mem.addr = tmpu64;
> > +          /* Get len.  */
> > +          record_read_dump (args, recfd, &tmpu64, 8);
> > +          if (BYTE_ORDER == LITTLE_ENDIAN)
> > +            tmpu64 = bswap_64 (tmpu64);
> > +          rec->u.mem.len = tmpu64;
> > +          rec->u.mem.mem_entry_not_accessible = 0;
> > +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> > +          /* Get val.  */
> > +          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
> > +          record_arch_list_add (rec);
> > +          break;
> > +
> > +        case record_end: /* end */
> > +          rec = (struct record_entry *) xmalloc (sizeof (struct
> record_entry));
> > +          rec->prev = NULL;
> > +          rec->next = NULL;
> > +          rec->type = record_end;
> > +          record_arch_list_add (rec);
> > +          insn_number ++;
> > +          break;
> > +
> > +        default:
> > +          error (_("Format of '%s' is not right."), args);
> > +          break;
> > +        }
> > +    }
> > +
> > +  discard_cleanups (old_cleanups2);
> > +
> > +  /* Add record_arch_list_head to the end of record list.  */
> > +  for (rec = record_list; rec->next; rec = rec->next);
> > +  rec->next = record_arch_list_head;
> > +  record_arch_list_head->prev = rec;
> > +
> > +  /* Update record_insn_num and record_insn_max_num.  */
> > +  record_insn_num += insn_number;
> > +  if (record_insn_num > record_insn_max_num)
> > +    {
> > +      record_insn_max_num = record_insn_num;
> > +      warning (_("Auto increase record/replay buffer limit to %d."),
> > +               record_insn_max_num);
> > +    }
> > +
> > +  do_cleanups (old_cleanups);
> > +
> > +  /* Succeeded.  */
> > +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
> > +}
> > +
> >  /* Truncate the record log from the present point
> >    of replay until the end.  */
> >
> > @@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int
> >                           "the first ones?\n"));
> >
> >       while (record_insn_num > record_insn_max_num)
> > -       record_list_release_first ();
> > +       record_list_release_first_insn ();
> >     }
> >  }
> >
> > @@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro
> >  void
> >  _initialize_record (void)
> >  {
> > +  struct cmd_list_element *c;
> > +
> >   /* Init record_first.  */
> >   record_first.prev = NULL;
> >   record_first.next = NULL;
> > @@ -1277,6 +1816,16 @@ _initialize_record (void)
> >                  "info record ", 0, &infolist);
> >   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
> >
> > +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> > +              _("Dump the execution records to a file.\n\
> > +Argument is optional filename.  Default filename is
> > 'gdb_record.<process_id>'."),
> > +               &record_cmdlist);
> > +  set_cmd_completer (c, filename_completer);
> > +  c = add_cmd ("load", class_obscure, cmd_record_load,
> > +              _("Load previously dumped execution records from \
> > +a file given as argument."),
> > +               &record_cmdlist);
> > +  set_cmd_completer (c, filename_completer);
> >
> >   add_cmd ("delete", class_obscure, cmd_record_delete,
> >           _("Delete the rest of execution log and start recording it
> anew."),
> >
> >
> >
> ------------------------------------------------------------------------
> >
> > ---
> >  record.c |  731
> +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
> >  1 file changed, 640 insertions(+), 91 deletions(-)
> >
> > --- a/record.c
> > +++ b/record.c
> > @@ -23,14 +23,22 @@
> >  #include "gdbthread.h"
> >  #include "event-top.h"
> >  #include "exceptions.h"
> > +#include "completer.h"
> > +#include "arch-utils.h"
> > +#include "gdbcore.h"
> > +#include "exec.h"
> >  #include "record.h"
> >  +#include <byteswap.h>
> >  #include <signal.h>
> > +#include <netinet/in.h>
> >   #define DEFAULT_RECORD_INSN_MAX_NUM    200000
> >   #define RECORD_IS_REPLAY \
> > -     (record_list->next || execution_direction == EXEC_REVERSE)
> > +     (record_list->next || execution_direction == EXEC_REVERSE ||
> record_core)
> > +
> > +#define RECORD_FILE_MAGIC      htonl(0x20090726)
> >   /* These are the core struct of record function.
> >  @@ -78,9 +86,23 @@ struct record_entry
> >   } u;
> >  };
> >  +struct record_core_buf_entry
> > +{
> > +  struct record_core_buf_entry *prev;
> > +  struct target_section *p;
> > +  bfd_byte *buf;
> > +};
> > +
> >  /* This is the debug switch for process record.  */
> >  int record_debug = 0;
> >  +/* Record with core target.  */
> > +static int record_core = 0;
> > +static gdb_byte *record_core_regbuf;
> > +static struct target_section *record_core_start;
> > +static struct target_section *record_core_end;
> > +static struct record_core_buf_entry *record_core_buf_list = NULL;
> > +
> >  /* These list is for execution log.  */
> >  static struct record_entry record_first;
> >  static struct record_entry *record_list = &record_first;
> > @@ -103,6 +125,14 @@ static struct target_ops *record_beneath
> >  static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
> >                                         struct target_waitstatus *,
> >                                         int);
> > +static struct target_ops
> *record_beneath_to_fetch_registers_ops;
> > +static void (*record_beneath_to_fetch_registers) (struct
> target_ops *,
> > +                                                  struct regcache *,
> > +                                                 int regno);
> > +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_store_registers_ops;
> >  static void (*record_beneath_to_store_registers) (struct
> target_ops *,
> >                                                   struct regcache *,
> > @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
> >                                                   struct bp_target_info
> *);
> >  static int (*record_beneath_to_remove_breakpoint)
> (struct gdbarch *,
> >                                                   struct bp_target_info
> *);
> > +static struct target_ops
> *record_beneath_to_has_execution_ops;
> > +static int (*record_beneath_to_has_execution) (struct
> target_ops *ops);
> > +static void (*record_beneath_to_prepare_to_store)
> (struct regcache *regcache);
> >   static void
> >  record_list_release (struct record_entry *rec)
> > @@ -169,7 +202,7 @@ record_list_release_next (void)
> >  }
> >   static void
> > -record_list_release_first (void)
> > +record_list_release_first_insn (void)
> >  {
> >   struct record_entry *tmp = NULL;
> >   enum record_type type;
> > @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
> >              if (q)
> >                record_stop_at_limit = 0;
> >              else
> > -               error (_("Process record: inferior program stopped."));
> > +               error (_("Process record: stoped by user."));
> >            }
> >        }
> >     }
> >  }
> >  +static void
> > +record_arch_list_cleanups (void *ignore)
> > +{
> > +  record_list_release (record_arch_list_tail);
> > +}
> > +
> >  /* Before inferior step (when GDB record the running message, inferior
> >    only can step), GDB will call this function to record the values to
> >    record_list.  This function will call gdbarch_process_record to
> >    record the running message of inferior and set them to
> >    record_arch_list, and add it to record_list.  */
> >  -static void
> > -record_message_cleanups (void *ignore)
> > -{
> > -  record_list_release (record_arch_list_tail);
> > -}
> > -
> >  static int
> >  record_message (void *args)
> >  {
> >   int ret;
> >   struct regcache *regcache = args;
> > -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups,
> 0);
> > +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups,
> 0);
> >     record_arch_list_head = NULL;
> >   record_arch_list_tail = NULL;
> > @@ -386,7 +419,7 @@ record_message (void *args)
> >   record_list = record_arch_list_tail;
> >     if (record_insn_num == record_insn_max_num && record_insn_max_num)
> > -    record_list_release_first ();
> > +    record_list_release_first_insn ();
> >   else
> >     record_insn_num++;
> >  @@ -416,6 +449,91 @@ record_gdb_operation_disable_set
> (void)
> >   return old_cleanups;
> >  }
> >  +static inline void
> > +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> > +                   struct record_entry *entry)
> > +{
> > +  switch (entry->type)
> > +    {
> > +    case record_reg: /* reg */
> > +      {
> > +        gdb_byte reg[MAX_REGISTER_SIZE];
> > +
> > +        if (record_debug > 1)
> > +          fprintf_unfiltered (gdb_stdlog,
> > +                              "Process record: record_reg %s to "
> > +                              "inferior num = %d.\n",
> > +                              host_address_to_string (entry),
> > +                              entry->u.reg.num);
> > +
> > +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> > +        regcache_cooked_write (regcache, entry->u.reg.num,
> entry->u.reg.val);
> > +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> > +      }
> > +      break;
> > +
> > +    case record_mem: /* mem */
> > +      {
> > +        if
> (!record_list->u.mem.mem_entry_not_accessible)
> > +          {
> > +            gdb_byte *mem = alloca (entry->u.mem.len);
> > +
> > +            if (record_debug > 1)
> > +              fprintf_unfiltered (gdb_stdlog,
> > +                                  "Process record: record_mem %s to "
> > +                                  "inferior addr = %s len = %d.\n",
> > +                                  host_address_to_string (entry),
> > +                                  paddress (gdbarch, entry->u.mem.addr),
> > +                                  record_list->u.mem.len);
> > +
> > +            if (target_read_memory (entry->u.mem.addr, mem,
> entry->u.mem.len))
> > +              {
> > +                 if ((execution_direction == EXEC_REVERSE &&
> !record_core)
> > +                     || (execution_direction != EXEC_REVERSE &&
> record_core))
> > +                  {
> > +
> record_list->u.mem.mem_entry_not_accessible = 1;
> > +                    if (record_debug)
> > +                      warning (_("Process record: error reading memory at
> "
> > +                                 "addr = %s len = %d."),
> > +                               paddress (gdbarch, entry->u.mem.addr),
> > +                               entry->u.mem.len);
> > +                  }
> > +                else
> > +                  error (_("Process record: error reading memory at "
> > +                           "addr = %s len = %d."),
> > +                         paddress (gdbarch, entry->u.mem.addr),
> > +                        entry->u.mem.len);
> > +              }
> > +            else
> > +              {
> > +                if (target_write_memory (entry->u.mem.addr,
> entry->u.mem.val,
> > +                                         entry->u.mem.len))
> > +                  {
> > +                     if ((execution_direction == EXEC_REVERSE &&
> !record_core)
> > +                         || (execution_direction != EXEC_REVERSE &&
> record_core))
> > +                      {
> > +
> record_list->u.mem.mem_entry_not_accessible = 1;
> > +                        if (record_debug)
> > +                          warning (_("Process record: error writing
> memory at "
> > +                                     "addr = %s len = %d."),
> > +                                   paddress (gdbarch, entry->u.mem.addr),
> > +                                   entry->u.mem.len);
> > +                      }
> > +                    else
> > +                      error (_("Process record: error writing memory at "
> > +                               "addr = %s len = %d."),
> > +                             paddress (gdbarch, entry->u.mem.addr),
> > +                            entry->u.mem.len);
> > +                  }
> > +              }
> > +
> > +            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> > +          }
> > +      }
> > +      break;
> > +    }
> > +}
> > +
> >  static void
> >  record_open (char *name, int from_tty)
> >  {
> > @@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
> >   if (record_debug)
> >     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> >  +  if (!strcmp (current_target.to_shortname, "core"))
> > +    record_core = 1;
> > +  else
> > +    record_core = 0;
> > +
> >   /* check exec */
> > -  if (!target_has_execution)
> > +  if (!target_has_execution && !record_core)
> >     error (_("Process record: the program is not being run."));
> >   if (non_stop)
> >     error (_("Process record target can't debug inferior in non-stop mode
> "
> > @@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
> >   record_beneath_to_xfer_partial = NULL;
> >   record_beneath_to_insert_breakpoint = NULL;
> >   record_beneath_to_remove_breakpoint = NULL;
> > +  record_beneath_to_has_execution = NULL;
> > +  record_beneath_to_prepare_to_store = NULL;
> >     /* Set the beneath function pointers.  */
> >   for (t = current_target.beneath; t != NULL; t = t->beneath)
> > @@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
> >          record_beneath_to_wait = t->to_wait;
> >          record_beneath_to_wait_ops = t;
> >         }
> > +      if (!record_beneath_to_fetch_registers)
> > +        {
> > +         record_beneath_to_fetch_registers =
> t->to_fetch_registers;
> > +         record_beneath_to_fetch_registers_ops = t;
> > +        }
> >       if (!record_beneath_to_store_registers)
> >         {
> >          record_beneath_to_store_registers =
> t->to_store_registers;
> > @@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
> >        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_has_execution)
> > +        {
> > +          record_beneath_to_has_execution_ops = t;
> > +         record_beneath_to_has_execution =
> t->to_has_execution;
> > +        }
> > +      if (!record_beneath_to_prepare_to_store)
> > +       record_beneath_to_prepare_to_store =
> t->to_prepare_to_store;
> >     }
> > -  if (!record_beneath_to_resume)
> > +  if (!record_beneath_to_resume && !record_core)
> >     error (_("Process record can't get to_resume."));
> > -  if (!record_beneath_to_wait)
> > +  if (!record_beneath_to_wait && !record_core)
> >     error (_("Process record can't get to_wait."));
> > -  if (!record_beneath_to_store_registers)
> > +  if (!record_beneath_to_fetch_registers)
> > +    error (_("Process record can't get to_fetch_registers."));
> > +  if (!record_beneath_to_store_registers &&
> !record_core)
> >     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)
> > +  if (!record_beneath_to_insert_breakpoint &&
> !record_core)
> >     error (_("Process record can't get to_insert_breakpoint."));
> > -  if (!record_beneath_to_remove_breakpoint)
> > +  if (!record_beneath_to_remove_breakpoint &&
> !record_core)
> >     error (_("Process record can't get to_remove_breakpoint."));
> > +  if (!record_beneath_to_has_execution && !record_core)
> > +    error (_("Process record can't get to_has_execution."));
> > +  if (!record_beneath_to_prepare_to_store &&
> !record_core)
> > +    error (_("Process record can't get to_prepare_to_store."));
> > +
> > +  if (record_core)
> > +    {
> > +      /* Get record_core_regbuf.  */
> > +      struct regcache *regcache = get_current_regcache ();
> > +      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
> > +      int i;
> > +
> > +      target_fetch_registers (regcache, -1);
> > +      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
> > +      for (i = 0; i < regnum; i ++)
> > +        regcache_raw_collect (regcache, i,
> > +                              record_core_regbuf + MAX_REGISTER_SIZE *
> i);
> > +
> > +      /* Get record_core_start and record_core_end.  */
> > +      if (build_section_table (core_bfd, &record_core_start,
> &record_core_end))
> > +        error (_("\"%s\": Can't find sections: %s"),
> > +               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error
> ()));
> > +    }
> >     push_target (&record_ops);
> >  @@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
> >  static void
> >  record_close (int quitting)
> >  {
> > +  struct record_core_buf_entry *entry;
> > +
> >   if (record_debug)
> >     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
> >     record_list_release (record_list);
> > +
> > +  /* Release record_core_regbuf.  */
> > +  xfree (record_core_regbuf);
> > +
> > +  /* Release record_core_buf_list.  */
> > +  if (record_core_buf_list)
> > +    {
> > +      for (entry = record_core_buf_list->prev; entry; entry =
> entry->prev)
> > +        {
> > +          xfree (record_core_buf_list);
> > +          record_core_buf_list = entry;
> > +        }
> > +      record_core_buf_list = NULL;
> > +    }
> >  }
> >   static int record_resume_step = 0;
> > @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
> >              break;
> >            }
> >  -         /* Set ptid, register and memory according to record_list.  */
> > -         if (record_list->type == record_reg)
> > -           {
> > -             /* reg */
> > -             gdb_byte reg[MAX_REGISTER_SIZE];
> > -             if (record_debug > 1)
> > -               fprintf_unfiltered (gdb_stdlog,
> > -                                   "Process record: record_reg %s to "
> > -                                   "inferior num = %d.\n",
> > -                                   host_address_to_string (record_list),
> > -                                   record_list->u.reg.num);
> > -             regcache_cooked_read (regcache, record_list->u.reg.num,
> reg);
> > -             regcache_cooked_write (regcache, record_list->u.reg.num,
> > -                                    record_list->u.reg.val);
> > -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> > -           }
> > -         else if (record_list->type == record_mem)
> > -           {
> > -             /* mem */
> > -             /* Nothing to do if the entry is flagged not_accessible.  */
> > -             if
> (!record_list->u.mem.mem_entry_not_accessible)
> > -               {
> > -                 gdb_byte *mem = alloca (record_list->u.mem.len);
> > -                 if (record_debug > 1)
> > -                   fprintf_unfiltered (gdb_stdlog,
> > -                                       "Process record: record_mem %s to
> "
> > -                                       "inferior addr = %s len = %d.\n",
> > -                                       host_address_to_string
> (record_list),
> > -                                       paddress (gdbarch,
> > -
> record_list->u.mem.addr),
> > -                                       record_list->u.mem.len);
> > +          record_exec_entry (regcache, gdbarch, record_list);
> >  -                 if (target_read_memory (record_list->u.mem.addr, mem,
> > -                                         record_list->u.mem.len))
> > -                   {
> > -                     if (execution_direction != EXEC_REVERSE)
> > -                       error (_("Process record: error reading memory at
> "
> > -                                "addr = %s len = %d."),
> > -                              paddress (gdbarch,
> record_list->u.mem.addr),
> > -                              record_list->u.mem.len);
> > -                     else
> > -                       /* Read failed -- -                          flag
> entry as not_accessible.  */
> > -
> record_list->u.mem.mem_entry_not_accessible = 1;
> > -                   }
> > -                 else
> > -                   {
> > -                     if (target_write_memory (record_list->u.mem.addr,
> > -                                              record_list->u.mem.val,
> > -                                              record_list->u.mem.len))
> > -                       {
> > -                         if (execution_direction != EXEC_REVERSE)
> > -                           error (_("Process record: error writing memory
> at "
> > -                                    "addr = %s len = %d."),
> > -                                  paddress (gdbarch,
> record_list->u.mem.addr),
> > -                                  record_list->u.mem.len);
> > -                         else
> > -                           /* Write failed -- -
>    flag entry as not_accessible.  */
> > -
> record_list->u.mem.mem_entry_not_accessible = 1;
> > -                       }
> > -                     else
> > -                       {
> > -                         memcpy (record_list->u.mem.val, mem,
> > -                                 record_list->u.mem.len);
> > -                       }
> > -                   }
> > -               }
> > -           }
> > -         else
> > +         if (record_list->type == record_end)
> >            {
> >              if (record_debug > 1)
> >                fprintf_unfiltered (gdb_stdlog,
> > @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
> >     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
> >     unpush_target (&record_ops);
> > -  target_kill ();
> > +
> > +  if (!record_core)
> > +    target_kill ();
> >  }
> >   /* Record registers change (by user or by GDB) to list as an
> instruction.  */
> > @@ -945,15 +1058,58 @@ record_registers_change (struct regcache
> >   record_list = record_arch_list_tail;
> >     if (record_insn_num == record_insn_max_num && record_insn_max_num)
> > -    record_list_release_first ();
> > +    record_list_release_first_insn ();
> >   else
> >     record_insn_num++;
> >  }
> >   static void
> > +record_fetch_registers (struct target_ops *ops, struct regcache
> *regcache,
> > +                        int regno)
> > +{
> > +  if (record_core)
> > +    {
> > +      if (regno < 0)
> > +        {
> > +          int num = gdbarch_num_regs (get_regcache_arch (regcache));
> > +          int i;
> > +
> > +          for (i = 0; i < num; i ++)
> > +            regcache_raw_supply (regcache, i,
> > +                                 record_core_regbuf + MAX_REGISTER_SIZE *
> i);
> > +        }
> > +      else
> > +        regcache_raw_supply (regcache, regno,
> > +                             record_core_regbuf + MAX_REGISTER_SIZE *
> regno);
> > +    }
> > +  else
> > +    record_beneath_to_fetch_registers
> (record_beneath_to_store_registers_ops,
> > +                                       regcache, regno);
> > +}
> > +
> > +static void
> > +record_prepare_to_store (struct regcache *regcache)
> > +{
> > +  if (!record_core)
> > +    record_beneath_to_prepare_to_store (regcache);
> > +}
> > +
> > +static void
> >  record_store_registers (struct target_ops *ops, struct regcache
> *regcache,
> >                         int regno)
> >  {
> > +  if (record_core)
> > +    {
> > +      /* Debug with core.  */
> > +      if (record_gdb_operation_disable)
> > +        regcache_raw_collect (regcache, regno,
> > +
> ...
>
> [Message clipped]


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-05 21:23                   ` [RFA/RFC] Add dump and load command to process record and replay Michael Snyder
@ 2009-08-06  3:14                     ` Eli Zaretskii
  2009-08-06 14:16                       ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-06  3:14 UTC (permalink / raw)
  To: Michael Snyder; +Cc: teawater, gdb-patches

> Date: Wed, 05 Aug 2009 14:22:10 -0700
> From: Michael Snyder <msnyder@vmware.com>
> CC: Eli Zaretskii <eliz@gnu.org>, 
>  "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> Here's a suggested comment to start documenting the file format:
> 
> /* Record log save-file format
>     Version 1
> 
>       Header:
>         4 bytes: magic number RECORD_FILE_MAGIC.
>           NOTE: be sure to change whenever this file format changes!
> 
>       Records:
>        record_end:
>         1 byte:  record type (record_end)
>        record_reg:
>         1 byte:  record type (record_reg)
>         8 bytes: register id
>        16 bytes: register value
>        record_mem:
>         1 byte:  record type (record_mem)
>         8 bytes: memory address
>         8 bytes: memory length
>         n bytes: memory value (n == memory length)

This doesn't document the byte order.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-06  3:14                     ` Eli Zaretskii
@ 2009-08-06 14:16                       ` Hui Zhu
  2009-08-07  3:27                         ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-06 14:16 UTC (permalink / raw)
  To: Eli Zaretskii, Michael Snyder; +Cc: gdb-patches

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

On Thu, Aug 6, 2009 at 11:14, Eli Zaretskii<eliz@gnu.org> wrote:
>> Date: Wed, 05 Aug 2009 14:22:10 -0700
>> From: Michael Snyder <msnyder@vmware.com>
>> CC: Eli Zaretskii <eliz@gnu.org>,
>>  "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> Here's a suggested comment to start documenting the file format:
>>
>> /* Record log save-file format
>>     Version 1
>>
>>       Header:
>>         4 bytes: magic number RECORD_FILE_MAGIC.
>>           NOTE: be sure to change whenever this file format changes!
>>
>>       Records:
>>        record_end:
>>         1 byte:  record type (record_end)
>>        record_reg:
>>         1 byte:  record type (record_reg)
>>         8 bytes: register id
>>        16 bytes: register value
>>        record_mem:
>>         1 byte:  record type (record_mem)
>>         8 bytes: memory address
>>         8 bytes: memory length
>>         n bytes: memory value (n == memory length)
>
> This doesn't document the byte order.
>

I make a new version include byte order.  Please help me review it.

Thanks,
Hui

2009-08-06  Hui Zhu  <teawater@gmail.com>

	Add dump and load command to process record and replay.

	* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
        byteswap.h, netinet/in.h): Include files.
	(RECORD_IS_REPLAY): Return true if record_core is true.
	(RECORD_FILE_MAGIC): New macro.
	(record_core_buf_entry): New struct.
	(record_core, record_core_regbuf, record_core_start,
	record_core_end, record_core_buf_list,
	record_beneath_to_fetch_registers_ops,
	record_beneath_to_fetch_registers,
	record_beneath_to_store_registers_ops,
	record_beneath_to_store_registers,
	record_beneath_to_has_execution_ops,
	record_beneath_to_has_execution,
	record_beneath_to_prepare_to_store): New variables.
	(record_list_release_first_insn): Change function
	record_list_release_first to this name.
	(record_arch_list_cleanups): New function.
	(record_message_cleanups): Removed.
	(record_message): Change to call record_arch_list_cleanups
	and record_list_release_first_insn.
	(record_exec_entry): New function.
	(record_open): Add support for target core.
	(record_close): Add support for target core.
	(record_wait): Call function 'record_exec_entry'.
	(record_kill): Add support for target core.
	(record_registers_change): Call record_list_release_first_insn.
	(record_fetch_registers): New function.
	(record_prepare_to_store): New function.
	(record_store_registers): Add support for target core.
	(record_xfer_partial): Add support for target core.
	(record_has_execution): New function.
	(init_record_ops): Set record_ops.to_fetch_registers,
	record_ops.to_prepare_to_store
	and record_ops.to_has_execution.
	(cmd_record_fd_cleanups): New function.
	(cmd_record_dump): New function.
	(cmd_record_load): New function.
	(set_record_insn_max_num): Call record_list_release_first_insn.
	(_initialize_record): Add commands "record dump"
	and "record load".

---
 record.c |  806 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 715 insertions(+), 91 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)

 /* These are the core struct of record function.

@@ -78,9 +86,23 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -103,6 +125,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);

 static void
 record_list_release (struct record_entry *rec)
@@ -169,7 +202,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +419,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }

+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                 if ((execution_direction == EXEC_REVERSE && !record_core)
+                     || (execution_direction != EXEC_REVERSE && record_core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                     if ((execution_direction == EXEC_REVERSE && !record_core)
+                         || (execution_direction != EXEC_REVERSE &&
record_core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");

+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }

   push_target (&record_ops);

@@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }

-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);

-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed --
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed --
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }

 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -945,15 +1058,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }

 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -1000,6 +1156,7 @@ record_store_registers (struct target_op

       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }

+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
   return 1;
 }

+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1129,11 +1375,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1154,6 +1403,369 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090726).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory address (network byte order).
+       8 bytes: memory length (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                        magic);
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           args, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (args, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+
+          /* Get num.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+
+          /* Get addr.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get len.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\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);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1204,7 +1816,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1244,6 +1856,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1277,6 +1891,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is
'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 35594 bytes --]

---
 record.c |  806 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 715 insertions(+), 91 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
-     (record_list->next || execution_direction == EXEC_REVERSE)
+     (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC	htonl(0x20090726)
 
 /* These are the core struct of record function.
 
@@ -78,9 +86,23 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record with core target.  */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -103,6 +125,14 @@ static struct target_ops *record_beneath
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
 					 struct target_waitstatus *,
 					 int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+                                                  struct regcache *,
+						  int regno);
+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_store_registers_ops;
 static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                   struct regcache *,
@@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br
 						   struct bp_target_info *);
 static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 						   struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
 
 static void
 record_list_release (struct record_entry *rec)
@@ -169,7 +202,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +419,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                 if ((execution_direction == EXEC_REVERSE && !record_core)
+                     || (execution_direction != EXEC_REVERSE && record_core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                     if ((execution_direction == EXEC_REVERSE && !record_core)
+                         || (execution_direction != EXEC_REVERSE && record_core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -424,8 +542,13 @@ record_open (char *name, int from_tty)
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
 
+  if (!strcmp (current_target.to_shortname, "core"))
+    record_core = 1;
+  else
+    record_core = 0;
+
   /* check exec */
-  if (!target_has_execution)
+  if (!target_has_execution && !record_core)
     error (_("Process record: the program is not being run."));
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
@@ -454,6 +577,8 @@ record_open (char *name, int from_tty)
   record_beneath_to_xfer_partial = NULL;
   record_beneath_to_insert_breakpoint = NULL;
   record_beneath_to_remove_breakpoint = NULL;
+  record_beneath_to_has_execution = NULL;
+  record_beneath_to_prepare_to_store = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -468,6 +593,11 @@ record_open (char *name, int from_tty)
 	  record_beneath_to_wait = t->to_wait;
 	  record_beneath_to_wait_ops = t;
         }
+      if (!record_beneath_to_fetch_registers)
+        {
+	  record_beneath_to_fetch_registers = t->to_fetch_registers;
+	  record_beneath_to_fetch_registers_ops = t;
+        }
       if (!record_beneath_to_store_registers)
         {
 	  record_beneath_to_store_registers = t->to_store_registers;
@@ -482,19 +612,51 @@ record_open (char *name, int from_tty)
 	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_has_execution)
+        {
+          record_beneath_to_has_execution_ops = t;
+	  record_beneath_to_has_execution = t->to_has_execution;
+        }
+      if (!record_beneath_to_prepare_to_store)
+	record_beneath_to_prepare_to_store = t->to_prepare_to_store;
     }
-  if (!record_beneath_to_resume)
+  if (!record_beneath_to_resume && !record_core)
     error (_("Process record can't get to_resume."));
-  if (!record_beneath_to_wait)
+  if (!record_beneath_to_wait && !record_core)
     error (_("Process record can't get to_wait."));
-  if (!record_beneath_to_store_registers)
+  if (!record_beneath_to_fetch_registers)
+    error (_("Process record can't get to_fetch_registers."));
+  if (!record_beneath_to_store_registers && !record_core)
     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)
+  if (!record_beneath_to_insert_breakpoint && !record_core)
     error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
+  if (!record_beneath_to_remove_breakpoint && !record_core)
     error (_("Process record can't get to_remove_breakpoint."));
+  if (!record_beneath_to_has_execution && !record_core)
+    error (_("Process record can't get to_has_execution."));
+  if (!record_beneath_to_prepare_to_store && !record_core)
+    error (_("Process record can't get to_prepare_to_store."));
+
+  if (record_core)
+    {
+      /* Get record_core_regbuf.  */
+      struct regcache *regcache = get_current_regcache ();
+      int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      target_fetch_registers (regcache, -1);
+      record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+      for (i = 0; i < regnum; i ++)
+        regcache_raw_collect (regcache, i,
+                              record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+      /* Get record_core_start and record_core_end.  */
+      if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+        error (_("\"%s\": Can't find sections: %s"),
+               bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
 
   push_target (&record_ops);
 
@@ -507,10 +669,26 @@ record_open (char *name, int from_tty)
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  xfree (record_core_regbuf);
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -712,76 +890,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);
 
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
-  target_kill ();
+
+  if (!record_core)
+    target_kill ();
 }
 
 /* Record registers change (by user or by GDB) to list as an instruction.  */
@@ -945,15 +1058,58 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
 
 static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+                        int regno)
+{
+  if (record_core)
+    {
+      if (regno < 0)
+        {
+          int num = gdbarch_num_regs (get_regcache_arch (regcache));
+          int i;
+
+          for (i = 0; i < num; i ++)
+            regcache_raw_supply (regcache, i,
+                                 record_core_regbuf + MAX_REGISTER_SIZE * i);
+        }
+      else
+        regcache_raw_supply (regcache, regno,
+                             record_core_regbuf + MAX_REGISTER_SIZE * regno);
+    }
+  else
+    record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+                                       regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+  if (!record_core)
+    record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
 record_store_registers (struct target_ops *ops, struct regcache *regcache,
                         int regno)
 {
+  if (record_core)
+    {
+      /* Debug with core.  */
+      if (record_gdb_operation_disable)
+        regcache_raw_collect (regcache, regno,
+                              record_core_regbuf + MAX_REGISTER_SIZE * regno);
+      else
+        error (_("You can't do that without a process to debug."));
+
+      return;
+    }
+
   if (!record_gdb_operation_disable)
     {
       if (RECORD_IS_REPLAY)
@@ -1000,6 +1156,7 @@ record_store_registers (struct target_op
 
       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops *
 {
   if (!record_gdb_operation_disable
       && (object == TARGET_OBJECT_MEMORY
-	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+	  || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
     {
       if (RECORD_IS_REPLAY)
 	{
@@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
 
+   if (record_core && object == TARGET_OBJECT_MEMORY)
+     {
+       /* Debug with core.  */
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return 0;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+
+       return 0;
+     }
+
   return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                          object, annex, readbuf, writebuf,
                                          offset, len);
@@ -1113,6 +1350,15 @@ record_can_execute_reverse (void)
   return 1;
 }
 
+int
+record_has_execution (struct target_ops *ops)
+{
+  if (record_core)
+    return 1;
+
+  return record_beneath_to_has_execution (ops);
+}
+
 static void
 init_record_ops (void)
 {
@@ -1129,11 +1375,14 @@ init_record_ops (void)
   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_fetch_registers = record_fetch_registers;
+  record_ops.to_prepare_to_store = record_prepare_to_store;
   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_has_execution = record_has_execution;
   record_ops.to_stratum = record_stratum;
   record_ops.to_magic = OPS_MAGIC;
 }
@@ -1154,6 +1403,369 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090726).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory address (network byte order).
+       8 bytes: memory length (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                        magic);
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (current_target.to_stratum != record_stratum)
+    {
+      cmd_record_start (NULL, from_tty);
+      printf_unfiltered (_("Auto start process record.\n"));
+    }
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  recfd = open (args, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           args, strerror (errno));
+  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (args, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), args);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), args);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+
+          /* Get num.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+
+          /* Get addr.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get len.  */
+          record_read_dump (args, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+
+          /* Get val.  */
+          record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\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);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), args);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1204,7 +1816,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1244,6 +1856,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1277,6 +1891,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-06 14:16                       ` Hui Zhu
@ 2009-08-07  3:27                         ` Michael Snyder
  2009-08-07  3:29                           ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-07  3:27 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

Hui Zhu wrote:

> +/* Load the execution log from a file.  */
> +
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +
> +  if (current_target.to_stratum != record_stratum)
> +    {
> +      cmd_record_start (NULL, from_tty);
> +      printf_unfiltered (_("Auto start process record.\n"));
> +    }
> +
> +  if (!args || (args && !*args))
> +    error (_("Argument for filename required.\n"));
> +
> +  /* Open the load file.  */
> +  recfd = open (args, O_RDONLY | O_BINARY);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for loading execution records: %s"),
> +           args, strerror (errno));
> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
> +
> +  /* Check the magic code.  */
> +  record_read_dump (args, recfd, &magic, 4);
> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a valid dump of execution records."), args);
> +
> +  /* Load the entries in recfd to the record_arch_list_head and
> +     record_arch_list_tail.  */
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;

Hi Hui,

It seems to me that you didn't discard the old recording entries
before loading the new ones -- you just added the new ones into
the existing list.

I don't think that is safe.  We can't be sure that there is
any relationship between any existing recording entries and
the new entries from the file.  We certainly can't assume
that they are consecutive.

I think you have to completely clear the record log at this point,
before you begin reading new entries from the dump file.

What I would suggest is that we add these lines to
record_list_release -- and then we can just call that here.

@@ -159,6 +159,11 @@ record_list_release (struct record_entry

    if (rec != &record_first)
      xfree (rec);
+
+  record_list = &record_first;
+  record_arch_list_tail = NULL;
+  record_arch_list_tail = NULL;
+  record_insn_num = 0;
  }



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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  3:27                         ` Michael Snyder
@ 2009-08-07  3:29                           ` Hui Zhu
  2009-08-07  3:34                             ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-07  3:29 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

About it, I think keep the old one can make the function more
flexible. I try it sometime.  It can make two or more gdb_record files
to one file.

What about add a warning to there? When the record list is not empty,
output a warning.

Thanks,
Hui

On Fri, Aug 7, 2009 at 11:04, Michael Snyder<msnyder@vmware.com> wrote:
> Hui Zhu wrote:
>
>> +/* Load the execution log from a file.  */
>> +
>> +static void
>> +cmd_record_load (char *args, int from_tty)
>> +{
>> +  int recfd;
>> +  uint32_t magic;
>> +  struct cleanup *old_cleanups;
>> +  struct cleanup *old_cleanups2;
>> +  struct record_entry *rec;
>> +  int insn_number = 0;
>> +
>> +  if (current_target.to_stratum != record_stratum)
>> +    {
>> +      cmd_record_start (NULL, from_tty);
>> +      printf_unfiltered (_("Auto start process record.\n"));
>> +    }
>> +
>> +  if (!args || (args && !*args))
>> +    error (_("Argument for filename required.\n"));
>> +
>> +  /* Open the load file.  */
>> +  recfd = open (args, O_RDONLY | O_BINARY);
>> +  if (recfd < 0)
>> +    error (_("Failed to open '%s' for loading execution records: %s"),
>> +           args, strerror (errno));
>> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
>> +
>> +  /* Check the magic code.  */
>> +  record_read_dump (args, recfd, &magic, 4);
>> +  if (magic != RECORD_FILE_MAGIC)
>> +    error (_("'%s' is not a valid dump of execution records."), args);
>> +
>> +  /* Load the entries in recfd to the record_arch_list_head and
>> +     record_arch_list_tail.  */
>> +  record_arch_list_head = NULL;
>> +  record_arch_list_tail = NULL;
>
> Hi Hui,
>
> It seems to me that you didn't discard the old recording entries
> before loading the new ones -- you just added the new ones into
> the existing list.
>
> I don't think that is safe.  We can't be sure that there is
> any relationship between any existing recording entries and
> the new entries from the file.  We certainly can't assume
> that they are consecutive.
>
> I think you have to completely clear the record log at this point,
> before you begin reading new entries from the dump file.
>
> What I would suggest is that we add these lines to
> record_list_release -- and then we can just call that here.
>
> @@ -159,6 +159,11 @@ record_list_release (struct record_entry
>
>   if (rec != &record_first)
>     xfree (rec);
> +
> +  record_list = &record_first;
> +  record_arch_list_tail = NULL;
> +  record_arch_list_tail = NULL;
> +  record_insn_num = 0;
>  }
>
>
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  3:29                           ` Hui Zhu
@ 2009-08-07  3:34                             ` Michael Snyder
  2009-08-07  4:06                               ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-07  3:34 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

I think it's too easy to get the debugger into an internally
inconsistent state.  If you don't know *exactly* what you are
doing, this is too likely to go wrong.

Sometimes we have to choose between flexibility and letting
the user shoot himself in the foot.

Hui Zhu wrote:
> About it, I think keep the old one can make the function more
> flexible. I try it sometime.  It can make two or more gdb_record files
> to one file.
> 
> What about add a warning to there? When the record list is not empty,
> output a warning.
> 
> Thanks,
> Hui
> 
> On Fri, Aug 7, 2009 at 11:04, Michael Snyder<msnyder@vmware.com> wrote:
>> Hui Zhu wrote:
>>
>>> +/* Load the execution log from a file.  */
>>> +
>>> +static void
>>> +cmd_record_load (char *args, int from_tty)
>>> +{
>>> +  int recfd;
>>> +  uint32_t magic;
>>> +  struct cleanup *old_cleanups;
>>> +  struct cleanup *old_cleanups2;
>>> +  struct record_entry *rec;
>>> +  int insn_number = 0;
>>> +
>>> +  if (current_target.to_stratum != record_stratum)
>>> +    {
>>> +      cmd_record_start (NULL, from_tty);
>>> +      printf_unfiltered (_("Auto start process record.\n"));
>>> +    }
>>> +
>>> +  if (!args || (args && !*args))
>>> +    error (_("Argument for filename required.\n"));
>>> +
>>> +  /* Open the load file.  */
>>> +  recfd = open (args, O_RDONLY | O_BINARY);
>>> +  if (recfd < 0)
>>> +    error (_("Failed to open '%s' for loading execution records: %s"),
>>> +           args, strerror (errno));
>>> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
>>> +
>>> +  /* Check the magic code.  */
>>> +  record_read_dump (args, recfd, &magic, 4);
>>> +  if (magic != RECORD_FILE_MAGIC)
>>> +    error (_("'%s' is not a valid dump of execution records."), args);
>>> +
>>> +  /* Load the entries in recfd to the record_arch_list_head and
>>> +     record_arch_list_tail.  */
>>> +  record_arch_list_head = NULL;
>>> +  record_arch_list_tail = NULL;
>> Hi Hui,
>>
>> It seems to me that you didn't discard the old recording entries
>> before loading the new ones -- you just added the new ones into
>> the existing list.
>>
>> I don't think that is safe.  We can't be sure that there is
>> any relationship between any existing recording entries and
>> the new entries from the file.  We certainly can't assume
>> that they are consecutive.
>>
>> I think you have to completely clear the record log at this point,
>> before you begin reading new entries from the dump file.
>>
>> What I would suggest is that we add these lines to
>> record_list_release -- and then we can just call that here.
>>
>> @@ -159,6 +159,11 @@ record_list_release (struct record_entry
>>
>>   if (rec != &record_first)
>>     xfree (rec);
>> +
>> +  record_list = &record_first;
>> +  record_arch_list_tail = NULL;
>> +  record_arch_list_tail = NULL;
>> +  record_insn_num = 0;
>>  }
>>
>>
>>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  3:34                             ` Michael Snyder
@ 2009-08-07  4:06                               ` Hui Zhu
  2009-08-07  8:41                                 ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-07  4:06 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

I think a warning is clear to most of people.

And when he get this warning.  He can delete the record list and load
again.  He will lost nothing.

If we delete the old record list, maybe he still need old record.  He
will lost something.

Hui

On Fri, Aug 7, 2009 at 11:28, Michael Snyder<msnyder@vmware.com> wrote:
> I think it's too easy to get the debugger into an internally
> inconsistent state.  If you don't know *exactly* what you are
> doing, this is too likely to go wrong.
>
> Sometimes we have to choose between flexibility and letting
> the user shoot himself in the foot.
>
> Hui Zhu wrote:
>>
>> About it, I think keep the old one can make the function more
>> flexible. I try it sometime.  It can make two or more gdb_record files
>> to one file.
>>
>> What about add a warning to there? When the record list is not empty,
>> output a warning.
>>
>> Thanks,
>> Hui
>>
>> On Fri, Aug 7, 2009 at 11:04, Michael Snyder<msnyder@vmware.com> wrote:
>>>
>>> Hui Zhu wrote:
>>>
>>>> +/* Load the execution log from a file.  */
>>>> +
>>>> +static void
>>>> +cmd_record_load (char *args, int from_tty)
>>>> +{
>>>> +  int recfd;
>>>> +  uint32_t magic;
>>>> +  struct cleanup *old_cleanups;
>>>> +  struct cleanup *old_cleanups2;
>>>> +  struct record_entry *rec;
>>>> +  int insn_number = 0;
>>>> +
>>>> +  if (current_target.to_stratum != record_stratum)
>>>> +    {
>>>> +      cmd_record_start (NULL, from_tty);
>>>> +      printf_unfiltered (_("Auto start process record.\n"));
>>>> +    }
>>>> +
>>>> +  if (!args || (args && !*args))
>>>> +    error (_("Argument for filename required.\n"));
>>>> +
>>>> +  /* Open the load file.  */
>>>> +  recfd = open (args, O_RDONLY | O_BINARY);
>>>> +  if (recfd < 0)
>>>> +    error (_("Failed to open '%s' for loading execution records: %s"),
>>>> +           args, strerror (errno));
>>>> +  old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
>>>> +
>>>> +  /* Check the magic code.  */
>>>> +  record_read_dump (args, recfd, &magic, 4);
>>>> +  if (magic != RECORD_FILE_MAGIC)
>>>> +    error (_("'%s' is not a valid dump of execution records."), args);
>>>> +
>>>> +  /* Load the entries in recfd to the record_arch_list_head and
>>>> +     record_arch_list_tail.  */
>>>> +  record_arch_list_head = NULL;
>>>> +  record_arch_list_tail = NULL;
>>>
>>> Hi Hui,
>>>
>>> It seems to me that you didn't discard the old recording entries
>>> before loading the new ones -- you just added the new ones into
>>> the existing list.
>>>
>>> I don't think that is safe.  We can't be sure that there is
>>> any relationship between any existing recording entries and
>>> the new entries from the file.  We certainly can't assume
>>> that they are consecutive.
>>>
>>> I think you have to completely clear the record log at this point,
>>> before you begin reading new entries from the dump file.
>>>
>>> What I would suggest is that we add these lines to
>>> record_list_release -- and then we can just call that here.
>>>
>>> @@ -159,6 +159,11 @@ record_list_release (struct record_entry
>>>
>>>  if (rec != &record_first)
>>>    xfree (rec);
>>> +
>>> +  record_list = &record_first;
>>> +  record_arch_list_tail = NULL;
>>> +  record_arch_list_tail = NULL;
>>> +  record_insn_num = 0;
>>>  }
>>>
>>>
>>>
>
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  4:06                               ` Hui Zhu
@ 2009-08-07  8:41                                 ` Eli Zaretskii
  2009-08-07  9:53                                   ` Hui Zhu
  2009-08-07 17:42                                   ` Michael Snyder
  0 siblings, 2 replies; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-07  8:41 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Fri, 7 Aug 2009 11:34:20 +0800
> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> I think a warning is clear to most of people.
> 
> And when he get this warning.  He can delete the record list and load
> again.  He will lost nothing.
> 
> If we delete the old record list, maybe he still need old record.  He
> will lost something.

Instead of a warning, how about asking the user whether to discard the
old records or keep them?


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  8:41                                 ` Eli Zaretskii
@ 2009-08-07  9:53                                   ` Hui Zhu
  2009-08-07 12:51                                     ` Eli Zaretskii
  2009-08-07 17:42                                   ` Michael Snyder
  1 sibling, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-07  9:53 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: msnyder, gdb-patches

On Fri, Aug 7, 2009 at 16:39, Eli Zaretskii<eliz@gnu.org> wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Fri, 7 Aug 2009 11:34:20 +0800
>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> I think a warning is clear to most of people.
>>
>> And when he get this warning.  He can delete the record list and load
>> again.  He will lost nothing.
>>
>> If we delete the old record list, maybe he still need old record.  He
>> will lost something.
>
> Instead of a warning, how about asking the user whether to discard the
> old records or keep them?
>

Agree.  What about a query?

Thanks,
Hui


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  9:53                                   ` Hui Zhu
@ 2009-08-07 12:51                                     ` Eli Zaretskii
  2009-08-07 16:22                                       ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-07 12:51 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Fri, 7 Aug 2009 17:21:57 +0800
> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
> 
> On Fri, Aug 7, 2009 at 16:39, Eli Zaretskii<eliz@gnu.org> wrote:
> >> From: Hui Zhu <teawater@gmail.com>
> >> Date: Fri, 7 Aug 2009 11:34:20 +0800
> >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> >>
> >> I think a warning is clear to most of people.
> >>
> >> And when he get this warning.  He can delete the record list and load
> >> again.  He will lost nothing.
> >>
> >> If we delete the old record list, maybe he still need old record.  He
> >> will lost something.
> >
> > Instead of a warning, how about asking the user whether to discard the
> > old records or keep them?
> >
> 
> Agree.  What about a query?

Sorry, I'm not sure I understand the question.  If you are asking how
to word the question we ask the user, here's my proposal:

  Keep the existing execution log? (yes or no)

I think the default should be NO, so that this does TRT when invoked
non-interactively.  If you agree, you need to use `nquery' for this
question.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07 12:51                                     ` Eli Zaretskii
@ 2009-08-07 16:22                                       ` Hui Zhu
  0 siblings, 0 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-07 16:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: msnyder, gdb-patches

On Fri, Aug 7, 2009 at 17:52, Eli Zaretskii<eliz@gnu.org> wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Fri, 7 Aug 2009 17:21:57 +0800
>> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
>>
>> On Fri, Aug 7, 2009 at 16:39, Eli Zaretskii<eliz@gnu.org> wrote:
>> >> From: Hui Zhu <teawater@gmail.com>
>> >> Date: Fri, 7 Aug 2009 11:34:20 +0800
>> >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>> >>
>> >> I think a warning is clear to most of people.
>> >>
>> >> And when he get this warning.  He can delete the record list and load
>> >> again.  He will lost nothing.
>> >>
>> >> If we delete the old record list, maybe he still need old record.  He
>> >> will lost something.
>> >
>> > Instead of a warning, how about asking the user whether to discard the
>> > old records or keep them?
>> >
>>
>> Agree.  What about a query?
>
> Sorry, I'm not sure I understand the question.  If you are asking how
> to word the question we ask the user, here's my proposal:
>
>  Keep the existing execution log? (yes or no)
>
> I think the default should be NO, so that this does TRT when invoked
> non-interactively.  If you agree, you need to use `nquery' for this
> question.
>

Looks like user interface's people don't like yquery and nquery, maybe
we can use query directly.

Thanks,
Hui


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07  8:41                                 ` Eli Zaretskii
  2009-08-07  9:53                                   ` Hui Zhu
@ 2009-08-07 17:42                                   ` Michael Snyder
  2009-08-08 13:28                                     ` Hui Zhu
  1 sibling, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-07 17:42 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches

Eli Zaretskii wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Fri, 7 Aug 2009 11:34:20 +0800
>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> I think a warning is clear to most of people.
>>
>> And when he get this warning.  He can delete the record list and load
>> again.  He will lost nothing.
>>
>> If we delete the old record list, maybe he still need old record.  He
>> will lost something.
> 
> Instead of a warning, how about asking the user whether to discard the
> old records or keep them?

My concern is, in most cases keeping them will be the wrong thing to do.
It will be very easy to create an internally inconsistent state, and
rather unlikely to create one that is *not* internally inconsistant.

Think about it -- we will be concatenating two independent sets of
state changes, with no way of knowing that the actual machine state
at the end of one is the same as the machine state at the beginning
of the other.  When these are then replayed, their effect may have
little or nothing to do with what the real machine would actually do.

To actually get this right, you would have to be *sure* that your
target machine is in the exact same state "now" (ie. when you do
the load command) as it was at the *beginning* of the previous
recording/debugging session.

I would rather either make this a separate, "expert mode"
command, or better still, leave it for a future patch to extend
the basic (and safe) patch that we first accept.



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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-07 17:42                                   ` Michael Snyder
@ 2009-08-08 13:28                                     ` Hui Zhu
  2009-08-10  3:09                                       ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-08 13:28 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

I think give him a query is very clear.

When he load, if there are some record log, it will query to user.  He
must choice remove the old record log or keep them.  He already know
what will happen.

Thanks,
Hui

On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote:
> Eli Zaretskii wrote:
>>>
>>> From: Hui Zhu <teawater@gmail.com>
>>> Date: Fri, 7 Aug 2009 11:34:20 +0800
>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org"
>>> <gdb-patches@sourceware.org>
>>>
>>> I think a warning is clear to most of people.
>>>
>>> And when he get this warning.  He can delete the record list and load
>>> again.  He will lost nothing.
>>>
>>> If we delete the old record list, maybe he still need old record.  He
>>> will lost something.
>>
>> Instead of a warning, how about asking the user whether to discard the
>> old records or keep them?
>
> My concern is, in most cases keeping them will be the wrong thing to do.
> It will be very easy to create an internally inconsistent state, and
> rather unlikely to create one that is *not* internally inconsistant.
>
> Think about it -- we will be concatenating two independent sets of
> state changes, with no way of knowing that the actual machine state
> at the end of one is the same as the machine state at the beginning
> of the other.  When these are then replayed, their effect may have
> little or nothing to do with what the real machine would actually do.
>
> To actually get this right, you would have to be *sure* that your
> target machine is in the exact same state "now" (ie. when you do
> the load command) as it was at the *beginning* of the previous
> recording/debugging session.
>
> I would rather either make this a separate, "expert mode"
> command, or better still, leave it for a future patch to extend
> the basic (and safe) patch that we first accept.
>
>
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-08 13:28                                     ` Hui Zhu
@ 2009-08-10  3:09                                       ` Michael Snyder
  2009-08-22 17:39                                         ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-10  3:09 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

Hui Zhu wrote:
> I think give him a query is very clear.
> 
> When he load, if there are some record log, it will query to user.  He
> must choice remove the old record log or keep them.  He already know
> what will happen.

This is my opinion.  The default should be to remove the old log
(not to query).  I think this will be both the most common case
and the safest.  We can maybe add a command option for those who
wish not to do that.

Anyone else have an opinion?

> On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote:
>> Eli Zaretskii wrote:
>>>> From: Hui Zhu <teawater@gmail.com>
>>>> Date: Fri, 7 Aug 2009 11:34:20 +0800
>>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org"
>>>> <gdb-patches@sourceware.org>
>>>>
>>>> I think a warning is clear to most of people.
>>>>
>>>> And when he get this warning.  He can delete the record list and load
>>>> again.  He will lost nothing.
>>>>
>>>> If we delete the old record list, maybe he still need old record.  He
>>>> will lost something.
>>> Instead of a warning, how about asking the user whether to discard the
>>> old records or keep them?
>> My concern is, in most cases keeping them will be the wrong thing to do.
>> It will be very easy to create an internally inconsistent state, and
>> rather unlikely to create one that is *not* internally inconsistant.
>>
>> Think about it -- we will be concatenating two independent sets of
>> state changes, with no way of knowing that the actual machine state
>> at the end of one is the same as the machine state at the beginning
>> of the other.  When these are then replayed, their effect may have
>> little or nothing to do with what the real machine would actually do.
>>
>> To actually get this right, you would have to be *sure* that your
>> target machine is in the exact same state "now" (ie. when you do
>> the load command) as it was at the *beginning* of the previous
>> recording/debugging session.
>>
>> I would rather either make this a separate, "expert mode"
>> command, or better still, leave it for a future patch to extend
>> the basic (and safe) patch that we first accept.
>>
>>
>>
> 


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

* Re: [RFA/RFC] Add dump and load command to process record (file   format etc)
  2009-08-06  2:17                     ` Hui Zhu
@ 2009-08-12 14:11                       ` Michael Snyder
  2009-08-12 15:16                         ` Tom Tromey
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-12 14:11 UTC (permalink / raw)
  To: Hui Zhu; +Cc: gdb-patches

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



Hui Zhu wrote:
> On 2009-08-06, Michael Snyder <msnyder@vmware.com> wrote:

>>  1) Store a pointer to the corefile in the record log file.
>>  2) Store a checksum, hash, or CRC of the corefile in the log file.
> 
> I think this way is easy to implement.
> 
>>  3) Store a CRC of the loadable sections, plus a snapshot of the
>>    register set, in the log file.
>>  4) Store both corefile and log file together in the same file.
> 
> For all of this way, I think we have a problem is when we dump the
> log, we don't know where is the core file.
> Maybe we can extend the record dump command, to let it point out where
> is the  core file or let it call gcore too.

OK, I've written an extension to gcore.c which exports some
functions which we can call from record.c, to save and load
core files for snapshots.

Using this, we can then create an extra section in the core
file, and save our record log in there.

That way, we are assured of consistency between the start state
and the record log, because they are both in the same file.

What do you think?


[-- Attachment #2: dumpload2.txt --]
[-- Type: text/plain, Size: 36515 bytes --]

2009-08-11  Hui Zhu  <teawater@gmail.com>
	    Michael Snyder  <msnyder@vmware.com>
	    
	* record.c (RECORD_FILE_MAGIC): New constant.
	(cmd_record_dump): New function.
	(cmd_record_load): New function.
	(bfdcore_read): New function.
	(bfdcore_write): New function.
	(record_exec_entry): New function, abstracted from record_wait.
	(record_wait): Call record_exec_entry.
	(_initialize_record): Add 'record dump' and 'record load' commands.
	(record_arch_list_add_reg): Use xcalloc instead of xmalloc.
	(record_list_release): Finish releasing record list.

	* gcore.c (create_gcore_bfd): New function, abstracted 
	from gcore_command for export.
	(write_gcore_file): New function, abstracted from 
	gcore_command for export.
	(gcore_command): Call helper functions (above).

	(call_target_sbrk): New function, abstracted from 
	derive_heap_segment.
	(derive_heap_segment): Call helper function (above).

	(load_core_segments): New function.
	(load_corefile): New function.

Index: gcore.c
===================================================================
RCS file: /cvs/src/src/gdb/gcore.c,v
retrieving revision 1.34
diff -u -p -r1.34 gcore.c
--- gcore.c	2 Jul 2009 17:21:06 -0000	1.34
+++ gcore.c	11 Aug 2009 18:44:27 -0000
@@ -25,6 +25,8 @@
 #include "gdbcore.h"
 #include "objfiles.h"
 #include "symfile.h"
+#include "arch-utils.h"
+#include "completer.h"
 
 #include "cli/cli-decode.h"
 
@@ -40,45 +42,27 @@ static enum bfd_architecture default_gco
 static unsigned long default_gcore_mach (void);
 static int gcore_memory_sections (bfd *);
 
-/* Generate a core file from the inferior process.  */
+/* create_gcore_bfd -- helper for gcore_command (exported).  */
 
-static void
-gcore_command (char *args, int from_tty)
+extern bfd *
+create_gcore_bfd (char *filename)
 {
-  struct cleanup *old_chain;
-  char *corefilename, corefilename_buffer[40];
-  asection *note_sec = NULL;
-  bfd *obfd;
-  void *note_data = NULL;
-  int note_size = 0;
-
-  /* No use generating a corefile without a target process.  */
-  if (!target_has_execution)
-    noprocess ();
-
-  if (args && *args)
-    corefilename = args;
-  else
-    {
-      /* Default corefile name is "core.PID".  */
-      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
-      corefilename = corefilename_buffer;
-    }
-
-  if (info_verbose)
-    fprintf_filtered (gdb_stdout,
-		      "Opening corefile '%s' for output.\n", corefilename);
-
-  /* Open the output file.  */
-  obfd = bfd_openw (corefilename, default_gcore_target ());
+  bfd *obfd = bfd_openw (filename, default_gcore_target ());
   if (!obfd)
-    error (_("Failed to open '%s' for output."), corefilename);
-
-  /* Need a cleanup that will close the file (FIXME: delete it?).  */
-  old_chain = make_cleanup_bfd_close (obfd);
-
+    error (_("Failed to open '%s' for output."), filename);
   bfd_set_format (obfd, bfd_core);
   bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ());
+  return obfd;
+}
+
+/* write_gcore_file -- helper for gcore_command (exported).  */
+
+extern void
+write_gcore_file (bfd *obfd)
+{
+  void *note_data = NULL;
+  int note_size = 0;
+  asection *note_sec = NULL;
 
   /* An external target method must build the notes section.  */
   note_data = target_make_corefile_notes (obfd, &note_size);
@@ -107,8 +91,46 @@ gcore_command (char *args, int from_tty)
   if (note_data != NULL && note_size != 0)
     {
       if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size))
-	warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error ()));
+	warning (_("writing note section (%s)"), 
+		 bfd_errmsg (bfd_get_error ()));
     }
+}
+
+/* gcore_command -- implements the 'gcore' command.
+   Generate a core file from the inferior process.  */
+
+static void
+gcore_command (char *args, int from_tty)
+{
+  struct cleanup *old_chain;
+  char *corefilename, corefilename_buffer[40];
+  bfd *obfd;
+
+  /* No use generating a corefile without a target process.  */
+  if (!target_has_execution)
+    noprocess ();
+
+  if (args && *args)
+    corefilename = args;
+  else
+    {
+      /* Default corefile name is "core.PID".  */
+      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
+      corefilename = corefilename_buffer;
+    }
+
+  if (info_verbose)
+    fprintf_filtered (gdb_stdout,
+		      "Opening corefile '%s' for output.\n", corefilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (corefilename);
+
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_chain = make_cleanup_bfd_close (obfd);
+
+  /* Call worker function.  */
+  write_gcore_file (obfd);
 
   /* Succeeded.  */
   fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename);
@@ -212,6 +234,47 @@ derive_stack_segment (bfd_vma *bottom, b
   return 1;
 }
 
+static bfd_vma
+call_target_sbrk (int sbrk_arg)
+{
+  struct objfile *sbrk_objf;
+  struct gdbarch *gdbarch;
+  bfd_vma top_of_heap;
+  struct value *target_sbrk_arg;
+  struct value *sbrk_fn, *ret;
+  bfd_vma tmp;
+
+  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else
+    return (bfd_vma) 0;
+
+  gdbarch = get_objfile_arch (sbrk_objf);
+  target_sbrk_arg = value_from_longest (builtin_type (gdbarch)->builtin_int, 
+					sbrk_arg);
+  gdb_assert (target_sbrk_arg);
+  ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg);
+  if (ret == NULL)
+    return (bfd_vma) 0;
+
+  tmp = value_as_long (ret);
+  if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff)
+    return (bfd_vma) 0;
+
+  top_of_heap = tmp;
+  return top_of_heap;
+}
+
 /* Derive a reasonable heap segment for ABFD by looking at sbrk and
    the static data sections.  Store its limits in *BOTTOM and *TOP.
    Return non-zero if successful.  */
@@ -219,12 +282,10 @@ derive_stack_segment (bfd_vma *bottom, b
 static int
 derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
 {
-  struct objfile *sbrk_objf;
   struct gdbarch *gdbarch;
   bfd_vma top_of_data_memory = 0;
   bfd_vma top_of_heap = 0;
   bfd_size_type sec_size;
-  struct value *zero, *sbrk;
   bfd_vma sec_vaddr;
   asection *sec;
 
@@ -259,29 +320,9 @@ derive_heap_segment (bfd *abfd, bfd_vma 
 	}
     }
 
-  /* Now get the top-of-heap by calling sbrk in the inferior.  */
-  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else
-    return 0;
-
-  gdbarch = get_objfile_arch (sbrk_objf);
-  zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0);
-  gdb_assert (zero);
-  sbrk = call_function_by_hand (sbrk, 1, &zero);
-  if (sbrk == NULL)
+  top_of_heap = call_target_sbrk (0);
+  if (top_of_heap == (bfd_vma) 0)
     return 0;
-  top_of_heap = value_as_long (sbrk);
 
   /* Return results.  */
   if (top_of_heap > top_of_data_memory)
@@ -299,13 +340,15 @@ static void
 make_output_phdrs (bfd *obfd, asection *osec, void *ignored)
 {
   int p_flags = 0;
-  int p_type;
+  int p_type = 0;
 
   /* FIXME: these constants may only be applicable for ELF.  */
   if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0)
     p_type = PT_LOAD;
-  else
+  else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0)
     p_type = PT_NOTE;
+  else
+    p_type = PT_NULL;
 
   p_flags |= PF_R;	/* Segment is readable.  */
   if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY))
@@ -516,6 +559,144 @@ gcore_memory_sections (bfd *obfd)
   return 1;
 }
 
+struct load_core_args_params {
+  int from_tty;
+  bfd_vma top_of_heap;
+};
+
+static void
+load_core_segments (bfd *abfd, asection *asect, void *arg)
+{
+  struct load_core_args_params *params = arg;
+  struct cleanup *old_chain;
+  char *memhunk;
+  int ret;
+
+  if ((bfd_section_size (abfd, asect) > 0) &&
+      (bfd_get_section_flags (abfd, asect) & SEC_LOAD) &&
+      !(bfd_get_section_flags (abfd, asect) & SEC_READONLY))
+    {
+      if (info_verbose && params->from_tty)
+	{
+	  printf_filtered (_("Load core section %s"), 
+			   bfd_section_name (abfd, asect));
+	  printf_filtered (_(", vma 0x%08lx to 0x%08lx"), 
+			   (unsigned long) bfd_section_vma (abfd, asect),
+			   (unsigned long) bfd_section_vma (abfd, asect) +
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(", size = %d"), 
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(".\n"));
+	}
+      /* Fixme cleanup? */
+      memhunk = xmalloc (bfd_section_size (abfd, asect));
+      bfd_get_section_contents (abfd, asect, memhunk, 0, 
+				bfd_section_size (abfd, asect));
+      if ((ret = target_write_memory (bfd_section_vma (abfd, asect), 
+				      memhunk, 
+				      bfd_section_size (abfd, asect))) != 0)
+	{
+	  print_sys_errmsg ("load_core_segments", ret);
+	  if ((LONGEST) params->top_of_heap < 
+	      (LONGEST) bfd_section_vma (abfd, asect) + 
+	      (LONGEST) bfd_section_size (abfd, asect))
+	    {
+	      int increment = bfd_section_vma (abfd, asect) +
+		bfd_section_size (abfd, asect) - params->top_of_heap;
+
+	      params->top_of_heap = call_target_sbrk (increment);
+	      if (params->top_of_heap == 0)
+		error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap);
+	      else
+		printf_filtered ("Increase TOH to 0x%08lx and retry.\n",
+				 (unsigned long) params->top_of_heap);
+	      if (target_write_memory (bfd_section_vma (abfd, asect), 
+				       memhunk, 
+				       bfd_section_size (abfd, asect)) != 0)
+		{
+		  error ("Nope, still failed.");
+		}
+	    }
+	}
+      xfree (memhunk);
+    }
+}
+
+#include <fcntl.h>
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#include "regcache.h"
+#include "regset.h"
+
+extern bfd *
+load_corefile (char *filename, int from_tty)
+{
+  struct load_core_args_params params;
+  struct bfd_section *regsect;
+  const struct regset *regset;
+  struct regcache *regcache;
+  struct cleanup *old_chain;
+  struct gdbarch *gdbarch;
+  char *scratch_path;
+  int scratch_chan;
+  char *contents;
+  bfd *core_bfd;
+  int size;
+
+  scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename, 
+			O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path);
+  if (scratch_chan < 0)
+    perror_with_name (filename);
+
+  core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan);
+  old_chain = make_cleanup_bfd_close (core_bfd);
+  if (!core_bfd)
+    perror_with_name (scratch_path);
+
+  if (!bfd_check_format (core_bfd, bfd_core))
+    error (_("\"%s\" is not a core file: %s"), 
+	   filename, bfd_errmsg (bfd_get_error ()));
+
+  params.from_tty = from_tty;
+  params.top_of_heap = call_target_sbrk (0);
+  if (params.top_of_heap == 0)
+    error (_("Couldn't get sbrk."));
+
+  bfd_map_over_sections (core_bfd, load_core_segments, (void *) &params);
+  /* Now need to get/set registers.  */
+  regsect = bfd_get_section_by_name (core_bfd, ".reg");
+
+  if (!regsect)
+    error (_("Couldn't find .reg section."));
+
+  size = bfd_section_size (core_bfd, regsect);
+  contents = alloca (size);
+  bfd_get_section_contents (core_bfd, regsect, contents, 0, size);
+
+  /* See FIXME kettenis/20031023 comment in corelow.c */
+  gdbarch = gdbarch_from_bfd (core_bfd);
+
+  if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch))
+    {
+      regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size);
+      if (!regset)
+	error (_("Failed to allocate regset."));
+
+      registers_changed ();
+      regcache = get_current_regcache ();
+      regset->supply_regset (regset, regcache, -1, contents, size);
+      reinit_frame_cache ();
+      target_store_registers (regcache, -1);
+    }
+  else
+    error (_("Failed to get regset from core section"));
+
+  discard_cleanups (old_chain);
+  return core_bfd;
+}
+
 /* Provide a prototype to silence -Wmissing-prototypes.  */
 extern initialize_file_ftype _initialize_gcore;
 
Index: record.c
===================================================================
RCS file: /cvs/src/src/gdb/record.c,v
retrieving revision 1.11
diff -u -p -r1.11 record.c
--- record.c	8 Aug 2009 01:57:44 -0000	1.11
+++ record.c	11 Aug 2009 18:44:27 -0000
@@ -23,15 +23,20 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)
 
+#define RECORD_FILE_MAGIC	htonl(0x20090726) /* Host to network order */
+
 /* These are the core struct of record function.
 
    An record_entry is a record of the value change of a register
@@ -146,6 +151,11 @@ record_list_release (struct record_entry
 
   if (rec != &record_first)
     xfree (rec);
+
+  record_list = &record_first;
+  record_arch_list_tail = NULL;
+  record_arch_list_tail = NULL;
+  record_insn_num = 0;
 }
 
 static void
@@ -241,7 +251,7 @@ record_arch_list_add_reg (struct regcach
 			num);
 
   rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
+  rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
   rec->prev = NULL;
   rec->next = NULL;
   rec->type = record_reg;
@@ -416,6 +426,95 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        memset (reg, 0, sizeof (reg));
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+	/* Nothing to do if the entry is flagged not_accessible.  */
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                if (execution_direction == EXEC_REVERSE)
+                  {
+		    /* Read failed -- 
+		       flag entry as not_accessible.  */
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+			 entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    if (execution_direction == EXEC_REVERSE)
+                      {
+			/* Write failed -- 
+			   flag entry as not_accessible.  */
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+			     entry->u.mem.len);
+                  }
+		else
+		  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+              }
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -712,76 +811,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
-
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+          record_exec_entry (regcache, gdbarch, record_list);
+
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -999,6 +1031,7 @@ record_store_registers (struct target_op
 
       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1153,6 +1186,453 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+/* Record log save-file format
+   Version 1
+
+     Header:
+       4 bytes: magic number RECORD_FILE_MAGIC.
+                NOTE: be sure to change whenever this file format changes!
+
+     Records: 
+      record_end:
+       1 byte:  record type (record_end)
+      record_reg:
+       1 byte:  record type (record_reg)
+       8 bytes: register id (network byte order)
+      16 bytes: register value (target byte order)
+      record_mem:
+       1 byte:  record type (record_mem)
+       8 bytes: memory address (network byte order)
+       8 bytes: memory length (network byte order)
+       n bytes: memory value (n == memory length, target byte order)
+
+   Version 2 (proposed)
+
+     Header:
+       4 bytes: magic number RECORD_FILE_MAGIC.
+                NOTE: be sure to change whenever this file format changes!
+       n bytes: architecture...
+       4 bytes: size of register snapshot
+       n bytes: register snapshot
+       4 bytes: number of section crcs
+       n bytes: section names with crcs
+       
+     Records:
+       See version 1.
+ */
+
+/* Dump the execution log to a file.  */
+
+#include "elf-bfd.h"
+
+static int
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+  extern void write_gcore_file (bfd *);
+  extern bfd *create_gcore_bfd (char *);
+  bfd *obfd;
+  int dump_size = 0;
+  asection *osec = NULL;
+  struct record_entry *p;
+  int bfd_offset = 0;
+
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default recfile name is "rec.PID".  */
+      sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, 
+		      _("Saving recording to file '%s'\n"), 
+		      recfilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (recfilename);
+
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_cleanups = make_cleanup_bfd_close (obfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Reverse execute to the begin of record list.  */
+  for (; record_list && record_list != &record_first; 
+       record_list = record_list->prev)
+    record_exec_entry (regcache, gdbarch, record_list);
+
+  /* Compute the size needed for the extra bfd section.  */
+  dump_size = 4;	/* magic cookie */
+  for (p = &record_first; p; p = p->next)
+    switch (p->type)
+      {
+      case record_end:
+	dump_size += 1;
+	break;
+      case record_reg:
+	dump_size += 1 + 8 + MAX_REGISTER_SIZE;
+	break;
+      case record_mem:
+	dump_size += 1 + 8 + 8 + p->u.mem.len;
+	break;
+      }
+
+  /* Make the new bfd section.  */
+  osec = bfd_make_section_anyway (obfd, "precord");
+  bfd_set_section_size (obfd, osec, dump_size);
+  bfd_set_section_vma (obfd, osec, 0);
+  bfd_section_lma (obfd, osec) = 0;
+  bfd_set_section_flags (obfd, osec, SEC_ALLOC | SEC_HAS_CONTENTS);
+
+  /* Save corefile state.  */
+  write_gcore_file (obfd);
+
+  /* Write out the record log (modified Hui method).  */
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, _("\
+  Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+		      magic);
+  if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset))
+    error (_("Failed to write 'magic' to %s (%s)"), 
+	   recfilename, bfd_errmsg (bfd_get_error ()));
+
+  /* Dump the entries into the new bfd section.  */
+  for (p = &record_first; p; p = p->next)
+    {
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      tmpu8 = p->type;
+      if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset))
+	error (_("Failed to write 'type' to %s (%s)"), 
+	       recfilename, bfd_errmsg (bfd_get_error ()));
+
+      switch (p->type)
+	{
+	case record_reg: /* reg */
+	  tmpu64 = p->u.reg.num;
+	  if (BYTE_ORDER == LITTLE_ENDIAN)
+	    tmpu64 = bswap_64 (tmpu64);
+
+	  if (record_debug)
+	    fprintf_filtered (gdb_stdlog, _("\
+  Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
+			      p->u.reg.num,
+			      *(ULONGEST *) p->u.reg.val, 
+			      MAX_REGISTER_SIZE);
+	  /* FIXME: register num does not need 8 bytes.  */
+	  if (!bfdcore_write (obfd, osec, &tmpu64, 
+			      sizeof (tmpu64), &bfd_offset))
+	    error (_("Failed to write regnum to %s (%s)"), 
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+
+	  /* FIXME: add a len field, and write the smaller value.  */
+	  if (!bfdcore_write (obfd, osec, p->u.reg.val,
+					MAX_REGISTER_SIZE, &bfd_offset))
+	    error (_("Failed to write regval to %s (%s)"), 
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+	  break;
+	case record_mem: /* mem */
+	  tmpu64 = p->u.mem.addr;
+	  if (BYTE_ORDER == LITTLE_ENDIAN)
+	    tmpu64 = bswap_64 (tmpu64);
+
+	  if (record_debug)
+	    fprintf_filtered (gdb_stdlog, _("\
+  Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+			      (unsigned int) p->u.mem.addr,
+			      p->u.mem.len);
+	  if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset))
+	    error (_("Failed to write memaddr to %s (%s)"),
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+
+	  tmpu64 = p->u.mem.len;
+	  if (BYTE_ORDER == LITTLE_ENDIAN)
+	    tmpu64 = bswap_64 (tmpu64);
+
+	  /* FIXME: len does not need 8 bytes.  */
+	  if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset))
+	    error (_("Failed to write memlen to %s (%s)"), 
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+
+	  if (!bfdcore_write (obfd, osec, p->u.mem.val,
+			      p->u.mem.len, &bfd_offset))
+	    error (_("Failed to write memval to %s (%s)"),
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+	  break;
+	case record_end:
+	  /* FIXME: record the contents of record_end rec.  */
+	  if (record_debug)
+	    fprintf_filtered (gdb_stdlog, _("\
+  Writing record_end (1 byte)\n"));
+	  break;
+	}
+    }
+
+  /* Now forward-execute back to the saved entry.  */
+  for (record_list = &record_first; 
+       record_list && record_list != cur_record_list; 
+       record_list = record_list->next)
+    record_exec_entry (regcache, gdbarch, record_list);
+
+  /* Clean-ups will close the output file and free malloc memory.  */
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
+}
+
+static int
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
+/* Load the execution log from a file.  */
+
+#include "gdbcore.h"
+#include <ctype.h>
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  extern void nullify_last_target_wait_ptid (void);
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+  bfd *core_bfd;
+  asection *osec;
+  extern bfd *load_corefile (char *, int);
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, 
+		      _("Restoring recording from file '%s'\n"), args);
+
+  /* Restore corefile regs and mem sections.  */
+  core_bfd = load_corefile (args, from_tty);
+  old_cleanups = make_cleanup_bfd_close (core_bfd);
+
+  /* Now need to find our special note section.  */
+  osec = bfd_get_section_by_name (core_bfd, "null0");
+  printf_filtered ("Find precord section %s.\n",
+		   osec ? "succeeded" : "failed");
+
+  if (osec)
+    {
+      int i, len;
+      int bfd_offset = 0;
+
+      if (record_debug)
+	fprintf_filtered (gdb_stdlog, "osec name = '%s'\n",
+			  bfd_section_name (core_bfd, osec));
+      len = (int) bfd_section_size (core_bfd, osec);
+      printf_filtered ("osec size = %d\n", len);
+
+      /* Check the magic code.  */
+      if (!bfdcore_read (core_bfd, osec, &magic, 
+			 sizeof (magic), &bfd_offset))
+	error (_("Failed to read 'magic' from %s (%s)"),
+	       args, bfd_errmsg (bfd_get_error ()));
+
+      if (magic != RECORD_FILE_MAGIC)
+	error (_("'%s', version mis-match / file format error."), 
+	       args);
+
+      if (record_debug)
+	fprintf_filtered (gdb_stdlog, _("\
+  Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+			  magic);
+      if (current_target.to_stratum != record_stratum)
+	{
+	  /* FIXME need cleanup!  We might error out.  */
+	  cmd_record_start (NULL, from_tty);
+	  printf_unfiltered (_("Auto start process record.\n"));
+	}
+
+      /* Free any existing record log, and load the entries in
+	 core_bfd to the new record log.  */
+      record_list_release (record_arch_list_tail);
+      old_cleanups2 = make_cleanup (record_message_cleanups, 0);
+
+      while (1)
+	{
+	  uint8_t tmpu8;
+	  uint64_t tmpu64;
+
+	  /* FIXME: Check offset for end-of-section.  */
+	  if (!bfdcore_read (core_bfd, osec, &tmpu8, 
+			     sizeof (tmpu8), &bfd_offset))
+	    break;
+
+	  switch (tmpu8)
+	    {
+	    case record_reg: /* reg */
+	      /* FIXME: abstract out into an 'insert' function.  */
+	      rec = (struct record_entry *) 
+		xmalloc (sizeof (struct record_entry));
+	      rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
+	      rec->prev = NULL;
+	      rec->next = NULL;
+	      rec->type = record_reg;
+	      /* Get num.  */
+	      /* FIXME: register num does not need 8 bytes.  */
+	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
+				 sizeof (tmpu64), &bfd_offset))
+		error (_("Failed to read regnum from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+
+	      if (BYTE_ORDER == LITTLE_ENDIAN)
+		tmpu64 = bswap_64 (tmpu64);
+	      rec->u.reg.num = tmpu64;
+
+	      /* Get val.  */
+	      if (!bfdcore_read (core_bfd, osec, rec->u.reg.val,
+				 MAX_REGISTER_SIZE, &bfd_offset))
+		error (_("Failed to read regval from  %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+
+	      if (record_debug)
+		fprintf_filtered (gdb_stdlog, _("\
+  Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
+				  rec->u.reg.num, 
+				  *(ULONGEST *) rec->u.reg.val, 
+				  MAX_REGISTER_SIZE);
+	      record_arch_list_add (rec);
+	      break;
+
+	    case record_mem: /* mem */
+	      rec = (struct record_entry *) 
+		xmalloc (sizeof (struct record_entry));
+	      rec->prev = NULL;
+	      rec->next = NULL;
+	      rec->type = record_mem;
+	      /* Get addr.  */
+	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
+				 sizeof (tmpu64), &bfd_offset))
+		error (_("Failed to read memaddr from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+	      if (BYTE_ORDER == LITTLE_ENDIAN)
+		tmpu64 = bswap_64 (tmpu64);
+	      rec->u.mem.addr = tmpu64;
+
+	      /* Get len.  */
+	      /* FIXME: len does not need 8 bytes.  */
+	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
+				 sizeof (tmpu64), &bfd_offset))
+		error (_("Failed to read memlen from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+	      if (BYTE_ORDER == LITTLE_ENDIAN)
+		tmpu64 = bswap_64 (tmpu64);
+	      rec->u.mem.len = tmpu64;
+
+	      rec->u.mem.mem_entry_not_accessible = 0;
+	      rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+	      /* Get val.  */
+	      if (!bfdcore_read (core_bfd, osec, rec->u.mem.val,
+				 rec->u.mem.len, &bfd_offset))
+		error (_("Failed to read memval from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+	      if (record_debug)
+		fprintf_filtered (gdb_stdlog, _("\
+  Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"),
+				  (unsigned int) rec->u.mem.addr,
+				  rec->u.mem.len);
+	      record_arch_list_add (rec);
+	      break;
+
+	    case record_end: /* end */
+	      /* FIXME: restore the contents of record_end rec.  */
+	      rec = (struct record_entry *) 
+		xmalloc (sizeof (struct record_entry));
+	      rec->prev = NULL;
+	      rec->next = NULL;
+	      rec->type = record_end;
+	      if (record_debug)
+		fprintf_filtered (gdb_stdlog, _("\
+  Reading record_end (one byte)\n"));
+	      record_arch_list_add (rec);
+	      insn_number ++;
+	      break;
+
+	    default:
+	      error (_("Format of '%s' is not right."), args);
+	      break;
+	    }
+	}
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  (??? FIXME)*/
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num = insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+	       record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+  registers_changed ();
+  reinit_frame_cache ();
+  nullify_last_target_wait_ptid ();
+  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1243,6 +1723,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1276,6 +1758,15 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution log to a file.\n\
+Argument is optional filename.  Default filename is 'rec.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load the execution log from a file.  Argument is filename."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),
@@ -1290,15 +1781,15 @@ _initialize_record (void)
 
   /* Record instructions number limit command.  */
   add_setshow_boolean_cmd ("stop-at-limit", no_class,
-			    &record_stop_at_limit, _("\
+			   &record_stop_at_limit, _("\
 Set whether record/replay stops when record/replay buffer becomes full."), _("\
 Show whether record/replay stops when record/replay buffer becomes full."), _("\
 Default is ON.\n\
 When ON, if the record/replay buffer becomes full, ask user what to do.\n\
 When OFF, if the record/replay buffer becomes full,\n\
 delete the oldest recorded instruction to make room for each new one."),
-			    NULL, NULL,
-                            &set_record_cmdlist, &show_record_cmdlist);
+			   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."),

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

* Re: [RFA/RFC] Add dump and load command to process record (file   format etc)
  2009-08-12 14:11                       ` Michael Snyder
@ 2009-08-12 15:16                         ` Tom Tromey
  2009-08-12 22:38                           ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Tom Tromey @ 2009-08-12 15:16 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Hui Zhu, gdb-patches

>>>>> "Michael" == Michael Snyder <msnyder@vmware.com> writes:

A few little nits in this patch.  Most of these appear more than once.

Michael> +extern bfd *
Michael> +create_gcore_bfd (char *filename)
Michael>  {

Don't use "extern" on definitions.
 
Michael> +static bfd_vma
Michael> +call_target_sbrk (int sbrk_arg)

New functions should have an explanatory comment.

Michael> +#include <fcntl.h>
Michael> +#ifndef O_BINARY
Michael> +#define O_BINARY 0
Michael> +#endif
Michael> +
Michael> +#include "regcache.h"
Michael> +#include "regset.h"

I think adding #includes in the middle of a file is ugly.

Also, I think the O_BINARY define isn't needed -- it looks to me that
defs.h does this.

Tom


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

* Re: [RFA/RFC] Add dump and load command to process record (file    format etc)
  2009-08-12 15:16                         ` Tom Tromey
@ 2009-08-12 22:38                           ` Michael Snyder
  2009-08-16  0:04                             ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-12 22:38 UTC (permalink / raw)
  To: tromey; +Cc: Hui Zhu, gdb-patches

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

Tom Tromey wrote:
>>>>>> "Michael" == Michael Snyder <msnyder@vmware.com> writes:
> 
> A few little nits in this patch.  Most of these appear more than once.
> 
> Michael> +extern bfd *
> Michael> +create_gcore_bfd (char *filename)
> Michael>  {
> 
> Don't use "extern" on definitions.
>  
> Michael> +static bfd_vma
> Michael> +call_target_sbrk (int sbrk_arg)
> 
> New functions should have an explanatory comment.
> 
> Michael> +#include <fcntl.h>
> Michael> +#ifndef O_BINARY
> Michael> +#define O_BINARY 0
> Michael> +#endif
> Michael> +
> Michael> +#include "regcache.h"
> Michael> +#include "regset.h"
> 
> I think adding #includes in the middle of a file is ugly.
> 
> Also, I think the O_BINARY define isn't needed -- it looks to me that
> defs.h does this.

Tom, thanks for the review.
Added gcore.h to export those externs, and moved one decl into 
inferior.h (always meant to do that for linux-fork).


[-- Attachment #2: dumpload3.txt --]
[-- Type: text/plain, Size: 39307 bytes --]

2009-08-11  Hui Zhu  <teawater@gmail.com>
	    Michael Snyder  <msnyder@vmware.com>
	    
	* record.c (RECORD_FILE_MAGIC): New constant.
	(cmd_record_dump): New function.
	(cmd_record_load): New function.
	(bfdcore_read): New function.
	(bfdcore_write): New function.
	(record_exec_entry): New function, abstracted from record_wait.
	(record_wait): Call record_exec_entry.
	(_initialize_record): Add 'record dump' and 'record load' commands.
	(record_arch_list_add_reg): Use xcalloc instead of xmalloc.
	(record_list_release): Finish releasing record list.

	* gcore.c (create_gcore_bfd): New function, abstracted 
	from gcore_command for export.
	(write_gcore_file): New function, abstracted from 
	gcore_command for export.
	(gcore_command): Call helper functions (above).

	(call_target_sbrk): New function, abstracted from 
	derive_heap_segment.
	(derive_heap_segment): Call helper function (above).

	(load_core_segments): New function.
	(load_corefile): New function.

	* gcore.h: New file.
	* inferior.h: Export nullify_last_target_wait_ptid from infrun.c
	for use by record.c and linux-fork.c.
	* linux-fork.c (fork_load_infrun_state): Delete extern decl.

Index: gcore.h
===================================================================
RCS file: gcore.h
diff -N gcore.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gcore.h	12 Aug 2009 22:28:43 -0000
@@ -0,0 +1,23 @@
+/* Support for reading/writing gcore files.
+
+   Copyright (C) 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/>.  */
+
+extern bfd *create_gcore_bfd (char *filename);
+extern void write_gcore_file (bfd *obfd);
+extern bfd *load_corefile (char *filename, int from_tty);
+
Index: inferior.h
===================================================================
RCS file: /cvs/src/src/gdb/inferior.h,v
retrieving revision 1.135
diff -u -p -r1.135 inferior.h
--- inferior.h	28 Jun 2009 00:20:22 -0000	1.135
+++ inferior.h	12 Aug 2009 22:28:43 -0000
@@ -236,6 +236,8 @@ extern void get_last_target_status(ptid_
 
 extern void follow_inferior_reset_breakpoints (void);
 
+extern void nullify_last_target_wait_ptid (void);
+
 /* Throw an error indicating the current thread is running.  */
 extern void error_is_running (void);
 
Index: gcore.c
===================================================================
RCS file: /cvs/src/src/gdb/gcore.c,v
retrieving revision 1.34
diff -u -p -r1.34 gcore.c
--- gcore.c	2 Jul 2009 17:21:06 -0000	1.34
+++ gcore.c	12 Aug 2009 22:28:43 -0000
@@ -25,10 +25,14 @@
 #include "gdbcore.h"
 #include "objfiles.h"
 #include "symfile.h"
-
+#include "arch-utils.h"
+#include "completer.h"
+#include "gcore.h"
 #include "cli/cli-decode.h"
-
 #include "gdb_assert.h"
+#include <fcntl.h>
+#include "regcache.h"
+#include "regset.h"
 
 /* The largest amount of memory to read from the target at once.  We
    must throttle it to limit the amount of memory used by GDB during
@@ -40,45 +44,27 @@ static enum bfd_architecture default_gco
 static unsigned long default_gcore_mach (void);
 static int gcore_memory_sections (bfd *);
 
-/* Generate a core file from the inferior process.  */
+/* create_gcore_bfd -- helper for gcore_command (exported).  */
 
-static void
-gcore_command (char *args, int from_tty)
+bfd *
+create_gcore_bfd (char *filename)
 {
-  struct cleanup *old_chain;
-  char *corefilename, corefilename_buffer[40];
-  asection *note_sec = NULL;
-  bfd *obfd;
-  void *note_data = NULL;
-  int note_size = 0;
-
-  /* No use generating a corefile without a target process.  */
-  if (!target_has_execution)
-    noprocess ();
-
-  if (args && *args)
-    corefilename = args;
-  else
-    {
-      /* Default corefile name is "core.PID".  */
-      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
-      corefilename = corefilename_buffer;
-    }
-
-  if (info_verbose)
-    fprintf_filtered (gdb_stdout,
-		      "Opening corefile '%s' for output.\n", corefilename);
-
-  /* Open the output file.  */
-  obfd = bfd_openw (corefilename, default_gcore_target ());
+  bfd *obfd = bfd_openw (filename, default_gcore_target ());
   if (!obfd)
-    error (_("Failed to open '%s' for output."), corefilename);
-
-  /* Need a cleanup that will close the file (FIXME: delete it?).  */
-  old_chain = make_cleanup_bfd_close (obfd);
-
+    error (_("Failed to open '%s' for output."), filename);
   bfd_set_format (obfd, bfd_core);
   bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ());
+  return obfd;
+}
+
+/* write_gcore_file -- helper for gcore_command (exported).  */
+
+void
+write_gcore_file (bfd *obfd)
+{
+  void *note_data = NULL;
+  int note_size = 0;
+  asection *note_sec = NULL;
 
   /* An external target method must build the notes section.  */
   note_data = target_make_corefile_notes (obfd, &note_size);
@@ -107,9 +93,47 @@ gcore_command (char *args, int from_tty)
   if (note_data != NULL && note_size != 0)
     {
       if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size))
-	warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error ()));
+	warning (_("writing note section (%s)"), 
+		 bfd_errmsg (bfd_get_error ()));
+    }
+}
+
+/* gcore_command -- implements the 'gcore' command.
+   Generate a core file from the inferior process.  */
+
+static void
+gcore_command (char *args, int from_tty)
+{
+  struct cleanup *old_chain;
+  char *corefilename, corefilename_buffer[40];
+  bfd *obfd;
+
+  /* No use generating a corefile without a target process.  */
+  if (!target_has_execution)
+    noprocess ();
+
+  if (args && *args)
+    corefilename = args;
+  else
+    {
+      /* Default corefile name is "core.PID".  */
+      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
+      corefilename = corefilename_buffer;
     }
 
+  if (info_verbose)
+    fprintf_filtered (gdb_stdout,
+		      "Opening corefile '%s' for output.\n", corefilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (corefilename);
+
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_chain = make_cleanup_bfd_close (obfd);
+
+  /* Call worker function.  */
+  write_gcore_file (obfd);
+
   /* Succeeded.  */
   fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename);
 
@@ -212,6 +236,50 @@ derive_stack_segment (bfd_vma *bottom, b
   return 1;
 }
 
+/* call_target_sbrk --
+   helper function for derive_heap_segment and load_core_segment.  */
+
+static bfd_vma
+call_target_sbrk (int sbrk_arg)
+{
+  struct objfile *sbrk_objf;
+  struct gdbarch *gdbarch;
+  bfd_vma top_of_heap;
+  struct value *target_sbrk_arg;
+  struct value *sbrk_fn, *ret;
+  bfd_vma tmp;
+
+  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else
+    return (bfd_vma) 0;
+
+  gdbarch = get_objfile_arch (sbrk_objf);
+  target_sbrk_arg = value_from_longest (builtin_type (gdbarch)->builtin_int, 
+					sbrk_arg);
+  gdb_assert (target_sbrk_arg);
+  ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg);
+  if (ret == NULL)
+    return (bfd_vma) 0;
+
+  tmp = value_as_long (ret);
+  if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff)
+    return (bfd_vma) 0;
+
+  top_of_heap = tmp;
+  return top_of_heap;
+}
+
 /* Derive a reasonable heap segment for ABFD by looking at sbrk and
    the static data sections.  Store its limits in *BOTTOM and *TOP.
    Return non-zero if successful.  */
@@ -219,12 +287,10 @@ derive_stack_segment (bfd_vma *bottom, b
 static int
 derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
 {
-  struct objfile *sbrk_objf;
   struct gdbarch *gdbarch;
   bfd_vma top_of_data_memory = 0;
   bfd_vma top_of_heap = 0;
   bfd_size_type sec_size;
-  struct value *zero, *sbrk;
   bfd_vma sec_vaddr;
   asection *sec;
 
@@ -259,29 +325,9 @@ derive_heap_segment (bfd *abfd, bfd_vma 
 	}
     }
 
-  /* Now get the top-of-heap by calling sbrk in the inferior.  */
-  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else
-    return 0;
-
-  gdbarch = get_objfile_arch (sbrk_objf);
-  zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0);
-  gdb_assert (zero);
-  sbrk = call_function_by_hand (sbrk, 1, &zero);
-  if (sbrk == NULL)
+  top_of_heap = call_target_sbrk (0);
+  if (top_of_heap == (bfd_vma) 0)
     return 0;
-  top_of_heap = value_as_long (sbrk);
 
   /* Return results.  */
   if (top_of_heap > top_of_data_memory)
@@ -299,13 +345,15 @@ static void
 make_output_phdrs (bfd *obfd, asection *osec, void *ignored)
 {
   int p_flags = 0;
-  int p_type;
+  int p_type = 0;
 
   /* FIXME: these constants may only be applicable for ELF.  */
   if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0)
     p_type = PT_LOAD;
-  else
+  else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0)
     p_type = PT_NOTE;
+  else
+    p_type = PT_NULL;
 
   p_flags |= PF_R;	/* Segment is readable.  */
   if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY))
@@ -516,6 +564,141 @@ gcore_memory_sections (bfd *obfd)
   return 1;
 }
 
+struct load_core_args_params {
+  int from_tty;
+  bfd_vma top_of_heap;
+};
+
+/* load_core_segments -- iterator function for bfd_map_over_sections.  */
+
+static void
+load_core_segment (bfd *abfd, asection *asect, void *arg)
+{
+  struct load_core_args_params *params = arg;
+  struct cleanup *old_chain;
+  char *memhunk;
+  int ret;
+
+  if ((bfd_section_size (abfd, asect) > 0) &&
+      (bfd_get_section_flags (abfd, asect) & SEC_LOAD) &&
+      !(bfd_get_section_flags (abfd, asect) & SEC_READONLY))
+    {
+      if (info_verbose && params->from_tty)
+	{
+	  printf_filtered (_("Load core section %s"), 
+			   bfd_section_name (abfd, asect));
+	  printf_filtered (_(", vma 0x%08lx to 0x%08lx"), 
+			   (unsigned long) bfd_section_vma (abfd, asect),
+			   (unsigned long) bfd_section_vma (abfd, asect) +
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(", size = %d"), 
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(".\n"));
+	}
+      /* Fixme cleanup? */
+      memhunk = xmalloc (bfd_section_size (abfd, asect));
+      bfd_get_section_contents (abfd, asect, memhunk, 0, 
+				bfd_section_size (abfd, asect));
+      if ((ret = target_write_memory (bfd_section_vma (abfd, asect), 
+				      memhunk, 
+				      bfd_section_size (abfd, asect))) != 0)
+	{
+	  print_sys_errmsg ("load_core_segment", ret);
+	  if ((LONGEST) params->top_of_heap < 
+	      (LONGEST) bfd_section_vma (abfd, asect) + 
+	      (LONGEST) bfd_section_size (abfd, asect))
+	    {
+	      int increment = bfd_section_vma (abfd, asect) +
+		bfd_section_size (abfd, asect) - params->top_of_heap;
+
+	      params->top_of_heap = call_target_sbrk (increment);
+	      if (params->top_of_heap == 0)
+		error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap);
+	      else
+		printf_filtered ("Increase TOH to 0x%08lx and retry.\n",
+				 (unsigned long) params->top_of_heap);
+	      if (target_write_memory (bfd_section_vma (abfd, asect), 
+				       memhunk, 
+				       bfd_section_size (abfd, asect)) != 0)
+		{
+		  error ("Nope, still failed.");
+		}
+	    }
+	}
+      xfree (memhunk);
+    }
+}
+
+/* load_corefile -- reads a corefile, copies its memory sections
+   into target memory, and its registers into target regcache.  */
+
+bfd *
+load_corefile (char *filename, int from_tty)
+{
+  struct load_core_args_params params;
+  struct bfd_section *regsect;
+  const struct regset *regset;
+  struct regcache *regcache;
+  struct cleanup *old_chain;
+  struct gdbarch *gdbarch;
+  char *scratch_path;
+  int scratch_chan;
+  char *contents;
+  bfd *core_bfd;
+  int size;
+
+  scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename, 
+			O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path);
+  if (scratch_chan < 0)
+    perror_with_name (filename);
+
+  core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan);
+  old_chain = make_cleanup_bfd_close (core_bfd);
+  if (!core_bfd)
+    perror_with_name (scratch_path);
+
+  if (!bfd_check_format (core_bfd, bfd_core))
+    error (_("\"%s\" is not a core file: %s"), 
+	   filename, bfd_errmsg (bfd_get_error ()));
+
+  params.from_tty = from_tty;
+  params.top_of_heap = call_target_sbrk (0);
+  if (params.top_of_heap == 0)
+    error (_("Couldn't get sbrk."));
+
+  bfd_map_over_sections (core_bfd, load_core_segment, (void *) &params);
+  /* Now need to get/set registers.  */
+  regsect = bfd_get_section_by_name (core_bfd, ".reg");
+
+  if (!regsect)
+    error (_("Couldn't find .reg section."));
+
+  size = bfd_section_size (core_bfd, regsect);
+  contents = alloca (size);
+  bfd_get_section_contents (core_bfd, regsect, contents, 0, size);
+
+  /* See FIXME kettenis/20031023 comment in corelow.c */
+  gdbarch = gdbarch_from_bfd (core_bfd);
+
+  if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch))
+    {
+      regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size);
+      if (!regset)
+	error (_("Failed to allocate regset."));
+
+      registers_changed ();
+      regcache = get_current_regcache ();
+      regset->supply_regset (regset, regcache, -1, contents, size);
+      reinit_frame_cache ();
+      target_store_registers (regcache, -1);
+    }
+  else
+    error (_("Failed to get regset from core section"));
+
+  discard_cleanups (old_chain);
+  return core_bfd;
+}
+
 /* Provide a prototype to silence -Wmissing-prototypes.  */
 extern initialize_file_ftype _initialize_gcore;
 
Index: record.c
===================================================================
RCS file: /cvs/src/src/gdb/record.c,v
retrieving revision 1.11
diff -u -p -r1.11 record.c
--- record.c	8 Aug 2009 01:57:44 -0000	1.11
+++ record.c	12 Aug 2009 22:28:43 -0000
@@ -23,15 +23,24 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
+#include "elf-bfd.h"
+#include "gdbcore.h"
+#include <ctype.h>
+#include "gcore.h"
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)
 
+#define RECORD_FILE_MAGIC	htonl(0x20090726) /* Host to network order */
+
 /* These are the core struct of record function.
 
    An record_entry is a record of the value change of a register
@@ -146,6 +155,11 @@ record_list_release (struct record_entry
 
   if (rec != &record_first)
     xfree (rec);
+
+  record_list = &record_first;
+  record_arch_list_tail = NULL;
+  record_arch_list_tail = NULL;
+  record_insn_num = 0;
 }
 
 static void
@@ -241,7 +255,7 @@ record_arch_list_add_reg (struct regcach
 			num);
 
   rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
+  rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
   rec->prev = NULL;
   rec->next = NULL;
   rec->type = record_reg;
@@ -416,6 +430,95 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        memset (reg, 0, sizeof (reg));
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+	/* Nothing to do if the entry is flagged not_accessible.  */
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                if (execution_direction == EXEC_REVERSE)
+                  {
+		    /* Read failed -- 
+		       flag entry as not_accessible.  */
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+			 entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    if (execution_direction == EXEC_REVERSE)
+                      {
+			/* Write failed -- 
+			   flag entry as not_accessible.  */
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+			     entry->u.mem.len);
+                  }
+		else
+		  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+              }
+          }
+      }
+      break;
+    }
+}
+
 static void
 record_open (char *name, int from_tty)
 {
@@ -712,76 +815,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
-
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+          record_exec_entry (regcache, gdbarch, record_list);
+
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -999,6 +1035,7 @@ record_store_registers (struct target_op
 
       record_registers_change (regcache, regno);
     }
+
   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
                                      regcache, regno);
 }
@@ -1153,6 +1190,448 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+/* Record log save-file format
+   Version 1
+
+     Header:
+       4 bytes: magic number RECORD_FILE_MAGIC.
+                NOTE: be sure to change whenever this file format changes!
+
+     Records: 
+      record_end:
+       1 byte:  record type (record_end)
+      record_reg:
+       1 byte:  record type (record_reg)
+       8 bytes: register id (network byte order)
+      16 bytes: register value (target byte order)
+      record_mem:
+       1 byte:  record type (record_mem)
+       8 bytes: memory address (network byte order)
+       8 bytes: memory length (network byte order)
+       n bytes: memory value (n == memory length, target byte order)
+
+   Version 2 (proposed)
+
+     Header:
+       4 bytes: magic number RECORD_FILE_MAGIC.
+                NOTE: be sure to change whenever this file format changes!
+       n bytes: architecture...
+       4 bytes: size of register snapshot
+       n bytes: register snapshot
+       4 bytes: number of section crcs
+       n bytes: section names with crcs
+       
+     Records:
+       See version 1.
+ */
+
+/* bfdcore_write -- write bytes into a core file section.  */
+
+static int
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+  bfd *obfd;
+  int dump_size = 0;
+  asection *osec = NULL;
+  struct record_entry *p;
+  int bfd_offset = 0;
+
+
+  if (current_target.to_stratum != record_stratum)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default recfile name is "rec.PID".  */
+      sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, 
+		      _("Saving recording to file '%s'\n"), 
+		      recfilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (recfilename);
+
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_cleanups = make_cleanup_bfd_close (obfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Reverse execute to the begin of record list.  */
+  for (; record_list && record_list != &record_first; 
+       record_list = record_list->prev)
+    record_exec_entry (regcache, gdbarch, record_list);
+
+  /* Compute the size needed for the extra bfd section.  */
+  dump_size = 4;	/* magic cookie */
+  for (p = &record_first; p; p = p->next)
+    switch (p->type)
+      {
+      case record_end:
+	dump_size += 1;
+	break;
+      case record_reg:
+	dump_size += 1 + 8 + MAX_REGISTER_SIZE;
+	break;
+      case record_mem:
+	dump_size += 1 + 8 + 8 + p->u.mem.len;
+	break;
+      }
+
+  /* Make the new bfd section.  */
+  osec = bfd_make_section_anyway (obfd, "precord");
+  bfd_set_section_size (obfd, osec, dump_size);
+  bfd_set_section_vma (obfd, osec, 0);
+  bfd_section_lma (obfd, osec) = 0;
+  bfd_set_section_flags (obfd, osec, SEC_ALLOC | SEC_HAS_CONTENTS);
+
+  /* Save corefile state.  */
+  write_gcore_file (obfd);
+
+  /* Write out the record log (modified Hui method).  */
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, _("\
+  Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+		      magic);
+  if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset))
+    error (_("Failed to write 'magic' to %s (%s)"), 
+	   recfilename, bfd_errmsg (bfd_get_error ()));
+
+  /* Dump the entries into the new bfd section.  */
+  for (p = &record_first; p; p = p->next)
+    {
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      tmpu8 = p->type;
+      if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset))
+	error (_("Failed to write 'type' to %s (%s)"), 
+	       recfilename, bfd_errmsg (bfd_get_error ()));
+
+      switch (p->type)
+	{
+	case record_reg: /* reg */
+	  tmpu64 = p->u.reg.num;
+	  if (BYTE_ORDER == LITTLE_ENDIAN)
+	    tmpu64 = bswap_64 (tmpu64);
+
+	  if (record_debug)
+	    fprintf_filtered (gdb_stdlog, _("\
+  Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
+			      p->u.reg.num,
+			      *(ULONGEST *) p->u.reg.val, 
+			      MAX_REGISTER_SIZE);
+	  /* FIXME: register num does not need 8 bytes.  */
+	  if (!bfdcore_write (obfd, osec, &tmpu64, 
+			      sizeof (tmpu64), &bfd_offset))
+	    error (_("Failed to write regnum to %s (%s)"), 
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+
+	  /* FIXME: add a len field, and write the smaller value.  */
+	  if (!bfdcore_write (obfd, osec, p->u.reg.val,
+					MAX_REGISTER_SIZE, &bfd_offset))
+	    error (_("Failed to write regval to %s (%s)"), 
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+	  break;
+	case record_mem: /* mem */
+	  tmpu64 = p->u.mem.addr;
+	  if (BYTE_ORDER == LITTLE_ENDIAN)
+	    tmpu64 = bswap_64 (tmpu64);
+
+	  if (record_debug)
+	    fprintf_filtered (gdb_stdlog, _("\
+  Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+			      (unsigned int) p->u.mem.addr,
+			      p->u.mem.len);
+	  if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset))
+	    error (_("Failed to write memaddr to %s (%s)"),
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+
+	  tmpu64 = p->u.mem.len;
+	  if (BYTE_ORDER == LITTLE_ENDIAN)
+	    tmpu64 = bswap_64 (tmpu64);
+
+	  /* FIXME: len does not need 8 bytes.  */
+	  if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset))
+	    error (_("Failed to write memlen to %s (%s)"), 
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+
+	  if (!bfdcore_write (obfd, osec, p->u.mem.val,
+			      p->u.mem.len, &bfd_offset))
+	    error (_("Failed to write memval to %s (%s)"),
+		   recfilename, bfd_errmsg (bfd_get_error ()));
+	  break;
+	case record_end:
+	  /* FIXME: record the contents of record_end rec.  */
+	  if (record_debug)
+	    fprintf_filtered (gdb_stdlog, _("\
+  Writing record_end (1 byte)\n"));
+	  break;
+	}
+    }
+
+  /* Now forward-execute back to the saved entry.  */
+  for (record_list = &record_first; 
+       record_list && record_list != cur_record_list; 
+       record_list = record_list->next)
+    record_exec_entry (regcache, gdbarch, record_list);
+
+  /* Clean-ups will close the output file and free malloc memory.  */
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
+}
+
+/* bfdcore_read -- read bytes from a core file section.  */
+
+static int
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
+/* Load the execution log from a file.  */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+  bfd *core_bfd;
+  asection *osec;
+
+  if (!args || (args && !*args))
+    error (_("Argument for filename required.\n"));
+
+  /* Open the load file.  */
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, 
+		      _("Restoring recording from file '%s'\n"), args);
+
+  /* Restore corefile regs and mem sections.  */
+  core_bfd = load_corefile (args, from_tty);
+  old_cleanups = make_cleanup_bfd_close (core_bfd);
+
+  /* Now need to find our special note section.  */
+  osec = bfd_get_section_by_name (core_bfd, "null0");
+  printf_filtered ("Find precord section %s.\n",
+		   osec ? "succeeded" : "failed");
+
+  if (osec)
+    {
+      int i, len;
+      int bfd_offset = 0;
+
+      if (record_debug)
+	fprintf_filtered (gdb_stdlog, "osec name = '%s'\n",
+			  bfd_section_name (core_bfd, osec));
+      len = (int) bfd_section_size (core_bfd, osec);
+      printf_filtered ("osec size = %d\n", len);
+
+      /* Check the magic code.  */
+      if (!bfdcore_read (core_bfd, osec, &magic, 
+			 sizeof (magic), &bfd_offset))
+	error (_("Failed to read 'magic' from %s (%s)"),
+	       args, bfd_errmsg (bfd_get_error ()));
+
+      if (magic != RECORD_FILE_MAGIC)
+	error (_("'%s', version mis-match / file format error."), 
+	       args);
+
+      if (record_debug)
+	fprintf_filtered (gdb_stdlog, _("\
+  Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+			  magic);
+      if (current_target.to_stratum != record_stratum)
+	{
+	  /* FIXME need cleanup!  We might error out.  */
+	  cmd_record_start (NULL, from_tty);
+	  printf_unfiltered (_("Auto start process record.\n"));
+	}
+
+      /* Free any existing record log, and load the entries in
+	 core_bfd to the new record log.  */
+      record_list_release (record_arch_list_tail);
+      old_cleanups2 = make_cleanup (record_message_cleanups, 0);
+
+      while (1)
+	{
+	  uint8_t tmpu8;
+	  uint64_t tmpu64;
+
+	  /* FIXME: Check offset for end-of-section.  */
+	  if (!bfdcore_read (core_bfd, osec, &tmpu8, 
+			     sizeof (tmpu8), &bfd_offset))
+	    break;
+
+	  switch (tmpu8)
+	    {
+	    case record_reg: /* reg */
+	      /* FIXME: abstract out into an 'insert' function.  */
+	      rec = (struct record_entry *) 
+		xmalloc (sizeof (struct record_entry));
+	      rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
+	      rec->prev = NULL;
+	      rec->next = NULL;
+	      rec->type = record_reg;
+	      /* Get num.  */
+	      /* FIXME: register num does not need 8 bytes.  */
+	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
+				 sizeof (tmpu64), &bfd_offset))
+		error (_("Failed to read regnum from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+
+	      if (BYTE_ORDER == LITTLE_ENDIAN)
+		tmpu64 = bswap_64 (tmpu64);
+	      rec->u.reg.num = tmpu64;
+
+	      /* Get val.  */
+	      if (!bfdcore_read (core_bfd, osec, rec->u.reg.val,
+				 MAX_REGISTER_SIZE, &bfd_offset))
+		error (_("Failed to read regval from  %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+
+	      if (record_debug)
+		fprintf_filtered (gdb_stdlog, _("\
+  Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
+				  rec->u.reg.num, 
+				  *(ULONGEST *) rec->u.reg.val, 
+				  MAX_REGISTER_SIZE);
+	      record_arch_list_add (rec);
+	      break;
+
+	    case record_mem: /* mem */
+	      rec = (struct record_entry *) 
+		xmalloc (sizeof (struct record_entry));
+	      rec->prev = NULL;
+	      rec->next = NULL;
+	      rec->type = record_mem;
+	      /* Get addr.  */
+	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
+				 sizeof (tmpu64), &bfd_offset))
+		error (_("Failed to read memaddr from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+	      if (BYTE_ORDER == LITTLE_ENDIAN)
+		tmpu64 = bswap_64 (tmpu64);
+	      rec->u.mem.addr = tmpu64;
+
+	      /* Get len.  */
+	      /* FIXME: len does not need 8 bytes.  */
+	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
+				 sizeof (tmpu64), &bfd_offset))
+		error (_("Failed to read memlen from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+	      if (BYTE_ORDER == LITTLE_ENDIAN)
+		tmpu64 = bswap_64 (tmpu64);
+	      rec->u.mem.len = tmpu64;
+
+	      rec->u.mem.mem_entry_not_accessible = 0;
+	      rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+	      /* Get val.  */
+	      if (!bfdcore_read (core_bfd, osec, rec->u.mem.val,
+				 rec->u.mem.len, &bfd_offset))
+		error (_("Failed to read memval from %s (%s)"),
+		       args, bfd_errmsg (bfd_get_error ()));
+	      if (record_debug)
+		fprintf_filtered (gdb_stdlog, _("\
+  Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"),
+				  (unsigned int) rec->u.mem.addr,
+				  rec->u.mem.len);
+	      record_arch_list_add (rec);
+	      break;
+
+	    case record_end: /* end */
+	      /* FIXME: restore the contents of record_end rec.  */
+	      rec = (struct record_entry *) 
+		xmalloc (sizeof (struct record_entry));
+	      rec->prev = NULL;
+	      rec->next = NULL;
+	      rec->type = record_end;
+	      if (record_debug)
+		fprintf_filtered (gdb_stdlog, _("\
+  Reading record_end (one byte)\n"));
+	      record_arch_list_add (rec);
+	      insn_number ++;
+	      break;
+
+	    default:
+	      error (_("Format of '%s' is not right."), args);
+	      break;
+	    }
+	}
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  (??? FIXME)*/
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num = insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+	       record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+  registers_changed ();
+  reinit_frame_cache ();
+  nullify_last_target_wait_ptid ();
+  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1243,6 +1722,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1276,6 +1757,15 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution log to a file.\n\
+Argument is optional filename.  Default filename is 'rec.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load the execution log from a file.  Argument is filename."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),
@@ -1290,15 +1780,15 @@ _initialize_record (void)
 
   /* Record instructions number limit command.  */
   add_setshow_boolean_cmd ("stop-at-limit", no_class,
-			    &record_stop_at_limit, _("\
+			   &record_stop_at_limit, _("\
 Set whether record/replay stops when record/replay buffer becomes full."), _("\
 Show whether record/replay stops when record/replay buffer becomes full."), _("\
 Default is ON.\n\
 When ON, if the record/replay buffer becomes full, ask user what to do.\n\
 When OFF, if the record/replay buffer becomes full,\n\
 delete the oldest recorded instruction to make room for each new one."),
-			    NULL, NULL,
-                            &set_record_cmdlist, &show_record_cmdlist);
+			   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."),
Index: linux-fork.c
===================================================================
RCS file: /cvs/src/src/gdb/linux-fork.c,v
retrieving revision 1.29
diff -u -p -r1.29 linux-fork.c
--- linux-fork.c	2 Jul 2009 22:24:07 -0000	1.29
+++ linux-fork.c	12 Aug 2009 22:28:43 -0000
@@ -235,7 +235,6 @@ call_lseek (int fd, off_t offset, int wh
 static void
 fork_load_infrun_state (struct fork_info *fp)
 {
-  extern void nullify_last_target_wait_ptid ();
   int i;
 
   linux_nat_switch_fork (fp->ptid);

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

* Re: [RFA/RFC] Add dump and load command to process record (file   format etc)
  2009-08-12 22:38                           ` Michael Snyder
@ 2009-08-16  0:04                             ` Hui Zhu
  0 siblings, 0 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-16  0:04 UTC (permalink / raw)
  To: Michael Snyder; +Cc: tromey, gdb-patches

Thanks for your work, Michael.

I make it in a amd64:
gcc -g   -I. -I../../src/gdb -I../../src/gdb/common
-I../../src/gdb/config -DLOCALEDIR="\"/usr/local/share/locale\""
-DHAVE_CONFIG_H -I../../src/gdb/../include/opcode
-I../../src/gdb/../readline/.. -I../bfd -I../../src/gdb/../bfd
-I../../src/gdb/../include -I../libdecnumber
-I../../src/gdb/../libdecnumber  -I../../src/gdb/gnulib -Ignulib
-DMI_OUT=1 -DTUI=1  -DGDBTK  -Wall -Wdeclaration-after-statement
-Wpointer-arith -Wformat-nonliteral -Wno-pointer-sign -Wno-unused
-Wno-switch -Wno-char-subscripts -Werror -c -o record.o -MT record.o
-MMD -MP -MF .deps/record.Tpo ../../src/gdb/record.c
cc1: warnings being treated as errors
../../src/gdb/record.c: In function 'cmd_record_dump':
../../src/gdb/record.c:1359: error: format '%016llx' expects type
'long long unsigned int', but argument 4 has type 'long unsigned int'
../../src/gdb/record.c: In function 'cmd_record_load':
../../src/gdb/record.c:1544: error: format '%016llx' expects type
'long long unsigned int', but argument 4 has type 'long unsigned int'
make[2]: *** [record.o] Error 1
make[2]: Leaving directory `/home/teawater/gdb/bgdbno/gdb'
make[1]: *** [all-gdb] Error 2
make[1]: Leaving directory `/home/teawater/gdb/bgdbno'
make: *** [all] Error 2

	      if (record_debug)
		fprintf_filtered (gdb_stdlog, _("\
  Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
				  rec->u.reg.num,
				  *(ULONGEST *) rec->u.reg.val,
				  MAX_REGISTER_SIZE);
The val is not very important.  Please remove it.

(top-gdb) n
29	  args.argc = argc;
(top-gdb) record dump
BFD: rec.20370: warning: allocated section `precord' not in segment
Saved recfile rec.20370.
(top-gdb) quit

I got a warning.



./gdb ./gdb
GNU gdb (GDB) 6.8.50.20090815-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Setting up the environment for debugging gdb.
During symbol reading, DW_AT_name missing from DW_TAG_base_type.
During symbol reading, unsupported tag: 'DW_TAG_const_type'.
Breakpoint 1 at 0x47ab5c: file ../../src/gdb/utils.c, line 1002.
During symbol reading, DW_AT_type missing from DW_TAG_subrange_type.
Breakpoint 2 at 0x4d79c6: file ../../src/gdb/cli/cli-cmds.c, line 204.
(top-gdb) record load ./rec.20370
You can't do that without a process to debug.
(top-gdb)

I found that you try to let prec load core without corelow. And make
the record message and core message together is a very cool idea.  But
I think it need change a lot of thing of prec.   For example: memory,
reg, exec.
If it need let the inferior exec first, it will not work in other arch host.



The following is the my opinion.


On Thu, Aug 13, 2009 at 06:31, Michael Snyder<msnyder@vmware.com> wrote:
> Tom Tromey wrote:
>>>>>>>
>>>>>>> "Michael" == Michael Snyder <msnyder@vmware.com> writes:
>>
>> A few little nits in this patch.  Most of these appear more than once.
>>
>> Michael> +extern bfd *
>> Michael> +create_gcore_bfd (char *filename)
>> Michael>  {
>>
>> Don't use "extern" on definitions.
>>  Michael> +static bfd_vma
>> Michael> +call_target_sbrk (int sbrk_arg)
>>
>> New functions should have an explanatory comment.
>>
>> Michael> +#include <fcntl.h>
>> Michael> +#ifndef O_BINARY
>> Michael> +#define O_BINARY 0
>> Michael> +#endif
>> Michael> +
>> Michael> +#include "regcache.h"
>> Michael> +#include "regset.h"
>>
>> I think adding #includes in the middle of a file is ugly.
>>
>> Also, I think the O_BINARY define isn't needed -- it looks to me that
>> defs.h does this.
>
> Tom, thanks for the review.
> Added gcore.h to export those externs, and moved one decl into inferior.h
> (always meant to do that for linux-fork).
>
>
> 2009-08-11  Hui Zhu  <teawater@gmail.com>
>            Michael Snyder  <msnyder@vmware.com>
>
>        * record.c (RECORD_FILE_MAGIC): New constant.
>        (cmd_record_dump): New function.
>        (cmd_record_load): New function.
>        (bfdcore_read): New function.
>        (bfdcore_write): New function.
>        (record_exec_entry): New function, abstracted from record_wait.
>        (record_wait): Call record_exec_entry.
>        (_initialize_record): Add 'record dump' and 'record load' commands.
>        (record_arch_list_add_reg): Use xcalloc instead of xmalloc.
>        (record_list_release): Finish releasing record list.
>
>        * gcore.c (create_gcore_bfd): New function, abstracted
>        from gcore_command for export.
>        (write_gcore_file): New function, abstracted from
>        gcore_command for export.
>        (gcore_command): Call helper functions (above).
>
>        (call_target_sbrk): New function, abstracted from
>        derive_heap_segment.
>        (derive_heap_segment): Call helper function (above).
>
>        (load_core_segments): New function.
>        (load_corefile): New function.
>
>        * gcore.h: New file.
>        * inferior.h: Export nullify_last_target_wait_ptid from infrun.c
>        for use by record.c and linux-fork.c.
>        * linux-fork.c (fork_load_infrun_state): Delete extern decl.
>
> Index: gcore.h
> ===================================================================
> RCS file: gcore.h
> diff -N gcore.h
> --- /dev/null   1 Jan 1970 00:00:00 -0000
> +++ gcore.h     12 Aug 2009 22:28:43 -0000
> @@ -0,0 +1,23 @@
> +/* Support for reading/writing gcore files.
> +
> +   Copyright (C) 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/>.
>  */
> +
> +extern bfd *create_gcore_bfd (char *filename);
> +extern void write_gcore_file (bfd *obfd);
> +extern bfd *load_corefile (char *filename, int from_tty);
> +
> Index: inferior.h
> ===================================================================
> RCS file: /cvs/src/src/gdb/inferior.h,v
> retrieving revision 1.135
> diff -u -p -r1.135 inferior.h
> --- inferior.h  28 Jun 2009 00:20:22 -0000      1.135
> +++ inferior.h  12 Aug 2009 22:28:43 -0000
> @@ -236,6 +236,8 @@ extern void get_last_target_status(ptid_
>
>  extern void follow_inferior_reset_breakpoints (void);
>
> +extern void nullify_last_target_wait_ptid (void);
> +
>  /* Throw an error indicating the current thread is running.  */
>  extern void error_is_running (void);
>
> Index: gcore.c
> ===================================================================
> RCS file: /cvs/src/src/gdb/gcore.c,v
> retrieving revision 1.34
> diff -u -p -r1.34 gcore.c
> --- gcore.c     2 Jul 2009 17:21:06 -0000       1.34
> +++ gcore.c     12 Aug 2009 22:28:43 -0000
> @@ -25,10 +25,14 @@
>  #include "gdbcore.h"
>  #include "objfiles.h"
>  #include "symfile.h"
> -
> +#include "arch-utils.h"
> +#include "completer.h"
> +#include "gcore.h"
>  #include "cli/cli-decode.h"
> -
>  #include "gdb_assert.h"
> +#include <fcntl.h>
> +#include "regcache.h"
> +#include "regset.h"
>
>  /* The largest amount of memory to read from the target at once.  We
>    must throttle it to limit the amount of memory used by GDB during
> @@ -40,45 +44,27 @@ static enum bfd_architecture default_gco
>  static unsigned long default_gcore_mach (void);
>  static int gcore_memory_sections (bfd *);
>
> -/* Generate a core file from the inferior process.  */
> +/* create_gcore_bfd -- helper for gcore_command (exported).  */
>
> -static void
> -gcore_command (char *args, int from_tty)
> +bfd *
> +create_gcore_bfd (char *filename)
>  {
> -  struct cleanup *old_chain;
> -  char *corefilename, corefilename_buffer[40];
> -  asection *note_sec = NULL;
> -  bfd *obfd;
> -  void *note_data = NULL;
> -  int note_size = 0;
> -
> -  /* No use generating a corefile without a target process.  */
> -  if (!target_has_execution)
> -    noprocess ();
> -
> -  if (args && *args)
> -    corefilename = args;
> -  else
> -    {
> -      /* Default corefile name is "core.PID".  */
> -      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
> -      corefilename = corefilename_buffer;
> -    }
> -
> -  if (info_verbose)
> -    fprintf_filtered (gdb_stdout,
> -                     "Opening corefile '%s' for output.\n", corefilename);
> -
> -  /* Open the output file.  */
> -  obfd = bfd_openw (corefilename, default_gcore_target ());
> +  bfd *obfd = bfd_openw (filename, default_gcore_target ());
>   if (!obfd)
> -    error (_("Failed to open '%s' for output."), corefilename);
> -
> -  /* Need a cleanup that will close the file (FIXME: delete it?).  */
> -  old_chain = make_cleanup_bfd_close (obfd);
> -
> +    error (_("Failed to open '%s' for output."), filename);
>   bfd_set_format (obfd, bfd_core);
>   bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ());
> +  return obfd;
> +}
> +
> +/* write_gcore_file -- helper for gcore_command (exported).  */
> +
> +void
> +write_gcore_file (bfd *obfd)
> +{
> +  void *note_data = NULL;
> +  int note_size = 0;
> +  asection *note_sec = NULL;
>
>   /* An external target method must build the notes section.  */
>   note_data = target_make_corefile_notes (obfd, &note_size);
> @@ -107,9 +93,47 @@ gcore_command (char *args, int from_tty)
>   if (note_data != NULL && note_size != 0)
>     {
>       if (!bfd_set_section_contents (obfd, note_sec, note_data, 0,
> note_size))
> -       warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error
> ()));
> +       warning (_("writing note section (%s)"),
> +                bfd_errmsg (bfd_get_error ()));
> +    }
> +}
> +
> +/* gcore_command -- implements the 'gcore' command.
> +   Generate a core file from the inferior process.  */
> +
> +static void
> +gcore_command (char *args, int from_tty)
> +{
> +  struct cleanup *old_chain;
> +  char *corefilename, corefilename_buffer[40];
> +  bfd *obfd;
> +
> +  /* No use generating a corefile without a target process.  */
> +  if (!target_has_execution)
> +    noprocess ();
> +
> +  if (args && *args)
> +    corefilename = args;
> +  else
> +    {
> +      /* Default corefile name is "core.PID".  */
> +      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
> +      corefilename = corefilename_buffer;
>     }
>
> +  if (info_verbose)
> +    fprintf_filtered (gdb_stdout,
> +                     "Opening corefile '%s' for output.\n", corefilename);
> +
> +  /* Open the output file.  */
> +  obfd = create_gcore_bfd (corefilename);
> +
> +  /* Need a cleanup that will close the file (FIXME: delete it?).  */
> +  old_chain = make_cleanup_bfd_close (obfd);
> +
> +  /* Call worker function.  */
> +  write_gcore_file (obfd);
> +
>   /* Succeeded.  */
>   fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename);
>
> @@ -212,6 +236,50 @@ derive_stack_segment (bfd_vma *bottom, b
>   return 1;
>  }
>
> +/* call_target_sbrk --
> +   helper function for derive_heap_segment and load_core_segment.  */
> +
> +static bfd_vma
> +call_target_sbrk (int sbrk_arg)
> +{
> +  struct objfile *sbrk_objf;
> +  struct gdbarch *gdbarch;
> +  bfd_vma top_of_heap;
> +  struct value *target_sbrk_arg;
> +  struct value *sbrk_fn, *ret;
> +  bfd_vma tmp;
> +
> +  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
> +    {
> +      sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf);
> +      if (sbrk_fn == NULL)
> +       return (bfd_vma) 0;
> +    }
> +  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
> +    {
> +      sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf);
> +      if (sbrk_fn == NULL)
> +       return (bfd_vma) 0;
> +    }
> +  else
> +    return (bfd_vma) 0;
> +
> +  gdbarch = get_objfile_arch (sbrk_objf);
> +  target_sbrk_arg = value_from_longest (builtin_type
> (gdbarch)->builtin_int,
> +                                       sbrk_arg);
> +  gdb_assert (target_sbrk_arg);
> +  ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg);
> +  if (ret == NULL)
> +    return (bfd_vma) 0;
> +
> +  tmp = value_as_long (ret);
> +  if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff)
> +    return (bfd_vma) 0;
> +
> +  top_of_heap = tmp;
> +  return top_of_heap;
> +}
> +
>  /* Derive a reasonable heap segment for ABFD by looking at sbrk and
>    the static data sections.  Store its limits in *BOTTOM and *TOP.
>    Return non-zero if successful.  */
> @@ -219,12 +287,10 @@ derive_stack_segment (bfd_vma *bottom, b
>  static int
>  derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
>  {
> -  struct objfile *sbrk_objf;
>   struct gdbarch *gdbarch;
>   bfd_vma top_of_data_memory = 0;
>   bfd_vma top_of_heap = 0;
>   bfd_size_type sec_size;
> -  struct value *zero, *sbrk;
>   bfd_vma sec_vaddr;
>   asection *sec;
>
> @@ -259,29 +325,9 @@ derive_heap_segment (bfd *abfd, bfd_vma
>        }
>     }
>
> -  /* Now get the top-of-heap by calling sbrk in the inferior.  */
> -  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
> -    {
> -      sbrk = find_function_in_inferior ("sbrk", &sbrk_objf);
> -      if (sbrk == NULL)
> -       return 0;
> -    }
> -  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
> -    {
> -      sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf);
> -      if (sbrk == NULL)
> -       return 0;
> -    }
> -  else
> -    return 0;
> -
> -  gdbarch = get_objfile_arch (sbrk_objf);
> -  zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0);
> -  gdb_assert (zero);
> -  sbrk = call_function_by_hand (sbrk, 1, &zero);
> -  if (sbrk == NULL)
> +  top_of_heap = call_target_sbrk (0);
> +  if (top_of_heap == (bfd_vma) 0)
>     return 0;
> -  top_of_heap = value_as_long (sbrk);
>
>   /* Return results.  */
>   if (top_of_heap > top_of_data_memory)
> @@ -299,13 +345,15 @@ static void
>  make_output_phdrs (bfd *obfd, asection *osec, void *ignored)
>  {
>   int p_flags = 0;
> -  int p_type;
> +  int p_type = 0;
>
>   /* FIXME: these constants may only be applicable for ELF.  */
>   if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0)
>     p_type = PT_LOAD;
> -  else
> +  else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0)
>     p_type = PT_NOTE;
> +  else
> +    p_type = PT_NULL;
>
>   p_flags |= PF_R;     /* Segment is readable.  */
>   if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY))
> @@ -516,6 +564,141 @@ gcore_memory_sections (bfd *obfd)
>   return 1;
>  }
>
> +struct load_core_args_params {
> +  int from_tty;
> +  bfd_vma top_of_heap;
> +};
> +
> +/* load_core_segments -- iterator function for bfd_map_over_sections.  */
> +
> +static void
> +load_core_segment (bfd *abfd, asection *asect, void *arg)
> +{
> +  struct load_core_args_params *params = arg;
> +  struct cleanup *old_chain;
> +  char *memhunk;
> +  int ret;
> +
> +  if ((bfd_section_size (abfd, asect) > 0) &&
> +      (bfd_get_section_flags (abfd, asect) & SEC_LOAD) &&
> +      !(bfd_get_section_flags (abfd, asect) & SEC_READONLY))
> +    {
> +      if (info_verbose && params->from_tty)
> +       {
> +         printf_filtered (_("Load core section %s"),
> +                          bfd_section_name (abfd, asect));
> +         printf_filtered (_(", vma 0x%08lx to 0x%08lx"),
> +                          (unsigned long) bfd_section_vma (abfd, asect),
> +                          (unsigned long) bfd_section_vma (abfd, asect) +
> +                          (int) bfd_section_size (abfd, asect));
> +         printf_filtered (_(", size = %d"),
> +                          (int) bfd_section_size (abfd, asect));
> +         printf_filtered (_(".\n"));
> +       }
> +      /* Fixme cleanup? */
> +      memhunk = xmalloc (bfd_section_size (abfd, asect));
> +      bfd_get_section_contents (abfd, asect, memhunk, 0,
> +                               bfd_section_size (abfd, asect));
> +      if ((ret = target_write_memory (bfd_section_vma (abfd, asect),
> +                                     memhunk,
> +                                     bfd_section_size (abfd, asect))) != 0)
> +       {
> +         print_sys_errmsg ("load_core_segment", ret);
> +         if ((LONGEST) params->top_of_heap <
> +             (LONGEST) bfd_section_vma (abfd, asect) +
> +             (LONGEST) bfd_section_size (abfd, asect))
> +           {
> +             int increment = bfd_section_vma (abfd, asect) +
> +               bfd_section_size (abfd, asect) - params->top_of_heap;
> +
> +             params->top_of_heap = call_target_sbrk (increment);
> +             if (params->top_of_heap == 0)
> +               error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap);
> +             else
> +               printf_filtered ("Increase TOH to 0x%08lx and retry.\n",
> +                                (unsigned long) params->top_of_heap);
> +             if (target_write_memory (bfd_section_vma (abfd, asect),
> +                                      memhunk,
> +                                      bfd_section_size (abfd, asect)) != 0)
> +               {
> +                 error ("Nope, still failed.");
> +               }
> +           }
> +       }
> +      xfree (memhunk);
> +    }
> +}
> +
> +/* load_corefile -- reads a corefile, copies its memory sections
> +   into target memory, and its registers into target regcache.  */
> +
> +bfd *
> +load_corefile (char *filename, int from_tty)
> +{

It sames that corelow.c core_open do something same with this
function, do you think we need share this function to core_open?

> +  struct load_core_args_params params;
> +  struct bfd_section *regsect;
> +  const struct regset *regset;
> +  struct regcache *regcache;
> +  struct cleanup *old_chain;
> +  struct gdbarch *gdbarch;
> +  char *scratch_path;
> +  int scratch_chan;
> +  char *contents;
> +  bfd *core_bfd;
> +  int size;
> +
> +  scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename,
> +                       O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path);
> +  if (scratch_chan < 0)
> +    perror_with_name (filename);
> +
> +  core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan);
> +  old_chain = make_cleanup_bfd_close (core_bfd);
> +  if (!core_bfd)
> +    perror_with_name (scratch_path);
> +
> +  if (!bfd_check_format (core_bfd, bfd_core))
> +    error (_("\"%s\" is not a core file: %s"),
> +          filename, bfd_errmsg (bfd_get_error ()));
> +
> +  params.from_tty = from_tty;
> +  params.top_of_heap = call_target_sbrk (0);
> +  if (params.top_of_heap == 0)
> +    error (_("Couldn't get sbrk."));
> +
> +  bfd_map_over_sections (core_bfd, load_core_segment, (void *) &params);
> +  /* Now need to get/set registers.  */
> +  regsect = bfd_get_section_by_name (core_bfd, ".reg");
> +
> +  if (!regsect)
> +    error (_("Couldn't find .reg section."));
> +
> +  size = bfd_section_size (core_bfd, regsect);
> +  contents = alloca (size);
> +  bfd_get_section_contents (core_bfd, regsect, contents, 0, size);
> +
> +  /* See FIXME kettenis/20031023 comment in corelow.c */
> +  gdbarch = gdbarch_from_bfd (core_bfd);
> +
> +  if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch))
> +    {
> +      regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size);
> +      if (!regset)
> +       error (_("Failed to allocate regset."));
> +
> +      registers_changed ();
> +      regcache = get_current_regcache ();
> +      regset->supply_regset (regset, regcache, -1, contents, size);
> +      reinit_frame_cache ();
> +      target_store_registers (regcache, -1);
> +    }
> +  else
> +    error (_("Failed to get regset from core section"));
> +
> +  discard_cleanups (old_chain);
> +  return core_bfd;
> +}
> +
>  /* Provide a prototype to silence -Wmissing-prototypes.  */
>  extern initialize_file_ftype _initialize_gcore;
>
> Index: record.c
> ===================================================================
> RCS file: /cvs/src/src/gdb/record.c,v
> retrieving revision 1.11
> diff -u -p -r1.11 record.c
> --- record.c    8 Aug 2009 01:57:44 -0000       1.11
> +++ record.c    12 Aug 2009 22:28:43 -0000
> @@ -23,15 +23,24 @@
>  #include "gdbthread.h"
>  #include "event-top.h"
>  #include "exceptions.h"
> +#include "completer.h"
>  #include "record.h"
>
> +#include <byteswap.h>
>  #include <signal.h>
> +#include <netinet/in.h>
> +#include "elf-bfd.h"
> +#include "gdbcore.h"
> +#include <ctype.h>
> +#include "gcore.h"
>
>  #define DEFAULT_RECORD_INSN_MAX_NUM    200000
>
>  #define RECORD_IS_REPLAY \
>      (record_list->next || execution_direction == EXEC_REVERSE)
>
> +#define RECORD_FILE_MAGIC      htonl(0x20090726) /* Host to network order
> */
> +
>  /* These are the core struct of record function.
>
>    An record_entry is a record of the value change of a register
> @@ -146,6 +155,11 @@ record_list_release (struct record_entry
>
>   if (rec != &record_first)
>     xfree (rec);
> +
> +  record_list = &record_first;
> +  record_arch_list_tail = NULL;
> +  record_arch_list_tail = NULL;
> +  record_insn_num = 0;
>  }

This function have other function except clear all
record_list_release.  I will post how to clear all record_list in
cmd_record_load.

>
>  static void
> @@ -241,7 +255,7 @@ record_arch_list_add_reg (struct regcach
>                        num);
>
>   rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> -  rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
> +  rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
>   rec->prev = NULL;
>   rec->next = NULL;
>   rec->type = record_reg;
> @@ -416,6 +430,95 @@ record_gdb_operation_disable_set (void)
>   return old_cleanups;
>  }
>
> +static inline void
> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> +                   struct record_entry *entry)
> +{
> +  switch (entry->type)
> +    {
> +    case record_reg: /* reg */
> +      {
> +        gdb_byte reg[MAX_REGISTER_SIZE];
> +
> +        if (record_debug > 1)
> +          fprintf_unfiltered (gdb_stdlog,
> +                              "Process record: record_reg %s to "
> +                              "inferior num = %d.\n",
> +                              host_address_to_string (entry),
> +                              entry->u.reg.num);
> +
> +        memset (reg, 0, sizeof (reg));
> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> +        regcache_cooked_write (regcache, entry->u.reg.num,
> entry->u.reg.val);
> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> +      }
> +      break;
> +
> +    case record_mem: /* mem */
> +      {
> +       /* Nothing to do if the entry is flagged not_accessible.  */
> +        if (!record_list->u.mem.mem_entry_not_accessible)
> +          {
> +            gdb_byte *mem = alloca (entry->u.mem.len);
> +
> +            if (record_debug > 1)
> +              fprintf_unfiltered (gdb_stdlog,
> +                                  "Process record: record_mem %s to "
> +                                  "inferior addr = %s len = %d.\n",
> +                                  host_address_to_string (entry),
> +                                  paddress (gdbarch, entry->u.mem.addr),
> +                                  record_list->u.mem.len);
> +
> +            if (target_read_memory (entry->u.mem.addr, mem,
> entry->u.mem.len))
> +              {
> +                if (execution_direction == EXEC_REVERSE)
> +                  {

When it works with core file, exec forware will get some error in lib,
it need change error to warning too.

> +                   /* Read failed --
> +                      flag entry as not_accessible.  */
> +                    record_list->u.mem.mem_entry_not_accessible = 1;
> +                    if (record_debug)
> +                      warning (_("Process record: error reading memory at "
> +                                 "addr = %s len = %d."),
> +                               paddress (gdbarch, entry->u.mem.addr),
> +                               entry->u.mem.len);
> +                  }
> +                else
> +                  error (_("Process record: error reading memory at "
> +                           "addr = %s len = %d."),
> +                         paddress (gdbarch, entry->u.mem.addr),
> +                        entry->u.mem.len);
> +              }
> +            else
> +              {
> +                if (target_write_memory (entry->u.mem.addr,
> entry->u.mem.val,
> +                                         entry->u.mem.len))
> +                  {
> +                    if (execution_direction == EXEC_REVERSE)
> +                      {
> +                       /* Write failed --
> +                          flag entry as not_accessible.  */
> +                        record_list->u.mem.mem_entry_not_accessible = 1;
> +                        if (record_debug)
> +                          warning (_("Process record: error writing memory
> at "
> +                                     "addr = %s len = %d."),
> +                                   paddress (gdbarch, entry->u.mem.addr),
> +                                   entry->u.mem.len);
> +                      }
> +                    else
> +                      error (_("Process record: error writing memory at "
> +                               "addr = %s len = %d."),
> +                             paddress (gdbarch, entry->u.mem.addr),
> +                            entry->u.mem.len);
> +                  }
> +               else
> +                 memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> +              }
> +          }
> +      }
> +      break;
> +    }
> +}
> +
>  static void
>  record_open (char *name, int from_tty)
>  {
> @@ -712,76 +815,9 @@ record_wait (struct target_ops *ops,
>              break;
>            }
>
> -         /* Set ptid, register and memory according to record_list.  */
> -         if (record_list->type == record_reg)
> -           {
> -             /* reg */
> -             gdb_byte reg[MAX_REGISTER_SIZE];
> -             if (record_debug > 1)
> -               fprintf_unfiltered (gdb_stdlog,
> -                                   "Process record: record_reg %s to "
> -                                   "inferior num = %d.\n",
> -                                   host_address_to_string (record_list),
> -                                   record_list->u.reg.num);
> -             regcache_cooked_read (regcache, record_list->u.reg.num, reg);
> -             regcache_cooked_write (regcache, record_list->u.reg.num,
> -                                    record_list->u.reg.val);
> -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> -           }
> -         else if (record_list->type == record_mem)
> -           {
> -             /* mem */
> -             /* Nothing to do if the entry is flagged not_accessible.  */
> -             if (!record_list->u.mem.mem_entry_not_accessible)
> -               {
> -                 gdb_byte *mem = alloca (record_list->u.mem.len);
> -                 if (record_debug > 1)
> -                   fprintf_unfiltered (gdb_stdlog,
> -                                       "Process record: record_mem %s to "
> -                                       "inferior addr = %s len = %d.\n",
> -                                       host_address_to_string
> (record_list),
> -                                       paddress (gdbarch,
> -                                                 record_list->u.mem.addr),
> -                                       record_list->u.mem.len);
> -
> -                 if (target_read_memory (record_list->u.mem.addr, mem,
> -                                         record_list->u.mem.len))
> -                   {
> -                     if (execution_direction != EXEC_REVERSE)
> -                       error (_("Process record: error reading memory at "
> -                                "addr = %s len = %d."),
> -                              paddress (gdbarch, record_list->u.mem.addr),
> -                              record_list->u.mem.len);
> -                     else
> -                       /* Read failed --
> -                          flag entry as not_accessible.  */
> -                       record_list->u.mem.mem_entry_not_accessible = 1;
> -                   }
> -                 else
> -                   {
> -                     if (target_write_memory (record_list->u.mem.addr,
> -                                              record_list->u.mem.val,
> -                                              record_list->u.mem.len))
> -                       {
> -                         if (execution_direction != EXEC_REVERSE)
> -                           error (_("Process record: error writing memory
> at "
> -                                    "addr = %s len = %d."),
> -                                  paddress (gdbarch,
> record_list->u.mem.addr),
> -                                  record_list->u.mem.len);
> -                         else
> -                           /* Write failed --
> -                              flag entry as not_accessible.  */
> -                           record_list->u.mem.mem_entry_not_accessible = 1;
> -                       }
> -                     else
> -                       {
> -                         memcpy (record_list->u.mem.val, mem,
> -                                 record_list->u.mem.len);
> -                       }
> -                   }
> -               }
> -           }
> -         else
> +          record_exec_entry (regcache, gdbarch, record_list);
> +
> +         if (record_list->type == record_end)
>            {
>              if (record_debug > 1)
>                fprintf_unfiltered (gdb_stdlog,
> @@ -999,6 +1035,7 @@ record_store_registers (struct target_op
>
>       record_registers_change (regcache, regno);
>     }
> +
>   record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
>                                      regcache, regno);
>  }
> @@ -1153,6 +1190,448 @@ cmd_record_start (char *args, int from_t
>   execute_command ("target record", from_tty);
>  }
>
> +/* Record log save-file format
> +   Version 1
> +
> +     Header:
> +       4 bytes: magic number RECORD_FILE_MAGIC.
> +                NOTE: be sure to change whenever this file format changes!
> +
> +     Records:
> +      record_end:
> +       1 byte:  record type (record_end)
> +      record_reg:
> +       1 byte:  record type (record_reg)
> +       8 bytes: register id (network byte order)
> +      16 bytes: register value (target byte order)
> +      record_mem:
> +       1 byte:  record type (record_mem)
> +       8 bytes: memory address (network byte order)
> +       8 bytes: memory length (network byte order)
> +       n bytes: memory value (n == memory length, target byte order)
> +
> +   Version 2 (proposed)
> +
> +     Header:
> +       4 bytes: magic number RECORD_FILE_MAGIC.
> +                NOTE: be sure to change whenever this file format changes!
> +       n bytes: architecture...
> +       4 bytes: size of register snapshot
> +       n bytes: register snapshot
> +       4 bytes: number of section crcs
> +       n bytes: section names with crcs
> +
> +     Records:
> +       See version 1.
> + */
> +
> +/* bfdcore_write -- write bytes into a core file section.  */
> +
> +static int
> +bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
> +{
> +  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
> +
> +  if (ret)
> +    *offset += len;
> +  return ret;
> +}
> +
> +/* Dump the execution log to a file.  */
> +
> +static void
> +cmd_record_dump (char *args, int from_tty)
> +{
> +  char *recfilename, recfilename_buffer[40];
> +  int recfd;
> +  struct record_entry *cur_record_list;
> +  uint32_t magic;
> +  struct regcache *regcache;
> +  struct gdbarch *gdbarch;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *set_cleanups;
> +  bfd *obfd;
> +  int dump_size = 0;
> +  asection *osec = NULL;
> +  struct record_entry *p;
> +  int bfd_offset = 0;
> +
> +
> +  if (current_target.to_stratum != record_stratum)
> +    error (_("Process record is not started.\n"));
> +
> +  if (args && *args)
> +    recfilename = args;
> +  else
> +    {
> +      /* Default recfile name is "rec.PID".  */
> +      sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
> +      recfilename = recfilename_buffer;
> +    }
> +
> +  /* Open the dump file.  */
> +  if (record_debug)
> +    fprintf_filtered (gdb_stdlog,
> +                     _("Saving recording to file '%s'\n"),
> +                     recfilename);
> +
> +  /* Open the output file.  */
> +  obfd = create_gcore_bfd (recfilename);
> +
> +  /* Need a cleanup that will close the file (FIXME: delete it?).  */
> +  old_cleanups = make_cleanup_bfd_close (obfd);
> +
> +  /* Save the current record entry to "cur_record_list".  */
> +  cur_record_list = record_list;
> +
> +  /* Get the values of regcache and gdbarch.  */
> +  regcache = get_current_regcache ();
> +  gdbarch = get_regcache_arch (regcache);
> +
> +  /* Disable the GDB operation record.  */
> +  set_cleanups = record_gdb_operation_disable_set ();
> +
> +  /* Reverse execute to the begin of record list.  */
> +  for (; record_list && record_list != &record_first;
> +       record_list = record_list->prev)
> +    record_exec_entry (regcache, gdbarch, record_list);
> +
> +  /* Compute the size needed for the extra bfd section.  */
> +  dump_size = 4;       /* magic cookie */
> +  for (p = &record_first; p; p = p->next)
> +    switch (p->type)
> +      {
> +      case record_end:
> +       dump_size += 1;
> +       break;
> +      case record_reg:
> +       dump_size += 1 + 8 + MAX_REGISTER_SIZE;
> +       break;
> +      case record_mem:
> +       dump_size += 1 + 8 + 8 + p->u.mem.len;
> +       break;
> +      }
> +
> +  /* Make the new bfd section.  */
> +  osec = bfd_make_section_anyway (obfd, "precord");
> +  bfd_set_section_size (obfd, osec, dump_size);
> +  bfd_set_section_vma (obfd, osec, 0);
> +  bfd_section_lma (obfd, osec) = 0;
> +  bfd_set_section_flags (obfd, osec, SEC_ALLOC | SEC_HAS_CONTENTS);
> +
> +  /* Save corefile state.  */
> +  write_gcore_file (obfd);
I am not sure it is a better idea that reverse and gcore.


> +
> +  /* Write out the record log (modified Hui method).  */
> +  /* Write the magic code.  */
> +  magic = RECORD_FILE_MAGIC;
> +  if (record_debug)
> +    fprintf_filtered (gdb_stdlog, _("\
> +  Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
> +                     magic);
> +  if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset))
> +    error (_("Failed to write 'magic' to %s (%s)"),
> +          recfilename, bfd_errmsg (bfd_get_error ()));
> +
> +  /* Dump the entries into the new bfd section.  */
> +  for (p = &record_first; p; p = p->next)
> +    {
> +      uint8_t tmpu8;
> +      uint64_t tmpu64;
> +
> +      tmpu8 = p->type;
> +      if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset))
> +       error (_("Failed to write 'type' to %s (%s)"),
> +              recfilename, bfd_errmsg (bfd_get_error ()));
> +
> +      switch (p->type)
> +       {
> +       case record_reg: /* reg */
> +         tmpu64 = p->u.reg.num;
> +         if (BYTE_ORDER == LITTLE_ENDIAN)
> +           tmpu64 = bswap_64 (tmpu64);
> +
> +         if (record_debug)
> +           fprintf_filtered (gdb_stdlog, _("\
> +  Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
> +                             p->u.reg.num,
> +                             *(ULONGEST *) p->u.reg.val,
> +                             MAX_REGISTER_SIZE);
> +         /* FIXME: register num does not need 8 bytes.  */
> +         if (!bfdcore_write (obfd, osec, &tmpu64,
> +                             sizeof (tmpu64), &bfd_offset))
> +           error (_("Failed to write regnum to %s (%s)"),
> +                  recfilename, bfd_errmsg (bfd_get_error ()));
> +
> +         /* FIXME: add a len field, and write the smaller value.  */
> +         if (!bfdcore_write (obfd, osec, p->u.reg.val,
> +                                       MAX_REGISTER_SIZE, &bfd_offset))
> +           error (_("Failed to write regval to %s (%s)"),
> +                  recfilename, bfd_errmsg (bfd_get_error ()));
> +         break;
> +       case record_mem: /* mem */
> +         tmpu64 = p->u.mem.addr;
> +         if (BYTE_ORDER == LITTLE_ENDIAN)
> +           tmpu64 = bswap_64 (tmpu64);
> +
> +         if (record_debug)
> +           fprintf_filtered (gdb_stdlog, _("\
> +  Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"),
> +                             (unsigned int) p->u.mem.addr,
> +                             p->u.mem.len);
> +         if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64),
> &bfd_offset))
> +           error (_("Failed to write memaddr to %s (%s)"),
> +                  recfilename, bfd_errmsg (bfd_get_error ()));
> +
> +         tmpu64 = p->u.mem.len;
> +         if (BYTE_ORDER == LITTLE_ENDIAN)
> +           tmpu64 = bswap_64 (tmpu64);
> +
> +         /* FIXME: len does not need 8 bytes.  */
> +         if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64),
> &bfd_offset))
> +           error (_("Failed to write memlen to %s (%s)"),
> +                  recfilename, bfd_errmsg (bfd_get_error ()));
> +
> +         if (!bfdcore_write (obfd, osec, p->u.mem.val,
> +                             p->u.mem.len, &bfd_offset))
> +           error (_("Failed to write memval to %s (%s)"),
> +                  recfilename, bfd_errmsg (bfd_get_error ()));
> +         break;
> +       case record_end:
> +         /* FIXME: record the contents of record_end rec.  */
> +         if (record_debug)
> +           fprintf_filtered (gdb_stdlog, _("\
> +  Writing record_end (1 byte)\n"));
> +         break;
> +       }
> +    }
> +
> +  /* Now forward-execute back to the saved entry.  */
> +  for (record_list = &record_first;
> +       record_list && record_list != cur_record_list;
> +       record_list = record_list->next)
> +    record_exec_entry (regcache, gdbarch, record_list);
> +
> +  /* Clean-ups will close the output file and free malloc memory.  */
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
> +}
> +
> +/* bfdcore_read -- read bytes from a core file section.  */
> +
> +static int
> +bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
> +{
> +  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
> +
> +  if (ret)
> +    *offset += len;
> +  return ret;
> +}
> +
> +/* Load the execution log from a file.  */
> +
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +  bfd *core_bfd;
> +  asection *osec;
> +
> +  if (!args || (args && !*args))
> +    error (_("Argument for filename required.\n"));
> +
> +  /* Open the load file.  */
> +  if (record_debug)
> +    fprintf_filtered (gdb_stdlog,
> +                     _("Restoring recording from file '%s'\n"), args);
> +
> +  /* Restore corefile regs and mem sections.  */
> +  core_bfd = load_corefile (args, from_tty);
> +  old_cleanups = make_cleanup_bfd_close (core_bfd);
> +
> +  /* Now need to find our special note section.  */
> +  osec = bfd_get_section_by_name (core_bfd, "null0");
> +  printf_filtered ("Find precord section %s.\n",
> +                  osec ? "succeeded" : "failed");
> +
> +  if (osec)
> +    {
> +      int i, len;
> +      int bfd_offset = 0;
> +
> +      if (record_debug)
> +       fprintf_filtered (gdb_stdlog, "osec name = '%s'\n",
> +                         bfd_section_name (core_bfd, osec));
> +      len = (int) bfd_section_size (core_bfd, osec);
> +      printf_filtered ("osec size = %d\n", len);
> +
> +      /* Check the magic code.  */
> +      if (!bfdcore_read (core_bfd, osec, &magic,
> +                        sizeof (magic), &bfd_offset))
> +       error (_("Failed to read 'magic' from %s (%s)"),
> +              args, bfd_errmsg (bfd_get_error ()));
> +
> +      if (magic != RECORD_FILE_MAGIC)
> +       error (_("'%s', version mis-match / file format error."),
> +              args);
> +
> +      if (record_debug)
> +       fprintf_filtered (gdb_stdlog, _("\
> +  Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
> +                         magic);
> +      if (current_target.to_stratum != record_stratum)
> +       {
> +         /* FIXME need cleanup!  We might error out.  */
> +         cmd_record_start (NULL, from_tty);
> +         printf_unfiltered (_("Auto start process record.\n"));
> +       }
> +
> +      /* Free any existing record log, and load the entries in
> +        core_bfd to the new record log.  */
> +      record_list_release (record_arch_list_tail);
I think this is not the right way.
First, you need check if it's empty or not.
if (record_first.next)
{
    //after it, use record_exec_entry to the first entry.
  /* Reverse execute to the begin of record list.  */
  for (; record_list && record_list != &record_first;
       record_list = record_list->prev)
    record_exec_entry (regcache, gdbarch, record_list);

//release all entry
record_list_release_next ();
}


> +      old_cleanups2 = make_cleanup (record_message_cleanups, 0);
> +
> +      while (1)
> +       {
> +         uint8_t tmpu8;
> +         uint64_t tmpu64;
> +
> +         /* FIXME: Check offset for end-of-section.  */
> +         if (!bfdcore_read (core_bfd, osec, &tmpu8,
> +                            sizeof (tmpu8), &bfd_offset))
> +           break;
> +
> +         switch (tmpu8)
> +           {
> +           case record_reg: /* reg */
> +             /* FIXME: abstract out into an 'insert' function.  */
> +             rec = (struct record_entry *)
> +               xmalloc (sizeof (struct record_entry));
> +             rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
> +             rec->prev = NULL;
> +             rec->next = NULL;
> +             rec->type = record_reg;
> +             /* Get num.  */
> +             /* FIXME: register num does not need 8 bytes.  */
> +             if (!bfdcore_read (core_bfd, osec, &tmpu64,
> +                                sizeof (tmpu64), &bfd_offset))
> +               error (_("Failed to read regnum from %s (%s)"),
> +                      args, bfd_errmsg (bfd_get_error ()));
> +
> +             if (BYTE_ORDER == LITTLE_ENDIAN)
> +               tmpu64 = bswap_64 (tmpu64);
> +             rec->u.reg.num = tmpu64;
> +
> +             /* Get val.  */
> +             if (!bfdcore_read (core_bfd, osec, rec->u.reg.val,
> +                                MAX_REGISTER_SIZE, &bfd_offset))
> +               error (_("Failed to read regval from  %s (%s)"),
> +                      args, bfd_errmsg (bfd_get_error ()));
> +
> +             if (record_debug)
> +               fprintf_filtered (gdb_stdlog, _("\
> +  Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
> +                                 rec->u.reg.num,
> +                                 *(ULONGEST *) rec->u.reg.val,
> +                                 MAX_REGISTER_SIZE);
> +             record_arch_list_add (rec);
> +             break;
> +
> +           case record_mem: /* mem */
> +             rec = (struct record_entry *)
> +               xmalloc (sizeof (struct record_entry));
> +             rec->prev = NULL;
> +             rec->next = NULL;
> +             rec->type = record_mem;
> +             /* Get addr.  */
> +             if (!bfdcore_read (core_bfd, osec, &tmpu64,
> +                                sizeof (tmpu64), &bfd_offset))
> +               error (_("Failed to read memaddr from %s (%s)"),
> +                      args, bfd_errmsg (bfd_get_error ()));
> +             if (BYTE_ORDER == LITTLE_ENDIAN)
> +               tmpu64 = bswap_64 (tmpu64);
> +             rec->u.mem.addr = tmpu64;
> +
> +             /* Get len.  */
> +             /* FIXME: len does not need 8 bytes.  */
> +             if (!bfdcore_read (core_bfd, osec, &tmpu64,
> +                                sizeof (tmpu64), &bfd_offset))
> +               error (_("Failed to read memlen from %s (%s)"),
> +                      args, bfd_errmsg (bfd_get_error ()));
> +             if (BYTE_ORDER == LITTLE_ENDIAN)
> +               tmpu64 = bswap_64 (tmpu64);
> +             rec->u.mem.len = tmpu64;
> +
> +             rec->u.mem.mem_entry_not_accessible = 0;
> +             rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> +             /* Get val.  */
> +             if (!bfdcore_read (core_bfd, osec, rec->u.mem.val,
> +                                rec->u.mem.len, &bfd_offset))
> +               error (_("Failed to read memval from %s (%s)"),
> +                      args, bfd_errmsg (bfd_get_error ()));
> +             if (record_debug)
> +               fprintf_filtered (gdb_stdlog, _("\
> +  Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"),
> +                                 (unsigned int) rec->u.mem.addr,
> +                                 rec->u.mem.len);
> +             record_arch_list_add (rec);
> +             break;
> +
> +           case record_end: /* end */
> +             /* FIXME: restore the contents of record_end rec.  */
> +             rec = (struct record_entry *)
> +               xmalloc (sizeof (struct record_entry));
> +             rec->prev = NULL;
> +             rec->next = NULL;
> +             rec->type = record_end;
> +             if (record_debug)
> +               fprintf_filtered (gdb_stdlog, _("\
> +  Reading record_end (one byte)\n"));
> +             record_arch_list_add (rec);
> +             insn_number ++;
> +             break;
> +
> +           default:
> +             error (_("Format of '%s' is not right."), args);
> +             break;
> +           }
> +       }
> +    }
> +
> +  discard_cleanups (old_cleanups2);
> +
> +  /* Add record_arch_list_head to the end of record list.  (??? FIXME)*/
> +  for (rec = record_list; rec->next; rec = rec->next);
> +  rec->next = record_arch_list_head;
> +  record_arch_list_head->prev = rec;
> +
> +  /* Update record_insn_num and record_insn_max_num.  */
> +  record_insn_num = insn_number;
> +  if (record_insn_num > record_insn_max_num)
> +    {
> +      record_insn_max_num = record_insn_num;
> +      warning (_("Auto increase record/replay buffer limit to %d."),
> +              record_insn_max_num);
> +    }
> +
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
> +  registers_changed ();
> +  reinit_frame_cache ();
> +  nullify_last_target_wait_ptid ();
> +  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
> +}
> +
>  /* Truncate the record log from the present point
>    of replay until the end.  */
>
> @@ -1243,6 +1722,8 @@ info_record_command (char *args, int fro
>  void
>  _initialize_record (void)
>  {
> +  struct cmd_list_element *c;
> +
>   /* Init record_first.  */
>   record_first.prev = NULL;
>   record_first.next = NULL;
> @@ -1276,6 +1757,15 @@ _initialize_record (void)
>                  "info record ", 0, &infolist);
>   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
>
> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> +              _("Dump the execution log to a file.\n\
> +Argument is optional filename.  Default filename is 'rec.<process_id>'."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> +  c = add_cmd ("load", class_obscure, cmd_record_load,
> +              _("Load the execution log from a file.  Argument is
> filename."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
>
>   add_cmd ("delete", class_obscure, cmd_record_delete,
>           _("Delete the rest of execution log and start recording it
> anew."),
> @@ -1290,15 +1780,15 @@ _initialize_record (void)
>
>   /* Record instructions number limit command.  */
>   add_setshow_boolean_cmd ("stop-at-limit", no_class,
> -                           &record_stop_at_limit, _("\
> +                          &record_stop_at_limit, _("\
>  Set whether record/replay stops when record/replay buffer becomes full."),
> _("\
>  Show whether record/replay stops when record/replay buffer becomes full."),
> _("\
>  Default is ON.\n\
>  When ON, if the record/replay buffer becomes full, ask user what to do.\n\
>  When OFF, if the record/replay buffer becomes full,\n\
>  delete the oldest recorded instruction to make room for each new one."),
> -                           NULL, NULL,
> -                            &set_record_cmdlist, &show_record_cmdlist);
> +                          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."),
> Index: linux-fork.c
> ===================================================================
> RCS file: /cvs/src/src/gdb/linux-fork.c,v
> retrieving revision 1.29
> diff -u -p -r1.29 linux-fork.c
> --- linux-fork.c        2 Jul 2009 22:24:07 -0000       1.29
> +++ linux-fork.c        12 Aug 2009 22:28:43 -0000
> @@ -235,7 +235,6 @@ call_lseek (int fd, off_t offset, int wh
>  static void
>  fork_load_infrun_state (struct fork_info *fp)
>  {
> -  extern void nullify_last_target_wait_ptid ();
>   int i;
>
>   linux_nat_switch_fork (fp->ptid);
>
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-10  3:09                                       ` Michael Snyder
@ 2009-08-22 17:39                                         ` Hui Zhu
  2009-08-23  1:14                                           ` Hui Zhu
  2009-08-23 23:43                                           ` Michael Snyder
  0 siblings, 2 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-22 17:39 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

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

On Mon, Aug 10, 2009 at 06:56, Michael Snyder<msnyder@vmware.com> wrote:
> Hui Zhu wrote:
>>
>> I think give him a query is very clear.
>>
>> When he load, if there are some record log, it will query to user.  He
>> must choice remove the old record log or keep them.  He already know
>> what will happen.
>
> This is my opinion.  The default should be to remove the old log
> (not to query).  I think this will be both the most common case
> and the safest.  We can maybe add a command option for those who
> wish not to do that.
>
> Anyone else have an opinion?
>
>> On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote:
>>>
>>> Eli Zaretskii wrote:
>>>>>
>>>>> From: Hui Zhu <teawater@gmail.com>
>>>>> Date: Fri, 7 Aug 2009 11:34:20 +0800
>>>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org"
>>>>> <gdb-patches@sourceware.org>
>>>>>
>>>>> I think a warning is clear to most of people.
>>>>>
>>>>> And when he get this warning.  He can delete the record list and load
>>>>> again.  He will lost nothing.
>>>>>
>>>>> If we delete the old record list, maybe he still need old record.  He
>>>>> will lost something.
>>>>
>>>> Instead of a warning, how about asking the user whether to discard the
>>>> old records or keep them?
>>>
>>> My concern is, in most cases keeping them will be the wrong thing to do.
>>> It will be very easy to create an internally inconsistent state, and
>>> rather unlikely to create one that is *not* internally inconsistant.
>>>
>>> Think about it -- we will be concatenating two independent sets of
>>> state changes, with no way of knowing that the actual machine state
>>> at the end of one is the same as the machine state at the beginning
>>> of the other.  When these are then replayed, their effect may have
>>> little or nothing to do with what the real machine would actually do.
>>>
>>> To actually get this right, you would have to be *sure* that your
>>> target machine is in the exact same state "now" (ie. when you do
>>> the load command) as it was at the *beginning* of the previous
>>> recording/debugging session.
>>>
>>> I would rather either make this a separate, "expert mode"
>>> command, or better still, leave it for a future patch to extend
>>> the basic (and safe) patch that we first accept.
>>>
>>>
>>>
>>
>
>

Hi Michael,

I make a new version patch.  It has a lot of changes.
Remove record_core and add a new target record_core for core target to
make the code more clear.
Make the load together with record_open.

Please help me review it.

Thanks,
Hui

2009-08-23  Hui Zhu  <teawater@gmail.com>

	Add dump and load command to process record and replay.

	* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
        byteswap.h, netinet/in.h): Include files.
	(RECORD_FILE_MAGIC): New macro.
	(record_core_buf_entry): New struct.
	(record_core_ops): New target_ops.
	(record_list_release_first_insn): Change function
	record_list_release_first to this name.
	(record_arch_list_cleanups): New function.
	(record_message_cleanups): Removed.
	(record_message): Change to call record_arch_list_cleanups
	and record_list_release_first_insn.
	(record_exec_entry, (record_read_dump, record_fd_cleanups,
	record_load, record_core_open_1, record_open_1): New function.
	(record_open): Add support for record_core_ops.
	(record_close): Add support for record_core_ops.
	(record_wait): Call function 'record_exec_entry' and
	add support for target core.
	(record_registers_change): Call record_list_release_first_insn.
	(record_core_resume, record_core_resume, record_core_kill,
	record_core_fetch_registers, record_core_prepare_to_store,
	record_core_store_registers, record_core_xfer_partial,
	record_core_insert_breakpoint, record_core_remove_breakpoint,
	record_core_has_execution, init_record_core_ops,
	cmd_record_load, record_write_dump,
	cmd_record_dump): New function.
	(cmd_record_stop): Add support for record_core_ops.
	(set_record_insn_max_num): Call record_list_release_first_insn.
	(_initialize_record): Add commands "record dump"
	and "record load".

---
 record.c |  970 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 844 insertions(+), 126 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,15 +23,23 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)

+#define RECORD_FILE_MAGIC	htonl(0x20090726)
+
 /* These are the core struct of record function.

    An record_entry is a record of the value change of a register
@@ -78,9 +86,22 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -94,6 +115,7 @@ static int record_insn_num = 0;

 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;

 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -169,7 +191,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +408,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -416,13 +438,309 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }

+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry, int core)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                 if ((execution_direction == EXEC_REVERSE && !core)
+                     || (execution_direction != EXEC_REVERSE && core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                     if ((execution_direction == EXEC_REVERSE && !core)
+                         || (execution_direction != EXEC_REVERSE && core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
 static void
-record_open (char *name, int from_tty)
+record_fd_cleanups (void *recfdp)
 {
-  struct target_ops *t;
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}

-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+/* Load the execution log from a file.  */
+
+static void
+record_load (char *name)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (!name || (name && !*name))
+    return;
+
+  /* Open the load file.  */
+  recfd = open (name, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           name, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (name, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), name);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), name);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+
+          /* Get num.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+
+          /* Get addr.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get len.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\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);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), name);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                              enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                              struct target_waitstatus *,
+                              int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                       struct regcache *,
+                                       int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                       enum target_object object,
+                                       const char *annex,
+                                       gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset,
+                                       LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  if (!name || (name && !*name))
+    error (_("Argument for gdb record filename required.\n"));
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                          record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+}
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+  struct target_ops *t;

   /* check exec */
   if (!target_has_execution)
@@ -438,6 +756,28 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
 	     "record function."));

+  if (!tmp_to_resume)
+    error (_("Process record can't get to_resume."));
+  if (!tmp_to_wait)
+    error (_("Process record can't get to_wait."));
+  if (!tmp_to_store_registers)
+    error (_("Process record can't get to_store_registers."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Process record can't get to_insert_breakpoint."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Process record can't get to_remove_breakpoint."));
+
+  push_target (&record_ops);
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
     {
@@ -447,70 +787,102 @@ record_open (char *name, int from_tty)
 	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;
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-	  record_beneath_to_resume = t->to_resume;
-	  record_beneath_to_resume_ops = t;
+	  tmp_to_resume = t->to_resume;
+	  tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-	  record_beneath_to_wait = t->to_wait;
-	  record_beneath_to_wait_ops = t;
+	  tmp_to_wait = t->to_wait;
+	  tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-	  record_beneath_to_store_registers = t->to_store_registers;
-	  record_beneath_to_store_registers_ops = t;
+	  tmp_to_store_registers = t->to_store_registers;
+	  tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-	  record_beneath_to_xfer_partial = t->to_xfer_partial;
-	  record_beneath_to_xfer_partial_ops = t;
+	  tmp_to_xfer_partial = t->to_xfer_partial;
+	  tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-	record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-	record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
     }
-  if (!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)
+  if (!tmp_to_xfer_partial)
     error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));

-  push_target (&record_ops);
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);

   /* Reset */
   record_insn_num = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+
+  /* Load if there is argument.  */
+  record_load (name);
 }

 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -584,7 +956,7 @@ record_wait (struct target_ops *ops,
 			"record_resume_step = %d\n",
 			record_resume_step);

-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
       if (record_resume_error)
 	{
@@ -712,76 +1084,10 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }

-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list,
+                             (ops == &record_core_ops) ? 1 : 0);

-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed --
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed --
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,6 +1207,7 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
+
   target_kill ();
 }

@@ -945,7 +1252,7 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
@@ -1058,7 +1365,7 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
@@ -1138,6 +1445,191 @@ init_record_ops (void)
 }

 static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+}
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+		          const char *annex, gdb_byte *readbuf,
+		          const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  record_core_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)
 {
@@ -1153,6 +1645,212 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+static void
+cmd_record_load (char *args, int from_tty)
+{
+  char buf[512];
+
+  snprintf (buf, 512, "target record %s", args);
+  execute_command (buf, from_tty);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090726).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory address (network byte order).
+       8 bytes: memory length (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, 40, "gdb_record.%d",
+                PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                        magic);
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list, 0);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list, 0);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list, 0);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1185,7 +1883,12 @@ cmd_record_stop (char *args, int from_tt
     {
       if (!record_list || !from_tty || query (_("Delete recorded log and "
 	                                        "stop recording?")))
-	unpush_target (&record_ops);
+        {
+          if (strcmp (current_target.to_shortname, "record") == 0)
+	    unpush_target (&record_ops);
+          else
+            unpush_target (&record_core_ops);
+        }
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1203,7 +1906,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1243,6 +1946,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +1955,8 @@ _initialize_record (void)

   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);

   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
 			    _("Set debugging of record/replay feature."),
@@ -1259,9 +1966,10 @@ _initialize_record (void)
 			    NULL, show_record_debug, &setdebuglist,
 			    &showdebuglist);

-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-		  _("Abbreviated form of \"target record\" command."),
- 		  &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+		      _("Abbreviated form of \"target record\" command."),
+ 		      &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
 		  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +1984,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is
'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 39833 bytes --]

---
 record.c |  970 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 844 insertions(+), 126 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,15 +23,23 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)
 
+#define RECORD_FILE_MAGIC	htonl(0x20090726)
+
 /* These are the core struct of record function.
 
    An record_entry is a record of the value change of a register
@@ -78,9 +86,22 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -94,6 +115,7 @@ static int record_insn_num = 0;
 
 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;
 
 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -169,7 +191,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +408,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -416,13 +438,309 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry, int core)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                 if ((execution_direction == EXEC_REVERSE && !core)
+                     || (execution_direction != EXEC_REVERSE && core))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error reading memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  error (_("Process record: error reading memory at "
+                           "addr = %s len = %d."),
+                         paddress (gdbarch, entry->u.mem.addr),
+                        entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                     if ((execution_direction == EXEC_REVERSE && !core)
+                         || (execution_direction != EXEC_REVERSE && core))
+                      {
+                        record_list->u.mem.mem_entry_not_accessible = 1;
+                        if (record_debug)
+                          warning (_("Process record: error writing memory at "
+                                     "addr = %s len = %d."),
+                                   paddress (gdbarch, entry->u.mem.addr),
+                                   entry->u.mem.len);
+                      }
+                    else
+                      error (_("Process record: error writing memory at "
+                               "addr = %s len = %d."),
+                             paddress (gdbarch, entry->u.mem.addr),
+                            entry->u.mem.len);
+                  }
+              }
+
+            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+          }
+      }
+      break;
+    }
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
 static void
-record_open (char *name, int from_tty)
+record_fd_cleanups (void *recfdp)
 {
-  struct target_ops *t;
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
 
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+/* Load the execution log from a file.  */
+
+static void
+record_load (char *name)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (!name || (name && !*name))
+    return;
+
+  /* Open the load file.  */
+  recfd = open (name, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           name, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (name, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), name);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), name);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+
+          /* Get num.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+
+          /* Get addr.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get len.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\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);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), name);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                              enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                              struct target_waitstatus *,
+                              int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                       struct regcache *,
+                                       int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                       enum target_object object,
+                                       const char *annex,
+                                       gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset,
+                                       LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  if (!name || (name && !*name))
+    error (_("Argument for gdb record filename required.\n"));
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                          record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+}
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+  struct target_ops *t;
 
   /* check exec */
   if (!target_has_execution)
@@ -438,6 +756,28 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
 	     "record function."));
 
+  if (!tmp_to_resume)
+    error (_("Process record can't get to_resume."));
+  if (!tmp_to_wait)
+    error (_("Process record can't get to_wait."));
+  if (!tmp_to_store_registers)
+    error (_("Process record can't get to_store_registers."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Process record can't get to_insert_breakpoint."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Process record can't get to_remove_breakpoint."));
+
+  push_target (&record_ops);
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
     {
@@ -447,70 +787,102 @@ record_open (char *name, int from_tty)
 	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;
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-	  record_beneath_to_resume = t->to_resume;
-	  record_beneath_to_resume_ops = t;
+	  tmp_to_resume = t->to_resume;
+	  tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-	  record_beneath_to_wait = t->to_wait;
-	  record_beneath_to_wait_ops = t;
+	  tmp_to_wait = t->to_wait;
+	  tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-	  record_beneath_to_store_registers = t->to_store_registers;
-	  record_beneath_to_store_registers_ops = t;
+	  tmp_to_store_registers = t->to_store_registers;
+	  tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-	  record_beneath_to_xfer_partial = t->to_xfer_partial;
-	  record_beneath_to_xfer_partial_ops = t;
+	  tmp_to_xfer_partial = t->to_xfer_partial;
+	  tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-	record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-	record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
     }
-  if (!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)
+  if (!tmp_to_xfer_partial)
     error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));
 
-  push_target (&record_ops);
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);
 
   /* Reset */
   record_insn_num = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+
+  /* Load if there is argument.  */
+  record_load (name);
 }
 
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -584,7 +956,7 @@ record_wait (struct target_ops *ops,
 			"record_resume_step = %d\n",
 			record_resume_step);
 
-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
       if (record_resume_error)
 	{
@@ -712,76 +1084,10 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list,
+                             (ops == &record_core_ops) ? 1 : 0);
 
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,6 +1207,7 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
+
   target_kill ();
 }
 
@@ -945,7 +1252,7 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
@@ -1058,7 +1365,7 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
@@ -1138,6 +1445,191 @@ init_record_ops (void)
 }
 
 static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+}
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+		          const char *annex, gdb_byte *readbuf,
+		          const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  record_core_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)
 {
@@ -1153,6 +1645,212 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  char buf[512];
+
+  snprintf (buf, 512, "target record %s", args);
+  execute_command (buf, from_tty);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090726).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory address (network byte order).
+       8 bytes: memory length (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, 40, "gdb_record.%d",
+                PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                        magic);
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list, 0);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list, 0);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list, 0);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1185,7 +1883,12 @@ cmd_record_stop (char *args, int from_tt
     {
       if (!record_list || !from_tty || query (_("Delete recorded log and "
 	                                        "stop recording?")))
-	unpush_target (&record_ops);
+        {
+          if (strcmp (current_target.to_shortname, "record") == 0)
+	    unpush_target (&record_ops);
+          else
+            unpush_target (&record_core_ops);
+        }
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1203,7 +1906,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1243,6 +1946,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +1955,8 @@ _initialize_record (void)
 
   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);
 
   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
 			    _("Set debugging of record/replay feature."),
@@ -1259,9 +1966,10 @@ _initialize_record (void)
 			    NULL, show_record_debug, &setdebuglist,
 			    &showdebuglist);
 
-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-		  _("Abbreviated form of \"target record\" command."),
- 		  &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+		      _("Abbreviated form of \"target record\" command."),
+ 		      &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
 		  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +1984,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-22 17:39                                         ` Hui Zhu
@ 2009-08-23  1:14                                           ` Hui Zhu
  2009-08-23 23:43                                           ` Michael Snyder
  1 sibling, 0 replies; 54+ messages in thread
From: Hui Zhu @ 2009-08-23  1:14 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

And about record load.

I keep it because "record" is a prefix_cmd (If it's not,  I can let
"record" command load file directly).  A file name cannot be the
argument of this command.  So I keep the command record load.

Thanks,
Hui

On Sun, Aug 23, 2009 at 01:34, Hui Zhu<teawater@gmail.com> wrote:
> On Mon, Aug 10, 2009 at 06:56, Michael Snyder<msnyder@vmware.com> wrote:
>> Hui Zhu wrote:
>>>
>>> I think give him a query is very clear.
>>>
>>> When he load, if there are some record log, it will query to user.  He
>>> must choice remove the old record log or keep them.  He already know
>>> what will happen.
>>
>> This is my opinion.  The default should be to remove the old log
>> (not to query).  I think this will be both the most common case
>> and the safest.  We can maybe add a command option for those who
>> wish not to do that.
>>
>> Anyone else have an opinion?
>>
>>> On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote:
>>>>
>>>> Eli Zaretskii wrote:
>>>>>>
>>>>>> From: Hui Zhu <teawater@gmail.com>
>>>>>> Date: Fri, 7 Aug 2009 11:34:20 +0800
>>>>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org"
>>>>>> <gdb-patches@sourceware.org>
>>>>>>
>>>>>> I think a warning is clear to most of people.
>>>>>>
>>>>>> And when he get this warning.  He can delete the record list and load
>>>>>> again.  He will lost nothing.
>>>>>>
>>>>>> If we delete the old record list, maybe he still need old record.  He
>>>>>> will lost something.
>>>>>
>>>>> Instead of a warning, how about asking the user whether to discard the
>>>>> old records or keep them?
>>>>
>>>> My concern is, in most cases keeping them will be the wrong thing to do.
>>>> It will be very easy to create an internally inconsistent state, and
>>>> rather unlikely to create one that is *not* internally inconsistant.
>>>>
>>>> Think about it -- we will be concatenating two independent sets of
>>>> state changes, with no way of knowing that the actual machine state
>>>> at the end of one is the same as the machine state at the beginning
>>>> of the other.  When these are then replayed, their effect may have
>>>> little or nothing to do with what the real machine would actually do.
>>>>
>>>> To actually get this right, you would have to be *sure* that your
>>>> target machine is in the exact same state "now" (ie. when you do
>>>> the load command) as it was at the *beginning* of the previous
>>>> recording/debugging session.
>>>>
>>>> I would rather either make this a separate, "expert mode"
>>>> command, or better still, leave it for a future patch to extend
>>>> the basic (and safe) patch that we first accept.
>>>>
>>>>
>>>>
>>>
>>
>>
>
> Hi Michael,
>
> I make a new version patch.  It has a lot of changes.
> Remove record_core and add a new target record_core for core target to
> make the code more clear.
> Make the load together with record_open.
>
> Please help me review it.
>
> Thanks,
> Hui
>
> 2009-08-23  Hui Zhu  <teawater@gmail.com>
>
>        Add dump and load command to process record and replay.
>
>        * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
>        byteswap.h, netinet/in.h): Include files.
>        (RECORD_FILE_MAGIC): New macro.
>        (record_core_buf_entry): New struct.
>        (record_core_ops): New target_ops.
>        (record_list_release_first_insn): Change function
>        record_list_release_first to this name.
>        (record_arch_list_cleanups): New function.
>        (record_message_cleanups): Removed.
>        (record_message): Change to call record_arch_list_cleanups
>        and record_list_release_first_insn.
>        (record_exec_entry, (record_read_dump, record_fd_cleanups,
>        record_load, record_core_open_1, record_open_1): New function.
>        (record_open): Add support for record_core_ops.
>        (record_close): Add support for record_core_ops.
>        (record_wait): Call function 'record_exec_entry' and
>        add support for target core.
>        (record_registers_change): Call record_list_release_first_insn.
>        (record_core_resume, record_core_resume, record_core_kill,
>        record_core_fetch_registers, record_core_prepare_to_store,
>        record_core_store_registers, record_core_xfer_partial,
>        record_core_insert_breakpoint, record_core_remove_breakpoint,
>        record_core_has_execution, init_record_core_ops,
>        cmd_record_load, record_write_dump,
>        cmd_record_dump): New function.
>        (cmd_record_stop): Add support for record_core_ops.
>        (set_record_insn_max_num): Call record_list_release_first_insn.
>        (_initialize_record): Add commands "record dump"
>        and "record load".
>
> ---
>  record.c |  970 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
>  1 file changed, 844 insertions(+), 126 deletions(-)
>
> --- a/record.c
> +++ b/record.c
> @@ -23,15 +23,23 @@
>  #include "gdbthread.h"
>  #include "event-top.h"
>  #include "exceptions.h"
> +#include "completer.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "exec.h"
>  #include "record.h"
>
> +#include <byteswap.h>
>  #include <signal.h>
> +#include <netinet/in.h>
>
>  #define DEFAULT_RECORD_INSN_MAX_NUM    200000
>
>  #define RECORD_IS_REPLAY \
>      (record_list->next || execution_direction == EXEC_REVERSE)
>
> +#define RECORD_FILE_MAGIC      htonl(0x20090726)
> +
>  /* These are the core struct of record function.
>
>    An record_entry is a record of the value change of a register
> @@ -78,9 +86,22 @@ struct record_entry
>   } u;
>  };
>
> +struct record_core_buf_entry
> +{
> +  struct record_core_buf_entry *prev;
> +  struct target_section *p;
> +  bfd_byte *buf;
> +};
> +
>  /* This is the debug switch for process record.  */
>  int record_debug = 0;
>
> +/* Record buf with core target.  */
> +static gdb_byte *record_core_regbuf = NULL;
> +static struct target_section *record_core_start;
> +static struct target_section *record_core_end;
> +static struct record_core_buf_entry *record_core_buf_list = NULL;
> +
>  /* These list is for execution log.  */
>  static struct record_entry record_first;
>  static struct record_entry *record_list = &record_first;
> @@ -94,6 +115,7 @@ static int record_insn_num = 0;
>
>  /* The target_ops of process record.  */
>  static struct target_ops record_ops;
> +static struct target_ops record_core_ops;
>
>  /* The beneath function pointers.  */
>  static struct target_ops *record_beneath_to_resume_ops;
> @@ -169,7 +191,7 @@ record_list_release_next (void)
>  }
>
>  static void
> -record_list_release_first (void)
> +record_list_release_first_insn (void)
>  {
>   struct record_entry *tmp = NULL;
>   enum record_type type;
> @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
>              if (q)
>                record_stop_at_limit = 0;
>              else
> -               error (_("Process record: inferior program stopped."));
> +               error (_("Process record: stoped by user."));
>            }
>        }
>     }
>  }
>
> +static void
> +record_arch_list_cleanups (void *ignore)
> +{
> +  record_list_release (record_arch_list_tail);
> +}
> +
>  /* Before inferior step (when GDB record the running message, inferior
>    only can step), GDB will call this function to record the values to
>    record_list.  This function will call gdbarch_process_record to
>    record the running message of inferior and set them to
>    record_arch_list, and add it to record_list.  */
>
> -static void
> -record_message_cleanups (void *ignore)
> -{
> -  record_list_release (record_arch_list_tail);
> -}
> -
>  static int
>  record_message (void *args)
>  {
>   int ret;
>   struct regcache *regcache = args;
> -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
> +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
>
>   record_arch_list_head = NULL;
>   record_arch_list_tail = NULL;
> @@ -386,7 +408,7 @@ record_message (void *args)
>   record_list = record_arch_list_tail;
>
>   if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>   else
>     record_insn_num++;
>
> @@ -416,13 +438,309 @@ record_gdb_operation_disable_set (void)
>   return old_cleanups;
>  }
>
> +static inline void
> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> +                   struct record_entry *entry, int core)
> +{
> +  switch (entry->type)
> +    {
> +    case record_reg: /* reg */
> +      {
> +        gdb_byte reg[MAX_REGISTER_SIZE];
> +
> +        if (record_debug > 1)
> +          fprintf_unfiltered (gdb_stdlog,
> +                              "Process record: record_reg %s to "
> +                              "inferior num = %d.\n",
> +                              host_address_to_string (entry),
> +                              entry->u.reg.num);
> +
> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> +        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> +      }
> +      break;
> +
> +    case record_mem: /* mem */
> +      {
> +        if (!record_list->u.mem.mem_entry_not_accessible)
> +          {
> +            gdb_byte *mem = alloca (entry->u.mem.len);
> +
> +            if (record_debug > 1)
> +              fprintf_unfiltered (gdb_stdlog,
> +                                  "Process record: record_mem %s to "
> +                                  "inferior addr = %s len = %d.\n",
> +                                  host_address_to_string (entry),
> +                                  paddress (gdbarch, entry->u.mem.addr),
> +                                  record_list->u.mem.len);
> +
> +            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
> +              {
> +                 if ((execution_direction == EXEC_REVERSE && !core)
> +                     || (execution_direction != EXEC_REVERSE && core))
> +                  {
> +                    record_list->u.mem.mem_entry_not_accessible = 1;
> +                    if (record_debug)
> +                      warning (_("Process record: error reading memory at "
> +                                 "addr = %s len = %d."),
> +                               paddress (gdbarch, entry->u.mem.addr),
> +                               entry->u.mem.len);
> +                  }
> +                else
> +                  error (_("Process record: error reading memory at "
> +                           "addr = %s len = %d."),
> +                         paddress (gdbarch, entry->u.mem.addr),
> +                        entry->u.mem.len);
> +              }
> +            else
> +              {
> +                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
> +                                         entry->u.mem.len))
> +                  {
> +                     if ((execution_direction == EXEC_REVERSE && !core)
> +                         || (execution_direction != EXEC_REVERSE && core))
> +                      {
> +                        record_list->u.mem.mem_entry_not_accessible = 1;
> +                        if (record_debug)
> +                          warning (_("Process record: error writing memory at "
> +                                     "addr = %s len = %d."),
> +                                   paddress (gdbarch, entry->u.mem.addr),
> +                                   entry->u.mem.len);
> +                      }
> +                    else
> +                      error (_("Process record: error writing memory at "
> +                               "addr = %s len = %d."),
> +                             paddress (gdbarch, entry->u.mem.addr),
> +                            entry->u.mem.len);
> +                  }
> +              }
> +
> +            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> +          }
> +      }
> +      break;
> +    }
> +}
> +
> +static inline void
> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
> +{
> +  if (read (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to read dump of execution records in '%s'."),
> +           recfilename);
> +}
> +
>  static void
> -record_open (char *name, int from_tty)
> +record_fd_cleanups (void *recfdp)
>  {
> -  struct target_ops *t;
> +  int recfd = *(int *) recfdp;
> +  close (recfd);
> +}
>
> -  if (record_debug)
> -    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> +/* Load the execution log from a file.  */
> +
> +static void
> +record_load (char *name)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +
> +  if (!name || (name && !*name))
> +    return;
> +
> +  /* Open the load file.  */
> +  recfd = open (name, O_RDONLY | O_BINARY);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for loading execution records: %s"),
> +           name, strerror (errno));
> +  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
> +
> +  /* Check the magic code.  */
> +  record_read_dump (name, recfd, &magic, 4);
> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a valid dump of execution records."), name);
> +
> +  /* Load the entries in recfd to the record_arch_list_head and
> +     record_arch_list_tail.  */
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
> +
> +  while (1)
> +    {
> +      int ret;
> +      uint8_t tmpu8;
> +      uint64_t tmpu64;
> +
> +      ret = read (recfd, &tmpu8, 1);
> +      if (ret < 0)
> +        error (_("Failed to read dump of execution records in '%s'."), name);
> +      if (ret == 0)
> +        break;
> +
> +      switch (tmpu8)
> +        {
> +        case record_reg: /* reg */
> +          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;
> +
> +          /* Get num.  */
> +          record_read_dump (name, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.reg.num = tmpu64;
> +
> +          /* Get val.  */
> +          record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
> +
> +          if (record_debug)
> +            fprintf_unfiltered (gdb_stdlog, _("\
> +Reading register %d (1 plus 8 plus %d bytes)\n"),
> +                                rec->u.reg.num,
> +                                MAX_REGISTER_SIZE);
> +
> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_mem: /* mem */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_mem;
> +
> +          /* Get addr.  */
> +          record_read_dump (name, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.addr = tmpu64;
> +
> +          /* Get len.  */
> +          record_read_dump (name, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.len = tmpu64;
> +          rec->u.mem.mem_entry_not_accessible = 0;
> +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> +
> +          /* Get val.  */
> +          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
> +
> +          if (record_debug)
> +            fprintf_unfiltered (gdb_stdlog, _("\
> +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
> +                                        paddress (get_current_arch (),
> +                                                  rec->u.mem.addr),
> +                                        rec->u.mem.len);
> +
> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_end: /* end */
> +          if (record_debug)
> +            fprintf_unfiltered (gdb_stdlog,
> +                                _("Reading record_end (1 byte)\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);
> +          insn_number ++;
> +          break;
> +
> +        default:
> +          error (_("Format of '%s' is not right."), name);
> +          break;
> +        }
> +    }
> +
> +  discard_cleanups (old_cleanups2);
> +
> +  /* Add record_arch_list_head to the end of record list.  */
> +  for (rec = record_list; rec->next; rec = rec->next);
> +  rec->next = record_arch_list_head;
> +  record_arch_list_head->prev = rec;
> +
> +  /* Update record_insn_num and record_insn_max_num.  */
> +  record_insn_num += insn_number;
> +  if (record_insn_num > record_insn_max_num)
> +    {
> +      record_insn_max_num = record_insn_num;
> +      warning (_("Auto increase record/replay buffer limit to %d."),
> +               record_insn_max_num);
> +    }
> +
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
> +}
> +
> +static struct target_ops *tmp_to_resume_ops;
> +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
> +                              enum target_signal);
> +static struct target_ops *tmp_to_wait_ops;
> +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
> +                              struct target_waitstatus *,
> +                              int);
> +static struct target_ops *tmp_to_store_registers_ops;
> +static void (*tmp_to_store_registers) (struct target_ops *,
> +                                       struct regcache *,
> +                                       int regno);
> +static struct target_ops *tmp_to_xfer_partial_ops;
> +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
> +                                       enum target_object object,
> +                                       const char *annex,
> +                                       gdb_byte *readbuf,
> +                                       const gdb_byte *writebuf,
> +                                       ULONGEST offset,
> +                                       LONGEST len);
> +static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
> +                                        struct bp_target_info *);
> +static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
> +                                        struct bp_target_info *);
> +
> +static void
> +record_core_open_1 (char *name, int from_tty)
> +{
> +  struct regcache *regcache = get_current_regcache ();
> +  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
> +  int i;
> +
> +  if (!name || (name && !*name))
> +    error (_("Argument for gdb record filename required.\n"));
> +
> +  /* Get record_core_regbuf.  */
> +  target_fetch_registers (regcache, -1);
> +  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
> +  for (i = 0; i < regnum; i ++)
> +    regcache_raw_collect (regcache, i,
> +                          record_core_regbuf + MAX_REGISTER_SIZE * i);
> +
> +  /* Get record_core_start and record_core_end.  */
> +  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
> +    {
> +      xfree (record_core_regbuf);
> +      record_core_regbuf = NULL;
> +      error (_("\"%s\": Can't find sections: %s"),
> +             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
> +    }
> +
> +  push_target (&record_core_ops);
> +}
> +
> +static void
> +record_open_1 (char *name, int from_tty)
> +{
> +  struct target_ops *t;
>
>   /* check exec */
>   if (!target_has_execution)
> @@ -438,6 +756,28 @@ record_open (char *name, int from_tty)
>     error (_("Process record: the current architecture doesn't support "
>             "record function."));
>
> +  if (!tmp_to_resume)
> +    error (_("Process record can't get to_resume."));
> +  if (!tmp_to_wait)
> +    error (_("Process record can't get to_wait."));
> +  if (!tmp_to_store_registers)
> +    error (_("Process record can't get to_store_registers."));
> +  if (!tmp_to_insert_breakpoint)
> +    error (_("Process record can't get to_insert_breakpoint."));
> +  if (!tmp_to_remove_breakpoint)
> +    error (_("Process record can't get to_remove_breakpoint."));
> +
> +  push_target (&record_ops);
> +}
> +
> +static void
> +record_open (char *name, int from_tty)
> +{
> +  struct target_ops *t;
> +
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> +
>   /* Check if record target is already running.  */
>   if (current_target.to_stratum == record_stratum)
>     {
> @@ -447,70 +787,102 @@ record_open (char *name, int from_tty)
>        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;
> +  /* Reset the tmp beneath pointers.  */
> +  tmp_to_resume_ops = NULL;
> +  tmp_to_resume = NULL;
> +  tmp_to_wait_ops = NULL;
> +  tmp_to_wait = NULL;
> +  tmp_to_store_registers_ops = NULL;
> +  tmp_to_store_registers = NULL;
> +  tmp_to_xfer_partial_ops = NULL;
> +  tmp_to_xfer_partial = NULL;
> +  tmp_to_insert_breakpoint = NULL;
> +  tmp_to_remove_breakpoint = NULL;
>
>   /* Set the beneath function pointers.  */
>   for (t = current_target.beneath; t != NULL; t = t->beneath)
>     {
> -      if (!record_beneath_to_resume)
> +      if (!tmp_to_resume)
>         {
> -         record_beneath_to_resume = t->to_resume;
> -         record_beneath_to_resume_ops = t;
> +         tmp_to_resume = t->to_resume;
> +         tmp_to_resume_ops = t;
>         }
> -      if (!record_beneath_to_wait)
> +      if (!tmp_to_wait)
>         {
> -         record_beneath_to_wait = t->to_wait;
> -         record_beneath_to_wait_ops = t;
> +         tmp_to_wait = t->to_wait;
> +         tmp_to_wait_ops = t;
>         }
> -      if (!record_beneath_to_store_registers)
> +      if (!tmp_to_store_registers)
>         {
> -         record_beneath_to_store_registers = t->to_store_registers;
> -         record_beneath_to_store_registers_ops = t;
> +         tmp_to_store_registers = t->to_store_registers;
> +         tmp_to_store_registers_ops = t;
>         }
> -      if (!record_beneath_to_xfer_partial)
> +      if (!tmp_to_xfer_partial)
>         {
> -         record_beneath_to_xfer_partial = t->to_xfer_partial;
> -         record_beneath_to_xfer_partial_ops = t;
> +         tmp_to_xfer_partial = t->to_xfer_partial;
> +         tmp_to_xfer_partial_ops = t;
>         }
> -      if (!record_beneath_to_insert_breakpoint)
> -       record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
> -      if (!record_beneath_to_remove_breakpoint)
> -       record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
> +      if (!tmp_to_insert_breakpoint)
> +       tmp_to_insert_breakpoint = t->to_insert_breakpoint;
> +      if (!tmp_to_remove_breakpoint)
> +       tmp_to_remove_breakpoint = t->to_remove_breakpoint;
>     }
> -  if (!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)
> +  if (!tmp_to_xfer_partial)
>     error (_("Process record can't get to_xfer_partial."));
> -  if (!record_beneath_to_insert_breakpoint)
> -    error (_("Process record can't get to_insert_breakpoint."));
> -  if (!record_beneath_to_remove_breakpoint)
> -    error (_("Process record can't get to_remove_breakpoint."));
>
> -  push_target (&record_ops);
> +  if (current_target.to_stratum == core_stratum)
> +    record_core_open_1 (name, from_tty);
> +  else
> +    record_open_1 (name, from_tty);
>
>   /* Reset */
>   record_insn_num = 0;
>   record_list = &record_first;
>   record_list->next = NULL;
> +
> +  /* Set the tmp beneath pointers to beneath pointers.  */
> +  record_beneath_to_resume_ops = tmp_to_resume_ops;
> +  record_beneath_to_resume = tmp_to_resume;
> +  record_beneath_to_wait_ops = tmp_to_wait_ops;
> +  record_beneath_to_wait = tmp_to_wait;
> +  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
> +  record_beneath_to_store_registers = tmp_to_store_registers;
> +  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
> +  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
> +  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
> +  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
> +
> +  /* Load if there is argument.  */
> +  record_load (name);
>  }
>
>  static void
>  record_close (int quitting)
>  {
> +  struct record_core_buf_entry *entry;
> +
>   if (record_debug)
>     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
>
>   record_list_release (record_list);
> +
> +  /* Release record_core_regbuf.  */
> +  if (record_core_regbuf)
> +    {
> +      xfree (record_core_regbuf);
> +      record_core_regbuf = NULL;
> +    }
> +
> +  /* Release record_core_buf_list.  */
> +  if (record_core_buf_list)
> +    {
> +      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
> +        {
> +          xfree (record_core_buf_list);
> +          record_core_buf_list = entry;
> +        }
> +      record_core_buf_list = NULL;
> +    }
>  }
>
>  static int record_resume_step = 0;
> @@ -584,7 +956,7 @@ record_wait (struct target_ops *ops,
>                        "record_resume_step = %d\n",
>                        record_resume_step);
>
> -  if (!RECORD_IS_REPLAY)
> +  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
>     {
>       if (record_resume_error)
>        {
> @@ -712,76 +1084,10 @@ record_wait (struct target_ops *ops,
>              break;
>            }
>
> -         /* Set ptid, register and memory according to record_list.  */
> -         if (record_list->type == record_reg)
> -           {
> -             /* reg */
> -             gdb_byte reg[MAX_REGISTER_SIZE];
> -             if (record_debug > 1)
> -               fprintf_unfiltered (gdb_stdlog,
> -                                   "Process record: record_reg %s to "
> -                                   "inferior num = %d.\n",
> -                                   host_address_to_string (record_list),
> -                                   record_list->u.reg.num);
> -             regcache_cooked_read (regcache, record_list->u.reg.num, reg);
> -             regcache_cooked_write (regcache, record_list->u.reg.num,
> -                                    record_list->u.reg.val);
> -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> -           }
> -         else if (record_list->type == record_mem)
> -           {
> -             /* mem */
> -             /* Nothing to do if the entry is flagged not_accessible.  */
> -             if (!record_list->u.mem.mem_entry_not_accessible)
> -               {
> -                 gdb_byte *mem = alloca (record_list->u.mem.len);
> -                 if (record_debug > 1)
> -                   fprintf_unfiltered (gdb_stdlog,
> -                                       "Process record: record_mem %s to "
> -                                       "inferior addr = %s len = %d.\n",
> -                                       host_address_to_string (record_list),
> -                                       paddress (gdbarch,
> -                                                 record_list->u.mem.addr),
> -                                       record_list->u.mem.len);
> +          record_exec_entry (regcache, gdbarch, record_list,
> +                             (ops == &record_core_ops) ? 1 : 0);
>
> -                 if (target_read_memory (record_list->u.mem.addr, mem,
> -                                         record_list->u.mem.len))
> -                   {
> -                     if (execution_direction != EXEC_REVERSE)
> -                       error (_("Process record: error reading memory at "
> -                                "addr = %s len = %d."),
> -                              paddress (gdbarch, record_list->u.mem.addr),
> -                              record_list->u.mem.len);
> -                     else
> -                       /* Read failed --
> -                          flag entry as not_accessible.  */
> -                       record_list->u.mem.mem_entry_not_accessible = 1;
> -                   }
> -                 else
> -                   {
> -                     if (target_write_memory (record_list->u.mem.addr,
> -                                              record_list->u.mem.val,
> -                                              record_list->u.mem.len))
> -                       {
> -                         if (execution_direction != EXEC_REVERSE)
> -                           error (_("Process record: error writing memory at "
> -                                    "addr = %s len = %d."),
> -                                  paddress (gdbarch, record_list->u.mem.addr),
> -                                  record_list->u.mem.len);
> -                         else
> -                           /* Write failed --
> -                              flag entry as not_accessible.  */
> -                           record_list->u.mem.mem_entry_not_accessible = 1;
> -                       }
> -                     else
> -                       {
> -                         memcpy (record_list->u.mem.val, mem,
> -                                 record_list->u.mem.len);
> -                       }
> -                   }
> -               }
> -           }
> -         else
> +         if (record_list->type == record_end)
>            {
>              if (record_debug > 1)
>                fprintf_unfiltered (gdb_stdlog,
> @@ -901,6 +1207,7 @@ record_kill (struct target_ops *ops)
>     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
>
>   unpush_target (&record_ops);
> +
>   target_kill ();
>  }
>
> @@ -945,7 +1252,7 @@ record_registers_change (struct regcache
>   record_list = record_arch_list_tail;
>
>   if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>   else
>     record_insn_num++;
>  }
> @@ -1058,7 +1365,7 @@ record_xfer_partial (struct target_ops *
>       record_list = record_arch_list_tail;
>
>       if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -       record_list_release_first ();
> +       record_list_release_first_insn ();
>       else
>        record_insn_num++;
>     }
> @@ -1138,6 +1445,191 @@ init_record_ops (void)
>  }
>
>  static void
> +record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
> +                    enum target_signal siggnal)
> +{
> +  record_resume_step = step;
> +  record_resume_siggnal = siggnal;
> +}
> +
> +static void
> +record_core_kill (struct target_ops *ops)
> +{
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
> +
> +  unpush_target (&record_core_ops);
> +}
> +
> +static void
> +record_core_fetch_registers (struct target_ops *ops,
> +                             struct regcache *regcache,
> +                             int regno)
> +{
> +  if (regno < 0)
> +    {
> +      int num = gdbarch_num_regs (get_regcache_arch (regcache));
> +      int i;
> +
> +      for (i = 0; i < num; i ++)
> +        regcache_raw_supply (regcache, i,
> +                             record_core_regbuf + MAX_REGISTER_SIZE * i);
> +    }
> +  else
> +    regcache_raw_supply (regcache, regno,
> +                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +}
> +
> +static void
> +record_core_prepare_to_store (struct regcache *regcache)
> +{
> +}
> +
> +static void
> +record_core_store_registers (struct target_ops *ops,
> +                             struct regcache *regcache,
> +                             int regno)
> +{
> +  if (record_gdb_operation_disable)
> +    regcache_raw_collect (regcache, regno,
> +                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +  else
> +    error (_("You can't do that without a process to debug."));
> +}
> +
> +static LONGEST
> +record_core_xfer_partial (struct target_ops *ops, enum target_object object,
> +                         const char *annex, gdb_byte *readbuf,
> +                         const gdb_byte *writebuf, ULONGEST offset,
> +                          LONGEST len)
> +{
> +   if (object == TARGET_OBJECT_MEMORY)
> +     {
> +       if (record_gdb_operation_disable || !writebuf)
> +         {
> +           struct target_section *p;
> +           for (p = record_core_start; p < record_core_end; p++)
> +             {
> +               if (offset >= p->addr)
> +                 {
> +                   struct record_core_buf_entry *entry;
> +
> +                   if (offset >= p->endaddr)
> +                     continue;
> +
> +                   if (offset + len > p->endaddr)
> +                     len = p->endaddr - offset;
> +
> +                   offset -= p->addr;
> +
> +                   /* Read readbuf or write writebuf p, offset, len.  */
> +                   /* Check flags.  */
> +                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
> +                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
> +                     {
> +                       if (readbuf)
> +                         memset (readbuf, 0, len);
> +                       return len;
> +                     }
> +                   /* Get record_core_buf_entry.  */
> +                   for (entry = record_core_buf_list; entry;
> +                        entry = entry->prev)
> +                     if (entry->p == p)
> +                       break;
> +                   if (writebuf)
> +                     {
> +                       if (!entry)
> +                         {
> +                           /* Add a new entry.  */
> +                           entry
> +                             = (struct record_core_buf_entry *)
> +                                 xmalloc
> +                                   (sizeof (struct record_core_buf_entry));
> +                           entry->p = p;
> +                           if (!bfd_malloc_and_get_section (p->bfd,
> +                                                            p->the_bfd_section,
> +                                                            &entry->buf))
> +                             {
> +                               xfree (entry);
> +                               return 0;
> +                             }
> +                           entry->prev = record_core_buf_list;
> +                           record_core_buf_list = entry;
> +                         }
> +
> +                        memcpy (entry->buf + offset, writebuf, (size_t) len);
> +                     }
> +                   else
> +                     {
> +                       if (!entry)
> +                         return record_beneath_to_xfer_partial
> +                                  (record_beneath_to_xfer_partial_ops,
> +                                   object, annex, readbuf, writebuf,
> +                                   offset, len);
> +
> +                       memcpy (readbuf, entry->buf + offset, (size_t) len);
> +                     }
> +
> +                   return len;
> +                 }
> +             }
> +
> +           return -1;
> +         }
> +       else
> +         error (_("You can't do that without a process to debug."));
> +     }
> +
> +  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
> +                                         object, annex, readbuf, writebuf,
> +                                         offset, len);
> +}
> +
> +static int
> +record_core_insert_breakpoint (struct gdbarch *gdbarch,
> +                              struct bp_target_info *bp_tgt)
> +{
> +  return 0;
> +}
> +
> +static int
> +record_core_remove_breakpoint (struct gdbarch *gdbarch,
> +                              struct bp_target_info *bp_tgt)
> +{
> +  return 0;
> +}
> +
> +int
> +record_core_has_execution (struct target_ops *ops)
> +{
> +  return 1;
> +}
> +
> +static void
> +init_record_core_ops (void)
> +{
> +  record_core_ops.to_shortname = "record_core";
> +  record_core_ops.to_longname = "Process record and replay target";
> +  record_core_ops.to_doc =
> +    "Log program while executing and replay execution from log.";
> +  record_core_ops.to_open = record_open;
> +  record_core_ops.to_close = record_close;
> +  record_core_ops.to_resume = record_core_resume;
> +  record_core_ops.to_wait = record_wait;
> +  record_core_ops.to_kill = record_core_kill;
> +  record_core_ops.to_fetch_registers = record_core_fetch_registers;
> +  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
> +  record_core_ops.to_store_registers = record_core_store_registers;
> +  record_core_ops.to_xfer_partial = record_core_xfer_partial;
> +  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
> +  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
> +  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
> +  record_core_ops.to_has_execution = record_core_has_execution;
> +  record_core_ops.to_stratum = record_stratum;
> +  record_core_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)
>  {
> @@ -1153,6 +1645,212 @@ cmd_record_start (char *args, int from_t
>   execute_command ("target record", from_tty);
>  }
>
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  char buf[512];
> +
> +  snprintf (buf, 512, "target record %s", args);
> +  execute_command (buf, from_tty);
> +}
> +
> +static inline void
> +record_write_dump (char *recfilename, int fildes, const void *buf,
> +                   size_t nbyte)
> +{
> +  if (write (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to write dump of execution records to '%s'."),
> +           recfilename);
> +}
> +
> +/* Record log save-file format
> +   Version 1
> +
> +   Header:
> +     4 bytes: magic number htonl(0x20090726).
> +       NOTE: be sure to change whenever this file format changes!
> +
> +   Records:
> +     record_end:
> +       1 byte:  record type (record_end).
> +     record_reg:
> +       1 byte:  record type (record_reg).
> +       8 bytes: register id (network byte order).
> +       MAX_REGISTER_SIZE bytes: register value.
> +     record_mem:
> +       1 byte:  record type (record_mem).
> +       8 bytes: memory address (network byte order).
> +       8 bytes: memory length (network byte order).
> +       n bytes: memory value (n == memory length).
> +*/
> +
> +/* Dump the execution log to a file.  */
> +
> +static void
> +cmd_record_dump (char *args, int from_tty)
> +{
> +  char *recfilename, recfilename_buffer[40];
> +  int recfd;
> +  struct record_entry *cur_record_list;
> +  uint32_t magic;
> +  struct regcache *regcache;
> +  struct gdbarch *gdbarch;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *set_cleanups;
> +
> +  if (strcmp (current_target.to_shortname, "record") != 0)
> +    error (_("Process record is not started.\n"));
> +
> +  if (args && *args)
> +    recfilename = args;
> +  else
> +    {
> +      /* Default corefile name is "gdb_record.PID".  */
> +      snprintf (recfilename_buffer, 40, "gdb_record.%d",
> +                PIDGET (inferior_ptid));
> +      recfilename = recfilename_buffer;
> +    }
> +
> +  /* Open the dump file.  */
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog,
> +                        _("Saving recording to file '%s'\n"),
> +                        recfilename);
> +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> +                S_IRUSR | S_IWUSR);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for dump execution records: %s"),
> +           recfilename, strerror (errno));
> +  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
> +
> +  /* Save the current record entry to "cur_record_list".  */
> +  cur_record_list = record_list;
> +
> +  /* Get the values of regcache and gdbarch.  */
> +  regcache = get_current_regcache ();
> +  gdbarch = get_regcache_arch (regcache);
> +
> +  /* Disable the GDB operation record.  */
> +  set_cleanups = record_gdb_operation_disable_set ();
> +
> +  /* Write the magic code.  */
> +  magic = RECORD_FILE_MAGIC;
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, _("\
> +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
> +                        magic);
> +  record_write_dump (recfilename, recfd, &magic, 4);
> +
> +  /* Reverse execute to the begin of record list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == &record_first)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list, 0);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  /* Dump the entries to recfd and forward execute to the end of
> +     record list.  */
> +  while (1)
> +    {
> +      /* Dump entry.  */
> +      if (record_list != &record_first)
> +        {
> +          uint8_t tmpu8;
> +          uint64_t tmpu64;
> +
> +          tmpu8 = record_list->type;
> +          record_write_dump (recfilename, recfd, &tmpu8, 1);
> +
> +          switch (record_list->type)
> +            {
> +            case record_reg: /* reg */
> +              if (record_debug)
> +                fprintf_unfiltered (gdb_stdlog, _("\
> +Writing register %d (1 plus 8 plus %d bytes)\n"),
> +                                    record_list->u.reg.num,
> +                                    MAX_REGISTER_SIZE);
> +
> +              tmpu64 = record_list->u.reg.num;
> +              if (BYTE_ORDER == LITTLE_ENDIAN)
> +                tmpu64 = bswap_64 (tmpu64);
> +              record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +              record_write_dump (recfilename, recfd, record_list->u.reg.val,
> +                                 MAX_REGISTER_SIZE);
> +              break;
> +
> +            case record_mem: /* mem */
> +              if (!record_list->u.mem.mem_entry_not_accessible)
> +                {
> +                  if (record_debug)
> +                    fprintf_unfiltered (gdb_stdlog, _("\
> +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
> +                                        paddress (gdbarch,
> +                                                  record_list->u.mem.addr),
> +                                        record_list->u.mem.len);
> +
> +                  tmpu64 = record_list->u.mem.addr;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  tmpu64 = record_list->u.mem.len;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  record_write_dump (recfilename, recfd,
> +                                     record_list->u.mem.val,
> +                                     record_list->u.mem.len);
> +                }
> +              break;
> +
> +              case record_end:
> +                /* FIXME: record the contents of record_end rec.  */
> +                if (record_debug)
> +                  fprintf_unfiltered (gdb_stdlog,
> +                                      _("Writing record_end (1 byte)\n"));
> +                break;
> +            }
> +        }
> +
> +      /* Execute entry.  */
> +      record_exec_entry (regcache, gdbarch, record_list, 0);
> +
> +      if (record_list->next)
> +        record_list = record_list->next;
> +      else
> +        break;
> +    }
> +
> +  /* Reverse execute to cur_record_list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == cur_record_list)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list, 0);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  do_cleanups (set_cleanups);
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
> +                                  "records to `%s'.\n"),
> +                    recfilename);
> +}
> +
>  /* Truncate the record log from the present point
>    of replay until the end.  */
>
> @@ -1185,7 +1883,12 @@ cmd_record_stop (char *args, int from_tt
>     {
>       if (!record_list || !from_tty || query (_("Delete recorded log and "
>                                                "stop recording?")))
> -       unpush_target (&record_ops);
> +        {
> +          if (strcmp (current_target.to_shortname, "record") == 0)
> +           unpush_target (&record_ops);
> +          else
> +            unpush_target (&record_core_ops);
> +        }
>     }
>   else
>     printf_unfiltered (_("Process record is not started.\n"));
> @@ -1203,7 +1906,7 @@ set_record_insn_max_num (char *args, int
>                           "the first ones?\n"));
>
>       while (record_insn_num > record_insn_max_num)
> -       record_list_release_first ();
> +       record_list_release_first_insn ();
>     }
>  }
>
> @@ -1243,6 +1946,8 @@ info_record_command (char *args, int fro
>  void
>  _initialize_record (void)
>  {
> +  struct cmd_list_element *c;
> +
>   /* Init record_first.  */
>   record_first.prev = NULL;
>   record_first.next = NULL;
> @@ -1250,6 +1955,8 @@ _initialize_record (void)
>
>   init_record_ops ();
>   add_target (&record_ops);
> +  init_record_core_ops ();
> +  add_target (&record_core_ops);
>
>   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
>                            _("Set debugging of record/replay feature."),
> @@ -1259,9 +1966,10 @@ _initialize_record (void)
>                            NULL, show_record_debug, &setdebuglist,
>                            &showdebuglist);
>
> -  add_prefix_cmd ("record", class_obscure, cmd_record_start,
> -                 _("Abbreviated form of \"target record\" command."),
> -                 &record_cmdlist, "record ", 0, &cmdlist);
> +  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
> +                     _("Abbreviated form of \"target record\" command."),
> +                     &record_cmdlist, "record ", 0, &cmdlist);
> +  set_cmd_completer (c, filename_completer);
>   add_com_alias ("rec", "record", class_obscure, 1);
>   add_prefix_cmd ("record", class_support, set_record_command,
>                  _("Set record options"), &set_record_cmdlist,
> @@ -1276,6 +1984,16 @@ _initialize_record (void)
>                  "info record ", 0, &infolist);
>   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
>
> +  c = add_cmd ("load", class_obscure, cmd_record_load,
> +              _("Load previously dumped execution records from \
> +a file given as argument."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> +              _("Dump the execution records to a file.\n\
> +Argument is optional filename.  Default filename is
> 'gdb_record.<process_id>'."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
>
>   add_cmd ("delete", class_obscure, cmd_record_delete,
>           _("Delete the rest of execution log and start recording it anew."),
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-22 17:39                                         ` Hui Zhu
  2009-08-23  1:14                                           ` Hui Zhu
@ 2009-08-23 23:43                                           ` Michael Snyder
  2009-08-24  8:20                                             ` Hui Zhu
  1 sibling, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-23 23:43 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

Hui Zhu wrote:

> 
> Hi Michael,
> 
> I make a new version patch.  It has a lot of changes.
> Remove record_core and add a new target record_core for core target to
> make the code more clear.
> Make the load together with record_open.
> 
> Please help me review it.

Hi Hui,

In this review, I'm going to comment only on the parts of the
patch that relate to the record_core_ops (ie. pushing the
record stratum on top of the core file stratum).

Those parts of the patch are much improved.  I like this
version a lot better.  Thanks for reworking it.

Here's the one major thing that still worries me:

> +static inline void
> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> +                   struct record_entry *entry, int core)
> +{
> +  switch (entry->type)
> +    {
> +    case record_reg: /* reg */
> +      {
> +        gdb_byte reg[MAX_REGISTER_SIZE];
> +
> +        if (record_debug > 1)
> +          fprintf_unfiltered (gdb_stdlog,
> +                              "Process record: record_reg %s to "
> +                              "inferior num = %d.\n",
> +                              host_address_to_string (entry),
> +                              entry->u.reg.num);
> +
> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> +        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> +      }
> +      break;
> +
> +    case record_mem: /* mem */
> +      {
> +        if (!record_list->u.mem.mem_entry_not_accessible)
> +          {
> +            gdb_byte *mem = alloca (entry->u.mem.len);
> +
> +            if (record_debug > 1)
> +              fprintf_unfiltered (gdb_stdlog,
> +                                  "Process record: record_mem %s to "
> +                                  "inferior addr = %s len = %d.\n",
> +                                  host_address_to_string (entry),
> +                                  paddress (gdbarch, entry->u.mem.addr),
> +                                  record_list->u.mem.len);
> +
> +            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
> +              {
> +                 if ((execution_direction == EXEC_REVERSE && !core)
> +                     || (execution_direction != EXEC_REVERSE && core))
> +                  {

It troubles me that we have to do one thing if we're going
forward and a different thing if we're going backward,
unles it's a core file, and in that case we do the opposite.

That's just not elegant.  We're really going to do the same
thing (swap the contents of target memory with the contents
of the record log) regardless of which direction we're going.

See below for my suggestion.

> +                    record_list->u.mem.mem_entry_not_accessible = 1;
> +                    if (record_debug)
> +                      warning (_("Process record: error reading memory at "
> +                                 "addr = %s len = %d."),
> +                               paddress (gdbarch, entry->u.mem.addr),
> +                               entry->u.mem.len);
> +                  }
> +                else
> +                  error (_("Process record: error reading memory at "
> +                           "addr = %s len = %d."),
> +                         paddress (gdbarch, entry->u.mem.addr),
> +                        entry->u.mem.len);
> +              }
> +            else
> +              {
> +                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
> +                                         entry->u.mem.len))
> +                  {
> +                     if ((execution_direction == EXEC_REVERSE && !core)
> +                         || (execution_direction != EXEC_REVERSE && core))

And the same thing here.  What if we just replace the above
with this?  What do you think?


             if (target_read_memory (entry->u.mem.addr, mem,
                                     entry->u.mem.len))
               {
                 record_list->u.mem.mem_entry_not_accessible = 1;
                 if (record_debug)
                   warning (_("Process record: error reading memory at "
                              "addr = %s len = %d."),
                            paddress (gdbarch, entry->u.mem.addr),
                            entry->u.mem.len);
               }
             else
               {
                 if (target_write_memory (entry->u.mem.addr,
                                          entry->u.mem.val,
                                          entry->u.mem.len))
                   {
                     record_list->u.mem.mem_entry_not_accessible = 1;
                     if (record_debug)
                       warning (_("Process record: error writing memory at "
                                  "addr = %s len = %d."),
                                paddress (gdbarch, entry->u.mem.addr),
                                entry->u.mem.len);
                   }
               }




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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-23 23:43                                           ` Michael Snyder
@ 2009-08-24  8:20                                             ` Hui Zhu
  2009-08-24 18:32                                               ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-24  8:20 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

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

On Mon, Aug 24, 2009 at 07:21, Michael Snyder<msnyder@vmware.com> wrote:
> Hui Zhu wrote:
>
>>
>> Hi Michael,
>>
>> I make a new version patch.  It has a lot of changes.
>> Remove record_core and add a new target record_core for core target to
>> make the code more clear.
>> Make the load together with record_open.
>>
>> Please help me review it.
>
> Hi Hui,
>
> In this review, I'm going to comment only on the parts of the
> patch that relate to the record_core_ops (ie. pushing the
> record stratum on top of the core file stratum).
>
> Those parts of the patch are much improved.  I like this
> version a lot better.  Thanks for reworking it.
>


Hi Michael,

Thanks for your review.  I make a new patch that update the memset code to:
            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
              {
                record_list->u.mem.mem_entry_not_accessible = 1;
                if (record_debug)
                  warning (_("Process record: error reading memory at "
                             "addr = %s len = %d."),
                           paddress (gdbarch, entry->u.mem.addr),
                           entry->u.mem.len);
              }
            else
              {
                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
                                         entry->u.mem.len))
                  {
                    record_list->u.mem.mem_entry_not_accessible = 1;
                    if (record_debug)
                      warning (_("Process record: error writing memory at "
                                 "addr = %s len = %d."),
                               paddress (gdbarch, entry->u.mem.addr),
                               entry->u.mem.len);
                  }
                else
                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
              }

Please help me review it.

Thanks,
Hui

---
 record.c |  951 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 825 insertions(+), 126 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,15 +23,23 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)

+#define RECORD_FILE_MAGIC	htonl(0x20090726)
+
 /* These are the core struct of record function.

    An record_entry is a record of the value change of a register
@@ -78,9 +86,22 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -94,6 +115,7 @@ static int record_insn_num = 0;

 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;

 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -169,7 +191,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +408,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }

+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                record_list->u.mem.mem_entry_not_accessible = 1;
+                if (record_debug)
+                  warning (_("Process record: error reading memory at "
+                             "addr = %s len = %d."),
+                           paddress (gdbarch, entry->u.mem.addr),
+                           entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error writing memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+              }
+          }
+      }
+      break;
+    }
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
 static void
-record_open (char *name, int from_tty)
+record_fd_cleanups (void *recfdp)
 {
-  struct target_ops *t;
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}

-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+/* Load the execution log from a file.  */
+
+static void
+record_load (char *name)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (!name || (name && !*name))
+    return;
+
+  /* Open the load file.  */
+  recfd = open (name, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           name, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (name, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), name);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), name);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+
+          /* Get num.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+
+          /* Get addr.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get len.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\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);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), name);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                              enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                              struct target_waitstatus *,
+                              int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                       struct regcache *,
+                                       int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                       enum target_object object,
+                                       const char *annex,
+                                       gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset,
+                                       LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  if (!name || (name && !*name))
+    error (_("Argument for gdb record filename required.\n"));
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                          record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+}
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+  struct target_ops *t;

   /* check exec */
   if (!target_has_execution)
@@ -438,6 +738,28 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
 	     "record function."));

+  if (!tmp_to_resume)
+    error (_("Process record can't get to_resume."));
+  if (!tmp_to_wait)
+    error (_("Process record can't get to_wait."));
+  if (!tmp_to_store_registers)
+    error (_("Process record can't get to_store_registers."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Process record can't get to_insert_breakpoint."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Process record can't get to_remove_breakpoint."));
+
+  push_target (&record_ops);
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
     {
@@ -447,70 +769,102 @@ record_open (char *name, int from_tty)
 	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;
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-	  record_beneath_to_resume = t->to_resume;
-	  record_beneath_to_resume_ops = t;
+	  tmp_to_resume = t->to_resume;
+	  tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-	  record_beneath_to_wait = t->to_wait;
-	  record_beneath_to_wait_ops = t;
+	  tmp_to_wait = t->to_wait;
+	  tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-	  record_beneath_to_store_registers = t->to_store_registers;
-	  record_beneath_to_store_registers_ops = t;
+	  tmp_to_store_registers = t->to_store_registers;
+	  tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-	  record_beneath_to_xfer_partial = t->to_xfer_partial;
-	  record_beneath_to_xfer_partial_ops = t;
+	  tmp_to_xfer_partial = t->to_xfer_partial;
+	  tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-	record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-	record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
     }
-  if (!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)
+  if (!tmp_to_xfer_partial)
     error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));

-  push_target (&record_ops);
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);

   /* Reset */
   record_insn_num = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+
+  /* Load if there is argument.  */
+  record_load (name);
 }

 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -584,7 +938,7 @@ record_wait (struct target_ops *ops,
 			"record_resume_step = %d\n",
 			record_resume_step);

-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
       if (record_resume_error)
 	{
@@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }

-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);

-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed --
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed --
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
+
   target_kill ();
 }

@@ -945,7 +1233,7 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
@@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
@@ -1138,6 +1426,191 @@ init_record_ops (void)
 }

 static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+}
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+		          const char *annex, gdb_byte *readbuf,
+		          const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  record_core_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)
 {
@@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+static void
+cmd_record_load (char *args, int from_tty)
+{
+  char buf[512];
+
+  snprintf (buf, 512, "target record %s", args);
+  execute_command (buf, from_tty);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090726).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory address (network byte order).
+       8 bytes: memory length (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, 40, "gdb_record.%d",
+                PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                        magic);
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt
     {
       if (!record_list || !from_tty || query (_("Delete recorded log and "
 	                                        "stop recording?")))
-	unpush_target (&record_ops);
+        {
+          if (strcmp (current_target.to_shortname, "record") == 0)
+	    unpush_target (&record_ops);
+          else
+            unpush_target (&record_core_ops);
+        }
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +1936,8 @@ _initialize_record (void)

   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);

   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
 			    _("Set debugging of record/replay feature."),
@@ -1259,9 +1947,10 @@ _initialize_record (void)
 			    NULL, show_record_debug, &setdebuglist,
 			    &showdebuglist);

-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-		  _("Abbreviated form of \"target record\" command."),
- 		  &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+		      _("Abbreviated form of \"target record\" command."),
+ 		      &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
 		  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +1965,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is
'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 38816 bytes --]

---
 record.c |  951 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 825 insertions(+), 126 deletions(-)

--- a/record.c
+++ b/record.c
@@ -23,15 +23,23 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)
 
+#define RECORD_FILE_MAGIC	htonl(0x20090726)
+
 /* These are the core struct of record function.
 
    An record_entry is a record of the value change of a register
@@ -78,9 +86,22 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -94,6 +115,7 @@ static int record_insn_num = 0;
 
 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;
 
 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -169,7 +191,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +408,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                record_list->u.mem.mem_entry_not_accessible = 1;
+                if (record_debug)
+                  warning (_("Process record: error reading memory at "
+                             "addr = %s len = %d."),
+                           paddress (gdbarch, entry->u.mem.addr),
+                           entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error writing memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+              }
+          }
+      }
+      break;
+    }
+}
+
+static inline void
+record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
+{
+  if (read (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to read dump of execution records in '%s'."),
+           recfilename);
+}
+
 static void
-record_open (char *name, int from_tty)
+record_fd_cleanups (void *recfdp)
 {
-  struct target_ops *t;
+  int recfd = *(int *) recfdp;
+  close (recfd);
+}
 
-  if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+/* Load the execution log from a file.  */
+
+static void
+record_load (char *name)
+{
+  int recfd;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct cleanup *old_cleanups2;
+  struct record_entry *rec;
+  int insn_number = 0;
+
+  if (!name || (name && !*name))
+    return;
+
+  /* Open the load file.  */
+  recfd = open (name, O_RDONLY | O_BINARY);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for loading execution records: %s"),
+           name, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Check the magic code.  */
+  record_read_dump (name, recfd, &magic, 4);
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("'%s' is not a valid dump of execution records."), name);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      ret = read (recfd, &tmpu8, 1);
+      if (ret < 0)
+        error (_("Failed to read dump of execution records in '%s'."), name);
+      if (ret == 0)
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          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;
+
+          /* Get num.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.reg.num = tmpu64;
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_mem: /* mem */
+          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+          rec->prev = NULL;
+          rec->next = NULL;
+          rec->type = record_mem;
+
+          /* Get addr.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get len.  */
+          record_read_dump (name, recfd, &tmpu64, 8);
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.len = tmpu64;
+          rec->u.mem.mem_entry_not_accessible = 0;
+          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+
+          /* Get val.  */
+          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+
+          record_arch_list_add (rec);
+          break;
+
+        case record_end: /* end */
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\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);
+          insn_number ++;
+          break;
+
+        default:
+          error (_("Format of '%s' is not right."), name);
+          break;
+        }
+    }
+
+  discard_cleanups (old_cleanups2);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  for (rec = record_list; rec->next; rec = rec->next);
+  rec->next = record_arch_list_head;
+  record_arch_list_head->prev = rec;
+
+  /* Update record_insn_num and record_insn_max_num.  */
+  record_insn_num += insn_number;
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                              enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                              struct target_waitstatus *,
+                              int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                       struct regcache *,
+                                       int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                       enum target_object object,
+                                       const char *annex,
+                                       gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset,
+                                       LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  if (!name || (name && !*name))
+    error (_("Argument for gdb record filename required.\n"));
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                          record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+}
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+  struct target_ops *t;
 
   /* check exec */
   if (!target_has_execution)
@@ -438,6 +738,28 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
 	     "record function."));
 
+  if (!tmp_to_resume)
+    error (_("Process record can't get to_resume."));
+  if (!tmp_to_wait)
+    error (_("Process record can't get to_wait."));
+  if (!tmp_to_store_registers)
+    error (_("Process record can't get to_store_registers."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Process record can't get to_insert_breakpoint."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Process record can't get to_remove_breakpoint."));
+
+  push_target (&record_ops);
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
     {
@@ -447,70 +769,102 @@ record_open (char *name, int from_tty)
 	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;
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-	  record_beneath_to_resume = t->to_resume;
-	  record_beneath_to_resume_ops = t;
+	  tmp_to_resume = t->to_resume;
+	  tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-	  record_beneath_to_wait = t->to_wait;
-	  record_beneath_to_wait_ops = t;
+	  tmp_to_wait = t->to_wait;
+	  tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-	  record_beneath_to_store_registers = t->to_store_registers;
-	  record_beneath_to_store_registers_ops = t;
+	  tmp_to_store_registers = t->to_store_registers;
+	  tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-	  record_beneath_to_xfer_partial = t->to_xfer_partial;
-	  record_beneath_to_xfer_partial_ops = t;
+	  tmp_to_xfer_partial = t->to_xfer_partial;
+	  tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-	record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-	record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
     }
-  if (!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)
+  if (!tmp_to_xfer_partial)
     error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));
 
-  push_target (&record_ops);
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);
 
   /* Reset */
   record_insn_num = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+
+  /* Load if there is argument.  */
+  record_load (name);
 }
 
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -584,7 +938,7 @@ record_wait (struct target_ops *ops,
 			"record_resume_step = %d\n",
 			record_resume_step);
 
-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
       if (record_resume_error)
 	{
@@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);
 
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
+
   target_kill ();
 }
 
@@ -945,7 +1233,7 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
@@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
@@ -1138,6 +1426,191 @@ init_record_ops (void)
 }
 
 static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+}
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+		          const char *annex, gdb_byte *readbuf,
+		          const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  record_core_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)
 {
@@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+static void
+cmd_record_load (char *args, int from_tty)
+{
+  char buf[512];
+
+  snprintf (buf, 512, "target record %s", args);
+  execute_command (buf, from_tty);
+}
+
+static inline void
+record_write_dump (char *recfilename, int fildes, const void *buf,
+                   size_t nbyte)
+{
+  if (write (fildes, buf, nbyte) != nbyte)
+    error (_("Failed to write dump of execution records to '%s'."),
+           recfilename);
+}
+
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090726).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory address (network byte order).
+       8 bytes: memory length (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default corefile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, 40, "gdb_record.%d",
+                PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                S_IRUSR | S_IWUSR);
+  if (recfd < 0)
+    error (_("Failed to open '%s' for dump execution records: %s"),
+           recfilename, strerror (errno));
+  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                        magic);
+  record_write_dump (recfilename, recfd, &magic, 4);
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          record_write_dump (recfilename, recfd, &tmpu8, 1);
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+              record_write_dump (recfilename, recfd, record_list->u.reg.val,
+                                 MAX_REGISTER_SIZE);
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  record_write_dump (recfilename, recfd, &tmpu64, 8);
+
+                  record_write_dump (recfilename, recfd,
+                                     record_list->u.mem.val,
+                                     record_list->u.mem.len);
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
+                                  "records to `%s'.\n"),
+                    recfilename);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt
     {
       if (!record_list || !from_tty || query (_("Delete recorded log and "
 	                                        "stop recording?")))
-	unpush_target (&record_ops);
+        {
+          if (strcmp (current_target.to_shortname, "record") == 0)
+	    unpush_target (&record_ops);
+          else
+            unpush_target (&record_core_ops);
+        }
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +1936,8 @@ _initialize_record (void)
 
   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);
 
   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
 			    _("Set debugging of record/replay feature."),
@@ -1259,9 +1947,10 @@ _initialize_record (void)
 			    NULL, show_record_debug, &setdebuglist,
 			    &showdebuglist);
 
-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-		  _("Abbreviated form of \"target record\" command."),
- 		  &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+		      _("Abbreviated form of \"target record\" command."),
+ 		      &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
 		  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +1965,16 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("load", class_obscure, cmd_record_load,
+	       _("Load previously dumped execution records from \
+a file given as argument."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-24  8:20                                             ` Hui Zhu
@ 2009-08-24 18:32                                               ` Michael Snyder
  2009-08-25  8:47                                                 ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-24 18:32 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

Hui Zhu wrote:
> On Mon, Aug 24, 2009 at 07:21, Michael Snyder<msnyder@vmware.com> wrote:
>> Hui Zhu wrote:
>>
>>> Hi Michael,
>>>
>>> I make a new version patch.  It has a lot of changes.
>>> Remove record_core and add a new target record_core for core target to
>>> make the code more clear.
>>> Make the load together with record_open.
>>>
>>> Please help me review it.
>> Hi Hui,
>>
>> In this review, I'm going to comment only on the parts of the
>> patch that relate to the record_core_ops (ie. pushing the
>> record stratum on top of the core file stratum).
>>
>> Those parts of the patch are much improved.  I like this
>> version a lot better.  Thanks for reworking it.
>>
> 
> 
> Hi Michael,
> 
> Thanks for your review.  I make a new patch that update the memset code to:

Way cool, thanks!

Now I can comment on the dump/load part of the patch.
It does not seem as if previous issues have been addressed:
1) Core file and record file are separate, no way to verify
    that they correspond to each other.
2) Previous log not cleared when loading a new log.

Have you considered combining this patch with the idea that I
proposed -- storing the record log in the core file?  Seems like
that would be very easy now -- gdb already has the core file open,
and there is a global pointer to the BFD.
See gdbcore.h:extern bfd* core_bfd;

In fact, just for fun, I've merged the two patches in my
local tree.  It seems to work quite well.  I can just post
it here if you like, or here's a hint to show you how it goes:

static void
record_load (void)
{
   int recfd;
   uint32_t magic;
   struct cleanup *old_cleanups2;
   struct record_entry *rec;
   int insn_number = 0;
   asection *osec;
   void nullify_last_target_wait_ptid (void);

   /* We load the execution log from the open core bfd,
      if there is one.  */
   if (core_bfd == NULL)
     return;

   if (record_debug)
     fprintf_filtered (gdb_stdlog,
                       _("Restoring recording from core file.\n"));

   /* Now need to find our special note section.  */
   osec = bfd_get_section_by_name (core_bfd, "null0");


>             if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
>               {
>                 record_list->u.mem.mem_entry_not_accessible = 1;
>                 if (record_debug)
>                   warning (_("Process record: error reading memory at "
>                              "addr = %s len = %d."),
>                            paddress (gdbarch, entry->u.mem.addr),
>                            entry->u.mem.len);
>               }
>             else
>               {
>                 if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
>                                          entry->u.mem.len))
>                   {
>                     record_list->u.mem.mem_entry_not_accessible = 1;
>                     if (record_debug)
>                       warning (_("Process record: error writing memory at "
>                                  "addr = %s len = %d."),
>                                paddress (gdbarch, entry->u.mem.addr),
>                                entry->u.mem.len);
>                   }
>                 else
>                   memcpy (entry->u.mem.val, mem, entry->u.mem.len);
>               }
> 
> Please help me review it.
> 
> Thanks,
> Hui
> 
> ---
>  record.c |  951 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
>  1 file changed, 825 insertions(+), 126 deletions(-)
> 
> --- a/record.c
> +++ b/record.c
> @@ -23,15 +23,23 @@
>  #include "gdbthread.h"
>  #include "event-top.h"
>  #include "exceptions.h"
> +#include "completer.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "exec.h"
>  #include "record.h"
> 
> +#include <byteswap.h>
>  #include <signal.h>
> +#include <netinet/in.h>
> 
>  #define DEFAULT_RECORD_INSN_MAX_NUM    200000
> 
>  #define RECORD_IS_REPLAY \
>       (record_list->next || execution_direction == EXEC_REVERSE)
> 
> +#define RECORD_FILE_MAGIC      htonl(0x20090726)
> +
>  /* These are the core struct of record function.
> 
>     An record_entry is a record of the value change of a register
> @@ -78,9 +86,22 @@ struct record_entry
>    } u;
>  };
> 
> +struct record_core_buf_entry
> +{
> +  struct record_core_buf_entry *prev;
> +  struct target_section *p;
> +  bfd_byte *buf;
> +};
> +
>  /* This is the debug switch for process record.  */
>  int record_debug = 0;
> 
> +/* Record buf with core target.  */
> +static gdb_byte *record_core_regbuf = NULL;
> +static struct target_section *record_core_start;
> +static struct target_section *record_core_end;
> +static struct record_core_buf_entry *record_core_buf_list = NULL;
> +
>  /* These list is for execution log.  */
>  static struct record_entry record_first;
>  static struct record_entry *record_list = &record_first;
> @@ -94,6 +115,7 @@ static int record_insn_num = 0;
> 
>  /* The target_ops of process record.  */
>  static struct target_ops record_ops;
> +static struct target_ops record_core_ops;
> 
>  /* The beneath function pointers.  */
>  static struct target_ops *record_beneath_to_resume_ops;
> @@ -169,7 +191,7 @@ record_list_release_next (void)
>  }
> 
>  static void
> -record_list_release_first (void)
> +record_list_release_first_insn (void)
>  {
>    struct record_entry *tmp = NULL;
>    enum record_type type;
> @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
>               if (q)
>                 record_stop_at_limit = 0;
>               else
> -               error (_("Process record: inferior program stopped."));
> +               error (_("Process record: stoped by user."));
>             }
>         }
>      }
>  }
> 
> +static void
> +record_arch_list_cleanups (void *ignore)
> +{
> +  record_list_release (record_arch_list_tail);
> +}
> +
>  /* Before inferior step (when GDB record the running message, inferior
>     only can step), GDB will call this function to record the values to
>     record_list.  This function will call gdbarch_process_record to
>     record the running message of inferior and set them to
>     record_arch_list, and add it to record_list.  */
> 
> -static void
> -record_message_cleanups (void *ignore)
> -{
> -  record_list_release (record_arch_list_tail);
> -}
> -
>  static int
>  record_message (void *args)
>  {
>    int ret;
>    struct regcache *regcache = args;
> -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
> +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
> 
>    record_arch_list_head = NULL;
>    record_arch_list_tail = NULL;
> @@ -386,7 +408,7 @@ record_message (void *args)
>    record_list = record_arch_list_tail;
> 
>    if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>    else
>      record_insn_num++;
> 
> @@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void)
>    return old_cleanups;
>  }
> 
> +static inline void
> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
> +                   struct record_entry *entry)
> +{
> +  switch (entry->type)
> +    {
> +    case record_reg: /* reg */
> +      {
> +        gdb_byte reg[MAX_REGISTER_SIZE];
> +
> +        if (record_debug > 1)
> +          fprintf_unfiltered (gdb_stdlog,
> +                              "Process record: record_reg %s to "
> +                              "inferior num = %d.\n",
> +                              host_address_to_string (entry),
> +                              entry->u.reg.num);
> +
> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
> +        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
> +      }
> +      break;
> +
> +    case record_mem: /* mem */
> +      {
> +        if (!record_list->u.mem.mem_entry_not_accessible)
> +          {
> +            gdb_byte *mem = alloca (entry->u.mem.len);
> +
> +            if (record_debug > 1)
> +              fprintf_unfiltered (gdb_stdlog,
> +                                  "Process record: record_mem %s to "
> +                                  "inferior addr = %s len = %d.\n",
> +                                  host_address_to_string (entry),
> +                                  paddress (gdbarch, entry->u.mem.addr),
> +                                  record_list->u.mem.len);
> +
> +            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
> +              {
> +                record_list->u.mem.mem_entry_not_accessible = 1;
> +                if (record_debug)
> +                  warning (_("Process record: error reading memory at "
> +                             "addr = %s len = %d."),
> +                           paddress (gdbarch, entry->u.mem.addr),
> +                           entry->u.mem.len);
> +              }
> +            else
> +              {
> +                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
> +                                         entry->u.mem.len))
> +                  {
> +                    record_list->u.mem.mem_entry_not_accessible = 1;
> +                    if (record_debug)
> +                      warning (_("Process record: error writing memory at "
> +                                 "addr = %s len = %d."),
> +                               paddress (gdbarch, entry->u.mem.addr),
> +                               entry->u.mem.len);
> +                  }
> +                else
> +                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
> +              }
> +          }
> +      }
> +      break;
> +    }
> +}
> +
> +static inline void
> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
> +{
> +  if (read (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to read dump of execution records in '%s'."),
> +           recfilename);
> +}
> +
>  static void
> -record_open (char *name, int from_tty)
> +record_fd_cleanups (void *recfdp)
>  {
> -  struct target_ops *t;
> +  int recfd = *(int *) recfdp;
> +  close (recfd);
> +}
> 
> -  if (record_debug)
> -    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> +/* Load the execution log from a file.  */
> +
> +static void
> +record_load (char *name)
> +{
> +  int recfd;
> +  uint32_t magic;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *old_cleanups2;
> +  struct record_entry *rec;
> +  int insn_number = 0;
> +
> +  if (!name || (name && !*name))
> +    return;
> +
> +  /* Open the load file.  */
> +  recfd = open (name, O_RDONLY | O_BINARY);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for loading execution records: %s"),
> +           name, strerror (errno));
> +  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
> +
> +  /* Check the magic code.  */
> +  record_read_dump (name, recfd, &magic, 4);
> +  if (magic != RECORD_FILE_MAGIC)
> +    error (_("'%s' is not a valid dump of execution records."), name);
> +
> +  /* Load the entries in recfd to the record_arch_list_head and
> +     record_arch_list_tail.  */
> +  record_arch_list_head = NULL;
> +  record_arch_list_tail = NULL;
> +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
> +
> +  while (1)
> +    {
> +      int ret;
> +      uint8_t tmpu8;
> +      uint64_t tmpu64;
> +
> +      ret = read (recfd, &tmpu8, 1);
> +      if (ret < 0)
> +        error (_("Failed to read dump of execution records in '%s'."), name);
> +      if (ret == 0)
> +        break;
> +
> +      switch (tmpu8)
> +        {
> +        case record_reg: /* reg */
> +          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;
> +
> +          /* Get num.  */
> +          record_read_dump (name, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.reg.num = tmpu64;
> +
> +          /* Get val.  */
> +          record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE);
> +
> +          if (record_debug)
> +            fprintf_unfiltered (gdb_stdlog, _("\
> +Reading register %d (1 plus 8 plus %d bytes)\n"),
> +                                rec->u.reg.num,
> +                                MAX_REGISTER_SIZE);
> +
> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_mem: /* mem */
> +          rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
> +          rec->prev = NULL;
> +          rec->next = NULL;
> +          rec->type = record_mem;
> +
> +          /* Get addr.  */
> +          record_read_dump (name, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.addr = tmpu64;
> +
> +          /* Get len.  */
> +          record_read_dump (name, recfd, &tmpu64, 8);
> +          if (BYTE_ORDER == LITTLE_ENDIAN)
> +            tmpu64 = bswap_64 (tmpu64);
> +          rec->u.mem.len = tmpu64;
> +          rec->u.mem.mem_entry_not_accessible = 0;
> +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
> +
> +          /* Get val.  */
> +          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
> +
> +          if (record_debug)
> +            fprintf_unfiltered (gdb_stdlog, _("\
> +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
> +                                        paddress (get_current_arch (),
> +                                                  rec->u.mem.addr),
> +                                        rec->u.mem.len);
> +
> +          record_arch_list_add (rec);
> +          break;
> +
> +        case record_end: /* end */
> +          if (record_debug)
> +            fprintf_unfiltered (gdb_stdlog,
> +                                _("Reading record_end (1 byte)\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);
> +          insn_number ++;
> +          break;
> +
> +        default:
> +          error (_("Format of '%s' is not right."), name);
> +          break;
> +        }
> +    }
> +
> +  discard_cleanups (old_cleanups2);
> +
> +  /* Add record_arch_list_head to the end of record list.  */
> +  for (rec = record_list; rec->next; rec = rec->next);
> +  rec->next = record_arch_list_head;
> +  record_arch_list_head->prev = rec;
> +
> +  /* Update record_insn_num and record_insn_max_num.  */
> +  record_insn_num += insn_number;
> +  if (record_insn_num > record_insn_max_num)
> +    {
> +      record_insn_max_num = record_insn_num;
> +      warning (_("Auto increase record/replay buffer limit to %d."),
> +               record_insn_max_num);
> +    }
> +
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
> +}
> +
> +static struct target_ops *tmp_to_resume_ops;
> +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
> +                              enum target_signal);
> +static struct target_ops *tmp_to_wait_ops;
> +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
> +                              struct target_waitstatus *,
> +                              int);
> +static struct target_ops *tmp_to_store_registers_ops;
> +static void (*tmp_to_store_registers) (struct target_ops *,
> +                                       struct regcache *,
> +                                       int regno);
> +static struct target_ops *tmp_to_xfer_partial_ops;
> +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
> +                                       enum target_object object,
> +                                       const char *annex,
> +                                       gdb_byte *readbuf,
> +                                       const gdb_byte *writebuf,
> +                                       ULONGEST offset,
> +                                       LONGEST len);
> +static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
> +                                        struct bp_target_info *);
> +static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
> +                                        struct bp_target_info *);
> +
> +static void
> +record_core_open_1 (char *name, int from_tty)
> +{
> +  struct regcache *regcache = get_current_regcache ();
> +  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
> +  int i;
> +
> +  if (!name || (name && !*name))
> +    error (_("Argument for gdb record filename required.\n"));
> +
> +  /* Get record_core_regbuf.  */
> +  target_fetch_registers (regcache, -1);
> +  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
> +  for (i = 0; i < regnum; i ++)
> +    regcache_raw_collect (regcache, i,
> +                          record_core_regbuf + MAX_REGISTER_SIZE * i);
> +
> +  /* Get record_core_start and record_core_end.  */
> +  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
> +    {
> +      xfree (record_core_regbuf);
> +      record_core_regbuf = NULL;
> +      error (_("\"%s\": Can't find sections: %s"),
> +             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
> +    }
> +
> +  push_target (&record_core_ops);
> +}
> +
> +static void
> +record_open_1 (char *name, int from_tty)
> +{
> +  struct target_ops *t;
> 
>    /* check exec */
>    if (!target_has_execution)
> @@ -438,6 +738,28 @@ record_open (char *name, int from_tty)
>      error (_("Process record: the current architecture doesn't support "
>              "record function."));
> 
> +  if (!tmp_to_resume)
> +    error (_("Process record can't get to_resume."));
> +  if (!tmp_to_wait)
> +    error (_("Process record can't get to_wait."));
> +  if (!tmp_to_store_registers)
> +    error (_("Process record can't get to_store_registers."));
> +  if (!tmp_to_insert_breakpoint)
> +    error (_("Process record can't get to_insert_breakpoint."));
> +  if (!tmp_to_remove_breakpoint)
> +    error (_("Process record can't get to_remove_breakpoint."));
> +
> +  push_target (&record_ops);
> +}
> +
> +static void
> +record_open (char *name, int from_tty)
> +{
> +  struct target_ops *t;
> +
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
> +
>    /* Check if record target is already running.  */
>    if (current_target.to_stratum == record_stratum)
>      {
> @@ -447,70 +769,102 @@ record_open (char *name, int from_tty)
>         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;
> +  /* Reset the tmp beneath pointers.  */
> +  tmp_to_resume_ops = NULL;
> +  tmp_to_resume = NULL;
> +  tmp_to_wait_ops = NULL;
> +  tmp_to_wait = NULL;
> +  tmp_to_store_registers_ops = NULL;
> +  tmp_to_store_registers = NULL;
> +  tmp_to_xfer_partial_ops = NULL;
> +  tmp_to_xfer_partial = NULL;
> +  tmp_to_insert_breakpoint = NULL;
> +  tmp_to_remove_breakpoint = NULL;
> 
>    /* Set the beneath function pointers.  */
>    for (t = current_target.beneath; t != NULL; t = t->beneath)
>      {
> -      if (!record_beneath_to_resume)
> +      if (!tmp_to_resume)
>          {
> -         record_beneath_to_resume = t->to_resume;
> -         record_beneath_to_resume_ops = t;
> +         tmp_to_resume = t->to_resume;
> +         tmp_to_resume_ops = t;
>          }
> -      if (!record_beneath_to_wait)
> +      if (!tmp_to_wait)
>          {
> -         record_beneath_to_wait = t->to_wait;
> -         record_beneath_to_wait_ops = t;
> +         tmp_to_wait = t->to_wait;
> +         tmp_to_wait_ops = t;
>          }
> -      if (!record_beneath_to_store_registers)
> +      if (!tmp_to_store_registers)
>          {
> -         record_beneath_to_store_registers = t->to_store_registers;
> -         record_beneath_to_store_registers_ops = t;
> +         tmp_to_store_registers = t->to_store_registers;
> +         tmp_to_store_registers_ops = t;
>          }
> -      if (!record_beneath_to_xfer_partial)
> +      if (!tmp_to_xfer_partial)
>          {
> -         record_beneath_to_xfer_partial = t->to_xfer_partial;
> -         record_beneath_to_xfer_partial_ops = t;
> +         tmp_to_xfer_partial = t->to_xfer_partial;
> +         tmp_to_xfer_partial_ops = t;
>          }
> -      if (!record_beneath_to_insert_breakpoint)
> -       record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
> -      if (!record_beneath_to_remove_breakpoint)
> -       record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
> +      if (!tmp_to_insert_breakpoint)
> +       tmp_to_insert_breakpoint = t->to_insert_breakpoint;
> +      if (!tmp_to_remove_breakpoint)
> +       tmp_to_remove_breakpoint = t->to_remove_breakpoint;
>      }
> -  if (!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)
> +  if (!tmp_to_xfer_partial)
>      error (_("Process record can't get to_xfer_partial."));
> -  if (!record_beneath_to_insert_breakpoint)
> -    error (_("Process record can't get to_insert_breakpoint."));
> -  if (!record_beneath_to_remove_breakpoint)
> -    error (_("Process record can't get to_remove_breakpoint."));
> 
> -  push_target (&record_ops);
> +  if (current_target.to_stratum == core_stratum)
> +    record_core_open_1 (name, from_tty);
> +  else
> +    record_open_1 (name, from_tty);
> 
>    /* Reset */
>    record_insn_num = 0;
>    record_list = &record_first;
>    record_list->next = NULL;
> +
> +  /* Set the tmp beneath pointers to beneath pointers.  */
> +  record_beneath_to_resume_ops = tmp_to_resume_ops;
> +  record_beneath_to_resume = tmp_to_resume;
> +  record_beneath_to_wait_ops = tmp_to_wait_ops;
> +  record_beneath_to_wait = tmp_to_wait;
> +  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
> +  record_beneath_to_store_registers = tmp_to_store_registers;
> +  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
> +  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
> +  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
> +  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
> +
> +  /* Load if there is argument.  */
> +  record_load (name);
>  }
> 
>  static void
>  record_close (int quitting)
>  {
> +  struct record_core_buf_entry *entry;
> +
>    if (record_debug)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
> 
>    record_list_release (record_list);
> +
> +  /* Release record_core_regbuf.  */
> +  if (record_core_regbuf)
> +    {
> +      xfree (record_core_regbuf);
> +      record_core_regbuf = NULL;
> +    }
> +
> +  /* Release record_core_buf_list.  */
> +  if (record_core_buf_list)
> +    {
> +      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
> +        {
> +          xfree (record_core_buf_list);
> +          record_core_buf_list = entry;
> +        }
> +      record_core_buf_list = NULL;
> +    }
>  }
> 
>  static int record_resume_step = 0;
> @@ -584,7 +938,7 @@ record_wait (struct target_ops *ops,
>                         "record_resume_step = %d\n",
>                         record_resume_step);
> 
> -  if (!RECORD_IS_REPLAY)
> +  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
>      {
>        if (record_resume_error)
>         {
> @@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops,
>               break;
>             }
> 
> -         /* Set ptid, register and memory according to record_list.  */
> -         if (record_list->type == record_reg)
> -           {
> -             /* reg */
> -             gdb_byte reg[MAX_REGISTER_SIZE];
> -             if (record_debug > 1)
> -               fprintf_unfiltered (gdb_stdlog,
> -                                   "Process record: record_reg %s to "
> -                                   "inferior num = %d.\n",
> -                                   host_address_to_string (record_list),
> -                                   record_list->u.reg.num);
> -             regcache_cooked_read (regcache, record_list->u.reg.num, reg);
> -             regcache_cooked_write (regcache, record_list->u.reg.num,
> -                                    record_list->u.reg.val);
> -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
> -           }
> -         else if (record_list->type == record_mem)
> -           {
> -             /* mem */
> -             /* Nothing to do if the entry is flagged not_accessible.  */
> -             if (!record_list->u.mem.mem_entry_not_accessible)
> -               {
> -                 gdb_byte *mem = alloca (record_list->u.mem.len);
> -                 if (record_debug > 1)
> -                   fprintf_unfiltered (gdb_stdlog,
> -                                       "Process record: record_mem %s to "
> -                                       "inferior addr = %s len = %d.\n",
> -                                       host_address_to_string (record_list),
> -                                       paddress (gdbarch,
> -                                                 record_list->u.mem.addr),
> -                                       record_list->u.mem.len);
> +          record_exec_entry (regcache, gdbarch, record_list);
> 
> -                 if (target_read_memory (record_list->u.mem.addr, mem,
> -                                         record_list->u.mem.len))
> -                   {
> -                     if (execution_direction != EXEC_REVERSE)
> -                       error (_("Process record: error reading memory at "
> -                                "addr = %s len = %d."),
> -                              paddress (gdbarch, record_list->u.mem.addr),
> -                              record_list->u.mem.len);
> -                     else
> -                       /* Read failed --
> -                          flag entry as not_accessible.  */
> -                       record_list->u.mem.mem_entry_not_accessible = 1;
> -                   }
> -                 else
> -                   {
> -                     if (target_write_memory (record_list->u.mem.addr,
> -                                              record_list->u.mem.val,
> -                                              record_list->u.mem.len))
> -                       {
> -                         if (execution_direction != EXEC_REVERSE)
> -                           error (_("Process record: error writing memory at "
> -                                    "addr = %s len = %d."),
> -                                  paddress (gdbarch, record_list->u.mem.addr),
> -                                  record_list->u.mem.len);
> -                         else
> -                           /* Write failed --
> -                              flag entry as not_accessible.  */
> -                           record_list->u.mem.mem_entry_not_accessible = 1;
> -                       }
> -                     else
> -                       {
> -                         memcpy (record_list->u.mem.val, mem,
> -                                 record_list->u.mem.len);
> -                       }
> -                   }
> -               }
> -           }
> -         else
> +         if (record_list->type == record_end)
>             {
>               if (record_debug > 1)
>                 fprintf_unfiltered (gdb_stdlog,
> @@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops)
>      fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
> 
>    unpush_target (&record_ops);
> +
>    target_kill ();
>  }
> 
> @@ -945,7 +1233,7 @@ record_registers_change (struct regcache
>    record_list = record_arch_list_tail;
> 
>    if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -    record_list_release_first ();
> +    record_list_release_first_insn ();
>    else
>      record_insn_num++;
>  }
> @@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops *
>        record_list = record_arch_list_tail;
> 
>        if (record_insn_num == record_insn_max_num && record_insn_max_num)
> -       record_list_release_first ();
> +       record_list_release_first_insn ();
>        else
>         record_insn_num++;
>      }
> @@ -1138,6 +1426,191 @@ init_record_ops (void)
>  }
> 
>  static void
> +record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
> +                    enum target_signal siggnal)
> +{
> +  record_resume_step = step;
> +  record_resume_siggnal = siggnal;
> +}
> +
> +static void
> +record_core_kill (struct target_ops *ops)
> +{
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
> +
> +  unpush_target (&record_core_ops);
> +}
> +
> +static void
> +record_core_fetch_registers (struct target_ops *ops,
> +                             struct regcache *regcache,
> +                             int regno)
> +{
> +  if (regno < 0)
> +    {
> +      int num = gdbarch_num_regs (get_regcache_arch (regcache));
> +      int i;
> +
> +      for (i = 0; i < num; i ++)
> +        regcache_raw_supply (regcache, i,
> +                             record_core_regbuf + MAX_REGISTER_SIZE * i);
> +    }
> +  else
> +    regcache_raw_supply (regcache, regno,
> +                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +}
> +
> +static void
> +record_core_prepare_to_store (struct regcache *regcache)
> +{
> +}
> +
> +static void
> +record_core_store_registers (struct target_ops *ops,
> +                             struct regcache *regcache,
> +                             int regno)
> +{
> +  if (record_gdb_operation_disable)
> +    regcache_raw_collect (regcache, regno,
> +                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
> +  else
> +    error (_("You can't do that without a process to debug."));
> +}
> +
> +static LONGEST
> +record_core_xfer_partial (struct target_ops *ops, enum target_object object,
> +                         const char *annex, gdb_byte *readbuf,
> +                         const gdb_byte *writebuf, ULONGEST offset,
> +                          LONGEST len)
> +{
> +   if (object == TARGET_OBJECT_MEMORY)
> +     {
> +       if (record_gdb_operation_disable || !writebuf)
> +         {
> +           struct target_section *p;
> +           for (p = record_core_start; p < record_core_end; p++)
> +             {
> +               if (offset >= p->addr)
> +                 {
> +                   struct record_core_buf_entry *entry;
> +
> +                   if (offset >= p->endaddr)
> +                     continue;
> +
> +                   if (offset + len > p->endaddr)
> +                     len = p->endaddr - offset;
> +
> +                   offset -= p->addr;
> +
> +                   /* Read readbuf or write writebuf p, offset, len.  */
> +                   /* Check flags.  */
> +                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
> +                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
> +                     {
> +                       if (readbuf)
> +                         memset (readbuf, 0, len);
> +                       return len;
> +                     }
> +                   /* Get record_core_buf_entry.  */
> +                   for (entry = record_core_buf_list; entry;
> +                        entry = entry->prev)
> +                     if (entry->p == p)
> +                       break;
> +                   if (writebuf)
> +                     {
> +                       if (!entry)
> +                         {
> +                           /* Add a new entry.  */
> +                           entry
> +                             = (struct record_core_buf_entry *)
> +                                 xmalloc
> +                                   (sizeof (struct record_core_buf_entry));
> +                           entry->p = p;
> +                           if (!bfd_malloc_and_get_section (p->bfd,
> +                                                            p->the_bfd_section,
> +                                                            &entry->buf))
> +                             {
> +                               xfree (entry);
> +                               return 0;
> +                             }
> +                           entry->prev = record_core_buf_list;
> +                           record_core_buf_list = entry;
> +                         }
> +
> +                        memcpy (entry->buf + offset, writebuf, (size_t) len);
> +                     }
> +                   else
> +                     {
> +                       if (!entry)
> +                         return record_beneath_to_xfer_partial
> +                                  (record_beneath_to_xfer_partial_ops,
> +                                   object, annex, readbuf, writebuf,
> +                                   offset, len);
> +
> +                       memcpy (readbuf, entry->buf + offset, (size_t) len);
> +                     }
> +
> +                   return len;
> +                 }
> +             }
> +
> +           return -1;
> +         }
> +       else
> +         error (_("You can't do that without a process to debug."));
> +     }
> +
> +  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
> +                                         object, annex, readbuf, writebuf,
> +                                         offset, len);
> +}
> +
> +static int
> +record_core_insert_breakpoint (struct gdbarch *gdbarch,
> +                              struct bp_target_info *bp_tgt)
> +{
> +  return 0;
> +}
> +
> +static int
> +record_core_remove_breakpoint (struct gdbarch *gdbarch,
> +                              struct bp_target_info *bp_tgt)
> +{
> +  return 0;
> +}
> +
> +int
> +record_core_has_execution (struct target_ops *ops)
> +{
> +  return 1;
> +}
> +
> +static void
> +init_record_core_ops (void)
> +{
> +  record_core_ops.to_shortname = "record_core";
> +  record_core_ops.to_longname = "Process record and replay target";
> +  record_core_ops.to_doc =
> +    "Log program while executing and replay execution from log.";
> +  record_core_ops.to_open = record_open;
> +  record_core_ops.to_close = record_close;
> +  record_core_ops.to_resume = record_core_resume;
> +  record_core_ops.to_wait = record_wait;
> +  record_core_ops.to_kill = record_core_kill;
> +  record_core_ops.to_fetch_registers = record_core_fetch_registers;
> +  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
> +  record_core_ops.to_store_registers = record_core_store_registers;
> +  record_core_ops.to_xfer_partial = record_core_xfer_partial;
> +  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
> +  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
> +  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
> +  record_core_ops.to_has_execution = record_core_has_execution;
> +  record_core_ops.to_stratum = record_stratum;
> +  record_core_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)
>  {
> @@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t
>    execute_command ("target record", from_tty);
>  }
> 
> +static void
> +cmd_record_load (char *args, int from_tty)
> +{
> +  char buf[512];
> +
> +  snprintf (buf, 512, "target record %s", args);
> +  execute_command (buf, from_tty);
> +}
> +
> +static inline void
> +record_write_dump (char *recfilename, int fildes, const void *buf,
> +                   size_t nbyte)
> +{
> +  if (write (fildes, buf, nbyte) != nbyte)
> +    error (_("Failed to write dump of execution records to '%s'."),
> +           recfilename);
> +}
> +
> +/* Record log save-file format
> +   Version 1
> +
> +   Header:
> +     4 bytes: magic number htonl(0x20090726).
> +       NOTE: be sure to change whenever this file format changes!
> +
> +   Records:
> +     record_end:
> +       1 byte:  record type (record_end).
> +     record_reg:
> +       1 byte:  record type (record_reg).
> +       8 bytes: register id (network byte order).
> +       MAX_REGISTER_SIZE bytes: register value.
> +     record_mem:
> +       1 byte:  record type (record_mem).
> +       8 bytes: memory address (network byte order).
> +       8 bytes: memory length (network byte order).
> +       n bytes: memory value (n == memory length).
> +*/
> +
> +/* Dump the execution log to a file.  */
> +
> +static void
> +cmd_record_dump (char *args, int from_tty)
> +{
> +  char *recfilename, recfilename_buffer[40];
> +  int recfd;
> +  struct record_entry *cur_record_list;
> +  uint32_t magic;
> +  struct regcache *regcache;
> +  struct gdbarch *gdbarch;
> +  struct cleanup *old_cleanups;
> +  struct cleanup *set_cleanups;
> +
> +  if (strcmp (current_target.to_shortname, "record") != 0)
> +    error (_("Process record is not started.\n"));
> +
> +  if (args && *args)
> +    recfilename = args;
> +  else
> +    {
> +      /* Default corefile name is "gdb_record.PID".  */
> +      snprintf (recfilename_buffer, 40, "gdb_record.%d",
> +                PIDGET (inferior_ptid));
> +      recfilename = recfilename_buffer;
> +    }
> +
> +  /* Open the dump file.  */
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog,
> +                        _("Saving recording to file '%s'\n"),
> +                        recfilename);
> +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> +                S_IRUSR | S_IWUSR);
> +  if (recfd < 0)
> +    error (_("Failed to open '%s' for dump execution records: %s"),
> +           recfilename, strerror (errno));
> +  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
> +
> +  /* Save the current record entry to "cur_record_list".  */
> +  cur_record_list = record_list;
> +
> +  /* Get the values of regcache and gdbarch.  */
> +  regcache = get_current_regcache ();
> +  gdbarch = get_regcache_arch (regcache);
> +
> +  /* Disable the GDB operation record.  */
> +  set_cleanups = record_gdb_operation_disable_set ();
> +
> +  /* Write the magic code.  */
> +  magic = RECORD_FILE_MAGIC;
> +  if (record_debug)
> +    fprintf_unfiltered (gdb_stdlog, _("\
> +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
> +                        magic);
> +  record_write_dump (recfilename, recfd, &magic, 4);
> +
> +  /* Reverse execute to the begin of record list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == &record_first)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  /* Dump the entries to recfd and forward execute to the end of
> +     record list.  */
> +  while (1)
> +    {
> +      /* Dump entry.  */
> +      if (record_list != &record_first)
> +        {
> +          uint8_t tmpu8;
> +          uint64_t tmpu64;
> +
> +          tmpu8 = record_list->type;
> +          record_write_dump (recfilename, recfd, &tmpu8, 1);
> +
> +          switch (record_list->type)
> +            {
> +            case record_reg: /* reg */
> +              if (record_debug)
> +                fprintf_unfiltered (gdb_stdlog, _("\
> +Writing register %d (1 plus 8 plus %d bytes)\n"),
> +                                    record_list->u.reg.num,
> +                                    MAX_REGISTER_SIZE);
> +
> +              tmpu64 = record_list->u.reg.num;
> +              if (BYTE_ORDER == LITTLE_ENDIAN)
> +                tmpu64 = bswap_64 (tmpu64);
> +              record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +              record_write_dump (recfilename, recfd, record_list->u.reg.val,
> +                                 MAX_REGISTER_SIZE);
> +              break;
> +
> +            case record_mem: /* mem */
> +              if (!record_list->u.mem.mem_entry_not_accessible)
> +                {
> +                  if (record_debug)
> +                    fprintf_unfiltered (gdb_stdlog, _("\
> +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
> +                                        paddress (gdbarch,
> +                                                  record_list->u.mem.addr),
> +                                        record_list->u.mem.len);
> +
> +                  tmpu64 = record_list->u.mem.addr;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  tmpu64 = record_list->u.mem.len;
> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
> +                    tmpu64 = bswap_64 (tmpu64);
> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
> +
> +                  record_write_dump (recfilename, recfd,
> +                                     record_list->u.mem.val,
> +                                     record_list->u.mem.len);
> +                }
> +              break;
> +
> +              case record_end:
> +                /* FIXME: record the contents of record_end rec.  */
> +                if (record_debug)
> +                  fprintf_unfiltered (gdb_stdlog,
> +                                      _("Writing record_end (1 byte)\n"));
> +                break;
> +            }
> +        }
> +
> +      /* Execute entry.  */
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->next)
> +        record_list = record_list->next;
> +      else
> +        break;
> +    }
> +
> +  /* Reverse execute to cur_record_list.  */
> +  while (1)
> +    {
> +      /* Check for beginning and end of log.  */
> +      if (record_list == cur_record_list)
> +        break;
> +
> +      record_exec_entry (regcache, gdbarch, record_list);
> +
> +      if (record_list->prev)
> +        record_list = record_list->prev;
> +    }
> +
> +  do_cleanups (set_cleanups);
> +  do_cleanups (old_cleanups);
> +
> +  /* Succeeded.  */
> +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
> +                                  "records to `%s'.\n"),
> +                    recfilename);
> +}
> +
>  /* Truncate the record log from the present point
>     of replay until the end.  */
> 
> @@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt
>      {
>        if (!record_list || !from_tty || query (_("Delete recorded log and "
>                                                 "stop recording?")))
> -       unpush_target (&record_ops);
> +        {
> +          if (strcmp (current_target.to_shortname, "record") == 0)
> +           unpush_target (&record_ops);
> +          else
> +            unpush_target (&record_core_ops);
> +        }
>      }
>    else
>      printf_unfiltered (_("Process record is not started.\n"));
> @@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int
>                            "the first ones?\n"));
> 
>        while (record_insn_num > record_insn_max_num)
> -       record_list_release_first ();
> +       record_list_release_first_insn ();
>      }
>  }
> 
> @@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro
>  void
>  _initialize_record (void)
>  {
> +  struct cmd_list_element *c;
> +
>    /* Init record_first.  */
>    record_first.prev = NULL;
>    record_first.next = NULL;
> @@ -1250,6 +1936,8 @@ _initialize_record (void)
> 
>    init_record_ops ();
>    add_target (&record_ops);
> +  init_record_core_ops ();
> +  add_target (&record_core_ops);
> 
>    add_setshow_zinteger_cmd ("record", no_class, &record_debug,
>                             _("Set debugging of record/replay feature."),
> @@ -1259,9 +1947,10 @@ _initialize_record (void)
>                             NULL, show_record_debug, &setdebuglist,
>                             &showdebuglist);
> 
> -  add_prefix_cmd ("record", class_obscure, cmd_record_start,
> -                 _("Abbreviated form of \"target record\" command."),
> -                 &record_cmdlist, "record ", 0, &cmdlist);
> +  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
> +                     _("Abbreviated form of \"target record\" command."),
> +                     &record_cmdlist, "record ", 0, &cmdlist);
> +  set_cmd_completer (c, filename_completer);
>    add_com_alias ("rec", "record", class_obscure, 1);
>    add_prefix_cmd ("record", class_support, set_record_command,
>                   _("Set record options"), &set_record_cmdlist,
> @@ -1276,6 +1965,16 @@ _initialize_record (void)
>                   "info record ", 0, &infolist);
>    add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
> 
> +  c = add_cmd ("load", class_obscure, cmd_record_load,
> +              _("Load previously dumped execution records from \
> +a file given as argument."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
> +              _("Dump the execution records to a file.\n\
> +Argument is optional filename.  Default filename is
> 'gdb_record.<process_id>'."),
> +               &record_cmdlist);
> +  set_cmd_completer (c, filename_completer);
> 
>    add_cmd ("delete", class_obscure, cmd_record_delete,
>            _("Delete the rest of execution log and start recording it anew."),


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-24 18:32                                               ` Michael Snyder
@ 2009-08-25  8:47                                                 ` Hui Zhu
  2009-08-26  1:40                                                   ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-25  8:47 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote:
> Hui Zhu wrote:
>>
>> On Mon, Aug 24, 2009 at 07:21, Michael Snyder<msnyder@vmware.com> wrote:
>>>
>>> Hui Zhu wrote:
>>>
>>>> Hi Michael,
>>>>
>>>> I make a new version patch.  It has a lot of changes.
>>>> Remove record_core and add a new target record_core for core target to
>>>> make the code more clear.
>>>> Make the load together with record_open.
>>>>
>>>> Please help me review it.
>>>
>>> Hi Hui,
>>>
>>> In this review, I'm going to comment only on the parts of the
>>> patch that relate to the record_core_ops (ie. pushing the
>>> record stratum on top of the core file stratum).
>>>
>>> Those parts of the patch are much improved.  I like this
>>> version a lot better.  Thanks for reworking it.
>>>
>>
>>
>> Hi Michael,
>>
>> Thanks for your review.  I make a new patch that update the memset code
>> to:
>
> Way cool, thanks!
>
> Now I can comment on the dump/load part of the patch.
> It does not seem as if previous issues have been addressed:
> 1) Core file and record file are separate, no way to verify
>   that they correspond to each other.

> 2) Previous log not cleared when loading a new log.
For it, I think we don't need worry about it.  Because I make the
record load together with record_open.  So each time when we load
record, the record list is empty.

>
> Have you considered combining this patch with the idea that I
> proposed -- storing the record log in the core file?  Seems like
> that would be very easy now -- gdb already has the core file open,
> and there is a global pointer to the BFD.
> See gdbcore.h:extern bfd* core_bfd;

Cool.  If we just want record_core_ops support load, we can remove the
cmd "record load".  :)

>
> In fact, just for fun, I've merged the two patches in my
> local tree.  It seems to work quite well.  I can just post
> it here if you like, or here's a hint to show you how it goes:


Why not?  Please post it.  I can not wait to try it.  :)


Thanks,
Hui

>
> static void
> record_load (void)
> {
>  int recfd;
>  uint32_t magic;
>  struct cleanup *old_cleanups2;
>  struct record_entry *rec;
>  int insn_number = 0;
>  asection *osec;
>  void nullify_last_target_wait_ptid (void);
>
>  /* We load the execution log from the open core bfd,
>     if there is one.  */
>  if (core_bfd == NULL)
>    return;
>
>  if (record_debug)
>    fprintf_filtered (gdb_stdlog,
>                      _("Restoring recording from core file.\n"));
>
>  /* Now need to find our special note section.  */
>  osec = bfd_get_section_by_name (core_bfd, "null0");
>
>



>>            if (target_read_memory (entry->u.mem.addr, mem,
>> entry->u.mem.len))
>>              {
>>                record_list->u.mem.mem_entry_not_accessible = 1;
>>                if (record_debug)
>>                  warning (_("Process record: error reading memory at "
>>                             "addr = %s len = %d."),
>>                           paddress (gdbarch, entry->u.mem.addr),
>>                           entry->u.mem.len);
>>              }
>>            else
>>              {
>>                if (target_write_memory (entry->u.mem.addr,
>> entry->u.mem.val,
>>                                         entry->u.mem.len))
>>                  {
>>                    record_list->u.mem.mem_entry_not_accessible = 1;
>>                    if (record_debug)
>>                      warning (_("Process record: error writing memory at "
>>                                 "addr = %s len = %d."),
>>                               paddress (gdbarch, entry->u.mem.addr),
>>                               entry->u.mem.len);
>>                  }
>>                else
>>                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
>>              }
>>
>> Please help me review it.
>>
>> Thanks,
>> Hui
>>
>> ---
>>  record.c |  951
>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
>>  1 file changed, 825 insertions(+), 126 deletions(-)
>>
>> --- a/record.c
>> +++ b/record.c
>> @@ -23,15 +23,23 @@
>>  #include "gdbthread.h"
>>  #include "event-top.h"
>>  #include "exceptions.h"
>> +#include "completer.h"
>> +#include "arch-utils.h"
>> +#include "gdbcore.h"
>> +#include "exec.h"
>>  #include "record.h"
>>
>> +#include <byteswap.h>
>>  #include <signal.h>
>> +#include <netinet/in.h>
>>
>>  #define DEFAULT_RECORD_INSN_MAX_NUM    200000
>>
>>  #define RECORD_IS_REPLAY \
>>      (record_list->next || execution_direction == EXEC_REVERSE)
>>
>> +#define RECORD_FILE_MAGIC      htonl(0x20090726)
>> +
>>  /* These are the core struct of record function.
>>
>>    An record_entry is a record of the value change of a register
>> @@ -78,9 +86,22 @@ struct record_entry
>>   } u;
>>  };
>>
>> +struct record_core_buf_entry
>> +{
>> +  struct record_core_buf_entry *prev;
>> +  struct target_section *p;
>> +  bfd_byte *buf;
>> +};
>> +
>>  /* This is the debug switch for process record.  */
>>  int record_debug = 0;
>>
>> +/* Record buf with core target.  */
>> +static gdb_byte *record_core_regbuf = NULL;
>> +static struct target_section *record_core_start;
>> +static struct target_section *record_core_end;
>> +static struct record_core_buf_entry *record_core_buf_list = NULL;
>> +
>>  /* These list is for execution log.  */
>>  static struct record_entry record_first;
>>  static struct record_entry *record_list = &record_first;
>> @@ -94,6 +115,7 @@ static int record_insn_num = 0;
>>
>>  /* The target_ops of process record.  */
>>  static struct target_ops record_ops;
>> +static struct target_ops record_core_ops;
>>
>>  /* The beneath function pointers.  */
>>  static struct target_ops *record_beneath_to_resume_ops;
>> @@ -169,7 +191,7 @@ record_list_release_next (void)
>>  }
>>
>>  static void
>> -record_list_release_first (void)
>> +record_list_release_first_insn (void)
>>  {
>>   struct record_entry *tmp = NULL;
>>   enum record_type type;
>> @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal)
>>              if (q)
>>                record_stop_at_limit = 0;
>>              else
>> -               error (_("Process record: inferior program stopped."));
>> +               error (_("Process record: stoped by user."));
>>            }
>>        }
>>     }
>>  }
>>
>> +static void
>> +record_arch_list_cleanups (void *ignore)
>> +{
>> +  record_list_release (record_arch_list_tail);
>> +}
>> +
>>  /* Before inferior step (when GDB record the running message, inferior
>>    only can step), GDB will call this function to record the values to
>>    record_list.  This function will call gdbarch_process_record to
>>    record the running message of inferior and set them to
>>    record_arch_list, and add it to record_list.  */
>>
>> -static void
>> -record_message_cleanups (void *ignore)
>> -{
>> -  record_list_release (record_arch_list_tail);
>> -}
>> -
>>  static int
>>  record_message (void *args)
>>  {
>>   int ret;
>>   struct regcache *regcache = args;
>> -  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups,
>> 0);
>> +  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups,
>> 0);
>>
>>   record_arch_list_head = NULL;
>>   record_arch_list_tail = NULL;
>> @@ -386,7 +408,7 @@ record_message (void *args)
>>   record_list = record_arch_list_tail;
>>
>>   if (record_insn_num == record_insn_max_num && record_insn_max_num)
>> -    record_list_release_first ();
>> +    record_list_release_first_insn ();
>>   else
>>     record_insn_num++;
>>
>> @@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void)
>>   return old_cleanups;
>>  }
>>
>> +static inline void
>> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
>> +                   struct record_entry *entry)
>> +{
>> +  switch (entry->type)
>> +    {
>> +    case record_reg: /* reg */
>> +      {
>> +        gdb_byte reg[MAX_REGISTER_SIZE];
>> +
>> +        if (record_debug > 1)
>> +          fprintf_unfiltered (gdb_stdlog,
>> +                              "Process record: record_reg %s to "
>> +                              "inferior num = %d.\n",
>> +                              host_address_to_string (entry),
>> +                              entry->u.reg.num);
>> +
>> +        regcache_cooked_read (regcache, entry->u.reg.num, reg);
>> +        regcache_cooked_write (regcache, entry->u.reg.num,
>> entry->u.reg.val);
>> +        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
>> +      }
>> +      break;
>> +
>> +    case record_mem: /* mem */
>> +      {
>> +        if (!record_list->u.mem.mem_entry_not_accessible)
>> +          {
>> +            gdb_byte *mem = alloca (entry->u.mem.len);
>> +
>> +            if (record_debug > 1)
>> +              fprintf_unfiltered (gdb_stdlog,
>> +                                  "Process record: record_mem %s to "
>> +                                  "inferior addr = %s len = %d.\n",
>> +                                  host_address_to_string (entry),
>> +                                  paddress (gdbarch, entry->u.mem.addr),
>> +                                  record_list->u.mem.len);
>> +
>> +            if (target_read_memory (entry->u.mem.addr, mem,
>> entry->u.mem.len))
>> +              {
>> +                record_list->u.mem.mem_entry_not_accessible = 1;
>> +                if (record_debug)
>> +                  warning (_("Process record: error reading memory at "
>> +                             "addr = %s len = %d."),
>> +                           paddress (gdbarch, entry->u.mem.addr),
>> +                           entry->u.mem.len);
>> +              }
>> +            else
>> +              {
>> +                if (target_write_memory (entry->u.mem.addr,
>> entry->u.mem.val,
>> +                                         entry->u.mem.len))
>> +                  {
>> +                    record_list->u.mem.mem_entry_not_accessible = 1;
>> +                    if (record_debug)
>> +                      warning (_("Process record: error writing memory at
>> "
>> +                                 "addr = %s len = %d."),
>> +                               paddress (gdbarch, entry->u.mem.addr),
>> +                               entry->u.mem.len);
>> +                  }
>> +                else
>> +                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
>> +              }
>> +          }
>> +      }
>> +      break;
>> +    }
>> +}
>> +
>> +static inline void
>> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte)
>> +{
>> +  if (read (fildes, buf, nbyte) != nbyte)
>> +    error (_("Failed to read dump of execution records in '%s'."),
>> +           recfilename);
>> +}
>> +
>>  static void
>> -record_open (char *name, int from_tty)
>> +record_fd_cleanups (void *recfdp)
>>  {
>> -  struct target_ops *t;
>> +  int recfd = *(int *) recfdp;
>> +  close (recfd);
>> +}
>>
>> -  if (record_debug)
>> -    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
>> +/* Load the execution log from a file.  */
>> +
>> +static void
>> +record_load (char *name)
>> +{
>> +  int recfd;
>> +  uint32_t magic;
>> +  struct cleanup *old_cleanups;
>> +  struct cleanup *old_cleanups2;
>> +  struct record_entry *rec;
>> +  int insn_number = 0;
>> +
>> +  if (!name || (name && !*name))
>> +    return;
>> +
>> +  /* Open the load file.  */
>> +  recfd = open (name, O_RDONLY | O_BINARY);
>> +  if (recfd < 0)
>> +    error (_("Failed to open '%s' for loading execution records: %s"),
>> +           name, strerror (errno));
>> +  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
>> +
>> +  /* Check the magic code.  */
>> +  record_read_dump (name, recfd, &magic, 4);
>> +  if (magic != RECORD_FILE_MAGIC)
>> +    error (_("'%s' is not a valid dump of execution records."), name);
>> +
>> +  /* Load the entries in recfd to the record_arch_list_head and
>> +     record_arch_list_tail.  */
>> +  record_arch_list_head = NULL;
>> +  record_arch_list_tail = NULL;
>> +  old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
>> +
>> +  while (1)
>> +    {
>> +      int ret;
>> +      uint8_t tmpu8;
>> +      uint64_t tmpu64;
>> +
>> +      ret = read (recfd, &tmpu8, 1);
>> +      if (ret < 0)
>> +        error (_("Failed to read dump of execution records in '%s'."),
>> name);
>> +      if (ret == 0)
>> +        break;
>> +
>> +      switch (tmpu8)
>> +        {
>> +        case record_reg: /* reg */
>> +          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;
>> +
>> +          /* Get num.  */
>> +          record_read_dump (name, recfd, &tmpu64, 8);
>> +          if (BYTE_ORDER == LITTLE_ENDIAN)
>> +            tmpu64 = bswap_64 (tmpu64);
>> +          rec->u.reg.num = tmpu64;
>> +
>> +          /* Get val.  */
>> +          record_read_dump (name, recfd, rec->u.reg.val,
>> MAX_REGISTER_SIZE);
>> +
>> +          if (record_debug)
>> +            fprintf_unfiltered (gdb_stdlog, _("\
>> +Reading register %d (1 plus 8 plus %d bytes)\n"),
>> +                                rec->u.reg.num,
>> +                                MAX_REGISTER_SIZE);
>> +
>> +          record_arch_list_add (rec);
>> +          break;
>> +
>> +        case record_mem: /* mem */
>> +          rec = (struct record_entry *) xmalloc (sizeof (struct
>> record_entry));
>> +          rec->prev = NULL;
>> +          rec->next = NULL;
>> +          rec->type = record_mem;
>> +
>> +          /* Get addr.  */
>> +          record_read_dump (name, recfd, &tmpu64, 8);
>> +          if (BYTE_ORDER == LITTLE_ENDIAN)
>> +            tmpu64 = bswap_64 (tmpu64);
>> +          rec->u.mem.addr = tmpu64;
>> +
>> +          /* Get len.  */
>> +          record_read_dump (name, recfd, &tmpu64, 8);
>> +          if (BYTE_ORDER == LITTLE_ENDIAN)
>> +            tmpu64 = bswap_64 (tmpu64);
>> +          rec->u.mem.len = tmpu64;
>> +          rec->u.mem.mem_entry_not_accessible = 0;
>> +          rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
>> +
>> +          /* Get val.  */
>> +          record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len);
>> +
>> +          if (record_debug)
>> +            fprintf_unfiltered (gdb_stdlog, _("\
>> +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
>> +                                        paddress (get_current_arch (),
>> +                                                  rec->u.mem.addr),
>> +                                        rec->u.mem.len);
>> +
>> +          record_arch_list_add (rec);
>> +          break;
>> +
>> +        case record_end: /* end */
>> +          if (record_debug)
>> +            fprintf_unfiltered (gdb_stdlog,
>> +                                _("Reading record_end (1 byte)\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);
>> +          insn_number ++;
>> +          break;
>> +
>> +        default:
>> +          error (_("Format of '%s' is not right."), name);
>> +          break;
>> +        }
>> +    }
>> +
>> +  discard_cleanups (old_cleanups2);
>> +
>> +  /* Add record_arch_list_head to the end of record list.  */
>> +  for (rec = record_list; rec->next; rec = rec->next);
>> +  rec->next = record_arch_list_head;
>> +  record_arch_list_head->prev = rec;
>> +
>> +  /* Update record_insn_num and record_insn_max_num.  */
>> +  record_insn_num += insn_number;
>> +  if (record_insn_num > record_insn_max_num)
>> +    {
>> +      record_insn_max_num = record_insn_num;
>> +      warning (_("Auto increase record/replay buffer limit to %d."),
>> +               record_insn_max_num);
>> +    }
>> +
>> +  do_cleanups (old_cleanups);
>> +
>> +  /* Succeeded.  */
>> +  fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name);
>> +}
>> +
>> +static struct target_ops *tmp_to_resume_ops;
>> +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
>> +                              enum target_signal);
>> +static struct target_ops *tmp_to_wait_ops;
>> +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
>> +                              struct target_waitstatus *,
>> +                              int);
>> +static struct target_ops *tmp_to_store_registers_ops;
>> +static void (*tmp_to_store_registers) (struct target_ops *,
>> +                                       struct regcache *,
>> +                                       int regno);
>> +static struct target_ops *tmp_to_xfer_partial_ops;
>> +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
>> +                                       enum target_object object,
>> +                                       const char *annex,
>> +                                       gdb_byte *readbuf,
>> +                                       const gdb_byte *writebuf,
>> +                                       ULONGEST offset,
>> +                                       LONGEST len);
>> +static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
>> +                                        struct bp_target_info *);
>> +static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
>> +                                        struct bp_target_info *);
>> +
>> +static void
>> +record_core_open_1 (char *name, int from_tty)
>> +{
>> +  struct regcache *regcache = get_current_regcache ();
>> +  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
>> +  int i;
>> +
>> +  if (!name || (name && !*name))
>> +    error (_("Argument for gdb record filename required.\n"));
>> +
>> +  /* Get record_core_regbuf.  */
>> +  target_fetch_registers (regcache, -1);
>> +  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
>> +  for (i = 0; i < regnum; i ++)
>> +    regcache_raw_collect (regcache, i,
>> +                          record_core_regbuf + MAX_REGISTER_SIZE * i);
>> +
>> +  /* Get record_core_start and record_core_end.  */
>> +  if (build_section_table (core_bfd, &record_core_start,
>> &record_core_end))
>> +    {
>> +      xfree (record_core_regbuf);
>> +      record_core_regbuf = NULL;
>> +      error (_("\"%s\": Can't find sections: %s"),
>> +             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
>> +    }
>> +
>> +  push_target (&record_core_ops);
>> +}
>> +
>> +static void
>> +record_open_1 (char *name, int from_tty)
>> +{
>> +  struct target_ops *t;
>>
>>   /* check exec */
>>   if (!target_has_execution)
>> @@ -438,6 +738,28 @@ record_open (char *name, int from_tty)
>>     error (_("Process record: the current architecture doesn't support "
>>             "record function."));
>>
>> +  if (!tmp_to_resume)
>> +    error (_("Process record can't get to_resume."));
>> +  if (!tmp_to_wait)
>> +    error (_("Process record can't get to_wait."));
>> +  if (!tmp_to_store_registers)
>> +    error (_("Process record can't get to_store_registers."));
>> +  if (!tmp_to_insert_breakpoint)
>> +    error (_("Process record can't get to_insert_breakpoint."));
>> +  if (!tmp_to_remove_breakpoint)
>> +    error (_("Process record can't get to_remove_breakpoint."));
>> +
>> +  push_target (&record_ops);
>> +}
>> +
>> +static void
>> +record_open (char *name, int from_tty)
>> +{
>> +  struct target_ops *t;
>> +
>> +  if (record_debug)
>> +    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
>> +
>>   /* Check if record target is already running.  */
>>   if (current_target.to_stratum == record_stratum)
>>     {
>> @@ -447,70 +769,102 @@ record_open (char *name, int from_tty)
>>        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;
>> +  /* Reset the tmp beneath pointers.  */
>> +  tmp_to_resume_ops = NULL;
>> +  tmp_to_resume = NULL;
>> +  tmp_to_wait_ops = NULL;
>> +  tmp_to_wait = NULL;
>> +  tmp_to_store_registers_ops = NULL;
>> +  tmp_to_store_registers = NULL;
>> +  tmp_to_xfer_partial_ops = NULL;
>> +  tmp_to_xfer_partial = NULL;
>> +  tmp_to_insert_breakpoint = NULL;
>> +  tmp_to_remove_breakpoint = NULL;
>>
>>   /* Set the beneath function pointers.  */
>>   for (t = current_target.beneath; t != NULL; t = t->beneath)
>>     {
>> -      if (!record_beneath_to_resume)
>> +      if (!tmp_to_resume)
>>         {
>> -         record_beneath_to_resume = t->to_resume;
>> -         record_beneath_to_resume_ops = t;
>> +         tmp_to_resume = t->to_resume;
>> +         tmp_to_resume_ops = t;
>>         }
>> -      if (!record_beneath_to_wait)
>> +      if (!tmp_to_wait)
>>         {
>> -         record_beneath_to_wait = t->to_wait;
>> -         record_beneath_to_wait_ops = t;
>> +         tmp_to_wait = t->to_wait;
>> +         tmp_to_wait_ops = t;
>>         }
>> -      if (!record_beneath_to_store_registers)
>> +      if (!tmp_to_store_registers)
>>         {
>> -         record_beneath_to_store_registers = t->to_store_registers;
>> -         record_beneath_to_store_registers_ops = t;
>> +         tmp_to_store_registers = t->to_store_registers;
>> +         tmp_to_store_registers_ops = t;
>>         }
>> -      if (!record_beneath_to_xfer_partial)
>> +      if (!tmp_to_xfer_partial)
>>         {
>> -         record_beneath_to_xfer_partial = t->to_xfer_partial;
>> -         record_beneath_to_xfer_partial_ops = t;
>> +         tmp_to_xfer_partial = t->to_xfer_partial;
>> +         tmp_to_xfer_partial_ops = t;
>>         }
>> -      if (!record_beneath_to_insert_breakpoint)
>> -       record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
>> -      if (!record_beneath_to_remove_breakpoint)
>> -       record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
>> +      if (!tmp_to_insert_breakpoint)
>> +       tmp_to_insert_breakpoint = t->to_insert_breakpoint;
>> +      if (!tmp_to_remove_breakpoint)
>> +       tmp_to_remove_breakpoint = t->to_remove_breakpoint;
>>     }
>> -  if (!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)
>> +  if (!tmp_to_xfer_partial)
>>     error (_("Process record can't get to_xfer_partial."));
>> -  if (!record_beneath_to_insert_breakpoint)
>> -    error (_("Process record can't get to_insert_breakpoint."));
>> -  if (!record_beneath_to_remove_breakpoint)
>> -    error (_("Process record can't get to_remove_breakpoint."));
>>
>> -  push_target (&record_ops);
>> +  if (current_target.to_stratum == core_stratum)
>> +    record_core_open_1 (name, from_tty);
>> +  else
>> +    record_open_1 (name, from_tty);
>>
>>   /* Reset */
>>   record_insn_num = 0;
>>   record_list = &record_first;
>>   record_list->next = NULL;
>> +
>> +  /* Set the tmp beneath pointers to beneath pointers.  */
>> +  record_beneath_to_resume_ops = tmp_to_resume_ops;
>> +  record_beneath_to_resume = tmp_to_resume;
>> +  record_beneath_to_wait_ops = tmp_to_wait_ops;
>> +  record_beneath_to_wait = tmp_to_wait;
>> +  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
>> +  record_beneath_to_store_registers = tmp_to_store_registers;
>> +  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
>> +  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
>> +  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
>> +  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
>> +
>> +  /* Load if there is argument.  */
>> +  record_load (name);
>>  }
>>
>>  static void
>>  record_close (int quitting)
>>  {
>> +  struct record_core_buf_entry *entry;
>> +
>>   if (record_debug)
>>     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
>>
>>   record_list_release (record_list);
>> +
>> +  /* Release record_core_regbuf.  */
>> +  if (record_core_regbuf)
>> +    {
>> +      xfree (record_core_regbuf);
>> +      record_core_regbuf = NULL;
>> +    }
>> +
>> +  /* Release record_core_buf_list.  */
>> +  if (record_core_buf_list)
>> +    {
>> +      for (entry = record_core_buf_list->prev; entry; entry =
>> entry->prev)
>> +        {
>> +          xfree (record_core_buf_list);
>> +          record_core_buf_list = entry;
>> +        }
>> +      record_core_buf_list = NULL;
>> +    }
>>  }
>>
>>  static int record_resume_step = 0;
>> @@ -584,7 +938,7 @@ record_wait (struct target_ops *ops,
>>                        "record_resume_step = %d\n",
>>                        record_resume_step);
>>
>> -  if (!RECORD_IS_REPLAY)
>> +  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
>>     {
>>       if (record_resume_error)
>>        {
>> @@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops,
>>              break;
>>            }
>>
>> -         /* Set ptid, register and memory according to record_list.  */
>> -         if (record_list->type == record_reg)
>> -           {
>> -             /* reg */
>> -             gdb_byte reg[MAX_REGISTER_SIZE];
>> -             if (record_debug > 1)
>> -               fprintf_unfiltered (gdb_stdlog,
>> -                                   "Process record: record_reg %s to "
>> -                                   "inferior num = %d.\n",
>> -                                   host_address_to_string (record_list),
>> -                                   record_list->u.reg.num);
>> -             regcache_cooked_read (regcache, record_list->u.reg.num,
>> reg);
>> -             regcache_cooked_write (regcache, record_list->u.reg.num,
>> -                                    record_list->u.reg.val);
>> -             memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
>> -           }
>> -         else if (record_list->type == record_mem)
>> -           {
>> -             /* mem */
>> -             /* Nothing to do if the entry is flagged not_accessible.  */
>> -             if (!record_list->u.mem.mem_entry_not_accessible)
>> -               {
>> -                 gdb_byte *mem = alloca (record_list->u.mem.len);
>> -                 if (record_debug > 1)
>> -                   fprintf_unfiltered (gdb_stdlog,
>> -                                       "Process record: record_mem %s to
>> "
>> -                                       "inferior addr = %s len = %d.\n",
>> -                                       host_address_to_string
>> (record_list),
>> -                                       paddress (gdbarch,
>> -
>> record_list->u.mem.addr),
>> -                                       record_list->u.mem.len);
>> +          record_exec_entry (regcache, gdbarch, record_list);
>>
>> -                 if (target_read_memory (record_list->u.mem.addr, mem,
>> -                                         record_list->u.mem.len))
>> -                   {
>> -                     if (execution_direction != EXEC_REVERSE)
>> -                       error (_("Process record: error reading memory at
>> "
>> -                                "addr = %s len = %d."),
>> -                              paddress (gdbarch,
>> record_list->u.mem.addr),
>> -                              record_list->u.mem.len);
>> -                     else
>> -                       /* Read failed --
>> -                          flag entry as not_accessible.  */
>> -                       record_list->u.mem.mem_entry_not_accessible = 1;
>> -                   }
>> -                 else
>> -                   {
>> -                     if (target_write_memory (record_list->u.mem.addr,
>> -                                              record_list->u.mem.val,
>> -                                              record_list->u.mem.len))
>> -                       {
>> -                         if (execution_direction != EXEC_REVERSE)
>> -                           error (_("Process record: error writing memory
>> at "
>> -                                    "addr = %s len = %d."),
>> -                                  paddress (gdbarch,
>> record_list->u.mem.addr),
>> -                                  record_list->u.mem.len);
>> -                         else
>> -                           /* Write failed --
>> -                              flag entry as not_accessible.  */
>> -                           record_list->u.mem.mem_entry_not_accessible =
>> 1;
>> -                       }
>> -                     else
>> -                       {
>> -                         memcpy (record_list->u.mem.val, mem,
>> -                                 record_list->u.mem.len);
>> -                       }
>> -                   }
>> -               }
>> -           }
>> -         else
>> +         if (record_list->type == record_end)
>>            {
>>              if (record_debug > 1)
>>                fprintf_unfiltered (gdb_stdlog,
>> @@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops)
>>     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
>>
>>   unpush_target (&record_ops);
>> +
>>   target_kill ();
>>  }
>>
>> @@ -945,7 +1233,7 @@ record_registers_change (struct regcache
>>   record_list = record_arch_list_tail;
>>
>>   if (record_insn_num == record_insn_max_num && record_insn_max_num)
>> -    record_list_release_first ();
>> +    record_list_release_first_insn ();
>>   else
>>     record_insn_num++;
>>  }
>> @@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops *
>>       record_list = record_arch_list_tail;
>>
>>       if (record_insn_num == record_insn_max_num && record_insn_max_num)
>> -       record_list_release_first ();
>> +       record_list_release_first_insn ();
>>       else
>>        record_insn_num++;
>>     }
>> @@ -1138,6 +1426,191 @@ init_record_ops (void)
>>  }
>>
>>  static void
>> +record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
>> +                    enum target_signal siggnal)
>> +{
>> +  record_resume_step = step;
>> +  record_resume_siggnal = siggnal;
>> +}
>> +
>> +static void
>> +record_core_kill (struct target_ops *ops)
>> +{
>> +  if (record_debug)
>> +    fprintf_unfiltered (gdb_stdlog, "Process record:
>> record_core_kill\n");
>> +
>> +  unpush_target (&record_core_ops);
>> +}
>> +
>> +static void
>> +record_core_fetch_registers (struct target_ops *ops,
>> +                             struct regcache *regcache,
>> +                             int regno)
>> +{
>> +  if (regno < 0)
>> +    {
>> +      int num = gdbarch_num_regs (get_regcache_arch (regcache));
>> +      int i;
>> +
>> +      for (i = 0; i < num; i ++)
>> +        regcache_raw_supply (regcache, i,
>> +                             record_core_regbuf + MAX_REGISTER_SIZE * i);
>> +    }
>> +  else
>> +    regcache_raw_supply (regcache, regno,
>> +                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
>> +}
>> +
>> +static void
>> +record_core_prepare_to_store (struct regcache *regcache)
>> +{
>> +}
>> +
>> +static void
>> +record_core_store_registers (struct target_ops *ops,
>> +                             struct regcache *regcache,
>> +                             int regno)
>> +{
>> +  if (record_gdb_operation_disable)
>> +    regcache_raw_collect (regcache, regno,
>> +                          record_core_regbuf + MAX_REGISTER_SIZE *
>> regno);
>> +  else
>> +    error (_("You can't do that without a process to debug."));
>> +}
>> +
>> +static LONGEST
>> +record_core_xfer_partial (struct target_ops *ops, enum target_object
>> object,
>> +                         const char *annex, gdb_byte *readbuf,
>> +                         const gdb_byte *writebuf, ULONGEST offset,
>> +                          LONGEST len)
>> +{
>> +   if (object == TARGET_OBJECT_MEMORY)
>> +     {
>> +       if (record_gdb_operation_disable || !writebuf)
>> +         {
>> +           struct target_section *p;
>> +           for (p = record_core_start; p < record_core_end; p++)
>> +             {
>> +               if (offset >= p->addr)
>> +                 {
>> +                   struct record_core_buf_entry *entry;
>> +
>> +                   if (offset >= p->endaddr)
>> +                     continue;
>> +
>> +                   if (offset + len > p->endaddr)
>> +                     len = p->endaddr - offset;
>> +
>> +                   offset -= p->addr;
>> +
>> +                   /* Read readbuf or write writebuf p, offset, len.  */
>> +                   /* Check flags.  */
>> +                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
>> +                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS)
>> == 0)
>> +                     {
>> +                       if (readbuf)
>> +                         memset (readbuf, 0, len);
>> +                       return len;
>> +                     }
>> +                   /* Get record_core_buf_entry.  */
>> +                   for (entry = record_core_buf_list; entry;
>> +                        entry = entry->prev)
>> +                     if (entry->p == p)
>> +                       break;
>> +                   if (writebuf)
>> +                     {
>> +                       if (!entry)
>> +                         {
>> +                           /* Add a new entry.  */
>> +                           entry
>> +                             = (struct record_core_buf_entry *)
>> +                                 xmalloc
>> +                                   (sizeof (struct
>> record_core_buf_entry));
>> +                           entry->p = p;
>> +                           if (!bfd_malloc_and_get_section (p->bfd,
>> +
>>  p->the_bfd_section,
>> +                                                            &entry->buf))
>> +                             {
>> +                               xfree (entry);
>> +                               return 0;
>> +                             }
>> +                           entry->prev = record_core_buf_list;
>> +                           record_core_buf_list = entry;
>> +                         }
>> +
>> +                        memcpy (entry->buf + offset, writebuf, (size_t)
>> len);
>> +                     }
>> +                   else
>> +                     {
>> +                       if (!entry)
>> +                         return record_beneath_to_xfer_partial
>> +                                  (record_beneath_to_xfer_partial_ops,
>> +                                   object, annex, readbuf, writebuf,
>> +                                   offset, len);
>> +
>> +                       memcpy (readbuf, entry->buf + offset, (size_t)
>> len);
>> +                     }
>> +
>> +                   return len;
>> +                 }
>> +             }
>> +
>> +           return -1;
>> +         }
>> +       else
>> +         error (_("You can't do that without a process to debug."));
>> +     }
>> +
>> +  return record_beneath_to_xfer_partial
>> (record_beneath_to_xfer_partial_ops,
>> +                                         object, annex, readbuf,
>> writebuf,
>> +                                         offset, len);
>> +}
>> +
>> +static int
>> +record_core_insert_breakpoint (struct gdbarch *gdbarch,
>> +                              struct bp_target_info *bp_tgt)
>> +{
>> +  return 0;
>> +}
>> +
>> +static int
>> +record_core_remove_breakpoint (struct gdbarch *gdbarch,
>> +                              struct bp_target_info *bp_tgt)
>> +{
>> +  return 0;
>> +}
>> +
>> +int
>> +record_core_has_execution (struct target_ops *ops)
>> +{
>> +  return 1;
>> +}
>> +
>> +static void
>> +init_record_core_ops (void)
>> +{
>> +  record_core_ops.to_shortname = "record_core";
>> +  record_core_ops.to_longname = "Process record and replay target";
>> +  record_core_ops.to_doc =
>> +    "Log program while executing and replay execution from log.";
>> +  record_core_ops.to_open = record_open;
>> +  record_core_ops.to_close = record_close;
>> +  record_core_ops.to_resume = record_core_resume;
>> +  record_core_ops.to_wait = record_wait;
>> +  record_core_ops.to_kill = record_core_kill;
>> +  record_core_ops.to_fetch_registers = record_core_fetch_registers;
>> +  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
>> +  record_core_ops.to_store_registers = record_core_store_registers;
>> +  record_core_ops.to_xfer_partial = record_core_xfer_partial;
>> +  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
>> +  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
>> +  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
>> +  record_core_ops.to_has_execution = record_core_has_execution;
>> +  record_core_ops.to_stratum = record_stratum;
>> +  record_core_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)
>>  {
>> @@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t
>>   execute_command ("target record", from_tty);
>>  }
>>
>> +static void
>> +cmd_record_load (char *args, int from_tty)
>> +{
>> +  char buf[512];
>> +
>> +  snprintf (buf, 512, "target record %s", args);
>> +  execute_command (buf, from_tty);
>> +}
>> +
>> +static inline void
>> +record_write_dump (char *recfilename, int fildes, const void *buf,
>> +                   size_t nbyte)
>> +{
>> +  if (write (fildes, buf, nbyte) != nbyte)
>> +    error (_("Failed to write dump of execution records to '%s'."),
>> +           recfilename);
>> +}
>> +
>> +/* Record log save-file format
>> +   Version 1
>> +
>> +   Header:
>> +     4 bytes: magic number htonl(0x20090726).
>> +       NOTE: be sure to change whenever this file format changes!
>> +
>> +   Records:
>> +     record_end:
>> +       1 byte:  record type (record_end).
>> +     record_reg:
>> +       1 byte:  record type (record_reg).
>> +       8 bytes: register id (network byte order).
>> +       MAX_REGISTER_SIZE bytes: register value.
>> +     record_mem:
>> +       1 byte:  record type (record_mem).
>> +       8 bytes: memory address (network byte order).
>> +       8 bytes: memory length (network byte order).
>> +       n bytes: memory value (n == memory length).
>> +*/
>> +
>> +/* Dump the execution log to a file.  */
>> +
>> +static void
>> +cmd_record_dump (char *args, int from_tty)
>> +{
>> +  char *recfilename, recfilename_buffer[40];
>> +  int recfd;
>> +  struct record_entry *cur_record_list;
>> +  uint32_t magic;
>> +  struct regcache *regcache;
>> +  struct gdbarch *gdbarch;
>> +  struct cleanup *old_cleanups;
>> +  struct cleanup *set_cleanups;
>> +
>> +  if (strcmp (current_target.to_shortname, "record") != 0)
>> +    error (_("Process record is not started.\n"));
>> +
>> +  if (args && *args)
>> +    recfilename = args;
>> +  else
>> +    {
>> +      /* Default corefile name is "gdb_record.PID".  */
>> +      snprintf (recfilename_buffer, 40, "gdb_record.%d",
>> +                PIDGET (inferior_ptid));
>> +      recfilename = recfilename_buffer;
>> +    }
>> +
>> +  /* Open the dump file.  */
>> +  if (record_debug)
>> +    fprintf_unfiltered (gdb_stdlog,
>> +                        _("Saving recording to file '%s'\n"),
>> +                        recfilename);
>> +  recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
>> +                S_IRUSR | S_IWUSR);
>> +  if (recfd < 0)
>> +    error (_("Failed to open '%s' for dump execution records: %s"),
>> +           recfilename, strerror (errno));
>> +  old_cleanups = make_cleanup (record_fd_cleanups, &recfd);
>> +
>> +  /* Save the current record entry to "cur_record_list".  */
>> +  cur_record_list = record_list;
>> +
>> +  /* Get the values of regcache and gdbarch.  */
>> +  regcache = get_current_regcache ();
>> +  gdbarch = get_regcache_arch (regcache);
>> +
>> +  /* Disable the GDB operation record.  */
>> +  set_cleanups = record_gdb_operation_disable_set ();
>> +
>> +  /* Write the magic code.  */
>> +  magic = RECORD_FILE_MAGIC;
>> +  if (record_debug)
>> +    fprintf_unfiltered (gdb_stdlog, _("\
>> +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
>> +                        magic);
>> +  record_write_dump (recfilename, recfd, &magic, 4);
>> +
>> +  /* Reverse execute to the begin of record list.  */
>> +  while (1)
>> +    {
>> +      /* Check for beginning and end of log.  */
>> +      if (record_list == &record_first)
>> +        break;
>> +
>> +      record_exec_entry (regcache, gdbarch, record_list);
>> +
>> +      if (record_list->prev)
>> +        record_list = record_list->prev;
>> +    }
>> +
>> +  /* Dump the entries to recfd and forward execute to the end of
>> +     record list.  */
>> +  while (1)
>> +    {
>> +      /* Dump entry.  */
>> +      if (record_list != &record_first)
>> +        {
>> +          uint8_t tmpu8;
>> +          uint64_t tmpu64;
>> +
>> +          tmpu8 = record_list->type;
>> +          record_write_dump (recfilename, recfd, &tmpu8, 1);
>> +
>> +          switch (record_list->type)
>> +            {
>> +            case record_reg: /* reg */
>> +              if (record_debug)
>> +                fprintf_unfiltered (gdb_stdlog, _("\
>> +Writing register %d (1 plus 8 plus %d bytes)\n"),
>> +                                    record_list->u.reg.num,
>> +                                    MAX_REGISTER_SIZE);
>> +
>> +              tmpu64 = record_list->u.reg.num;
>> +              if (BYTE_ORDER == LITTLE_ENDIAN)
>> +                tmpu64 = bswap_64 (tmpu64);
>> +              record_write_dump (recfilename, recfd, &tmpu64, 8);
>> +
>> +              record_write_dump (recfilename, recfd,
>> record_list->u.reg.val,
>> +                                 MAX_REGISTER_SIZE);
>> +              break;
>> +
>> +            case record_mem: /* mem */
>> +              if (!record_list->u.mem.mem_entry_not_accessible)
>> +                {
>> +                  if (record_debug)
>> +                    fprintf_unfiltered (gdb_stdlog, _("\
>> +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
>> +                                        paddress (gdbarch,
>> +
>>  record_list->u.mem.addr),
>> +                                        record_list->u.mem.len);
>> +
>> +                  tmpu64 = record_list->u.mem.addr;
>> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
>> +                    tmpu64 = bswap_64 (tmpu64);
>> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
>> +
>> +                  tmpu64 = record_list->u.mem.len;
>> +                  if (BYTE_ORDER == LITTLE_ENDIAN)
>> +                    tmpu64 = bswap_64 (tmpu64);
>> +                  record_write_dump (recfilename, recfd, &tmpu64, 8);
>> +
>> +                  record_write_dump (recfilename, recfd,
>> +                                     record_list->u.mem.val,
>> +                                     record_list->u.mem.len);
>> +                }
>> +              break;
>> +
>> +              case record_end:
>> +                /* FIXME: record the contents of record_end rec.  */
>> +                if (record_debug)
>> +                  fprintf_unfiltered (gdb_stdlog,
>> +                                      _("Writing record_end (1
>> byte)\n"));
>> +                break;
>> +            }
>> +        }
>> +
>> +      /* Execute entry.  */
>> +      record_exec_entry (regcache, gdbarch, record_list);
>> +
>> +      if (record_list->next)
>> +        record_list = record_list->next;
>> +      else
>> +        break;
>> +    }
>> +
>> +  /* Reverse execute to cur_record_list.  */
>> +  while (1)
>> +    {
>> +      /* Check for beginning and end of log.  */
>> +      if (record_list == cur_record_list)
>> +        break;
>> +
>> +      record_exec_entry (regcache, gdbarch, record_list);
>> +
>> +      if (record_list->prev)
>> +        record_list = record_list->prev;
>> +    }
>> +
>> +  do_cleanups (set_cleanups);
>> +  do_cleanups (old_cleanups);
>> +
>> +  /* Succeeded.  */
>> +  fprintf_filtered (gdb_stdout, _("Saved dump of execution "
>> +                                  "records to `%s'.\n"),
>> +                    recfilename);
>> +}
>> +
>>  /* Truncate the record log from the present point
>>    of replay until the end.  */
>>
>> @@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt
>>     {
>>       if (!record_list || !from_tty || query (_("Delete recorded log and "
>>                                                "stop recording?")))
>> -       unpush_target (&record_ops);
>> +        {
>> +          if (strcmp (current_target.to_shortname, "record") == 0)
>> +           unpush_target (&record_ops);
>> +          else
>> +            unpush_target (&record_core_ops);
>> +        }
>>     }
>>   else
>>     printf_unfiltered (_("Process record is not started.\n"));
>> @@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int
>>                           "the first ones?\n"));
>>
>>       while (record_insn_num > record_insn_max_num)
>> -       record_list_release_first ();
>> +       record_list_release_first_insn ();
>>     }
>>  }
>>
>> @@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro
>>  void
>>  _initialize_record (void)
>>  {
>> +  struct cmd_list_element *c;
>> +
>>   /* Init record_first.  */
>>   record_first.prev = NULL;
>>   record_first.next = NULL;
>> @@ -1250,6 +1936,8 @@ _initialize_record (void)
>>
>>   init_record_ops ();
>>   add_target (&record_ops);
>> +  init_record_core_ops ();
>> +  add_target (&record_core_ops);
>>
>>   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
>>                            _("Set debugging of record/replay feature."),
>> @@ -1259,9 +1947,10 @@ _initialize_record (void)
>>                            NULL, show_record_debug, &setdebuglist,
>>                            &showdebuglist);
>>
>> -  add_prefix_cmd ("record", class_obscure, cmd_record_start,
>> -                 _("Abbreviated form of \"target record\" command."),
>> -                 &record_cmdlist, "record ", 0, &cmdlist);
>> +  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
>> +                     _("Abbreviated form of \"target record\" command."),
>> +                     &record_cmdlist, "record ", 0, &cmdlist);
>> +  set_cmd_completer (c, filename_completer);
>>   add_com_alias ("rec", "record", class_obscure, 1);
>>   add_prefix_cmd ("record", class_support, set_record_command,
>>                  _("Set record options"), &set_record_cmdlist,
>> @@ -1276,6 +1965,16 @@ _initialize_record (void)
>>                  "info record ", 0, &infolist);
>>   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
>>
>> +  c = add_cmd ("load", class_obscure, cmd_record_load,
>> +              _("Load previously dumped execution records from \
>> +a file given as argument."),
>> +               &record_cmdlist);
>> +  set_cmd_completer (c, filename_completer);
>> +  c = add_cmd ("dump", class_obscure, cmd_record_dump,
>> +              _("Dump the execution records to a file.\n\
>> +Argument is optional filename.  Default filename is
>> 'gdb_record.<process_id>'."),
>> +               &record_cmdlist);
>> +  set_cmd_completer (c, filename_completer);
>>
>>   add_cmd ("delete", class_obscure, cmd_record_delete,
>>           _("Delete the rest of execution log and start recording it
>> anew."),
>
>


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-25  8:47                                                 ` Hui Zhu
@ 2009-08-26  1:40                                                   ` Michael Snyder
  2009-08-26  2:59                                                     ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-26  1:40 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

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

Hui Zhu wrote:
> On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote:

>> Now I can comment on the dump/load part of the patch.
>> It does not seem as if previous issues have been addressed:
>> 1) Core file and record file are separate, no way to verify
>>   that they correspond to each other.
> 
>> 2) Previous log not cleared when loading a new log.
> For it, I think we don't need worry about it.  Because I make the
> record load together with record_open.  So each time when we load
> record, the record list is empty.

I would like to see it made explicit, please.


>> Have you considered combining this patch with the idea that I
>> proposed -- storing the record log in the core file?  Seems like
>> that would be very easy now -- gdb already has the core file open,
>> and there is a global pointer to the BFD.
>> See gdbcore.h:extern bfd* core_bfd;
> 
> Cool.  If we just want record_core_ops support load, we can remove the
> cmd "record load".  :)
> 
>> In fact, just for fun, I've merged the two patches in my
>> local tree.  It seems to work quite well.  I can just post
>> it here if you like, or here's a hint to show you how it goes:
> 
> 
> Why not?  Please post it.  I can not wait to try it.  :)

Great -- attached.


[-- Attachment #2: record.c --]
[-- Type: text/x-csrc, Size: 60387 bytes --]

/* 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 "completer.h"
#include "arch-utils.h"
#include "gdbcore.h"
#include "exec.h"
#include "record.h"

#include <byteswap.h>
#include <signal.h>
#include <netinet/in.h>
#include "elf-bfd.h" /* For dump/load commands.  */
#include "gcore.h"

#define DEFAULT_RECORD_INSN_MAX_NUM	200000

#define RECORD_IS_REPLAY \
     (record_list->next || execution_direction == EXEC_REVERSE)

#define RECORD_FILE_MAGIC	htonl(0x20090726)

/* 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;
  /* Set this flag if target memory for this entry
     can no longer be accessed.  */
  int mem_entry_not_accessible;
  gdb_byte *val;
};

enum record_type
{
  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;
};

struct record_core_buf_entry
{
  struct record_core_buf_entry *prev;
  struct target_section *p;
  bfd_byte *buf;
};

/* This is the debug switch for process record.  */
int record_debug = 0;

/* Record buf with core target.  */
static gdb_byte *record_core_regbuf = NULL;
static struct target_section *record_core_start;
static struct target_section *record_core_end;
static struct record_core_buf_entry *record_core_buf_list = NULL;

/* 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;
static struct target_ops record_core_ops;

/* The beneath function pointers.  */
static struct target_ops *record_beneath_to_resume_ops;
static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
                                         enum target_signal);
static struct target_ops *record_beneath_to_wait_ops;
static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
					 struct target_waitstatus *,
					 int);
static struct target_ops *record_beneath_to_store_registers_ops;
static void (*record_beneath_to_store_registers) (struct target_ops *,
                                                  struct regcache *,
						  int regno);
static struct target_ops *record_beneath_to_xfer_partial_ops;
static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops,
						  enum target_object object,
						  const char *annex,
						  gdb_byte *readbuf,
						  const gdb_byte *writebuf,
						  ULONGEST offset,
						  LONGEST len);
static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *,
						   struct bp_target_info *);
static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
						   struct bp_target_info *);

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);

  record_insn_num = 0;
  record_list = &record_first;
  record_list->next = NULL;
  record_arch_list_tail = NULL;
  record_arch_list_tail = NULL;
}

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_insn (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 = %s len = %d to "
			"record list.\n",
			paddress (target_gdbarch, addr), len);

  if (!addr)
    return 0;

  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
  rec->u.mem.val = (gdb_byte *) xmalloc (len);
  rec->prev = NULL;
  rec->next = NULL;
  rec->type = record_mem;
  rec->u.mem.addr = addr;
  rec->u.mem.len = len;
  rec->u.mem.mem_entry_not_accessible = 0;

  if (target_read_memory (addr, rec->u.mem.val, len))
    {
      if (record_debug)
	fprintf_unfiltered (gdb_stdlog,
			    "Process record: error reading memory at "
			    "addr = %s len = %d.\n",
			    paddress (target_gdbarch, addr), len);
      xfree (rec->u.mem.val);
      xfree (rec);
      return -1;
    }

  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: stoped by user."));
	    }
	}
    }
}

static void
record_arch_list_cleanups (void *ignore)
{
  record_list_release (record_arch_list_tail);
}

/* Before inferior step (when GDB record the running message, inferior
   only can step), GDB will call this function to record the values to
   record_list.  This function will call gdbarch_process_record to
   record the running message of inferior and set them to
   record_arch_list, and add it to record_list.  */

static int
record_message (void *args)
{
  int ret;
  struct regcache *regcache = args;
  struct cleanup *old_cleanups = make_cleanup (record_arch_list_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_insn ();
  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 inline void
record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
                   struct record_entry *entry)
{
  switch (entry->type)
    {
    case record_reg: /* reg */
      {
        gdb_byte reg[MAX_REGISTER_SIZE];

        if (record_debug > 1)
          fprintf_unfiltered (gdb_stdlog,
                              "Process record: record_reg %s to "
                              "inferior num = %d.\n",
                              host_address_to_string (entry),
                              entry->u.reg.num);

        regcache_cooked_read (regcache, entry->u.reg.num, reg);
        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
      }
      break;

    case record_mem: /* mem */
      {
        if (!record_list->u.mem.mem_entry_not_accessible)
          {
            gdb_byte *mem = alloca (entry->u.mem.len);

            if (record_debug > 1)
              fprintf_unfiltered (gdb_stdlog,
                                  "Process record: record_mem %s to "
                                  "inferior addr = %s len = %d.\n",
                                  host_address_to_string (entry),
                                  paddress (gdbarch, entry->u.mem.addr),
                                  record_list->u.mem.len);

            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
              {
		record_list->u.mem.mem_entry_not_accessible = 1;
		if (record_debug)
		  warning (_("Process record: error reading memory at "
			     "addr = %s len = %d."),
			   paddress (gdbarch, entry->u.mem.addr),
			   entry->u.mem.len);
	      }
            else
              {
                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
                                         entry->u.mem.len))
                  {
		    record_list->u.mem.mem_entry_not_accessible = 1;
		    if (record_debug)
		      warning (_("Process record: error writing memory at "
				 "addr = %s len = %d."),
			       paddress (gdbarch, entry->u.mem.addr),
			       entry->u.mem.len);
		  }
              }

            memcpy (entry->u.mem.val, mem, entry->u.mem.len);
          }
      }
      break;
    }
}

/* bfdcore_read -- read bytes from a core file section.  */

static int
bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
{
  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);

  if (ret)
    *offset += len;
  return ret;
}

/* Load the execution log from a file.  */

static void
record_load (void)
{
  int recfd;
  uint32_t magic;
  struct cleanup *old_cleanups2;
  struct record_entry *rec;
  int insn_number = 0;
  asection *osec;
  void nullify_last_target_wait_ptid (void);

  /* We load the execution log from the open core bfd, 
     if there is one.  */
  if (core_bfd == NULL)
    return;

  /* Open the load file.  */
  if (record_debug)
    fprintf_filtered (gdb_stdlog, 
		      _("Restoring recording from core file.\n"));

  /* Now need to find our special note section.  */
  osec = bfd_get_section_by_name (core_bfd, "null0");
  printf_filtered ("Find precord section %s.\n",
		   osec ? "succeeded" : "failed");

  if (osec)
    {
      int i, len;
      int bfd_offset = 0;

      if (record_debug)
	fprintf_filtered (gdb_stdlog, "osec name = '%s'\n",
			  bfd_section_name (core_bfd, osec));
      len = (int) bfd_section_size (core_bfd, osec);
      printf_filtered ("osec size = %d\n", len);

      /* Check the magic code.  */
      if (!bfdcore_read (core_bfd, osec, &magic, 
			 sizeof (magic), &bfd_offset))
	error (_("Failed to read 'magic' from core file (%s)"),
	       bfd_errmsg (bfd_get_error ()));

      if (magic != RECORD_FILE_MAGIC)
	error (_("version mis-match / file format error."));

      if (record_debug)
	fprintf_filtered (gdb_stdlog, _("\
  Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
			  magic);

      /* Free any existing record log, and load the entries in
	 core_bfd to the new record log.  */
      record_list_release (record_arch_list_tail);
      old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);

      while (1)
	{
	  uint8_t tmpu8;
	  uint64_t tmpu64;

	  /* FIXME: Check offset for end-of-section.  */
	  if (!bfdcore_read (core_bfd, osec, &tmpu8, 
			     sizeof (tmpu8), &bfd_offset))
	    break;

	  switch (tmpu8)
	    {
	    case record_reg: /* reg */
	      /* FIXME: abstract out into an 'insert' function.  */
	      rec = (struct record_entry *) 
		xmalloc (sizeof (struct record_entry));
	      rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE);
	      rec->prev = NULL;
	      rec->next = NULL;
	      rec->type = record_reg;
	      /* Get num.  */
	      /* FIXME: register num does not need 8 bytes.  */
	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
				 sizeof (tmpu64), &bfd_offset))
		error (_("Failed to read regnum from core file (%s)"),
		       bfd_errmsg (bfd_get_error ()));

	      if (BYTE_ORDER == LITTLE_ENDIAN)
		tmpu64 = bswap_64 (tmpu64);
	      rec->u.reg.num = tmpu64;

	      /* Get val.  */
	      if (!bfdcore_read (core_bfd, osec, rec->u.reg.val,
				 MAX_REGISTER_SIZE, &bfd_offset))
		error (_("Failed to read regval from  core file (%s)"),
		       bfd_errmsg (bfd_get_error ()));

	      if (record_debug)
		fprintf_filtered (gdb_stdlog, _("\
  Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
				  rec->u.reg.num, 
				  *(ULONGEST *) rec->u.reg.val, 
				  MAX_REGISTER_SIZE);
	      record_arch_list_add (rec);
	      break;

	    case record_mem: /* mem */
	      rec = (struct record_entry *) 
		xmalloc (sizeof (struct record_entry));
	      rec->prev = NULL;
	      rec->next = NULL;
	      rec->type = record_mem;
	      /* Get addr.  */
	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
				 sizeof (tmpu64), &bfd_offset))
		error (_("Failed to read memaddr from core file (%s)"),
		       bfd_errmsg (bfd_get_error ()));
	      if (BYTE_ORDER == LITTLE_ENDIAN)
		tmpu64 = bswap_64 (tmpu64);
	      rec->u.mem.addr = tmpu64;

	      /* Get len.  */
	      /* FIXME: len does not need 8 bytes.  */
	      if (!bfdcore_read (core_bfd, osec, &tmpu64, 
				 sizeof (tmpu64), &bfd_offset))
		error (_("Failed to read memlen from core file (%s)"),
		       bfd_errmsg (bfd_get_error ()));
	      if (BYTE_ORDER == LITTLE_ENDIAN)
		tmpu64 = bswap_64 (tmpu64);
	      rec->u.mem.len = tmpu64;

	      rec->u.mem.mem_entry_not_accessible = 0;
	      rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
	      /* Get val.  */
	      if (!bfdcore_read (core_bfd, osec, rec->u.mem.val,
				 rec->u.mem.len, &bfd_offset))
		error (_("Failed to read memval from core file (%s)"),
		       bfd_errmsg (bfd_get_error ()));
	      if (record_debug)
		fprintf_filtered (gdb_stdlog, _("\
  Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"),
				  (unsigned int) rec->u.mem.addr,
				  rec->u.mem.len);
	      record_arch_list_add (rec);
	      break;

	    case record_end: /* end */
	      /* FIXME: restore the contents of record_end rec.  */
	      rec = (struct record_entry *) 
		xmalloc (sizeof (struct record_entry));
	      rec->prev = NULL;
	      rec->next = NULL;
	      rec->type = record_end;
	      if (record_debug)
		fprintf_filtered (gdb_stdlog, _("\
  Reading record_end (one byte)\n"));
	      record_arch_list_add (rec);
	      insn_number ++;
	      break;

	    default:
	      error (_("Format of core file is not right."));
	      break;
	    }
	}
    }

  discard_cleanups (old_cleanups2);

  /* Add record_arch_list_head to the end of record list.  (??? FIXME)*/
  for (rec = record_list; rec->next; rec = rec->next)
    ;
  rec->next = record_arch_list_head;
  record_arch_list_head->prev = rec;

  /* Update record_insn_num and record_insn_max_num.  */
  record_insn_num = insn_number;
  if (record_insn_num > record_insn_max_num)
    {
      record_insn_max_num = record_insn_num;
      warning (_("Auto increase record/replay buffer limit to %d."),
	       record_insn_max_num);
    }

  /* Succeeded.  */
  fprintf_filtered (gdb_stdout, "Loaded records from core file.\n");
  registers_changed ();
  reinit_frame_cache ();
  nullify_last_target_wait_ptid ();
  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
}

static struct target_ops *tmp_to_resume_ops;
static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
                              enum target_signal);
static struct target_ops *tmp_to_wait_ops;
static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
                              struct target_waitstatus *,
                              int);
static struct target_ops *tmp_to_store_registers_ops;
static void (*tmp_to_store_registers) (struct target_ops *,
                                       struct regcache *,
                                       int regno);
static struct target_ops *tmp_to_xfer_partial_ops;
static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
                                       enum target_object object,
                                       const char *annex,
                                       gdb_byte *readbuf,
                                       const gdb_byte *writebuf,
                                       ULONGEST offset,
                                       LONGEST len);
static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
                                        struct bp_target_info *);
static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
                                        struct bp_target_info *);

static void
record_core_open_1 (char *name, int from_tty)
{
  struct regcache *regcache = get_current_regcache ();
  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
  int i;

  if (!name || (name && !*name))
    error (_("Argument for gdb record filename required.\n"));

  /* Get record_core_regbuf.  */
  target_fetch_registers (regcache, -1);
  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
  for (i = 0; i < regnum; i ++)
    regcache_raw_collect (regcache, i,
                          record_core_regbuf + MAX_REGISTER_SIZE * i);

  /* Get record_core_start and record_core_end.  */
  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
    {
      xfree (record_core_regbuf);
      record_core_regbuf = NULL;
      error (_("\"%s\": Can't find sections: %s"),
             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
    }

  push_target (&record_core_ops);
}

static void
record_open_1 (char *name, int from_tty)
{
  struct target_ops *t;

  /* check exec */
  if (!target_has_execution)
    error (_("Process record: the program is not being run."));
  if (non_stop)
    error (_("Process record target can't debug inferior in non-stop mode "
	     "(non-stop)."));
  if (target_async_permitted)
    error (_("Process record target can't debug inferior in asynchronous "
	     "mode (target-async)."));

  if (!gdbarch_process_record_p (target_gdbarch))
    error (_("Process record: the current architecture doesn't support "
	     "record function."));

  if (!tmp_to_resume)
    error (_("Process record can't get to_resume."));
  if (!tmp_to_wait)
    error (_("Process record can't get to_wait."));
  if (!tmp_to_store_registers)
    error (_("Process record can't get to_store_registers."));
  if (!tmp_to_insert_breakpoint)
    error (_("Process record can't get to_insert_breakpoint."));
  if (!tmp_to_remove_breakpoint)
    error (_("Process record can't get to_remove_breakpoint."));

  push_target (&record_ops);
}

static void
record_open (char *name, int from_tty)
{
  struct target_ops *t;

  if (record_debug)
    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");

  /* Check if record target is already running.  */
  if (current_target.to_stratum == record_stratum)
    {
      if (!nquery
	  (_("Process record target already running, do you want to delete "
	     "the old record log?")))
	return;
    }

  /* Reset the tmp beneath pointers.  */
  tmp_to_resume_ops = NULL;
  tmp_to_resume = NULL;
  tmp_to_wait_ops = NULL;
  tmp_to_wait = NULL;
  tmp_to_store_registers_ops = NULL;
  tmp_to_store_registers = NULL;
  tmp_to_xfer_partial_ops = NULL;
  tmp_to_xfer_partial = NULL;
  tmp_to_insert_breakpoint = NULL;
  tmp_to_remove_breakpoint = NULL;

  /* Set the beneath function pointers.  */
  for (t = current_target.beneath; t != NULL; t = t->beneath)
    {
      if (!tmp_to_resume)
        {
	  tmp_to_resume = t->to_resume;
	  tmp_to_resume_ops = t;
        }
      if (!tmp_to_wait)
        {
	  tmp_to_wait = t->to_wait;
	  tmp_to_wait_ops = t;
        }
      if (!tmp_to_store_registers)
        {
	  tmp_to_store_registers = t->to_store_registers;
	  tmp_to_store_registers_ops = t;
        }
      if (!tmp_to_xfer_partial)
        {
	  tmp_to_xfer_partial = t->to_xfer_partial;
	  tmp_to_xfer_partial_ops = t;
        }
      if (!tmp_to_insert_breakpoint)
	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
      if (!tmp_to_remove_breakpoint)
	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
    }
  if (!tmp_to_xfer_partial)
    error (_("Process record can't get to_xfer_partial."));

  if (current_target.to_stratum == core_stratum)
    record_core_open_1 (name, from_tty);
  else
    record_open_1 (name, from_tty);

  /* Reset */
  record_insn_num = 0;
  record_list = &record_first;
  record_list->next = NULL;

  /* Set the tmp beneath pointers to beneath pointers.  */
  record_beneath_to_resume_ops = tmp_to_resume_ops;
  record_beneath_to_resume = tmp_to_resume;
  record_beneath_to_wait_ops = tmp_to_wait_ops;
  record_beneath_to_wait = tmp_to_wait;
  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
  record_beneath_to_store_registers = tmp_to_store_registers;
  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;

  /* Load record log if corefile contains one.  */
  record_load ();
}

static void
record_close (int quitting)
{
  struct record_core_buf_entry *entry;

  if (record_debug)
    fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

  record_list_release (record_list);

  /* Release record_core_regbuf.  */
  if (record_core_regbuf)
    {
      xfree (record_core_regbuf);
      record_core_regbuf = NULL;
    }

  /* Release record_core_buf_list.  */
  if (record_core_buf_list)
    {
      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
        {
          xfree (record_core_buf_list);
          record_core_buf_list = entry;
        }
      record_core_buf_list = NULL;
    }
}

static int record_resume_step = 0;
static enum target_signal record_resume_siggnal;
static int record_resume_error;

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,
	     int options)
{
  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 && ops != &record_core_ops)
    {
      if (record_resume_error)
	{
	  /* If record_resume get error, return directly.  */
	  status->kind = TARGET_WAITKIND_STOPPED;
	  status->value.sig = TARGET_SIGNAL_ABRT;
	  return inferior_ptid;
	}

      if (record_resume_step)
	{
	  /* This is a single step.  */
	  return record_beneath_to_wait (record_beneath_to_wait_ops,
					 ptid, status, 0);
	}
      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, 0);

	      if (status->kind == TARGET_WAITKIND_STOPPED
		  && status->value.sig == TARGET_SIGNAL_TRAP)
		{
		  /* Check if there is a breakpoint.  */
		  registers_changed ();
		  tmp_pc = regcache_read_pc (get_current_regcache ());
		  if (breakpoint_inserted_here_p (tmp_pc))
		    {
		      /* There is a breakpoint.  */
		      CORE_ADDR decr_pc_after_break =
			gdbarch_decr_pc_after_break
			(get_regcache_arch (get_current_regcache ()));
		      if (decr_pc_after_break)
			{
			  regcache_write_pc (get_thread_regcache (ret),
					     tmp_pc + decr_pc_after_break);
			}
		    }
		  else
		    {
		      /* There is not a breakpoint.  */
		      if (!do_record_message (get_current_regcache ()))
			{
                          break;
			}
		      record_beneath_to_resume (record_beneath_to_resume_ops,
						ptid, 1,
						record_resume_siggnal);
		      continue;
		    }
		}

	      /* The inferior is broken by a breakpoint or a signal.  */
	      break;
	    }

	  return ret;
	}
    }
  else
    {
      struct regcache *regcache = get_current_regcache ();
      struct gdbarch *gdbarch = get_regcache_arch (regcache);
      int continue_flag = 1;
      int first_record_end = 1;
      struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
      CORE_ADDR tmp_pc;

      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 %s.\n",
				    paddress (gdbarch, tmp_pc));
	      if (gdbarch_decr_pc_after_break (gdbarch)
		  && !record_resume_step)
		regcache_write_pc (regcache,
				   tmp_pc +
				   gdbarch_decr_pc_after_break (gdbarch));
	      goto replay_out;
	    }
	}

      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;
	    }

          record_exec_entry (regcache, gdbarch, record_list);

	  if (record_list->type == record_end)
	    {
	      if (record_debug > 1)
		fprintf_unfiltered (gdb_stdlog,
				    "Process record: record_end %s to "
				    "inferior.\n",
				    host_address_to_string (record_list));

	      if (first_record_end && execution_direction == EXEC_REVERSE)
		{
		  /* When reverse excute, the first record_end is the part of
		     current instruction.  */
		  first_record_end = 0;
		}
	      else
		{
		  /* In EXEC_REVERSE mode, this is the record_end of prev
		     instruction.
		     In EXEC_FORWARD mode, this is the record_end of current
		     instruction.  */
		  /* step */
		  if (record_resume_step)
		    {
		      if (record_debug > 1)
			fprintf_unfiltered (gdb_stdlog,
					    "Process record: step.\n");
		      continue_flag = 0;
		    }

		  /* check breakpoint */
		  tmp_pc = regcache_read_pc (regcache);
		  if (breakpoint_inserted_here_p (tmp_pc))
		    {
		      if (record_debug)
			fprintf_unfiltered (gdb_stdlog,
					    "Process record: break "
					    "at %s.\n",
					    paddress (gdbarch, tmp_pc));
		      if (gdbarch_decr_pc_after_break (gdbarch)
			  && execution_direction == EXEC_FORWARD
			  && !record_resume_step)
			regcache_write_pc (regcache,
					   tmp_pc +
					   gdbarch_decr_pc_after_break (gdbarch));
		      continue_flag = 0;
		    }
		}
	    }

	  if (continue_flag)
	    {
	      if (execution_direction == EXEC_REVERSE)
		{
		  if (record_list->prev)
		    record_list = record_list->prev;
		}
	      else
		{
		  if (record_list->next)
		    record_list = record_list->next;
		}
	    }
	}
      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_insn ();
  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;

	  /* 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 %s?"),
		       paddress (target_gdbarch, offset)))
	    error (_("Process record canceled the operation."));

	  /* 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_insn ();
      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 gdbarch *gdbarch,
			  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 (gdbarch, bp_tgt);

      do_cleanups (old_cleanups);

      return ret;
    }

  return 0;
}

static int
record_remove_breakpoint (struct gdbarch *gdbarch,
			  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 (gdbarch, 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
record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
                    enum target_signal siggnal)
{
  record_resume_step = step;
  record_resume_siggnal = siggnal;
}

static void
record_core_kill (struct target_ops *ops)
{
  if (record_debug)
    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");

  unpush_target (&record_core_ops);
}

static void
record_core_fetch_registers (struct target_ops *ops,
                             struct regcache *regcache,
                             int regno)
{
  if (regno < 0)
    {
      int num = gdbarch_num_regs (get_regcache_arch (regcache));
      int i;

      for (i = 0; i < num; i ++)
        regcache_raw_supply (regcache, i,
                             record_core_regbuf + MAX_REGISTER_SIZE * i);
    }
  else
    regcache_raw_supply (regcache, regno,
                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
}

static void
record_core_prepare_to_store (struct regcache *regcache)
{
}

static void
record_core_store_registers (struct target_ops *ops,
                             struct regcache *regcache,
                             int regno)
{
  if (record_gdb_operation_disable)
    regcache_raw_collect (regcache, regno,
                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
  else
    error (_("You can't do that without a process to debug."));
}

static LONGEST
record_core_xfer_partial (struct target_ops *ops, enum target_object object,
		          const char *annex, gdb_byte *readbuf,
		          const gdb_byte *writebuf, ULONGEST offset,
                          LONGEST len)
{
   if (object == TARGET_OBJECT_MEMORY)
     {
       if (record_gdb_operation_disable || !writebuf)
         {
           struct target_section *p;
           for (p = record_core_start; p < record_core_end; p++)
             {
               if (offset >= p->addr)
                 {
                   struct record_core_buf_entry *entry;

                   if (offset >= p->endaddr)
                     continue;

                   if (offset + len > p->endaddr)
                     len = p->endaddr - offset;

                   offset -= p->addr;

                   /* Read readbuf or write writebuf p, offset, len.  */
                   /* Check flags.  */
                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
                     {
                       if (readbuf)
                         memset (readbuf, 0, len);
                       return len;
                     }
                   /* Get record_core_buf_entry.  */
                   for (entry = record_core_buf_list; entry;
                        entry = entry->prev)
                     if (entry->p == p)
                       break;
                   if (writebuf)
                     {
                       if (!entry)
                         {
                           /* Add a new entry.  */
                           entry
                             = (struct record_core_buf_entry *)
                                 xmalloc
                                   (sizeof (struct record_core_buf_entry));
                           entry->p = p;
                           if (!bfd_malloc_and_get_section (p->bfd,
                                                            p->the_bfd_section,
                                                            &entry->buf))
                             {
                               xfree (entry);
                               return 0;
                             }
                           entry->prev = record_core_buf_list;
                           record_core_buf_list = entry;
                         }

                        memcpy (entry->buf + offset, writebuf, (size_t) len);
                     }
                   else
                     {
                       if (!entry)
                         return record_beneath_to_xfer_partial
                                  (record_beneath_to_xfer_partial_ops,
                                   object, annex, readbuf, writebuf,
                                   offset, len);

                       memcpy (readbuf, entry->buf + offset, (size_t) len);
                     }

                   return len;
                 }
             }

           return -1;
         }
       else
         error (_("You can't do that without a process to debug."));
     }

  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
                                         object, annex, readbuf, writebuf,
                                         offset, len);
}

static int
record_core_insert_breakpoint (struct gdbarch *gdbarch,
			       struct bp_target_info *bp_tgt)
{
  return 0;
}

static int
record_core_remove_breakpoint (struct gdbarch *gdbarch,
			       struct bp_target_info *bp_tgt)
{
  return 0;
}

int
record_core_has_execution (struct target_ops *ops)
{
  return 1;
}

static void
init_record_core_ops (void)
{
  record_core_ops.to_shortname = "record_core";
  record_core_ops.to_longname = "Process record and replay target";
  record_core_ops.to_doc =
    "Log program while executing and replay execution from log.";
  record_core_ops.to_open = record_open;
  record_core_ops.to_close = record_close;
  record_core_ops.to_resume = record_core_resume;
  record_core_ops.to_wait = record_wait;
  record_core_ops.to_kill = record_core_kill;
  record_core_ops.to_fetch_registers = record_core_fetch_registers;
  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
  record_core_ops.to_store_registers = record_core_store_registers;
  record_core_ops.to_xfer_partial = record_core_xfer_partial;
  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
  record_core_ops.to_has_execution = record_core_has_execution;
  record_core_ops.to_stratum = record_stratum;
  record_core_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);
}

static void
cmd_record_load (char *args, int from_tty)
{
  char buf[512];

  snprintf (buf, 512, "target record %s", args);
  execute_command (buf, from_tty);
}

/* Record log save-file format
   Version 1

   Header:
     4 bytes: magic number htonl(0x20090726).
       NOTE: be sure to change whenever this file format changes!

   Records:
     record_end:
       1 byte:  record type (record_end).
     record_reg:
       1 byte:  record type (record_reg).
       8 bytes: register id (network byte order).
       MAX_REGISTER_SIZE bytes: register value.
     record_mem:
       1 byte:  record type (record_mem).
       8 bytes: memory address (network byte order).
       8 bytes: memory length (network byte order).
       n bytes: memory value (n == memory length).
*/

/* bfdcore_write -- write bytes into a core file section.  */

static int
bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
{
  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);

  if (ret)
    *offset += len;
  return ret;
}

/* Dump the execution log to a file.  */

static void
cmd_record_dump (char *args, int from_tty)
{
  char *recfilename, recfilename_buffer[40];
  int recfd;
  struct record_entry *cur_record_list;
  uint32_t magic;
  struct regcache *regcache;
  struct gdbarch *gdbarch;
  struct cleanup *old_cleanups;
  struct cleanup *set_cleanups;
  bfd *obfd;
  int dump_size = 0;
  asection *osec = NULL;
  struct record_entry *p;
  int bfd_offset = 0;


  if (current_target.to_stratum != record_stratum)
    error (_("Process record is not started.\n"));

  if (args && *args)
    recfilename = args;
  else
    {
      /* Default recfile name is "rec.PID".  */
      snprintf (recfilename_buffer, sizeof (recfilename_buffer), 
		"gdb_record.%d", PIDGET (inferior_ptid));
      recfilename = recfilename_buffer;
    }

  /* Open the dump file.  */
  if (record_debug)
    fprintf_filtered (gdb_stdlog, 
			_("Saving recording to file '%s'\n"), 
			recfilename);

  /* Open the output file.  */
  obfd = create_gcore_bfd (recfilename);

  /* Need a cleanup that will close the file (FIXME: delete it?).  */
  old_cleanups = make_cleanup_bfd_close (obfd);

  /* Save the current record entry to "cur_record_list".  */
  cur_record_list = record_list;

  /* Get the values of regcache and gdbarch.  */
  regcache = get_current_regcache ();
  gdbarch = get_regcache_arch (regcache);

  /* Disable the GDB operation record.  */
  set_cleanups = record_gdb_operation_disable_set ();

  /* Reverse execute to the begin of record list.  */
  for (; record_list && record_list != &record_first; 
       record_list = record_list->prev)
    record_exec_entry (regcache, gdbarch, record_list);

  /* Compute the size needed for the extra bfd section.  */
  dump_size = 4;	/* magic cookie */
  for (p = &record_first; p; p = p->next)
    switch (p->type)
      {
      case record_end:
	dump_size += 1;
	break;
      case record_reg:
	dump_size += 1 + 8 + MAX_REGISTER_SIZE;
	break;
      case record_mem:
	dump_size += 1 + 8 + 8 + p->u.mem.len;
	break;
      }

  /* Make the new bfd section.  */
  osec = bfd_make_section_anyway (obfd, "precord");
  bfd_set_section_size (obfd, osec, dump_size);
  bfd_set_section_vma (obfd, osec, 0);
  bfd_section_lma (obfd, osec) = 0;
  bfd_set_section_flags (obfd, osec, SEC_ALLOC | SEC_HAS_CONTENTS);

  /* Save corefile state.  */
  write_gcore_file (obfd);

  /* Write out the record log (modified Hui method).  */
  /* Write the magic code.  */
  magic = RECORD_FILE_MAGIC;
  if (record_debug)
    fprintf_filtered (gdb_stdlog, _("\
  Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
			magic);
  if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset))
    error (_("Failed to write 'magic' to %s (%s)"), 
	   recfilename, bfd_errmsg (bfd_get_error ()));

  /* Dump the entries into the new bfd section.  */
  for (p = &record_first; p; p = p->next)
    {
      uint8_t tmpu8;
      uint64_t tmpu64;

      tmpu8 = p->type;
      if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset))
	error (_("Failed to write 'type' to %s (%s)"), 
	       recfilename, bfd_errmsg (bfd_get_error ()));

      switch (p->type)
	{
	case record_reg: /* reg */
	  tmpu64 = p->u.reg.num;
	  if (BYTE_ORDER == LITTLE_ENDIAN)
	    tmpu64 = bswap_64 (tmpu64);

	  if (record_debug)
	    fprintf_filtered (gdb_stdlog, _("\
  Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"),
				p->u.reg.num,
				*(ULONGEST *) p->u.reg.val, 
				MAX_REGISTER_SIZE);
	  /* FIXME: register num does not need 8 bytes.  */
	  if (!bfdcore_write (obfd, osec, &tmpu64, 
			      sizeof (tmpu64), &bfd_offset))
	    error (_("Failed to write regnum to %s (%s)"), 
		   recfilename, bfd_errmsg (bfd_get_error ()));

	  /* FIXME: add a len field, and write the smaller value.  */
	  if (!bfdcore_write (obfd, osec, p->u.reg.val,
					MAX_REGISTER_SIZE, &bfd_offset))
	    error (_("Failed to write regval to %s (%s)"), 
		   recfilename, bfd_errmsg (bfd_get_error ()));
	  break;
	case record_mem: /* mem */
	  tmpu64 = p->u.mem.addr;
	  if (BYTE_ORDER == LITTLE_ENDIAN)
	    tmpu64 = bswap_64 (tmpu64);

	  if (record_debug)
	    fprintf_filtered (gdb_stdlog, _("\
  Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"),
				(unsigned int) p->u.mem.addr,
				p->u.mem.len);
	  if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset))
	    error (_("Failed to write memaddr to %s (%s)"),
		   recfilename, bfd_errmsg (bfd_get_error ()));

	  tmpu64 = p->u.mem.len;
	  if (BYTE_ORDER == LITTLE_ENDIAN)
	    tmpu64 = bswap_64 (tmpu64);

	  /* FIXME: len does not need 8 bytes.  */
	  if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset))
	    error (_("Failed to write memlen to %s (%s)"), 
		   recfilename, bfd_errmsg (bfd_get_error ()));

	  if (!bfdcore_write (obfd, osec, p->u.mem.val,
			      p->u.mem.len, &bfd_offset))
	    error (_("Failed to write memval to %s (%s)"),
		   recfilename, bfd_errmsg (bfd_get_error ()));
	  break;
	case record_end:
	  /* FIXME: record the contents of record_end rec.  */
	  if (record_debug)
	    fprintf_filtered (gdb_stdlog, _("\
  Writing record_end (1 byte)\n"));
	  break;
	}
    }

  /* Now forward-execute back to the saved entry.  */
  for (record_list = &record_first; 
       record_list && record_list != cur_record_list; 
       record_list = record_list->next)
    record_exec_entry (regcache, gdbarch, record_list);

  /* Clean-ups will close the output file and free malloc memory.  */
  do_cleanups (old_cleanups);

  /* Succeeded.  */
  fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
}

/* 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?")))
        {
          if (strcmp (current_target.to_shortname, "record") == 0)
	    unpush_target (&record_ops);
          else
            unpush_target (&record_core_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_insn ();
    }
}

/* 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)
{
  struct cmd_list_element *c;

  /* Init record_first.  */
  record_first.prev = NULL;
  record_first.next = NULL;
  record_first.type = record_end;

  init_record_ops ();
  add_target (&record_ops);
  init_record_core_ops ();
  add_target (&record_core_ops);

  add_setshow_zinteger_cmd ("record", no_class, &record_debug,
			    _("Set debugging of record/replay feature."),
			    _("Show debugging of record/replay feature."),
			    _("When enabled, debugging output for "
			      "record/replay feature is displayed."),
			    NULL, show_record_debug, &setdebuglist,
			    &showdebuglist);

  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
		      _("Abbreviated form of \"target record\" command."),
 		      &record_cmdlist, "record ", 0, &cmdlist);
  set_cmd_completer (c, filename_completer);
  add_com_alias ("rec", "record", class_obscure, 1);
  add_prefix_cmd ("record", class_support, set_record_command,
		  _("Set record options"), &set_record_cmdlist,
		  "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);

  c = add_cmd ("load", class_obscure, cmd_record_load,
	       _("Load previously dumped execution records from \
a file given as argument."),
               &record_cmdlist);
  set_cmd_completer (c, filename_completer);
  c = add_cmd ("dump", class_obscure, cmd_record_dump,
	       _("Dump the execution records to a file.\n\
Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
               &record_cmdlist);
  set_cmd_completer (c, filename_completer);

  add_cmd ("delete", class_obscure, cmd_record_delete,
	   _("Delete the rest of execution log and start recording it anew."),
           &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 stops when record/replay buffer becomes full."), _("\
Show whether record/replay stops when record/replay buffer becomes full."), _("\
Default is ON.\n\
When ON, if the record/replay buffer becomes full, ask user what to do.\n\
When OFF, if the record/replay buffer becomes full,\n\
delete the oldest recorded instruction to make room for each new one."),
			    NULL, NULL,
                            &set_record_cmdlist, &show_record_cmdlist);
  add_setshow_zinteger_cmd ("insn-number-max", no_class,
			    &record_insn_max_num,
			    _("Set record/replay buffer limit."),
			    _("Show record/replay buffer limit."), _("\
Set the maximum number of instructions to be stored in the\n\
record/replay buffer.  Zero means unlimited.  Default is 200000."),
			    set_record_insn_max_num,
			    NULL, &set_record_cmdlist, &show_record_cmdlist);
  add_cmd ("insn-number", class_obscure, show_record_insn_number,
	    _("Show the current number of instructions in the "
	      "record/replay buffer."), &info_record_cmdlist);
}

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-26  1:40                                                   ` Michael Snyder
@ 2009-08-26  2:59                                                     ` Michael Snyder
  2009-08-29 15:53                                                       ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-26  2:59 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Hui Zhu, Eli Zaretskii, gdb-patches

Michael Snyder wrote:
> Hui Zhu wrote:
>> On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote:
>>> Have you considered combining this patch with the idea that I
>>> proposed -- storing the record log in the core file?  Seems like
>>> that would be very easy now -- gdb already has the core file open,
>>> and there is a global pointer to the BFD.
>>> See gdbcore.h:extern bfd* core_bfd;
>> Cool.  If we just want record_core_ops support load, we can remove the
>> cmd "record load".  :)
>>
>>> In fact, just for fun, I've merged the two patches in my
>>> local tree.  It seems to work quite well.  I can just post
>>> it here if you like, or here's a hint to show you how it goes:
>>
>> Why not?  Please post it.  I can not wait to try it.  :)
> 
> Great -- attached.

This is "quick and dirty", by the way.
It's not meant to be finished!


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-26  2:59                                                     ` Michael Snyder
@ 2009-08-29 15:53                                                       ` Hui Zhu
  2009-08-29 18:06                                                         ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-29 15:53 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

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

On Wed, Aug 26, 2009 at 09:35, Michael Snyder<msnyder@vmware.com> wrote:
> Michael Snyder wrote:
>>
>> Hui Zhu wrote:
>>>
>>> On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote:
>>>>
>>>> Have you considered combining this patch with the idea that I
>>>> proposed -- storing the record log in the core file?  Seems like
>>>> that would be very easy now -- gdb already has the core file open,
>>>> and there is a global pointer to the BFD.
>>>> See gdbcore.h:extern bfd* core_bfd;
>>>
>>> Cool.  If we just want record_core_ops support load, we can remove the
>>> cmd "record load".  :)
>>>
>>>> In fact, just for fun, I've merged the two patches in my
>>>> local tree.  It seems to work quite well.  I can just post
>>>> it here if you like, or here's a hint to show you how it goes:
>>>
>>> Why not?  Please post it.  I can not wait to try it.  :)
>>
>> Great -- attached.
>
> This is "quick and dirty", by the way.
> It's not meant to be finished!
>
>

Hi Michael,

I make a new patch that make the patches together.  Prec can load the
record log from a core file and debug it with core target.  To use it,
Call "record" after load the gdb_record.xxx file.

Please help me review it.

Thanks,
Hui

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

	Add dump and load command to process record and replay.

	* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
        elf-bfd.h, gcore.h, byteswap.h, netinet/in.h): Include files.
	(RECORD_FILE_MAGIC): New macro.
	(record_core_buf_entry): New struct.
	(record_core_ops): New target_ops.
	(record_list_release_first_insn): Change function
	record_list_release_first to this name.
	(record_reg_alloc, record_mem_alloc, record_end_alloc): New
	functions.
	(record_arch_list_add_reg): Call record_reg_alloc.
	(record_arch_list_add_mem): Call record_mem_alloc.
	(record_arch_list_add_end): Call record_end_alloc.
	(record_arch_list_cleanups): New function.
	(record_message_cleanups): Removed.
	(record_message): Change to call record_arch_list_cleanups
	and record_list_release_first_insn.
	(record_exec_entry, (bfdcore_read, record_load,
	record_core_open_1, record_open_1): New function.
	(record_open): Add support for record_core_ops.
	(record_close): Add support for record_core_ops.
	(record_wait): Call function 'record_exec_entry' and
	add support for target core.
	(record_registers_change): Call record_list_release_first_insn.
	(record_core_resume, record_core_resume, record_core_kill,
	record_core_fetch_registers, record_core_prepare_to_store,
	record_core_store_registers, record_core_xfer_partial,
	record_core_insert_breakpoint, record_core_remove_breakpoint,
	record_core_has_execution, init_record_core_ops,
	cmd_record_load, bfdcore_write,
	cmd_record_dump): New function.
	(cmd_record_stop): Add support for record_core_ops.
	(set_record_insn_max_num): Call record_list_release_first_insn.
	(_initialize_record): Add commands "record dump".
	* gcore.c (create_gcore_bfd): New function, abstracted
	from gcore_command for export.
	(write_gcore_file): New function, abstracted from
	gcore_command for export.
	(gcore_command): Call helper functions (above).
	(call_target_sbrk): New function, abstracted from
	derive_heap_segment.
	(derive_heap_segment): Call helper function (above).
	(load_core_segments): New function.
	(load_corefile): New function.
	* gcore.h: New file.

2009-08-29  Hui Zhu  <teawater@gmail.com>

	* gdb.texinfo (Process Record and Replay): Document the
	"record dump" commands.

---
 gcore.c  |  309 ++++++++++++++----
 gcore.h  |   23 +
 record.c | 1072 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 1199 insertions(+), 205 deletions(-)

--- a/gcore.c
+++ b/gcore.c
@@ -25,10 +25,14 @@
 #include "gdbcore.h"
 #include "objfiles.h"
 #include "symfile.h"
-
+#include "arch-utils.h"
+#include "completer.h"
+#include "gcore.h"
 #include "cli/cli-decode.h"
-
 #include "gdb_assert.h"
+#include <fcntl.h>
+#include "regcache.h"
+#include "regset.h"

 /* The largest amount of memory to read from the target at once.  We
    must throttle it to limit the amount of memory used by GDB during
@@ -40,45 +44,27 @@ static enum bfd_architecture default_gco
 static unsigned long default_gcore_mach (void);
 static int gcore_memory_sections (bfd *);

-/* Generate a core file from the inferior process.  */
+/* create_gcore_bfd -- helper for gcore_command (exported).  */

-static void
-gcore_command (char *args, int from_tty)
+bfd *
+create_gcore_bfd (char *filename)
 {
-  struct cleanup *old_chain;
-  char *corefilename, corefilename_buffer[40];
-  asection *note_sec = NULL;
-  bfd *obfd;
-  void *note_data = NULL;
-  int note_size = 0;
-
-  /* No use generating a corefile without a target process.  */
-  if (!target_has_execution)
-    noprocess ();
-
-  if (args && *args)
-    corefilename = args;
-  else
-    {
-      /* Default corefile name is "core.PID".  */
-      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
-      corefilename = corefilename_buffer;
-    }
-
-  if (info_verbose)
-    fprintf_filtered (gdb_stdout,
-		      "Opening corefile '%s' for output.\n", corefilename);
-
-  /* Open the output file.  */
-  obfd = bfd_openw (corefilename, default_gcore_target ());
+  bfd *obfd = bfd_openw (filename, default_gcore_target ());
   if (!obfd)
-    error (_("Failed to open '%s' for output."), corefilename);
-
-  /* Need a cleanup that will close the file (FIXME: delete it?).  */
-  old_chain = make_cleanup_bfd_close (obfd);
-
+    error (_("Failed to open '%s' for output."), filename);
   bfd_set_format (obfd, bfd_core);
   bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ());
+  return obfd;
+}
+
+/* write_gcore_file -- helper for gcore_command (exported).  */
+
+void
+write_gcore_file (bfd *obfd)
+{
+  void *note_data = NULL;
+  int note_size = 0;
+  asection *note_sec = NULL;

   /* An external target method must build the notes section.  */
   note_data = target_make_corefile_notes (obfd, &note_size);
@@ -107,8 +93,46 @@ gcore_command (char *args, int from_tty)
   if (note_data != NULL && note_size != 0)
     {
       if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size))
-	warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error ()));
+	warning (_("writing note section (%s)"),
+		 bfd_errmsg (bfd_get_error ()));
     }
+}
+
+/* gcore_command -- implements the 'gcore' command.
+   Generate a core file from the inferior process.  */
+
+static void
+gcore_command (char *args, int from_tty)
+{
+  struct cleanup *old_chain;
+  char *corefilename, corefilename_buffer[40];
+  bfd *obfd;
+
+  /* No use generating a corefile without a target process.  */
+  if (!target_has_execution)
+    noprocess ();
+
+  if (args && *args)
+    corefilename = args;
+  else
+    {
+      /* Default corefile name is "core.PID".  */
+      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
+      corefilename = corefilename_buffer;
+    }
+
+  if (info_verbose)
+    fprintf_filtered (gdb_stdout,
+		      "Opening corefile '%s' for output.\n", corefilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (corefilename);
+
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_chain = make_cleanup_bfd_close (obfd);
+
+  /* Call worker function.  */
+  write_gcore_file (obfd);

   /* Succeeded.  */
   fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename);
@@ -212,6 +236,50 @@ derive_stack_segment (bfd_vma *bottom, b
   return 1;
 }

+/* call_target_sbrk --
+   helper function for derive_heap_segment and load_core_segment.  */
+
+static bfd_vma
+call_target_sbrk (int sbrk_arg)
+{
+  struct objfile *sbrk_objf;
+  struct gdbarch *gdbarch;
+  bfd_vma top_of_heap;
+  struct value *target_sbrk_arg;
+  struct value *sbrk_fn, *ret;
+  bfd_vma tmp;
+
+  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else
+    return (bfd_vma) 0;
+
+  gdbarch = get_objfile_arch (sbrk_objf);
+  target_sbrk_arg = value_from_longest (builtin_type (gdbarch)->builtin_int,
+					sbrk_arg);
+  gdb_assert (target_sbrk_arg);
+  ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg);
+  if (ret == NULL)
+    return (bfd_vma) 0;
+
+  tmp = value_as_long (ret);
+  if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff)
+    return (bfd_vma) 0;
+
+  top_of_heap = tmp;
+  return top_of_heap;
+}
+
 /* Derive a reasonable heap segment for ABFD by looking at sbrk and
    the static data sections.  Store its limits in *BOTTOM and *TOP.
    Return non-zero if successful.  */
@@ -219,12 +287,10 @@ derive_stack_segment (bfd_vma *bottom, b
 static int
 derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
 {
-  struct objfile *sbrk_objf;
   struct gdbarch *gdbarch;
   bfd_vma top_of_data_memory = 0;
   bfd_vma top_of_heap = 0;
   bfd_size_type sec_size;
-  struct value *zero, *sbrk;
   bfd_vma sec_vaddr;
   asection *sec;

@@ -259,29 +325,9 @@ derive_heap_segment (bfd *abfd, bfd_vma
 	}
     }

-  /* Now get the top-of-heap by calling sbrk in the inferior.  */
-  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else
-    return 0;
-
-  gdbarch = get_objfile_arch (sbrk_objf);
-  zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0);
-  gdb_assert (zero);
-  sbrk = call_function_by_hand (sbrk, 1, &zero);
-  if (sbrk == NULL)
+  top_of_heap = call_target_sbrk (0);
+  if (top_of_heap == (bfd_vma) 0)
     return 0;
-  top_of_heap = value_as_long (sbrk);

   /* Return results.  */
   if (top_of_heap > top_of_data_memory)
@@ -299,13 +345,15 @@ static void
 make_output_phdrs (bfd *obfd, asection *osec, void *ignored)
 {
   int p_flags = 0;
-  int p_type;
+  int p_type = 0;

   /* FIXME: these constants may only be applicable for ELF.  */
   if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0)
     p_type = PT_LOAD;
-  else
+  else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0)
     p_type = PT_NOTE;
+  else
+    p_type = PT_NULL;

   p_flags |= PF_R;	/* Segment is readable.  */
   if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY))
@@ -516,6 +564,141 @@ gcore_memory_sections (bfd *obfd)
   return 1;
 }

+struct load_core_args_params {
+  int from_tty;
+  bfd_vma top_of_heap;
+};
+
+/* load_core_segments -- iterator function for bfd_map_over_sections.  */
+
+static void
+load_core_segment (bfd *abfd, asection *asect, void *arg)
+{
+  struct load_core_args_params *params = arg;
+  struct cleanup *old_chain;
+  char *memhunk;
+  int ret;
+
+  if ((bfd_section_size (abfd, asect) > 0) &&
+      (bfd_get_section_flags (abfd, asect) & SEC_LOAD) &&
+      !(bfd_get_section_flags (abfd, asect) & SEC_READONLY))
+    {
+      if (info_verbose && params->from_tty)
+	{
+	  printf_filtered (_("Load core section %s"),
+			   bfd_section_name (abfd, asect));
+	  printf_filtered (_(", vma 0x%08lx to 0x%08lx"),
+			   (unsigned long) bfd_section_vma (abfd, asect),
+			   (unsigned long) bfd_section_vma (abfd, asect) +
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(", size = %d"),
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(".\n"));
+	}
+      /* Fixme cleanup? */
+      memhunk = xmalloc (bfd_section_size (abfd, asect));
+      bfd_get_section_contents (abfd, asect, memhunk, 0,
+				bfd_section_size (abfd, asect));
+      if ((ret = target_write_memory (bfd_section_vma (abfd, asect),
+				      memhunk,
+				      bfd_section_size (abfd, asect))) != 0)
+	{
+	  print_sys_errmsg ("load_core_segment", ret);
+	  if ((LONGEST) params->top_of_heap <
+	      (LONGEST) bfd_section_vma (abfd, asect) +
+	      (LONGEST) bfd_section_size (abfd, asect))
+	    {
+	      int increment = bfd_section_vma (abfd, asect) +
+		bfd_section_size (abfd, asect) - params->top_of_heap;
+
+	      params->top_of_heap = call_target_sbrk (increment);
+	      if (params->top_of_heap == 0)
+		error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap);
+	      else
+		printf_filtered ("Increase TOH to 0x%08lx and retry.\n",
+				 (unsigned long) params->top_of_heap);
+	      if (target_write_memory (bfd_section_vma (abfd, asect),
+				       memhunk,
+				       bfd_section_size (abfd, asect)) != 0)
+		{
+		  error ("Nope, still failed.");
+		}
+	    }
+	}
+      xfree (memhunk);
+    }
+}
+
+/* load_corefile -- reads a corefile, copies its memory sections
+   into target memory, and its registers into target regcache.  */
+
+bfd *
+load_corefile (char *filename, int from_tty)
+{
+  struct load_core_args_params params;
+  struct bfd_section *regsect;
+  const struct regset *regset;
+  struct regcache *regcache;
+  struct cleanup *old_chain;
+  struct gdbarch *gdbarch;
+  char *scratch_path;
+  int scratch_chan;
+  char *contents;
+  bfd *core_bfd;
+  int size;
+
+  scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename,
+			O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path);
+  if (scratch_chan < 0)
+    perror_with_name (filename);
+
+  core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan);
+  old_chain = make_cleanup_bfd_close (core_bfd);
+  if (!core_bfd)
+    perror_with_name (scratch_path);
+
+  if (!bfd_check_format (core_bfd, bfd_core))
+    error (_("\"%s\" is not a core file: %s"),
+	   filename, bfd_errmsg (bfd_get_error ()));
+
+  params.from_tty = from_tty;
+  params.top_of_heap = call_target_sbrk (0);
+  if (params.top_of_heap == 0)
+    error (_("Couldn't get sbrk."));
+
+  bfd_map_over_sections (core_bfd, load_core_segment, (void *) &params);
+  /* Now need to get/set registers.  */
+  regsect = bfd_get_section_by_name (core_bfd, ".reg");
+
+  if (!regsect)
+    error (_("Couldn't find .reg section."));
+
+  size = bfd_section_size (core_bfd, regsect);
+  contents = alloca (size);
+  bfd_get_section_contents (core_bfd, regsect, contents, 0, size);
+
+  /* See FIXME kettenis/20031023 comment in corelow.c */
+  gdbarch = gdbarch_from_bfd (core_bfd);
+
+  if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch))
+    {
+      regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size);
+      if (!regset)
+	error (_("Failed to allocate regset."));
+
+      registers_changed ();
+      regcache = get_current_regcache ();
+      regset->supply_regset (regset, regcache, -1, contents, size);
+      reinit_frame_cache ();
+      target_store_registers (regcache, -1);
+    }
+  else
+    error (_("Failed to get regset from core section"));
+
+  discard_cleanups (old_chain);
+  return core_bfd;
+}
+
 /* Provide a prototype to silence -Wmissing-prototypes.  */
 extern initialize_file_ftype _initialize_gcore;

--- /dev/null
+++ b/gcore.h
@@ -0,0 +1,23 @@
+/* Support for reading/writing gcore files.
+
+   Copyright (C) 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/>.  */
+
+extern bfd *create_gcore_bfd (char *filename);
+extern void write_gcore_file (bfd *obfd);
+extern bfd *load_corefile (char *filename, int from_tty);
+
--- a/record.c
+++ b/record.c
@@ -23,15 +23,25 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
+#include "elf-bfd.h"
+#include "gcore.h"

+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>

 #define DEFAULT_RECORD_INSN_MAX_NUM	200000

 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)

+#define RECORD_FILE_MAGIC	htonl(0x20090829)
+
 /* These are the core struct of record function.

    An record_entry is a record of the value change of a register
@@ -78,9 +88,22 @@ struct record_entry
   } u;
 };

+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;

+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -94,6 +117,7 @@ static int record_insn_num = 0;

 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;

 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -169,7 +193,7 @@ record_list_release_next (void)
 }

 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -227,6 +251,56 @@ record_arch_list_add (struct record_entr
     }
 }

+/* Alloc a record_reg record entry. */
+
+static inline struct record_entry *
+record_reg_alloc (int num)
+{
+  struct record_entry *rec;
+
+  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;
+
+  return rec;
+}
+
+/* Alloc a record_mem record entry. */
+
+static inline struct record_entry *
+record_mem_alloc (int len)
+{
+  struct record_entry *rec;
+
+  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.len = len;
+  rec->u.mem.mem_entry_not_accessible = 0;
+
+  return rec;
+}
+
+/* Alloc a record_mem record entry. */
+
+static inline struct record_entry *
+record_end_alloc (void)
+{
+  struct record_entry *rec;
+
+  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+  rec->prev = NULL;
+  rec->next = NULL;
+  rec->type = record_end;
+
+  return rec;
+}
+
 /* Record the value of a register NUM to record_arch_list.  */

 int
@@ -240,12 +314,7 @@ record_arch_list_add_reg (struct regcach
 			"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;
+  rec = record_reg_alloc (num);

   regcache_raw_read (regcache, num, rec->u.reg.val);

@@ -271,14 +340,8 @@ record_arch_list_add_mem (CORE_ADDR addr
   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 = record_mem_alloc (len);
   rec->u.mem.addr = addr;
-  rec->u.mem.len = len;
-  rec->u.mem.mem_entry_not_accessible = 0;

   if (target_read_memory (addr, rec->u.mem.val, len))
     {
@@ -308,10 +371,7 @@ record_arch_list_add_end (void)
     fprintf_unfiltered (gdb_stdlog,
 			"Process record: add end to arch list.\n");

-  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->prev = NULL;
-  rec->next = NULL;
-  rec->type = record_end;
+  rec = record_end_alloc ();

   record_arch_list_add (rec);

@@ -340,30 +400,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }

+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */

-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);

   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +446,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;

@@ -416,13 +476,297 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }

+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                record_list->u.mem.mem_entry_not_accessible = 1;
+                if (record_debug)
+                  warning (_("Process record: error reading memory at "
+                             "addr = %s len = %d."),
+                           paddress (gdbarch, entry->u.mem.addr),
+                           entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error writing memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+              }
+          }
+      }
+      break;
+    }
+}
+
+/* Load the execution log from core_bfd.  */
+
+static int
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
 static void
-record_open (char *name, int from_tty)
+record_load (void)
 {
-  struct target_ops *t;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct record_entry *rec;
+  asection *osec;
+  int bfd_offset = 0;
+
+  /* We load the execution log from the open core bfd,
+     if there is one.  */
+  if (core_bfd == NULL)
+    return;
+
+  /* "record_load" just can be called when record list is empty.  */
+  gdb_assert (record_first.next == NULL);

   if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+    fprintf_filtered (gdb_stdlog,
+		      _("Restoring recording from core file.\n"));
+
+  /* Now need to find our special note section.  */
+  osec = bfd_get_section_by_name (core_bfd, "null0");
+  printf_filtered ("Find precord section %s.\n",
+		   osec ? "succeeded" : "failed");
+  if (!osec)
+    return;
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, "osec name = '%s'\n",
+                      bfd_section_name (core_bfd, osec));
+
+  /* Check the magic code.  */
+  if (!bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset))
+    error (_("Failed to read 'magic' from core file (%s)"),
+           bfd_errmsg (bfd_get_error ()));
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("Version mis-match or file format error."));
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, _("\
+Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                      magic);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  record_insn_num = 0;
+  old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      /* FIXME: Check offset for end-of-section.  */
+      if (!bfdcore_read (core_bfd, osec, &tmpu8,
+                         sizeof (tmpu8), &bfd_offset))
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          /* Get num to tmpu64.  */
+          if (!bfdcore_read (core_bfd, osec, &tmpu64,
+                             sizeof (tmpu64), &bfd_offset))
+            error (_("Failed to read regnum from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+
+          rec = record_reg_alloc ((int) tmpu64);
+
+          /* Get val.  */
+          if (!bfdcore_read (core_bfd, osec, rec->u.reg.val,
+                             MAX_REGISTER_SIZE, &bfd_offset))
+            error (_("Failed to read regval from  core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+          break;
+
+        case record_mem: /* mem */
+          /* Get len.  */
+          if (!bfdcore_read (core_bfd, osec, &tmpu64,
+                             sizeof (tmpu64), &bfd_offset))
+            error (_("Failed to read memlen from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+
+          rec = record_mem_alloc ((int) tmpu64);
+
+          /* Get addr.  */
+          if (!bfdcore_read (core_bfd, osec, &tmpu64,
+                             sizeof (tmpu64), &bfd_offset))
+            error (_("Failed to read memaddr from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get val.  */
+          if (!bfdcore_read (core_bfd, osec, rec->u.mem.val,
+                             rec->u.mem.len, &bfd_offset))
+            error (_("Failed to read memval from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+          break;
+
+        case record_end: /* end */
+          rec = record_end_alloc ();
+          record_insn_num ++;
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\n"));
+          break;
+
+        default:
+          error (_("Format of core file is not right."));
+          break;
+        }
+
+      /* Add rec to record arch list.  */
+      record_arch_list_add (rec);
+    }
+
+  discard_cleanups (old_cleanups);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  record_first.next = record_arch_list_head;
+  record_arch_list_head->prev = &record_first;
+
+  /* Update record_insn_max_num.  */
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded records from core file.\n");
+
+  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                              enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                              struct target_waitstatus *,
+                              int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                       struct regcache *,
+                                       int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                       enum target_object object,
+                                       const char *annex,
+                                       gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset,
+                                       LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                          record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+}
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+  struct target_ops *t;

   /* check exec */
   if (!target_has_execution)
@@ -438,6 +782,28 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
 	     "record function."));

+  if (!tmp_to_resume)
+    error (_("Process record can't get to_resume."));
+  if (!tmp_to_wait)
+    error (_("Process record can't get to_wait."));
+  if (!tmp_to_store_registers)
+    error (_("Process record can't get to_store_registers."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Process record can't get to_insert_breakpoint."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Process record can't get to_remove_breakpoint."));
+
+  push_target (&record_ops);
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
     {
@@ -447,70 +813,102 @@ record_open (char *name, int from_tty)
 	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;
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;

   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-	  record_beneath_to_resume = t->to_resume;
-	  record_beneath_to_resume_ops = t;
+	  tmp_to_resume = t->to_resume;
+	  tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-	  record_beneath_to_wait = t->to_wait;
-	  record_beneath_to_wait_ops = t;
+	  tmp_to_wait = t->to_wait;
+	  tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-	  record_beneath_to_store_registers = t->to_store_registers;
-	  record_beneath_to_store_registers_ops = t;
+	  tmp_to_store_registers = t->to_store_registers;
+	  tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-	  record_beneath_to_xfer_partial = t->to_xfer_partial;
-	  record_beneath_to_xfer_partial_ops = t;
+	  tmp_to_xfer_partial = t->to_xfer_partial;
+	  tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-	record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-	record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
     }
-  if (!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)
+  if (!tmp_to_xfer_partial)
     error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));

-  push_target (&record_ops);
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);

   /* Reset */
   record_insn_num = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+
+  if (strcmp (current_target.to_shortname, "record_core") == 0)
+    record_load ();
 }

 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");

   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }

 static int record_resume_step = 0;
@@ -584,7 +982,7 @@ record_wait (struct target_ops *ops,
 			"record_resume_step = %d\n",
 			record_resume_step);

-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
       if (record_resume_error)
 	{
@@ -712,76 +1110,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }

-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);

-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed --
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed --
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,6 +1232,7 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");

   unpush_target (&record_ops);
+
   target_kill ();
 }

@@ -945,7 +1277,7 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;

   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
@@ -1058,7 +1390,7 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;

       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
@@ -1138,6 +1470,191 @@ init_record_ops (void)
 }

 static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+}
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+		          const char *annex, gdb_byte *readbuf,
+		          const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  record_core_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)
 {
@@ -1153,6 +1670,262 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }

+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090829).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory length (network byte order).
+       8 bytes: memory address (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* bfdcore_write -- write bytes into a core file section.  */
+
+static int
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+  bfd *obfd;
+  int dump_size = 0;
+  asection *osec = NULL;
+  int bfd_offset = 0;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default recfile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, sizeof (recfilename_buffer),
+                "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (recfilename);
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_cleanups = make_cleanup_bfd_close (obfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Compute the size needed for the extra bfd section.  */
+  dump_size = 4;	/* magic cookie */
+  for (record_list = &record_first; record_list;
+       record_list = record_list->next)
+    switch (record_list->type)
+      {
+      case record_end:
+	dump_size += 1;
+	break;
+      case record_reg:
+	dump_size += 1 + 8 + MAX_REGISTER_SIZE;
+	break;
+      case record_mem:
+	dump_size += 1 + 8 + 8 + record_list->u.mem.len;
+	break;
+      }
+
+  /* Make the new bfd section.  */
+  osec = bfd_make_section_anyway_with_flags (obfd, "precord",
+                                             SEC_HAS_CONTENTS
+                                             | SEC_READONLY);
+  if (osec == NULL)
+    error (_("Failed to create 'precord' section for corefile: %s"),
+           bfd_errmsg (bfd_get_error ()));
+  bfd_set_section_size (obfd, osec, dump_size);
+  bfd_set_section_vma (obfd, osec, 0);
+  bfd_set_section_alignment (obfd, osec, 0);
+  bfd_section_lma (obfd, osec) = 0;
+
+  /* Save corefile state.  */
+  write_gcore_file (obfd);
+
+  /* Write out the record log.  */
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                      magic);
+  if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset))
+    error (_("Failed to write 'magic' to %s (%s)"),
+           recfilename, bfd_errmsg (bfd_get_error ()));
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  record_list = &record_first;
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8),
+                              &bfd_offset))
+            error (_("Failed to write 'type' to %s (%s)"),
+	           recfilename, bfd_errmsg (bfd_get_error ()));
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              /* Write regnum.  */
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              if (!bfdcore_write (obfd, osec, &tmpu64,
+                                  sizeof (tmpu64), &bfd_offset))
+                error (_("Failed to write regnum to %s (%s)"),
+              recfilename, bfd_errmsg (bfd_get_error ()));
+
+              /* Write regval.  */
+              if (!bfdcore_write (obfd, osec, record_list->u.reg.val,
+                                  MAX_REGISTER_SIZE, &bfd_offset))
+                error (_("Failed to write regval to %s (%s)"),
+                       recfilename, bfd_errmsg (bfd_get_error ()));
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  /* Write memlen.  */
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  if (!bfdcore_write (obfd, osec, &tmpu64,
+                                      sizeof (tmpu64), &bfd_offset))
+                    error (_("Failed to write memlen to %s (%s)"),
+                           recfilename, bfd_errmsg (bfd_get_error ()));
+
+                  /* Write memaddr.  */
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  if (!bfdcore_write (obfd, osec, &tmpu64,
+                                      sizeof (tmpu64), &bfd_offset))
+                    error (_("Failed to write memaddr to %s (%s)"),
+                           recfilename, bfd_errmsg (bfd_get_error ()));
+
+                  /* Write memval.  */
+                  if (!bfdcore_write (obfd, osec, record_list->u.mem.val,
+                                      record_list->u.mem.len, &bfd_offset))
+                    error (_("Failed to write memval to %s (%s)"),
+                           recfilename, bfd_errmsg (bfd_get_error ()));
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved recfile %s.\n"), recfilename);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */

@@ -1185,7 +1958,12 @@ cmd_record_stop (char *args, int from_tt
     {
       if (!record_list || !from_tty || query (_("Delete recorded log and "
 	                                        "stop recording?")))
-	unpush_target (&record_ops);
+        {
+          if (strcmp (current_target.to_shortname, "record") == 0)
+	    unpush_target (&record_ops);
+          else
+            unpush_target (&record_core_ops);
+        }
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1203,7 +1981,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));

       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }

@@ -1243,6 +2021,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +2030,8 @@ _initialize_record (void)

   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);

   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
 			    _("Set debugging of record/replay feature."),
@@ -1259,9 +2041,10 @@ _initialize_record (void)
 			    NULL, show_record_debug, &setdebuglist,
 			    &showdebuglist);

-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-		  _("Abbreviated form of \"target record\" command."),
- 		  &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+		      _("Abbreviated form of \"target record\" command."),
+ 		      &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
 		  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +2059,11 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);

+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is
'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);

   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),


---
 doc/gdb.texinfo |   10 ++++++++++
 1 file changed, 10 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5214,6 +5214,16 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Dump the execution records of the inferior process to a core file.
+The optional argument @var{file} specifies the file name where to put
+the record dump.  If not specified, the file name defaults
+to @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
+inferior process.
 @end table

[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 56516 bytes --]

---
 gcore.c  |  309 ++++++++++++++----
 gcore.h  |   23 +
 record.c | 1072 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 1199 insertions(+), 205 deletions(-)

--- a/gcore.c
+++ b/gcore.c
@@ -25,10 +25,14 @@
 #include "gdbcore.h"
 #include "objfiles.h"
 #include "symfile.h"
-
+#include "arch-utils.h"
+#include "completer.h"
+#include "gcore.h"
 #include "cli/cli-decode.h"
-
 #include "gdb_assert.h"
+#include <fcntl.h>
+#include "regcache.h"
+#include "regset.h"
 
 /* The largest amount of memory to read from the target at once.  We
    must throttle it to limit the amount of memory used by GDB during
@@ -40,45 +44,27 @@ static enum bfd_architecture default_gco
 static unsigned long default_gcore_mach (void);
 static int gcore_memory_sections (bfd *);
 
-/* Generate a core file from the inferior process.  */
+/* create_gcore_bfd -- helper for gcore_command (exported).  */
 
-static void
-gcore_command (char *args, int from_tty)
+bfd *
+create_gcore_bfd (char *filename)
 {
-  struct cleanup *old_chain;
-  char *corefilename, corefilename_buffer[40];
-  asection *note_sec = NULL;
-  bfd *obfd;
-  void *note_data = NULL;
-  int note_size = 0;
-
-  /* No use generating a corefile without a target process.  */
-  if (!target_has_execution)
-    noprocess ();
-
-  if (args && *args)
-    corefilename = args;
-  else
-    {
-      /* Default corefile name is "core.PID".  */
-      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
-      corefilename = corefilename_buffer;
-    }
-
-  if (info_verbose)
-    fprintf_filtered (gdb_stdout,
-		      "Opening corefile '%s' for output.\n", corefilename);
-
-  /* Open the output file.  */
-  obfd = bfd_openw (corefilename, default_gcore_target ());
+  bfd *obfd = bfd_openw (filename, default_gcore_target ());
   if (!obfd)
-    error (_("Failed to open '%s' for output."), corefilename);
-
-  /* Need a cleanup that will close the file (FIXME: delete it?).  */
-  old_chain = make_cleanup_bfd_close (obfd);
-
+    error (_("Failed to open '%s' for output."), filename);
   bfd_set_format (obfd, bfd_core);
   bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ());
+  return obfd;
+}
+
+/* write_gcore_file -- helper for gcore_command (exported).  */
+
+void
+write_gcore_file (bfd *obfd)
+{
+  void *note_data = NULL;
+  int note_size = 0;
+  asection *note_sec = NULL;
 
   /* An external target method must build the notes section.  */
   note_data = target_make_corefile_notes (obfd, &note_size);
@@ -107,8 +93,46 @@ gcore_command (char *args, int from_tty)
   if (note_data != NULL && note_size != 0)
     {
       if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size))
-	warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error ()));
+	warning (_("writing note section (%s)"),
+		 bfd_errmsg (bfd_get_error ()));
     }
+}
+
+/* gcore_command -- implements the 'gcore' command.
+   Generate a core file from the inferior process.  */
+
+static void
+gcore_command (char *args, int from_tty)
+{
+  struct cleanup *old_chain;
+  char *corefilename, corefilename_buffer[40];
+  bfd *obfd;
+
+  /* No use generating a corefile without a target process.  */
+  if (!target_has_execution)
+    noprocess ();
+
+  if (args && *args)
+    corefilename = args;
+  else
+    {
+      /* Default corefile name is "core.PID".  */
+      sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid));
+      corefilename = corefilename_buffer;
+    }
+
+  if (info_verbose)
+    fprintf_filtered (gdb_stdout,
+		      "Opening corefile '%s' for output.\n", corefilename);
+
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (corefilename);
+
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_chain = make_cleanup_bfd_close (obfd);
+
+  /* Call worker function.  */
+  write_gcore_file (obfd);
 
   /* Succeeded.  */
   fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename);
@@ -212,6 +236,50 @@ derive_stack_segment (bfd_vma *bottom, b
   return 1;
 }
 
+/* call_target_sbrk --
+   helper function for derive_heap_segment and load_core_segment.  */
+
+static bfd_vma
+call_target_sbrk (int sbrk_arg)
+{
+  struct objfile *sbrk_objf;
+  struct gdbarch *gdbarch;
+  bfd_vma top_of_heap;
+  struct value *target_sbrk_arg;
+  struct value *sbrk_fn, *ret;
+  bfd_vma tmp;
+
+  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
+    {
+      sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf);
+      if (sbrk_fn == NULL)
+	return (bfd_vma) 0;
+    }
+  else
+    return (bfd_vma) 0;
+
+  gdbarch = get_objfile_arch (sbrk_objf);
+  target_sbrk_arg = value_from_longest (builtin_type (gdbarch)->builtin_int,
+					sbrk_arg);
+  gdb_assert (target_sbrk_arg);
+  ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg);
+  if (ret == NULL)
+    return (bfd_vma) 0;
+
+  tmp = value_as_long (ret);
+  if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff)
+    return (bfd_vma) 0;
+
+  top_of_heap = tmp;
+  return top_of_heap;
+}
+
 /* Derive a reasonable heap segment for ABFD by looking at sbrk and
    the static data sections.  Store its limits in *BOTTOM and *TOP.
    Return non-zero if successful.  */
@@ -219,12 +287,10 @@ derive_stack_segment (bfd_vma *bottom, b
 static int
 derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
 {
-  struct objfile *sbrk_objf;
   struct gdbarch *gdbarch;
   bfd_vma top_of_data_memory = 0;
   bfd_vma top_of_heap = 0;
   bfd_size_type sec_size;
-  struct value *zero, *sbrk;
   bfd_vma sec_vaddr;
   asection *sec;
 
@@ -259,29 +325,9 @@ derive_heap_segment (bfd *abfd, bfd_vma 
 	}
     }
 
-  /* Now get the top-of-heap by calling sbrk in the inferior.  */
-  if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL)
-    {
-      sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf);
-      if (sbrk == NULL)
-	return 0;
-    }
-  else
-    return 0;
-
-  gdbarch = get_objfile_arch (sbrk_objf);
-  zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0);
-  gdb_assert (zero);
-  sbrk = call_function_by_hand (sbrk, 1, &zero);
-  if (sbrk == NULL)
+  top_of_heap = call_target_sbrk (0);
+  if (top_of_heap == (bfd_vma) 0)
     return 0;
-  top_of_heap = value_as_long (sbrk);
 
   /* Return results.  */
   if (top_of_heap > top_of_data_memory)
@@ -299,13 +345,15 @@ static void
 make_output_phdrs (bfd *obfd, asection *osec, void *ignored)
 {
   int p_flags = 0;
-  int p_type;
+  int p_type = 0;
 
   /* FIXME: these constants may only be applicable for ELF.  */
   if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0)
     p_type = PT_LOAD;
-  else
+  else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0)
     p_type = PT_NOTE;
+  else
+    p_type = PT_NULL;
 
   p_flags |= PF_R;	/* Segment is readable.  */
   if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY))
@@ -516,6 +564,141 @@ gcore_memory_sections (bfd *obfd)
   return 1;
 }
 
+struct load_core_args_params {
+  int from_tty;
+  bfd_vma top_of_heap;
+};
+
+/* load_core_segments -- iterator function for bfd_map_over_sections.  */
+
+static void
+load_core_segment (bfd *abfd, asection *asect, void *arg)
+{
+  struct load_core_args_params *params = arg;
+  struct cleanup *old_chain;
+  char *memhunk;
+  int ret;
+
+  if ((bfd_section_size (abfd, asect) > 0) &&
+      (bfd_get_section_flags (abfd, asect) & SEC_LOAD) &&
+      !(bfd_get_section_flags (abfd, asect) & SEC_READONLY))
+    {
+      if (info_verbose && params->from_tty)
+	{
+	  printf_filtered (_("Load core section %s"),
+			   bfd_section_name (abfd, asect));
+	  printf_filtered (_(", vma 0x%08lx to 0x%08lx"),
+			   (unsigned long) bfd_section_vma (abfd, asect),
+			   (unsigned long) bfd_section_vma (abfd, asect) +
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(", size = %d"),
+			   (int) bfd_section_size (abfd, asect));
+	  printf_filtered (_(".\n"));
+	}
+      /* Fixme cleanup? */
+      memhunk = xmalloc (bfd_section_size (abfd, asect));
+      bfd_get_section_contents (abfd, asect, memhunk, 0,
+				bfd_section_size (abfd, asect));
+      if ((ret = target_write_memory (bfd_section_vma (abfd, asect),
+				      memhunk,
+				      bfd_section_size (abfd, asect))) != 0)
+	{
+	  print_sys_errmsg ("load_core_segment", ret);
+	  if ((LONGEST) params->top_of_heap <
+	      (LONGEST) bfd_section_vma (abfd, asect) +
+	      (LONGEST) bfd_section_size (abfd, asect))
+	    {
+	      int increment = bfd_section_vma (abfd, asect) +
+		bfd_section_size (abfd, asect) - params->top_of_heap;
+
+	      params->top_of_heap = call_target_sbrk (increment);
+	      if (params->top_of_heap == 0)
+		error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap);
+	      else
+		printf_filtered ("Increase TOH to 0x%08lx and retry.\n",
+				 (unsigned long) params->top_of_heap);
+	      if (target_write_memory (bfd_section_vma (abfd, asect),
+				       memhunk,
+				       bfd_section_size (abfd, asect)) != 0)
+		{
+		  error ("Nope, still failed.");
+		}
+	    }
+	}
+      xfree (memhunk);
+    }
+}
+
+/* load_corefile -- reads a corefile, copies its memory sections
+   into target memory, and its registers into target regcache.  */
+
+bfd *
+load_corefile (char *filename, int from_tty)
+{
+  struct load_core_args_params params;
+  struct bfd_section *regsect;
+  const struct regset *regset;
+  struct regcache *regcache;
+  struct cleanup *old_chain;
+  struct gdbarch *gdbarch;
+  char *scratch_path;
+  int scratch_chan;
+  char *contents;
+  bfd *core_bfd;
+  int size;
+
+  scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename,
+			O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path);
+  if (scratch_chan < 0)
+    perror_with_name (filename);
+
+  core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan);
+  old_chain = make_cleanup_bfd_close (core_bfd);
+  if (!core_bfd)
+    perror_with_name (scratch_path);
+
+  if (!bfd_check_format (core_bfd, bfd_core))
+    error (_("\"%s\" is not a core file: %s"),
+	   filename, bfd_errmsg (bfd_get_error ()));
+
+  params.from_tty = from_tty;
+  params.top_of_heap = call_target_sbrk (0);
+  if (params.top_of_heap == 0)
+    error (_("Couldn't get sbrk."));
+
+  bfd_map_over_sections (core_bfd, load_core_segment, (void *) &params);
+  /* Now need to get/set registers.  */
+  regsect = bfd_get_section_by_name (core_bfd, ".reg");
+
+  if (!regsect)
+    error (_("Couldn't find .reg section."));
+
+  size = bfd_section_size (core_bfd, regsect);
+  contents = alloca (size);
+  bfd_get_section_contents (core_bfd, regsect, contents, 0, size);
+
+  /* See FIXME kettenis/20031023 comment in corelow.c */
+  gdbarch = gdbarch_from_bfd (core_bfd);
+
+  if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch))
+    {
+      regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size);
+      if (!regset)
+	error (_("Failed to allocate regset."));
+
+      registers_changed ();
+      regcache = get_current_regcache ();
+      regset->supply_regset (regset, regcache, -1, contents, size);
+      reinit_frame_cache ();
+      target_store_registers (regcache, -1);
+    }
+  else
+    error (_("Failed to get regset from core section"));
+
+  discard_cleanups (old_chain);
+  return core_bfd;
+}
+
 /* Provide a prototype to silence -Wmissing-prototypes.  */
 extern initialize_file_ftype _initialize_gcore;
 
--- /dev/null
+++ b/gcore.h
@@ -0,0 +1,23 @@
+/* Support for reading/writing gcore files.
+
+   Copyright (C) 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/>.  */
+
+extern bfd *create_gcore_bfd (char *filename);
+extern void write_gcore_file (bfd *obfd);
+extern bfd *load_corefile (char *filename, int from_tty);
+
--- a/record.c
+++ b/record.c
@@ -23,15 +23,25 @@
 #include "gdbthread.h"
 #include "event-top.h"
 #include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
 #include "record.h"
+#include "elf-bfd.h"
+#include "gcore.h"
 
+#include <byteswap.h>
 #include <signal.h>
+#include <netinet/in.h>
 
 #define DEFAULT_RECORD_INSN_MAX_NUM	200000
 
 #define RECORD_IS_REPLAY \
      (record_list->next || execution_direction == EXEC_REVERSE)
 
+#define RECORD_FILE_MAGIC	htonl(0x20090829)
+
 /* These are the core struct of record function.
 
    An record_entry is a record of the value change of a register
@@ -78,9 +88,22 @@ struct record_entry
   } u;
 };
 
+struct record_core_buf_entry
+{
+  struct record_core_buf_entry *prev;
+  struct target_section *p;
+  bfd_byte *buf;
+};
+
 /* This is the debug switch for process record.  */
 int record_debug = 0;
 
+/* Record buf with core target.  */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
 /* These list is for execution log.  */
 static struct record_entry record_first;
 static struct record_entry *record_list = &record_first;
@@ -94,6 +117,7 @@ static int record_insn_num = 0;
 
 /* The target_ops of process record.  */
 static struct target_ops record_ops;
+static struct target_ops record_core_ops;
 
 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
@@ -169,7 +193,7 @@ record_list_release_next (void)
 }
 
 static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
 {
   struct record_entry *tmp = NULL;
   enum record_type type;
@@ -227,6 +251,56 @@ record_arch_list_add (struct record_entr
     }
 }
 
+/* Alloc a record_reg record entry. */
+
+static inline struct record_entry *
+record_reg_alloc (int num)
+{
+  struct record_entry *rec;
+
+  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;
+
+  return rec;
+}
+
+/* Alloc a record_mem record entry. */
+
+static inline struct record_entry *
+record_mem_alloc (int len)
+{
+  struct record_entry *rec;
+
+  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.len = len;
+  rec->u.mem.mem_entry_not_accessible = 0;
+
+  return rec;
+}
+
+/* Alloc a record_mem record entry. */
+
+static inline struct record_entry *
+record_end_alloc (void)
+{
+  struct record_entry *rec;
+
+  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+  rec->prev = NULL;
+  rec->next = NULL;
+  rec->type = record_end;
+
+  return rec;
+}
+
 /* Record the value of a register NUM to record_arch_list.  */
 
 int
@@ -240,12 +314,7 @@ record_arch_list_add_reg (struct regcach
 			"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;
+  rec = record_reg_alloc (num);
 
   regcache_raw_read (regcache, num, rec->u.reg.val);
 
@@ -271,14 +340,8 @@ record_arch_list_add_mem (CORE_ADDR addr
   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 = record_mem_alloc (len);
   rec->u.mem.addr = addr;
-  rec->u.mem.len = len;
-  rec->u.mem.mem_entry_not_accessible = 0;
 
   if (target_read_memory (addr, rec->u.mem.val, len))
     {
@@ -308,10 +371,7 @@ record_arch_list_add_end (void)
     fprintf_unfiltered (gdb_stdlog,
 			"Process record: add end to arch list.\n");
 
-  rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
-  rec->prev = NULL;
-  rec->next = NULL;
-  rec->type = record_end;
+  rec = record_end_alloc ();
 
   record_arch_list_add (rec);
 
@@ -340,30 +400,30 @@ record_check_insn_num (int set_terminal)
 	      if (q)
 		record_stop_at_limit = 0;
 	      else
-		error (_("Process record: inferior program stopped."));
+		error (_("Process record: stoped by user."));
 	    }
 	}
     }
 }
 
+static void
+record_arch_list_cleanups (void *ignore)
+{
+  record_list_release (record_arch_list_tail);
+}
+
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
    record_list.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
    record_arch_list, and add it to record_list.  */
 
-static void
-record_message_cleanups (void *ignore)
-{
-  record_list_release (record_arch_list_tail);
-}
-
 static int
 record_message (void *args)
 {
   int ret;
   struct regcache *regcache = args;
-  struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+  struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
 
   record_arch_list_head = NULL;
   record_arch_list_tail = NULL;
@@ -386,7 +446,7 @@ record_message (void *args)
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 
@@ -416,13 +476,297 @@ record_gdb_operation_disable_set (void)
   return old_cleanups;
 }
 
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+                   struct record_entry *entry)
+{
+  switch (entry->type)
+    {
+    case record_reg: /* reg */
+      {
+        gdb_byte reg[MAX_REGISTER_SIZE];
+
+        if (record_debug > 1)
+          fprintf_unfiltered (gdb_stdlog,
+                              "Process record: record_reg %s to "
+                              "inferior num = %d.\n",
+                              host_address_to_string (entry),
+                              entry->u.reg.num);
+
+        regcache_cooked_read (regcache, entry->u.reg.num, reg);
+        regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+        memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+      }
+      break;
+
+    case record_mem: /* mem */
+      {
+        if (!record_list->u.mem.mem_entry_not_accessible)
+          {
+            gdb_byte *mem = alloca (entry->u.mem.len);
+
+            if (record_debug > 1)
+              fprintf_unfiltered (gdb_stdlog,
+                                  "Process record: record_mem %s to "
+                                  "inferior addr = %s len = %d.\n",
+                                  host_address_to_string (entry),
+                                  paddress (gdbarch, entry->u.mem.addr),
+                                  record_list->u.mem.len);
+
+            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+              {
+                record_list->u.mem.mem_entry_not_accessible = 1;
+                if (record_debug)
+                  warning (_("Process record: error reading memory at "
+                             "addr = %s len = %d."),
+                           paddress (gdbarch, entry->u.mem.addr),
+                           entry->u.mem.len);
+              }
+            else
+              {
+                if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+                                         entry->u.mem.len))
+                  {
+                    record_list->u.mem.mem_entry_not_accessible = 1;
+                    if (record_debug)
+                      warning (_("Process record: error writing memory at "
+                                 "addr = %s len = %d."),
+                               paddress (gdbarch, entry->u.mem.addr),
+                               entry->u.mem.len);
+                  }
+                else
+                  memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+              }
+          }
+      }
+      break;
+    }
+}
+
+/* Load the execution log from core_bfd.  */
+
+static int
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
 static void
-record_open (char *name, int from_tty)
+record_load (void)
 {
-  struct target_ops *t;
+  uint32_t magic;
+  struct cleanup *old_cleanups;
+  struct record_entry *rec;
+  asection *osec;
+  int bfd_offset = 0;
+
+  /* We load the execution log from the open core bfd,
+     if there is one.  */
+  if (core_bfd == NULL)
+    return;
+
+  /* "record_load" just can be called when record list is empty.  */
+  gdb_assert (record_first.next == NULL);
 
   if (record_debug)
-    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+    fprintf_filtered (gdb_stdlog,
+		      _("Restoring recording from core file.\n"));
+
+  /* Now need to find our special note section.  */
+  osec = bfd_get_section_by_name (core_bfd, "null0");
+  printf_filtered ("Find precord section %s.\n",
+		   osec ? "succeeded" : "failed");
+  if (!osec)
+    return;
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, "osec name = '%s'\n",
+                      bfd_section_name (core_bfd, osec));
+
+  /* Check the magic code.  */
+  if (!bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset))
+    error (_("Failed to read 'magic' from core file (%s)"),
+           bfd_errmsg (bfd_get_error ()));
+  if (magic != RECORD_FILE_MAGIC)
+    error (_("Version mis-match or file format error."));
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, _("\
+Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                      magic);
+
+  /* Load the entries in recfd to the record_arch_list_head and
+     record_arch_list_tail.  */
+  record_arch_list_head = NULL;
+  record_arch_list_tail = NULL;
+  record_insn_num = 0;
+  old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+
+  while (1)
+    {
+      int ret;
+      uint8_t tmpu8;
+      uint64_t tmpu64;
+
+      /* FIXME: Check offset for end-of-section.  */
+      if (!bfdcore_read (core_bfd, osec, &tmpu8,
+                         sizeof (tmpu8), &bfd_offset))
+        break;
+
+      switch (tmpu8)
+        {
+        case record_reg: /* reg */
+          /* Get num to tmpu64.  */
+          if (!bfdcore_read (core_bfd, osec, &tmpu64,
+                             sizeof (tmpu64), &bfd_offset))
+            error (_("Failed to read regnum from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+
+          rec = record_reg_alloc ((int) tmpu64);
+
+          /* Get val.  */
+          if (!bfdcore_read (core_bfd, osec, rec->u.reg.val,
+                             MAX_REGISTER_SIZE, &bfd_offset))
+            error (_("Failed to read regval from  core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading register %d (1 plus 8 plus %d bytes)\n"),
+                                rec->u.reg.num,
+                                MAX_REGISTER_SIZE);
+          break;
+
+        case record_mem: /* mem */
+          /* Get len.  */
+          if (!bfdcore_read (core_bfd, osec, &tmpu64,
+                             sizeof (tmpu64), &bfd_offset))
+            error (_("Failed to read memlen from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+
+          rec = record_mem_alloc ((int) tmpu64);
+
+          /* Get addr.  */
+          if (!bfdcore_read (core_bfd, osec, &tmpu64,
+                             sizeof (tmpu64), &bfd_offset))
+            error (_("Failed to read memaddr from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+          if (BYTE_ORDER == LITTLE_ENDIAN)
+            tmpu64 = bswap_64 (tmpu64);
+          rec->u.mem.addr = tmpu64;
+
+          /* Get val.  */
+          if (!bfdcore_read (core_bfd, osec, rec->u.mem.val,
+                             rec->u.mem.len, &bfd_offset))
+            error (_("Failed to read memval from core file (%s)"),
+                   bfd_errmsg (bfd_get_error ()));
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog, _("\
+Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (get_current_arch (),
+                                                  rec->u.mem.addr),
+                                        rec->u.mem.len);
+          break;
+
+        case record_end: /* end */
+          rec = record_end_alloc ();
+          record_insn_num ++;
+
+          if (record_debug)
+            fprintf_unfiltered (gdb_stdlog,
+                                _("Reading record_end (1 byte)\n"));
+          break;
+
+        default:
+          error (_("Format of core file is not right."));
+          break;
+        }
+
+      /* Add rec to record arch list.  */
+      record_arch_list_add (rec);
+    }
+
+  discard_cleanups (old_cleanups);
+
+  /* Add record_arch_list_head to the end of record list.  */
+  record_first.next = record_arch_list_head;
+  record_arch_list_head->prev = &record_first;
+
+  /* Update record_insn_max_num.  */
+  if (record_insn_num > record_insn_max_num)
+    {
+      record_insn_max_num = record_insn_num;
+      warning (_("Auto increase record/replay buffer limit to %d."),
+               record_insn_max_num);
+    }
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, "Loaded records from core file.\n");
+
+  print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+                              enum target_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+                              struct target_waitstatus *,
+                              int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+                                       struct regcache *,
+                                       int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+                                       enum target_object object,
+                                       const char *annex,
+                                       gdb_byte *readbuf,
+                                       const gdb_byte *writebuf,
+                                       ULONGEST offset,
+                                       LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+                                        struct bp_target_info *);
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+  struct regcache *regcache = get_current_regcache ();
+  int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+  int i;
+
+  /* Get record_core_regbuf.  */
+  target_fetch_registers (regcache, -1);
+  record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+  for (i = 0; i < regnum; i ++)
+    regcache_raw_collect (regcache, i,
+                          record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+  /* Get record_core_start and record_core_end.  */
+  if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+      error (_("\"%s\": Can't find sections: %s"),
+             bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+    }
+
+  push_target (&record_core_ops);
+}
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+  struct target_ops *t;
 
   /* check exec */
   if (!target_has_execution)
@@ -438,6 +782,28 @@ record_open (char *name, int from_tty)
     error (_("Process record: the current architecture doesn't support "
 	     "record function."));
 
+  if (!tmp_to_resume)
+    error (_("Process record can't get to_resume."));
+  if (!tmp_to_wait)
+    error (_("Process record can't get to_wait."));
+  if (!tmp_to_store_registers)
+    error (_("Process record can't get to_store_registers."));
+  if (!tmp_to_insert_breakpoint)
+    error (_("Process record can't get to_insert_breakpoint."));
+  if (!tmp_to_remove_breakpoint)
+    error (_("Process record can't get to_remove_breakpoint."));
+
+  push_target (&record_ops);
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+  struct target_ops *t;
+
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
   /* Check if record target is already running.  */
   if (current_target.to_stratum == record_stratum)
     {
@@ -447,70 +813,102 @@ record_open (char *name, int from_tty)
 	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;
+  /* Reset the tmp beneath pointers.  */
+  tmp_to_resume_ops = NULL;
+  tmp_to_resume = NULL;
+  tmp_to_wait_ops = NULL;
+  tmp_to_wait = NULL;
+  tmp_to_store_registers_ops = NULL;
+  tmp_to_store_registers = NULL;
+  tmp_to_xfer_partial_ops = NULL;
+  tmp_to_xfer_partial = NULL;
+  tmp_to_insert_breakpoint = NULL;
+  tmp_to_remove_breakpoint = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
     {
-      if (!record_beneath_to_resume)
+      if (!tmp_to_resume)
         {
-	  record_beneath_to_resume = t->to_resume;
-	  record_beneath_to_resume_ops = t;
+	  tmp_to_resume = t->to_resume;
+	  tmp_to_resume_ops = t;
         }
-      if (!record_beneath_to_wait)
+      if (!tmp_to_wait)
         {
-	  record_beneath_to_wait = t->to_wait;
-	  record_beneath_to_wait_ops = t;
+	  tmp_to_wait = t->to_wait;
+	  tmp_to_wait_ops = t;
         }
-      if (!record_beneath_to_store_registers)
+      if (!tmp_to_store_registers)
         {
-	  record_beneath_to_store_registers = t->to_store_registers;
-	  record_beneath_to_store_registers_ops = t;
+	  tmp_to_store_registers = t->to_store_registers;
+	  tmp_to_store_registers_ops = t;
         }
-      if (!record_beneath_to_xfer_partial)
+      if (!tmp_to_xfer_partial)
         {
-	  record_beneath_to_xfer_partial = t->to_xfer_partial;
-	  record_beneath_to_xfer_partial_ops = t;
+	  tmp_to_xfer_partial = t->to_xfer_partial;
+	  tmp_to_xfer_partial_ops = t;
         }
-      if (!record_beneath_to_insert_breakpoint)
-	record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
-      if (!record_beneath_to_remove_breakpoint)
-	record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
+      if (!tmp_to_insert_breakpoint)
+	tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+      if (!tmp_to_remove_breakpoint)
+	tmp_to_remove_breakpoint = t->to_remove_breakpoint;
     }
-  if (!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)
+  if (!tmp_to_xfer_partial)
     error (_("Process record can't get to_xfer_partial."));
-  if (!record_beneath_to_insert_breakpoint)
-    error (_("Process record can't get to_insert_breakpoint."));
-  if (!record_beneath_to_remove_breakpoint)
-    error (_("Process record can't get to_remove_breakpoint."));
 
-  push_target (&record_ops);
+  if (current_target.to_stratum == core_stratum)
+    record_core_open_1 (name, from_tty);
+  else
+    record_open_1 (name, from_tty);
 
   /* Reset */
   record_insn_num = 0;
   record_list = &record_first;
   record_list->next = NULL;
+
+  /* Set the tmp beneath pointers to beneath pointers.  */
+  record_beneath_to_resume_ops = tmp_to_resume_ops;
+  record_beneath_to_resume = tmp_to_resume;
+  record_beneath_to_wait_ops = tmp_to_wait_ops;
+  record_beneath_to_wait = tmp_to_wait;
+  record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+  record_beneath_to_store_registers = tmp_to_store_registers;
+  record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+  record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+  record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+  record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+
+  if (strcmp (current_target.to_shortname, "record_core") == 0)
+    record_load ();
 }
 
 static void
 record_close (int quitting)
 {
+  struct record_core_buf_entry *entry;
+
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
 
   record_list_release (record_list);
+
+  /* Release record_core_regbuf.  */
+  if (record_core_regbuf)
+    {
+      xfree (record_core_regbuf);
+      record_core_regbuf = NULL;
+    }
+
+  /* Release record_core_buf_list.  */
+  if (record_core_buf_list)
+    {
+      for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+        {
+          xfree (record_core_buf_list);
+          record_core_buf_list = entry;
+        }
+      record_core_buf_list = NULL;
+    }
 }
 
 static int record_resume_step = 0;
@@ -584,7 +982,7 @@ record_wait (struct target_ops *ops,
 			"record_resume_step = %d\n",
 			record_resume_step);
 
-  if (!RECORD_IS_REPLAY)
+  if (!RECORD_IS_REPLAY && ops != &record_core_ops)
     {
       if (record_resume_error)
 	{
@@ -712,76 +1110,9 @@ record_wait (struct target_ops *ops,
 	      break;
 	    }
 
-	  /* Set ptid, register and memory according to record_list.  */
-	  if (record_list->type == record_reg)
-	    {
-	      /* reg */
-	      gdb_byte reg[MAX_REGISTER_SIZE];
-	      if (record_debug > 1)
-		fprintf_unfiltered (gdb_stdlog,
-				    "Process record: record_reg %s to "
-				    "inferior num = %d.\n",
-				    host_address_to_string (record_list),
-				    record_list->u.reg.num);
-	      regcache_cooked_read (regcache, record_list->u.reg.num, reg);
-	      regcache_cooked_write (regcache, record_list->u.reg.num,
-				     record_list->u.reg.val);
-	      memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
-	    }
-	  else if (record_list->type == record_mem)
-	    {
-	      /* mem */
-	      /* Nothing to do if the entry is flagged not_accessible.  */
-	      if (!record_list->u.mem.mem_entry_not_accessible)
-		{
-		  gdb_byte *mem = alloca (record_list->u.mem.len);
-		  if (record_debug > 1)
-		    fprintf_unfiltered (gdb_stdlog,
-				        "Process record: record_mem %s to "
-				        "inferior addr = %s len = %d.\n",
-				        host_address_to_string (record_list),
-				        paddress (gdbarch,
-					          record_list->u.mem.addr),
-				        record_list->u.mem.len);
+          record_exec_entry (regcache, gdbarch, record_list);
 
-		  if (target_read_memory (record_list->u.mem.addr, mem,
-		                          record_list->u.mem.len))
-	            {
-		      if (execution_direction != EXEC_REVERSE)
-		        error (_("Process record: error reading memory at "
-			         "addr = %s len = %d."),
-		               paddress (gdbarch, record_list->u.mem.addr),
-		               record_list->u.mem.len);
-		      else
-			/* Read failed -- 
-			   flag entry as not_accessible.  */
-		        record_list->u.mem.mem_entry_not_accessible = 1;
-		    }
-		  else
-		    {
-		      if (target_write_memory (record_list->u.mem.addr,
-			                       record_list->u.mem.val,
-		                               record_list->u.mem.len))
-	                {
-			  if (execution_direction != EXEC_REVERSE)
-			    error (_("Process record: error writing memory at "
-			             "addr = %s len = %d."),
-		                   paddress (gdbarch, record_list->u.mem.addr),
-		                   record_list->u.mem.len);
-			  else
-			    /* Write failed -- 
-			       flag entry as not_accessible.  */
-			    record_list->u.mem.mem_entry_not_accessible = 1;
-			}
-		      else
-		        {
-			  memcpy (record_list->u.mem.val, mem,
-				  record_list->u.mem.len);
-			}
-		    }
-		}
-	    }
-	  else
+	  if (record_list->type == record_end)
 	    {
 	      if (record_debug > 1)
 		fprintf_unfiltered (gdb_stdlog,
@@ -901,6 +1232,7 @@ record_kill (struct target_ops *ops)
     fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
 
   unpush_target (&record_ops);
+
   target_kill ();
 }
 
@@ -945,7 +1277,7 @@ record_registers_change (struct regcache
   record_list = record_arch_list_tail;
 
   if (record_insn_num == record_insn_max_num && record_insn_max_num)
-    record_list_release_first ();
+    record_list_release_first_insn ();
   else
     record_insn_num++;
 }
@@ -1058,7 +1390,7 @@ record_xfer_partial (struct target_ops *
       record_list = record_arch_list_tail;
 
       if (record_insn_num == record_insn_max_num && record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
       else
 	record_insn_num++;
     }
@@ -1138,6 +1470,191 @@ init_record_ops (void)
 }
 
 static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+                    enum target_signal siggnal)
+{
+  record_resume_step = step;
+  record_resume_siggnal = siggnal;
+}
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+  unpush_target (&record_core_ops);
+}
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (regno < 0)
+    {
+      int num = gdbarch_num_regs (get_regcache_arch (regcache));
+      int i;
+
+      for (i = 0; i < num; i ++)
+        regcache_raw_supply (regcache, i,
+                             record_core_regbuf + MAX_REGISTER_SIZE * i);
+    }
+  else
+    regcache_raw_supply (regcache, regno,
+                         record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+static void
+record_core_store_registers (struct target_ops *ops,
+                             struct regcache *regcache,
+                             int regno)
+{
+  if (record_gdb_operation_disable)
+    regcache_raw_collect (regcache, regno,
+                          record_core_regbuf + MAX_REGISTER_SIZE * regno);
+  else
+    error (_("You can't do that without a process to debug."));
+}
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+		          const char *annex, gdb_byte *readbuf,
+		          const gdb_byte *writebuf, ULONGEST offset,
+                          LONGEST len)
+{
+   if (object == TARGET_OBJECT_MEMORY)
+     {
+       if (record_gdb_operation_disable || !writebuf)
+         {
+           struct target_section *p;
+           for (p = record_core_start; p < record_core_end; p++)
+             {
+               if (offset >= p->addr)
+                 {
+                   struct record_core_buf_entry *entry;
+
+                   if (offset >= p->endaddr)
+                     continue;
+
+                   if (offset + len > p->endaddr)
+                     len = p->endaddr - offset;
+
+                   offset -= p->addr;
+
+                   /* Read readbuf or write writebuf p, offset, len.  */
+                   /* Check flags.  */
+                   if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+                       || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+                     {
+                       if (readbuf)
+                         memset (readbuf, 0, len);
+                       return len;
+                     }
+                   /* Get record_core_buf_entry.  */
+                   for (entry = record_core_buf_list; entry;
+                        entry = entry->prev)
+                     if (entry->p == p)
+                       break;
+                   if (writebuf)
+                     {
+                       if (!entry)
+                         {
+                           /* Add a new entry.  */
+                           entry
+                             = (struct record_core_buf_entry *)
+                                 xmalloc
+                                   (sizeof (struct record_core_buf_entry));
+                           entry->p = p;
+                           if (!bfd_malloc_and_get_section (p->bfd,
+                                                            p->the_bfd_section,
+                                                            &entry->buf))
+                             {
+                               xfree (entry);
+                               return 0;
+                             }
+                           entry->prev = record_core_buf_list;
+                           record_core_buf_list = entry;
+                         }
+
+                        memcpy (entry->buf + offset, writebuf, (size_t) len);
+                     }
+                   else
+                     {
+                       if (!entry)
+                         return record_beneath_to_xfer_partial
+                                  (record_beneath_to_xfer_partial_ops,
+                                   object, annex, readbuf, writebuf,
+                                   offset, len);
+
+                       memcpy (readbuf, entry->buf + offset, (size_t) len);
+                     }
+
+                   return len;
+                 }
+             }
+
+           return -1;
+         }
+       else
+         error (_("You can't do that without a process to debug."));
+     }
+
+  return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+                                         object, annex, readbuf, writebuf,
+                                         offset, len);
+}
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+			       struct bp_target_info *bp_tgt)
+{
+  return 0;
+}
+
+int
+record_core_has_execution (struct target_ops *ops)
+{
+  return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+  record_core_ops.to_shortname = "record_core";
+  record_core_ops.to_longname = "Process record and replay target";
+  record_core_ops.to_doc =
+    "Log program while executing and replay execution from log.";
+  record_core_ops.to_open = record_open;
+  record_core_ops.to_close = record_close;
+  record_core_ops.to_resume = record_core_resume;
+  record_core_ops.to_wait = record_wait;
+  record_core_ops.to_kill = record_core_kill;
+  record_core_ops.to_fetch_registers = record_core_fetch_registers;
+  record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+  record_core_ops.to_store_registers = record_core_store_registers;
+  record_core_ops.to_xfer_partial = record_core_xfer_partial;
+  record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+  record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+  record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+  record_core_ops.to_has_execution = record_core_has_execution;
+  record_core_ops.to_stratum = record_stratum;
+  record_core_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)
 {
@@ -1153,6 +1670,262 @@ cmd_record_start (char *args, int from_t
   execute_command ("target record", from_tty);
 }
 
+/* Record log save-file format
+   Version 1
+
+   Header:
+     4 bytes: magic number htonl(0x20090829).
+       NOTE: be sure to change whenever this file format changes!
+
+   Records:
+     record_end:
+       1 byte:  record type (record_end).
+     record_reg:
+       1 byte:  record type (record_reg).
+       8 bytes: register id (network byte order).
+       MAX_REGISTER_SIZE bytes: register value.
+     record_mem:
+       1 byte:  record type (record_mem).
+       8 bytes: memory length (network byte order).
+       8 bytes: memory address (network byte order).
+       n bytes: memory value (n == memory length).
+*/
+
+/* bfdcore_write -- write bytes into a core file section.  */
+
+static int
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+  int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+  if (ret)
+    *offset += len;
+  return ret;
+}
+
+/* Dump the execution log to a file.  */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+  char *recfilename, recfilename_buffer[40];
+  int recfd;
+  struct record_entry *cur_record_list;
+  uint32_t magic;
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
+  struct cleanup *old_cleanups;
+  struct cleanup *set_cleanups;
+  bfd *obfd;
+  int dump_size = 0;
+  asection *osec = NULL;
+  int bfd_offset = 0;
+
+  if (strcmp (current_target.to_shortname, "record") != 0)
+    error (_("Process record is not started.\n"));
+
+  if (args && *args)
+    recfilename = args;
+  else
+    {
+      /* Default recfile name is "gdb_record.PID".  */
+      snprintf (recfilename_buffer, sizeof (recfilename_buffer),
+                "gdb_record.%d", PIDGET (inferior_ptid));
+      recfilename = recfilename_buffer;
+    }
+
+  /* Open the dump file.  */
+  if (record_debug)
+    fprintf_unfiltered (gdb_stdlog,
+                        _("Saving recording to file '%s'\n"),
+                        recfilename);
+  /* Open the output file.  */
+  obfd = create_gcore_bfd (recfilename);
+  /* Need a cleanup that will close the file (FIXME: delete it?).  */
+  old_cleanups = make_cleanup_bfd_close (obfd);
+
+  /* Save the current record entry to "cur_record_list".  */
+  cur_record_list = record_list;
+
+  /* Get the values of regcache and gdbarch.  */
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+
+  /* Disable the GDB operation record.  */
+  set_cleanups = record_gdb_operation_disable_set ();
+
+  /* Reverse execute to the begin of record list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == &record_first)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  /* Compute the size needed for the extra bfd section.  */
+  dump_size = 4;	/* magic cookie */
+  for (record_list = &record_first; record_list;
+       record_list = record_list->next)
+    switch (record_list->type)
+      {
+      case record_end:
+	dump_size += 1;
+	break;
+      case record_reg:
+	dump_size += 1 + 8 + MAX_REGISTER_SIZE;
+	break;
+      case record_mem:
+	dump_size += 1 + 8 + 8 + record_list->u.mem.len;
+	break;
+      }
+
+  /* Make the new bfd section.  */
+  osec = bfd_make_section_anyway_with_flags (obfd, "precord",
+                                             SEC_HAS_CONTENTS
+                                             | SEC_READONLY);
+  if (osec == NULL)
+    error (_("Failed to create 'precord' section for corefile: %s"),
+           bfd_errmsg (bfd_get_error ()));
+  bfd_set_section_size (obfd, osec, dump_size);
+  bfd_set_section_vma (obfd, osec, 0);
+  bfd_set_section_alignment (obfd, osec, 0);
+  bfd_section_lma (obfd, osec) = 0;
+
+  /* Save corefile state.  */
+  write_gcore_file (obfd);
+
+  /* Write out the record log.  */
+  /* Write the magic code.  */
+  magic = RECORD_FILE_MAGIC;
+  if (record_debug)
+    fprintf_filtered (gdb_stdlog, _("\
+Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"),
+                      magic);
+  if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset))
+    error (_("Failed to write 'magic' to %s (%s)"),
+           recfilename, bfd_errmsg (bfd_get_error ()));
+
+  /* Dump the entries to recfd and forward execute to the end of
+     record list.  */
+  record_list = &record_first;
+  while (1)
+    {
+      /* Dump entry.  */
+      if (record_list != &record_first)
+        {
+          uint8_t tmpu8;
+          uint64_t tmpu64;
+
+          tmpu8 = record_list->type;
+          if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8),
+                              &bfd_offset))
+            error (_("Failed to write 'type' to %s (%s)"),
+	           recfilename, bfd_errmsg (bfd_get_error ()));
+
+          switch (record_list->type)
+            {
+            case record_reg: /* reg */
+              if (record_debug)
+                fprintf_unfiltered (gdb_stdlog, _("\
+Writing register %d (1 plus 8 plus %d bytes)\n"),
+                                    record_list->u.reg.num,
+                                    MAX_REGISTER_SIZE);
+
+              /* Write regnum.  */
+              tmpu64 = record_list->u.reg.num;
+              if (BYTE_ORDER == LITTLE_ENDIAN)
+                tmpu64 = bswap_64 (tmpu64);
+              if (!bfdcore_write (obfd, osec, &tmpu64,
+                                  sizeof (tmpu64), &bfd_offset))
+                error (_("Failed to write regnum to %s (%s)"),
+              recfilename, bfd_errmsg (bfd_get_error ()));
+
+              /* Write regval.  */
+              if (!bfdcore_write (obfd, osec, record_list->u.reg.val,
+                                  MAX_REGISTER_SIZE, &bfd_offset))
+                error (_("Failed to write regval to %s (%s)"),
+                       recfilename, bfd_errmsg (bfd_get_error ()));
+              break;
+
+            case record_mem: /* mem */
+              if (!record_list->u.mem.mem_entry_not_accessible)
+                {
+                  if (record_debug)
+                    fprintf_unfiltered (gdb_stdlog, _("\
+Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"),
+                                        paddress (gdbarch,
+                                                  record_list->u.mem.addr),
+                                        record_list->u.mem.len);
+
+                  /* Write memlen.  */
+                  tmpu64 = record_list->u.mem.len;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  if (!bfdcore_write (obfd, osec, &tmpu64,
+                                      sizeof (tmpu64), &bfd_offset))
+                    error (_("Failed to write memlen to %s (%s)"),
+                           recfilename, bfd_errmsg (bfd_get_error ()));
+
+                  /* Write memaddr.  */
+                  tmpu64 = record_list->u.mem.addr;
+                  if (BYTE_ORDER == LITTLE_ENDIAN)
+                    tmpu64 = bswap_64 (tmpu64);
+                  if (!bfdcore_write (obfd, osec, &tmpu64,
+                                      sizeof (tmpu64), &bfd_offset))
+                    error (_("Failed to write memaddr to %s (%s)"),
+                           recfilename, bfd_errmsg (bfd_get_error ()));
+
+                  /* Write memval.  */
+                  if (!bfdcore_write (obfd, osec, record_list->u.mem.val,
+                                      record_list->u.mem.len, &bfd_offset))
+                    error (_("Failed to write memval to %s (%s)"),
+                           recfilename, bfd_errmsg (bfd_get_error ()));
+                }
+              break;
+
+              case record_end:
+                /* FIXME: record the contents of record_end rec.  */
+                if (record_debug)
+                  fprintf_unfiltered (gdb_stdlog,
+                                      _("Writing record_end (1 byte)\n"));
+                break;
+            }
+        }
+
+      /* Execute entry.  */
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->next)
+        record_list = record_list->next;
+      else
+        break;
+    }
+
+  /* Reverse execute to cur_record_list.  */
+  while (1)
+    {
+      /* Check for beginning and end of log.  */
+      if (record_list == cur_record_list)
+        break;
+
+      record_exec_entry (regcache, gdbarch, record_list);
+
+      if (record_list->prev)
+        record_list = record_list->prev;
+    }
+
+  do_cleanups (set_cleanups);
+  do_cleanups (old_cleanups);
+
+  /* Succeeded.  */
+  fprintf_filtered (gdb_stdout, _("Saved recfile %s.\n"), recfilename);
+}
+
 /* Truncate the record log from the present point
    of replay until the end.  */
 
@@ -1185,7 +1958,12 @@ cmd_record_stop (char *args, int from_tt
     {
       if (!record_list || !from_tty || query (_("Delete recorded log and "
 	                                        "stop recording?")))
-	unpush_target (&record_ops);
+        {
+          if (strcmp (current_target.to_shortname, "record") == 0)
+	    unpush_target (&record_ops);
+          else
+            unpush_target (&record_core_ops);
+        }
     }
   else
     printf_unfiltered (_("Process record is not started.\n"));
@@ -1203,7 +1981,7 @@ set_record_insn_max_num (char *args, int
 		           "the first ones?\n"));
 
       while (record_insn_num > record_insn_max_num)
-	record_list_release_first ();
+	record_list_release_first_insn ();
     }
 }
 
@@ -1243,6 +2021,8 @@ info_record_command (char *args, int fro
 void
 _initialize_record (void)
 {
+  struct cmd_list_element *c;
+
   /* Init record_first.  */
   record_first.prev = NULL;
   record_first.next = NULL;
@@ -1250,6 +2030,8 @@ _initialize_record (void)
 
   init_record_ops ();
   add_target (&record_ops);
+  init_record_core_ops ();
+  add_target (&record_core_ops);
 
   add_setshow_zinteger_cmd ("record", no_class, &record_debug,
 			    _("Set debugging of record/replay feature."),
@@ -1259,9 +2041,10 @@ _initialize_record (void)
 			    NULL, show_record_debug, &setdebuglist,
 			    &showdebuglist);
 
-  add_prefix_cmd ("record", class_obscure, cmd_record_start,
-		  _("Abbreviated form of \"target record\" command."),
- 		  &record_cmdlist, "record ", 0, &cmdlist);
+  c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+		      _("Abbreviated form of \"target record\" command."),
+ 		      &record_cmdlist, "record ", 0, &cmdlist);
+  set_cmd_completer (c, filename_completer);
   add_com_alias ("rec", "record", class_obscure, 1);
   add_prefix_cmd ("record", class_support, set_record_command,
 		  _("Set record options"), &set_record_cmdlist,
@@ -1276,6 +2059,11 @@ _initialize_record (void)
 		  "info record ", 0, &infolist);
   add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
 
+  c = add_cmd ("dump", class_obscure, cmd_record_dump,
+	       _("Dump the execution records to a file.\n\
+Argument is optional filename.  Default filename is 'gdb_record.<process_id>'."),
+               &record_cmdlist);
+  set_cmd_completer (c, filename_completer);
 
   add_cmd ("delete", class_obscure, cmd_record_delete,
 	   _("Delete the rest of execution log and start recording it anew."),

[-- Attachment #3: prec-dump-doc.txt --]
[-- Type: text/plain, Size: 794 bytes --]

---
 doc/gdb.texinfo |   10 ++++++++++
 1 file changed, 10 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5214,6 +5214,16 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Dump the execution records of the inferior process to a core file.
+The optional argument @var{file} specifies the file name where to put
+the record dump.  If not specified, the file name defaults
+to @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
+inferior process.
 @end table
 
 

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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 15:53                                                       ` Hui Zhu
@ 2009-08-29 18:06                                                         ` Eli Zaretskii
  2009-08-29 18:28                                                           ` Hui Zhu
  2009-08-29 20:05                                                           ` Michael Snyder
  0 siblings, 2 replies; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-29 18:06 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Sat, 29 Aug 2009 23:18:29 +0800
> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> +@kindex record dump
> +@kindex rec dump
> +@item record dump [@var{file}]
> +@itemx rec dump [@var{file}]
> +Dump the execution records of the inferior process to a core file.

"A core file"? you didn't really mean that, did you?

Okay, with that bit fixed.

Thanks.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 18:06                                                         ` Eli Zaretskii
@ 2009-08-29 18:28                                                           ` Hui Zhu
  2009-08-29 20:26                                                             ` Eli Zaretskii
  2009-08-29 20:05                                                           ` Michael Snyder
  1 sibling, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-29 18:28 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: msnyder, gdb-patches

On Sun, Aug 30, 2009 at 02:01, Eli Zaretskii<eliz@gnu.org> wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Sat, 29 Aug 2009 23:18:29 +0800
>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> +@kindex record dump
>> +@kindex rec dump
>> +@item record dump [@var{file}]
>> +@itemx rec dump [@var{file}]
>> +Dump the execution records of the inferior process to a core file.
>
> "A core file"? you didn't really mean that, did you?
>
> Okay, with that bit fixed.
>
> Thanks.
>

Yes, it really dump record log to a core file.  Now, record log is
together with core file.

Thanks,
Hui


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 18:06                                                         ` Eli Zaretskii
  2009-08-29 18:28                                                           ` Hui Zhu
@ 2009-08-29 20:05                                                           ` Michael Snyder
  2009-08-29 20:33                                                             ` Eli Zaretskii
  1 sibling, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-29 20:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches

Eli Zaretskii wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Sat, 29 Aug 2009 23:18:29 +0800
>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> +@kindex record dump
>> +@kindex rec dump
>> +@item record dump [@var{file}]
>> +@itemx rec dump [@var{file}]
>> +Dump the execution records of the inferior process to a core file.
> 
> "A core file"? you didn't really mean that, did you?

Yep!  That was my suggestion.  We use a core file to capture the
state at the beginning of the recording, then add an extra bfd
section to the corefile and write the recording log into that.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 18:28                                                           ` Hui Zhu
@ 2009-08-29 20:26                                                             ` Eli Zaretskii
  2009-08-29 20:39                                                               ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-29 20:26 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Sun, 30 Aug 2009 02:05:37 +0800
> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
> 
> > "A core file"? you didn't really mean that, did you?
> 
> Yes, it really dump record log to a core file.  Now, record log is
> together with core file.

Sorry, I don't understand: why would we want to have the record log in
a core file?  What am I missing here?


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 20:05                                                           ` Michael Snyder
@ 2009-08-29 20:33                                                             ` Eli Zaretskii
  2009-08-29 21:20                                                               ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-29 20:33 UTC (permalink / raw)
  To: Michael Snyder; +Cc: teawater, gdb-patches

> Date: Sat, 29 Aug 2009 11:29:04 -0700
> From: Michael Snyder <msnyder@vmware.com>
> CC: Hui Zhu <teawater@gmail.com>, 
>  "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> > "A core file"? you didn't really mean that, did you?
> 
> Yep!  That was my suggestion.  We use a core file to capture the
> state at the beginning of the recording, then add an extra bfd
> section to the corefile and write the recording log into that.

What is this useful for?


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 20:26                                                             ` Eli Zaretskii
@ 2009-08-29 20:39                                                               ` Michael Snyder
  2009-08-30  3:03                                                                 ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-08-29 20:39 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches

Eli Zaretskii wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Sun, 30 Aug 2009 02:05:37 +0800
>> Cc: msnyder@vmware.com, gdb-patches@sourceware.org
>>
>>> "A core file"? you didn't really mean that, did you?
>> Yes, it really dump record log to a core file.  Now, record log is
>> together with core file.
> 
> Sorry, I don't understand: why would we want to have the record log in
> a core file?  What am I missing here?

Because the record-log itself does not record starting state --
only changes of state.  It is useless by itself if you don't
restore the starting state first.

Hui's original patch used the "gcore" command to save the
starting state, and "target core" to restore it.

The problem that I found with that was that it created two
independent files, core file and record-log file, and that
there was no way to assure ourselves (or the user) that the
two files corresponded to one another when they were re-loaded.

That's why I suggested combining them into one file --
in this implementation, by adding the record-log to an
extra section in the core file.



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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 20:33                                                             ` Eli Zaretskii
@ 2009-08-29 21:20                                                               ` Michael Snyder
  0 siblings, 0 replies; 54+ messages in thread
From: Michael Snyder @ 2009-08-29 21:20 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: teawater, gdb-patches

Eli Zaretskii wrote:
>> Date: Sat, 29 Aug 2009 11:29:04 -0700
>> From: Michael Snyder <msnyder@vmware.com>
>> CC: Hui Zhu <teawater@gmail.com>, 
>>  "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>>> "A core file"? you didn't really mean that, did you?
>> Yep!  That was my suggestion.  We use a core file to capture the
>> state at the beginning of the recording, then add an extra bfd
>> section to the corefile and write the recording log into that.
> 
> What is this useful for?

I'm replying twice, just in case one of my explanations happens
to 'click' better than the other.

The record-log that process-record keeps in memory records only
changes in state -- it does not contain a 'snapshot' of the entire
state at any point (eg. at the beginning of the session).  Therefore
you can't use it unles you can first duplicate the starting state.

Hui's approach was to first save a corefile, to record the starting
state, and then save the changes file separately.  A brilliant idea,
but it has a drawback -- since they are separate files, there is no
way to protect users against loading a start state from one program
and a change set from a completely different program.



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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-29 20:39                                                               ` Michael Snyder
@ 2009-08-30  3:03                                                                 ` Eli Zaretskii
  2009-08-30  5:36                                                                   ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-30  3:03 UTC (permalink / raw)
  To: Michael Snyder; +Cc: teawater, gdb-patches

> Date: Sat, 29 Aug 2009 13:33:34 -0700
> From: Michael Snyder <msnyder@vmware.com>
> CC: Hui Zhu <teawater@gmail.com>, 
>  "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> > Sorry, I don't understand: why would we want to have the record log in
> > a core file?  What am I missing here?
> 
> Because the record-log itself does not record starting state --
> only changes of state.  It is useless by itself if you don't
> restore the starting state first.
> 
> Hui's original patch used the "gcore" command to save the
> starting state, and "target core" to restore it.
> 
> The problem that I found with that was that it created two
> independent files, core file and record-log file, and that
> there was no way to assure ourselves (or the user) that the
> two files corresponded to one another when they were re-loaded.
> 
> That's why I suggested combining them into one file --
> in this implementation, by adding the record-log to an
> extra section in the core file.

Thanks for explaining.

This all needs to be said in the manual (in a form suitable for the
manual, omitting the technicalities and describing this from user
perspective).  We cannot just say "dump record log to core file".  So
I hereby revoke my approval of the patch for the manual.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-30  3:03                                                                 ` Eli Zaretskii
@ 2009-08-30  5:36                                                                   ` Hui Zhu
  2009-08-30 23:40                                                                     ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-30  5:36 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Michael Snyder, gdb-patches

On Sun, Aug 30, 2009 at 10:56, Eli Zaretskii<eliz@gnu.org> wrote:
>> Date: Sat, 29 Aug 2009 13:33:34 -0700
>> From: Michael Snyder <msnyder@vmware.com>
>> CC: Hui Zhu <teawater@gmail.com>,
>>  "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> > Sorry, I don't understand: why would we want to have the record log in
>> > a core file?  What am I missing here?
>>
>> Because the record-log itself does not record starting state --
>> only changes of state.  It is useless by itself if you don't
>> restore the starting state first.
>>
>> Hui's original patch used the "gcore" command to save the
>> starting state, and "target core" to restore it.
>>
>> The problem that I found with that was that it created two
>> independent files, core file and record-log file, and that
>> there was no way to assure ourselves (or the user) that the
>> two files corresponded to one another when they were re-loaded.
>>
>> That's why I suggested combining them into one file --
>> in this implementation, by adding the record-log to an
>> extra section in the core file.
>
> Thanks for explaining.
>
> This all needs to be said in the manual (in a form suitable for the
> manual, omitting the technicalities and describing this from user
> perspective).  We cannot just say "dump record log to core file".  So
> I hereby revoke my approval of the patch for the manual.
>

Agree with you, Eli.  Do you have more better words on it?  You know
my poor english.  :)

Thanks,
Hui


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-30  5:36                                                                   ` Hui Zhu
@ 2009-08-30 23:40                                                                     ` Eli Zaretskii
  2009-08-31  7:10                                                                       ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2009-08-30 23:40 UTC (permalink / raw)
  To: Hui Zhu; +Cc: msnyder, gdb-patches

> From: Hui Zhu <teawater@gmail.com>
> Date: Sun, 30 Aug 2009 11:20:32 +0800
> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org
> 
> > This all needs to be said in the manual (in a form suitable for the
> > manual, omitting the technicalities and describing this from user
> > perspective).  We cannot just say "dump record log to core file".  So
> > I hereby revoke my approval of the patch for the manual.
> >
> 
> Agree with you, Eli.  Do you have more better words on it?  You know
> my poor english.  :)

Something like this:

  @kindex record dump
  @kindex rec dump
  @item record dump [@var{file}]
  @itemx rec dump [@var{file}]
  Dump the execution records of the inferior process to the named
  @var{file}.  If not specified, @var{file} defaults to
  @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
  inferior process.

  The file created by this command is actually a kind of core file,
  with an extra section that holds the recorded execution log.  The
  sections usually present in a core file capture the state of the
  inferior before the recording started, so that the file produced by
  this command can be used to replay the entire recorded session
  without the need to restore the initial state by some other means.


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-30 23:40                                                                     ` Eli Zaretskii
@ 2009-08-31  7:10                                                                       ` Hui Zhu
  2009-09-05  3:30                                                                         ` Michael Snyder
  0 siblings, 1 reply; 54+ messages in thread
From: Hui Zhu @ 2009-08-31  7:10 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: msnyder, gdb-patches

On Mon, Aug 31, 2009 at 01:58, Eli Zaretskii<eliz@gnu.org> wrote:
>> From: Hui Zhu <teawater@gmail.com>
>> Date: Sun, 30 Aug 2009 11:20:32 +0800
>> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org
>>
>> > This all needs to be said in the manual (in a form suitable for the
>> > manual, omitting the technicalities and describing this from user
>> > perspective).  We cannot just say "dump record log to core file".  So
>> > I hereby revoke my approval of the patch for the manual.
>> >
>>
>> Agree with you, Eli.  Do you have more better words on it?  You know
>> my poor english.  :)
>
> Something like this:
>
>  @kindex record dump
>  @kindex rec dump
>  @item record dump [@var{file}]
>  @itemx rec dump [@var{file}]
>  Dump the execution records of the inferior process to the named
>  @var{file}.  If not specified, @var{file} defaults to
>  @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
>  inferior process.
>
>  The file created by this command is actually a kind of core file,
>  with an extra section that holds the recorded execution log.  The
>  sections usually present in a core file capture the state of the
>  inferior before the recording started, so that the file produced by
>  this command can be used to replay the entire recorded session
>  without the need to restore the initial state by some other means.
>
>

Great.  Thanks a lot.

I make a new doc patch according to it.

Hui

2009-08-31  Hui Zhu  <teawater@gmail.com>

	* gdb.texinfo (Process Record and Replay): Document the
	"record dump" commands.

---
 doc/gdb.texinfo |   16 ++++++++++++++++
 1 file changed, 16 insertions(+)

--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5214,6 +5214,22 @@ When record target runs in replay mode (
 subsequent execution log and begin to record a new execution log starting
 from the current address.  This means you will abandon the previously
 recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Dump the execution records of the inferior process to the named
+@var{file}.  If not specified, @var{file} defaults to
+@file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
+inferior process.
+
+The file created by this command is actually a kind of core file,
+with an extra section that holds the recorded execution log.  The
+sections usually present in a core file capture the state of the
+inferior before the recording started, so that the file produced by
+this command can be used to replay the entire recorded session
+without the need to restore the initial state by some other means.
 @end table


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-08-31  7:10                                                                       ` Hui Zhu
@ 2009-09-05  3:30                                                                         ` Michael Snyder
  2009-09-06 16:29                                                                           ` Hui Zhu
  0 siblings, 1 reply; 54+ messages in thread
From: Michael Snyder @ 2009-09-05  3:30 UTC (permalink / raw)
  To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches

Hui Zhu wrote:
> On Mon, Aug 31, 2009 at 01:58, Eli Zaretskii<eliz@gnu.org> wrote:
>>> From: Hui Zhu <teawater@gmail.com>
>>> Date: Sun, 30 Aug 2009 11:20:32 +0800
>>> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org
>>>
>>>> This all needs to be said in the manual (in a form suitable for the
>>>> manual, omitting the technicalities and describing this from user
>>>> perspective).  We cannot just say "dump record log to core file".  So
>>>> I hereby revoke my approval of the patch for the manual.
>>>>
>>> Agree with you, Eli.  Do you have more better words on it?  You know
>>> my poor english.  :)
>> Something like this:
>>
>>  @kindex record dump
>>  @kindex rec dump
>>  @item record dump [@var{file}]
>>  @itemx rec dump [@var{file}]
>>  Dump the execution records of the inferior process to the named
>>  @var{file}.  If not specified, @var{file} defaults to
>>  @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
>>  inferior process.
>>
>>  The file created by this command is actually a kind of core file,
>>  with an extra section that holds the recorded execution log.  The
>>  sections usually present in a core file capture the state of the
>>  inferior before the recording started, so that the file produced by
>>  this command can be used to replay the entire recorded session
>>  without the need to restore the initial state by some other means.
>>
>>
> 
> Great.  Thanks a lot.
> 
> I make a new doc patch according to it.
> 
> Hui
> 
> 2009-08-31  Hui Zhu  <teawater@gmail.com>
> 
> 	* gdb.texinfo (Process Record and Replay): Document the
> 	"record dump" commands.
> 
> ---
>  doc/gdb.texinfo |   16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
> 
> --- a/doc/gdb.texinfo
> +++ b/doc/gdb.texinfo
> @@ -5214,6 +5214,22 @@ When record target runs in replay mode (
>  subsequent execution log and begin to record a new execution log starting
>  from the current address.  This means you will abandon the previously
>  recorded ``future'' and begin recording a new ``future''.
> +
> +@kindex record dump
> +@kindex rec dump
> +@item record dump [@var{file}]
> +@itemx rec dump [@var{file}]
> +Dump the execution records of the inferior process to the named
> +@var{file}.  If not specified, @var{file} defaults to
> +@file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
> +inferior process.
> +
> +The file created by this command is actually a kind of core file,
> +with an extra section that holds the recorded execution log.  The
> +sections usually present in a core file capture the state of the
> +inferior before the recording started, so that the file produced by
> +this command can be used to replay the entire recorded session
> +without the need to restore the initial state by some other means.

Since there is no "record load" command in this version, perhaps
we should say something here about how to reload the file?

Something like:

To reload the execution record file, first open it like an
ordinary core file, then use "target record".

Alternatively, maybe you want to add a new version of
"record load <filename>" which will do the necessary,
by first invoking "core <filename>", and then "target
record".


>  @end table
> 


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

* Re: [RFA/RFC] Add dump and load command to process record and replay
  2009-09-05  3:30                                                                         ` Michael Snyder
@ 2009-09-06 16:29                                                                           ` Hui Zhu
  0 siblings, 0 replies; 54+ messages in thread
From: Hui Zhu @ 2009-09-06 16:29 UTC (permalink / raw)
  To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches

On Sat, Sep 5, 2009 at 11:29, Michael Snyder<msnyder@vmware.com> wrote:
> Hui Zhu wrote:
>>
>> On Mon, Aug 31, 2009 at 01:58, Eli Zaretskii<eliz@gnu.org> wrote:
>>>>
>>>> From: Hui Zhu <teawater@gmail.com>
>>>> Date: Sun, 30 Aug 2009 11:20:32 +0800
>>>> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org
>>>>
>>>>> This all needs to be said in the manual (in a form suitable for the
>>>>> manual, omitting the technicalities and describing this from user
>>>>> perspective).  We cannot just say "dump record log to core file".  So
>>>>> I hereby revoke my approval of the patch for the manual.
>>>>>
>>>> Agree with you, Eli.  Do you have more better words on it?  You know
>>>> my poor english.  :)
>>>
>>> Something like this:
>>>
>>>  @kindex record dump
>>>  @kindex rec dump
>>>  @item record dump [@var{file}]
>>>  @itemx rec dump [@var{file}]
>>>  Dump the execution records of the inferior process to the named
>>>  @var{file}.  If not specified, @var{file} defaults to
>>>  @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
>>>  inferior process.
>>>
>>>  The file created by this command is actually a kind of core file,
>>>  with an extra section that holds the recorded execution log.  The
>>>  sections usually present in a core file capture the state of the
>>>  inferior before the recording started, so that the file produced by
>>>  this command can be used to replay the entire recorded session
>>>  without the need to restore the initial state by some other means.
>>>
>>>
>>
>> Great.  Thanks a lot.
>>
>> I make a new doc patch according to it.
>>
>> Hui
>>
>> 2009-08-31  Hui Zhu  <teawater@gmail.com>
>>
>>        * gdb.texinfo (Process Record and Replay): Document the
>>        "record dump" commands.
>>
>> ---
>>  doc/gdb.texinfo |   16 ++++++++++++++++
>>  1 file changed, 16 insertions(+)
>>
>> --- a/doc/gdb.texinfo
>> +++ b/doc/gdb.texinfo
>> @@ -5214,6 +5214,22 @@ When record target runs in replay mode (
>>  subsequent execution log and begin to record a new execution log starting
>>  from the current address.  This means you will abandon the previously
>>  recorded ``future'' and begin recording a new ``future''.
>> +
>> +@kindex record dump
>> +@kindex rec dump
>> +@item record dump [@var{file}]
>> +@itemx rec dump [@var{file}]
>> +Dump the execution records of the inferior process to the named
>> +@var{file}.  If not specified, @var{file} defaults to
>> +@file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the
>> +inferior process.
>> +
>> +The file created by this command is actually a kind of core file,
>> +with an extra section that holds the recorded execution log.  The
>> +sections usually present in a core file capture the state of the
>> +inferior before the recording started, so that the file produced by
>> +this command can be used to replay the entire recorded session
>> +without the need to restore the initial state by some other means.
>
> Since there is no "record load" command in this version, perhaps
> we should say something here about how to reload the file?
>
> Something like:
>
> To reload the execution record file, first open it like an
> ordinary core file, then use "target record".
>
> Alternatively, maybe you want to add a new version of
> "record load <filename>" which will do the necessary,
> by first invoking "core <filename>", and then "target
> record".
>
>
>>  @end table
>>
>
>

I am not sure which way is better.   Could you help me with it?


Thanks,
Hui


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

end of thread, other threads:[~2009-09-06 16:29 UTC | newest]

Thread overview: 54+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-08-01  7:31 [RFA/RFC] Add dump and load command to process record and replay Hui Zhu
2009-08-01  9:57 ` Eli Zaretskii
2009-08-01 19:20 ` Michael Snyder
2009-08-02  3:18   ` Michael Snyder
2009-08-02  5:58     ` Hui Zhu
2009-08-03  4:12       ` Hui Zhu
2009-08-03 18:29         ` Eli Zaretskii
2009-08-04  1:58           ` Hui Zhu
2009-08-04  2:07           ` Hui Zhu
2009-08-04 18:26             ` Eli Zaretskii
2009-08-04 20:01               ` Michael Snyder
2009-08-05  9:21                 ` Hui Zhu
2009-08-05 20:19                   ` [RFA/RFC] Add dump and load command to process record (file format etc) Michael Snyder
2009-08-06  2:17                     ` Hui Zhu
2009-08-12 14:11                       ` Michael Snyder
2009-08-12 15:16                         ` Tom Tromey
2009-08-12 22:38                           ` Michael Snyder
2009-08-16  0:04                             ` Hui Zhu
2009-08-05 21:23                   ` [RFA/RFC] Add dump and load command to process record and replay Michael Snyder
2009-08-06  3:14                     ` Eli Zaretskii
2009-08-06 14:16                       ` Hui Zhu
2009-08-07  3:27                         ` Michael Snyder
2009-08-07  3:29                           ` Hui Zhu
2009-08-07  3:34                             ` Michael Snyder
2009-08-07  4:06                               ` Hui Zhu
2009-08-07  8:41                                 ` Eli Zaretskii
2009-08-07  9:53                                   ` Hui Zhu
2009-08-07 12:51                                     ` Eli Zaretskii
2009-08-07 16:22                                       ` Hui Zhu
2009-08-07 17:42                                   ` Michael Snyder
2009-08-08 13:28                                     ` Hui Zhu
2009-08-10  3:09                                       ` Michael Snyder
2009-08-22 17:39                                         ` Hui Zhu
2009-08-23  1:14                                           ` Hui Zhu
2009-08-23 23:43                                           ` Michael Snyder
2009-08-24  8:20                                             ` Hui Zhu
2009-08-24 18:32                                               ` Michael Snyder
2009-08-25  8:47                                                 ` Hui Zhu
2009-08-26  1:40                                                   ` Michael Snyder
2009-08-26  2:59                                                     ` Michael Snyder
2009-08-29 15:53                                                       ` Hui Zhu
2009-08-29 18:06                                                         ` Eli Zaretskii
2009-08-29 18:28                                                           ` Hui Zhu
2009-08-29 20:26                                                             ` Eli Zaretskii
2009-08-29 20:39                                                               ` Michael Snyder
2009-08-30  3:03                                                                 ` Eli Zaretskii
2009-08-30  5:36                                                                   ` Hui Zhu
2009-08-30 23:40                                                                     ` Eli Zaretskii
2009-08-31  7:10                                                                       ` Hui Zhu
2009-09-05  3:30                                                                         ` Michael Snyder
2009-09-06 16:29                                                                           ` Hui Zhu
2009-08-29 20:05                                                           ` Michael Snyder
2009-08-29 20:33                                                             ` Eli Zaretskii
2009-08-29 21:20                                                               ` Michael Snyder

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