summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog93
-rw-r--r--gdb/Makefile.in5
-rw-r--r--gdb/amd64-linux-tdep.c2
-rw-r--r--gdb/arm-tdep.c1
-rw-r--r--gdb/i386-linux-tdep.c2
-rw-r--r--gdb/i386-tdep.c1
-rw-r--r--gdb/infrun.c1
-rw-r--r--gdb/linux-record.c1
-rw-r--r--gdb/moxie-tdep.c1
-rw-r--r--gdb/record-full.c3019
-rw-r--r--gdb/record-full.h30
-rw-r--r--gdb/record.c2936
-rw-r--r--gdb/record.h15
-rw-r--r--gdb/target.c130
-rw-r--r--gdb/target.h44
15 files changed, 3408 insertions, 2873 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 8655973fc8c..21e2c2d715a 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,98 @@
2013-03-11 Markus Metzger <markus.t.metzger@intel.com>
+ * record.h: Split into this and ...
+ * record-full.h: ... this.
+ * record.c: Split into this and ...
+ * record-full.c: ... this.
+ * target.h (target_ops): Add new fields to_info_record,
+ to_save_record, to_delete_record, to_record_is_replaying,
+ to_goto_record_begin, to_goto_record_end, to_goto_record.
+ (target_info_record): New.
+ (target_save_record): New.
+ (target_supports_delete_record): New.
+ (target_delete_record): New.
+ (target_record_is_replaying): New.
+ (target_goto_record_begin): New.
+ (target_goto_record_end): New.
+ (target_goto_record): New.
+ * target.c (target_info_record): New.
+ (target_save_record): New.
+ (target_supports_delete_record): New.
+ (target_delete_record): New.
+ (target_record_is_replaying): New.
+ (target_goto_record_begin): New.
+ (target_goto_record_end): New.
+ (target_goto_record): New.
+ * record.h: Declare struct cmd_list_element.
+ (record_cmdlist): New declaration.
+ (set_record_cmdlist): New declaration.
+ (show_record_cmdlist): New declaration.
+ (info_record_cmdlist): New declaration.
+ (cmd_record_goto): New declaration.
+ * record.c: Remove unnecessary includes.
+ Include inferior.h.
+ (cmd_record_goto): Remove declaration.
+ (record_cmdlist): Now extern. Initialize.
+ (set_record_cmdlist): Now extern. Initialize.
+ (show_record_cmdlist): Now extern. Initialize.
+ (info_record_cmdlist): Now extern. Initialize.
+ (find_record_target): New.
+ (require_record_target): New.
+ (cmd_record_start): Update.
+ (cmd_record_delete): Remove target-specific code.
+ Call target_delete_record.
+ (cmd_record_stop): Unpush any record target.
+ (set_record_insn_max_num): Move to record-full.c
+ (set_record_command): Add comment.
+ (show_record_command): Add comment.
+ (info_record_command): Update comment.
+ Remove target-specific code.
+ Call the record target's to_info_record.
+ (cmd_record_start): New.
+ (cmd_record_goto): Now extern.
+ Remove target-specific code.
+ Call target_goto_begin, target_goto_end, or target_goto.
+ (_initialize_record): Move record target ops initialization to
+ record-full.c.
+ Change "record" command help text.
+ Move "record restore", "record set", and "record show" commands to
+ record-full.c.
+ * Makefile.in (SFILES): Add record-full.c.
+ (HFILES_NO_SRCDIR): Add record-full.h.
+ (COMMON_OBS): Add record-full.o.
+ * amd64-linux-tdep.c: Include record-full.h instead of record.h.
+ * arm-tdep.c: Include record-full.h.
+ * i386-linux-tdep.c: Include record-full.h instead of record.h.
+ * i386-tdep.c: Include record-full.h.
+ * infrun.c: Include record-full.h.
+ * linux-record.c: Include record-full.h.
+ * moxie-tdep.c: Include record-full.h.
+ * record-full.c: Include record-full.h.
+ Change module comment.
+ (set_record_full_cmdlist): New.
+ (show_record_full_cmdlist): New.
+ (record_full_cmdlist): New.
+ (record_goto_insn): New declaration.
+ (record_save): New declaration.
+ (record_check_insn_num): Change query string.
+ (record_info): New.
+ (record_delete): New.
+ (record_is_replaying): New.
+ (record_goto_entry): New.
+ (record_goto_begin): New.
+ (record_goto_end): New.
+ (record_goto): New.
+ (init_record_ops): Update.
+ (init_record_core_ops): Update.
+ (cmd_record_save): Rename to record_save. Remove target and arg checks.
+ (cmd_record_start): New.
+ (set_record_insn_max_num): Moved from record.c
+ (set_record_full_command): New.
+ (show_record_full_command): New.
+ (_initialize_record_full): New.
+
+2013-03-11 Markus Metzger <markus.t.metzger@intel.com>
+
* target.h (add_deprecated_target_alias): New.
* target.c (add_deprecated_target_alias): New.
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index e11e3d17926..a36d576e0a9 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -753,7 +753,7 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \
valarith.c valops.c valprint.c value.c varobj.c common/vec.c \
xml-tdesc.c xml-support.c \
inferior.c gdb_usleep.c \
- record.c gcore.c \
+ record.c record-full.c gcore.c \
jit.c \
xml-syscall.c \
annotate.c common/signals.c copying.c dfp.c gdb.c inf-child.c \
@@ -829,6 +829,7 @@ dicos-tdep.h filesystem.h gcore.h gdb_wchar.h hppabsd-tdep.h \
i386-darwin-tdep.h i386-nat.h linux-record.h moxie-tdep.h \
osdata.h procfs.h python/py-event.h python/py-events.h python/py-stopevent.h \
python/python-internal.h python/python.h ravenscar-thread.h record.h \
+record-full.h \
solib-darwin.h solib-ia64-hpux.h solib-spu.h windows-nat.h xcoffread.h \
gnulib/import/extra/snippet/arg-nonnull.h gnulib/import/extra/snippet/c++defs.h \
gnulib/import/extra/snippet/warn-on-use.h \
@@ -926,7 +927,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
prologue-value.o memory-map.o memrange.o \
xml-support.o xml-syscall.o xml-utils.o \
target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
- inferior.o osdata.o gdb_usleep.o record.o gcore.o \
+ inferior.o osdata.o gdb_usleep.o record.o record-full.o gcore.o \
gdb_vecs.o jit.o progspace.o skip.o probe.o \
common-utils.o buffer.o ptid.o gdb-dlfcn.o common-agent.o \
format.o registry.o btrace.o
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index e262c199e7d..4f383db88ae 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -48,7 +48,7 @@
/* The syscall's XML filename for i386. */
#define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
-#include "record.h"
+#include "record-full.h"
#include "linux-record.h"
/* Supported register note sections. */
diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index d890dcbbac2..33b8d5d513b 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -56,6 +56,7 @@
#include "vec.h"
#include "record.h"
+#include "record-full.h"
#include "features/arm-with-m.c"
#include "features/arm-with-m-fpa-layout.c"
diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
index 15a124718ed..f96fc816b6b 100644
--- a/gdb/i386-linux-tdep.c
+++ b/gdb/i386-linux-tdep.c
@@ -44,7 +44,7 @@
/* The syscall's XML filename for i386. */
#define XML_SYSCALL_FILENAME_I386 "syscalls/i386-linux.xml"
-#include "record.h"
+#include "record-full.h"
#include "linux-record.h"
#include <stdint.h>
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index 637c44d837b..a36a83d1beb 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -52,6 +52,7 @@
#include "i386-xstate.h"
#include "record.h"
+#include "record-full.h"
#include <stdint.h>
#include "features/i386/i386.c"
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 1cf30feff2c..1e2addc609d 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -49,6 +49,7 @@
#include "mi/mi-common.h"
#include "event-top.h"
#include "record.h"
+#include "record-full.h"
#include "inline-frame.h"
#include "jit.h"
#include "tracepoint.h"
diff --git a/gdb/linux-record.c b/gdb/linux-record.c
index c7697000b83..2b62219f3df 100644
--- a/gdb/linux-record.c
+++ b/gdb/linux-record.c
@@ -22,6 +22,7 @@
#include "gdbtypes.h"
#include "regcache.h"
#include "record.h"
+#include "record-full.h"
#include "linux-record.h"
/* These macros are the values of the first argument of system call
diff --git a/gdb/moxie-tdep.c b/gdb/moxie-tdep.c
index 4b250f81204..fc0f85cfb67 100644
--- a/gdb/moxie-tdep.c
+++ b/gdb/moxie-tdep.c
@@ -37,6 +37,7 @@
#include "trad-frame.h"
#include "dis-asm.h"
#include "record.h"
+#include "record-full.h"
#include "gdb_assert.h"
diff --git a/gdb/record-full.c b/gdb/record-full.c
new file mode 100644
index 00000000000..1cbd724781b
--- /dev/null
+++ b/gdb/record-full.c
@@ -0,0 +1,3019 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+ Copyright (C) 2013 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 "record-full.h"
+#include "elf-bfd.h"
+#include "gcore.h"
+#include "event-loop.h"
+#include "inf-loop.h"
+#include "gdb_bfd.h"
+#include "observer.h"
+
+#include <signal.h>
+
+/* This module implements "target record-full", also known as "process
+ record and replay". This target sits on top of a "normal" target
+ (a target that "has execution"), and provides a record and replay
+ functionality, including reverse debugging.
+
+ Target record has two modes: recording, and replaying.
+
+ In record mode, we intercept the to_resume and to_wait methods.
+ Whenever gdb resumes the target, we run the target in single step
+ mode, and we build up an execution log in which, for each executed
+ instruction, we record all changes in memory and register state.
+ This is invisible to the user, to whom it just looks like an
+ ordinary debugging session (except for performance degredation).
+
+ In replay mode, instead of actually letting the inferior run as a
+ process, we simulate its execution by playing back the recorded
+ execution log. For each instruction in the log, we simulate the
+ instruction's side effects by duplicating the changes that it would
+ have made on memory and registers. */
+
+#define DEFAULT_RECORD_INSN_MAX_NUM 200000
+
+#define RECORD_IS_REPLAY \
+ (record_list->next || execution_direction == EXEC_REVERSE)
+
+#define RECORD_FILE_MAGIC netorder32(0x20091016)
+
+/* These are the core structs of the process record functionality.
+
+ A record_entry is a record of the value change of a register
+ ("record_reg") or a part of memory ("record_mem"). And each
+ instruction must have a struct record_entry ("record_end") that
+ indicates that this is the last struct record_entry of this
+ instruction.
+
+ Each struct record_entry is linked to "record_list" by "prev" and
+ "next" pointers. */
+
+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;
+ union
+ {
+ gdb_byte *ptr;
+ gdb_byte buf[sizeof (gdb_byte *)];
+ } u;
+};
+
+struct record_reg_entry
+{
+ unsigned short num;
+ unsigned short len;
+ union
+ {
+ gdb_byte *ptr;
+ gdb_byte buf[2 * sizeof (gdb_byte *)];
+ } u;
+};
+
+struct record_end_entry
+{
+ enum gdb_signal sigval;
+ ULONGEST insn_num;
+};
+
+enum record_type
+{
+ record_end = 0,
+ record_reg,
+ record_mem
+};
+
+/* This is the data structure that makes up the execution log.
+
+ The execution log consists of a single linked list of entries
+ of type "struct record_entry". It is doubly linked so that it
+ can be traversed in either direction.
+
+ The start of the list is anchored by a struct called
+ "record_first". The pointer "record_list" either points to the
+ last entry that was added to the list (in record mode), or to the
+ next entry in the list that will be executed (in replay mode).
+
+ Each list element (struct record_entry), in addition to next and
+ prev pointers, consists of a union of three entry types: mem, reg,
+ and end. A field called "type" determines which entry type is
+ represented by a given list element.
+
+ Each instruction that is added to the execution log is represented
+ by a variable number of list elements ('entries'). The instruction
+ will have one "reg" entry for each register that is changed by
+ executing the instruction (including the PC in every case). It
+ will also have one "mem" entry for each memory change. Finally,
+ each instruction will have an "end" entry that separates it from
+ the changes associated with the next instruction. */
+
+struct record_entry
+{
+ struct record_entry *prev;
+ struct record_entry *next;
+ enum record_type type;
+ union
+ {
+ /* reg */
+ struct record_reg_entry reg;
+ /* mem */
+ struct record_mem_entry mem;
+ /* end */
+ struct record_end_entry end;
+ } u;
+};
+
+/* If true, query if PREC cannot record memory
+ change of next instruction. */
+int record_memory_query = 0;
+
+struct record_core_buf_entry
+{
+ struct record_core_buf_entry *prev;
+ struct target_section *p;
+ bfd_byte *buf;
+};
+
+/* Record buf with core target. */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
+/* The following variables are used for managing the linked list that
+ represents the execution log.
+
+ record_first is the anchor that holds down the beginning of the list.
+
+ record_list serves two functions:
+ 1) In record mode, it anchors the end of the list.
+ 2) In replay mode, it traverses the list and points to
+ the next instruction that must be emulated.
+
+ record_arch_list_head and record_arch_list_tail are used to manage
+ a separate list, which is used to build up the change elements of
+ the currently executing instruction during record mode. When this
+ instruction has been completely annotated in the "arch list", it
+ will be appended to the main execution log. */
+
+static struct record_entry record_first;
+static struct record_entry *record_list = &record_first;
+static struct record_entry *record_arch_list_head = NULL;
+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;
+/* Maximum allowed number of insns in execution log. */
+static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
+/* Actual count of insns presently in execution log. */
+static int record_insn_num = 0;
+/* Count of insns logged so far (may be larger
+ than count of insns presently in execution log). */
+static ULONGEST record_insn_count;
+
+/* The target_ops of process record. */
+static struct target_ops record_ops;
+static struct target_ops record_core_ops;
+
+/* Command lists for "set/show record full". */
+static struct cmd_list_element *set_record_full_cmdlist;
+static struct cmd_list_element *show_record_full_cmdlist;
+
+/* Command list for "record full". */
+static struct cmd_list_element *record_full_cmdlist;
+
+/* 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 gdb_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 int (*record_beneath_to_stopped_by_watchpoint) (void);
+static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
+ CORE_ADDR *);
+static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *);
+
+static void record_goto_insn (struct record_entry *entry,
+ enum exec_direction_kind dir);
+static void record_save (char *recfilename);
+
+/* Alloc and free functions for record_reg, record_mem, and record_end
+ entries. */
+
+/* Alloc a record_reg record entry. */
+
+static inline struct record_entry *
+record_reg_alloc (struct regcache *regcache, int regnum)
+{
+ struct record_entry *rec;
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+ rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+ rec->type = record_reg;
+ rec->u.reg.num = regnum;
+ rec->u.reg.len = register_size (gdbarch, regnum);
+ if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+ rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
+
+ return rec;
+}
+
+/* Free a record_reg record entry. */
+
+static inline void
+record_reg_release (struct record_entry *rec)
+{
+ gdb_assert (rec->type == record_reg);
+ if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+ xfree (rec->u.reg.u.ptr);
+ xfree (rec);
+}
+
+/* Alloc a record_mem record entry. */
+
+static inline struct record_entry *
+record_mem_alloc (CORE_ADDR addr, int len)
+{
+ struct record_entry *rec;
+
+ rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+ rec->type = record_mem;
+ rec->u.mem.addr = addr;
+ rec->u.mem.len = len;
+ if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+ rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
+
+ return rec;
+}
+
+/* Free a record_mem record entry. */
+
+static inline void
+record_mem_release (struct record_entry *rec)
+{
+ gdb_assert (rec->type == record_mem);
+ if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+ xfree (rec->u.mem.u.ptr);
+ xfree (rec);
+}
+
+/* Alloc a record_end record entry. */
+
+static inline struct record_entry *
+record_end_alloc (void)
+{
+ struct record_entry *rec;
+
+ rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+ rec->type = record_end;
+
+ return rec;
+}
+
+/* Free a record_end record entry. */
+
+static inline void
+record_end_release (struct record_entry *rec)
+{
+ xfree (rec);
+}
+
+/* Free one record entry, any type.
+ Return entry->type, in case caller wants to know. */
+
+static inline enum record_type
+record_entry_release (struct record_entry *rec)
+{
+ enum record_type type = rec->type;
+
+ switch (type) {
+ case record_reg:
+ record_reg_release (rec);
+ break;
+ case record_mem:
+ record_mem_release (rec);
+ break;
+ case record_end:
+ record_end_release (rec);
+ break;
+ }
+ return type;
+}
+
+/* Free all record entries in list pointed to by REC. */
+
+static void
+record_list_release (struct record_entry *rec)
+{
+ if (!rec)
+ return;
+
+ while (rec->next)
+ rec = rec->next;
+
+ while (rec->prev)
+ {
+ rec = rec->prev;
+ record_entry_release (rec->next);
+ }
+
+ if (rec == &record_first)
+ {
+ record_insn_num = 0;
+ record_first.next = NULL;
+ }
+ else
+ record_entry_release (rec);
+}
+
+/* Free all record entries forward of the given list position. */
+
+static void
+record_list_release_following (struct record_entry *rec)
+{
+ struct record_entry *tmp = rec->next;
+
+ rec->next = NULL;
+ while (tmp)
+ {
+ rec = tmp->next;
+ if (record_entry_release (tmp) == record_end)
+ {
+ record_insn_num--;
+ record_insn_count--;
+ }
+ tmp = rec;
+ }
+}
+
+/* Delete the first instruction from the beginning of the log, to make
+ room for adding a new instruction at the end of the log.
+
+ Note -- this function does not modify record_insn_num. */
+
+static void
+record_list_release_first (void)
+{
+ struct record_entry *tmp;
+
+ if (!record_first.next)
+ return;
+
+ /* Loop until a record_end. */
+ while (1)
+ {
+ /* Cut record_first.next out of the linked list. */
+ tmp = record_first.next;
+ record_first.next = tmp->next;
+ tmp->next->prev = &record_first;
+
+ /* tmp is now isolated, and can be deleted. */
+ if (record_entry_release (tmp) == record_end)
+ break; /* End loop at first record_end. */
+
+ if (!record_first.next)
+ {
+ gdb_assert (record_insn_num == 1);
+ break; /* End loop when list is empty. */
+ }
+ }
+}
+
+/* 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;
+ }
+}
+
+/* Return the value storage location of a record entry. */
+static inline gdb_byte *
+record_get_loc (struct record_entry *rec)
+{
+ switch (rec->type) {
+ case record_mem:
+ if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+ return rec->u.mem.u.ptr;
+ else
+ return rec->u.mem.u.buf;
+ case record_reg:
+ if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+ return rec->u.reg.u.ptr;
+ else
+ return rec->u.reg.u.buf;
+ case record_end:
+ default:
+ gdb_assert_not_reached ("unexpected record_entry type");
+ return NULL;
+ }
+}
+
+/* Record the value of a register NUM to record_arch_list. */
+
+int
+record_arch_list_add_reg (struct regcache *regcache, int regnum)
+{
+ struct record_entry *rec;
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add register num = %d to "
+ "record list.\n",
+ regnum);
+
+ rec = record_reg_alloc (regcache, regnum);
+
+ regcache_raw_read (regcache, regnum, record_get_loc (rec));
+
+ 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) /* FIXME: Why? Some arch must permit it... */
+ return 0;
+
+ rec = record_mem_alloc (addr, len);
+
+ if (record_read_memory (target_gdbarch (), addr, record_get_loc (rec), len))
+ {
+ record_mem_release (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 = record_end_alloc ();
+ rec->u.end.sigval = GDB_SIGNAL_0;
+ rec->u.end.insn_num = ++record_insn_count;
+
+ 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 full stop-at-limit)?"));
+ if (set_terminal)
+ target_terminal_inferior ();
+ if (q)
+ record_stop_at_limit = 0;
+ else
+ error (_("Process record: stopped by user."));
+ }
+ }
+ }
+}
+
+static void
+record_arch_list_cleanups (void *ignore)
+{
+ record_list_release (record_arch_list_tail);
+}
+
+/* Before inferior step (when GDB record the running message, inferior
+ only can step), GDB will call this function to record the values to
+ record_list. This function will call gdbarch_process_record to
+ record the running message of inferior and set them to
+ record_arch_list, and add it to record_list. */
+
+static int
+record_message (struct regcache *regcache, enum gdb_signal signal)
+{
+ int ret;
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+
+ /* Check record_insn_num. */
+ record_check_insn_num (1);
+
+ /* If gdb sends a signal value to target_resume,
+ save it in the 'end' field of the previous instruction.
+
+ Maybe process record should record what really happened,
+ rather than what gdb pretends has happened.
+
+ So if Linux delivered the signal to the child process during
+ the record mode, we will record it and deliver it again in
+ the replay mode.
+
+ If user says "ignore this signal" during the record mode, then
+ it will be ignored again during the replay mode (no matter if
+ the user says something different, like "deliver this signal"
+ during the replay mode).
+
+ User should understand that nothing he does during the replay
+ mode will change the behavior of the child. If he tries,
+ then that is a user error.
+
+ But we should still deliver the signal to gdb during the replay,
+ if we delivered it during the recording. Therefore we should
+ record the signal during record_wait, not record_resume. */
+ if (record_list != &record_first) /* FIXME better way to check */
+ {
+ gdb_assert (record_list->type == record_end);
+ record_list->u.end.sigval = signal;
+ }
+
+ if (signal == GDB_SIGNAL_0
+ || !gdbarch_process_record_signal_p (gdbarch))
+ ret = gdbarch_process_record (gdbarch,
+ regcache,
+ regcache_read_pc (regcache));
+ else
+ ret = gdbarch_process_record_signal (gdbarch,
+ regcache,
+ signal);
+
+ if (ret > 0)
+ error (_("Process record: inferior program stopped."));
+ if (ret < 0)
+ error (_("Process record: failed to record execution log."));
+
+ discard_cleanups (old_cleanups);
+
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ record_list_release_first ();
+ else
+ record_insn_num++;
+
+ return 1;
+}
+
+struct record_message_args {
+ struct regcache *regcache;
+ enum gdb_signal signal;
+};
+
+static int
+record_message_wrapper (void *args)
+{
+ struct record_message_args *record_args = args;
+
+ return record_message (record_args->regcache, record_args->signal);
+}
+
+static int
+record_message_wrapper_safe (struct regcache *regcache,
+ enum gdb_signal signal)
+{
+ struct record_message_args args;
+
+ args.regcache = regcache;
+ args.signal = signal;
+
+ return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+ 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;
+}
+
+/* Flag set to TRUE for target_stopped_by_watchpoint. */
+static int record_hw_watchpoint = 0;
+
+/* Execute one instruction from the record log. Each instruction in
+ the log will be represented by an arbitrary sequence of register
+ entries and memory entries, followed by an 'end' entry. */
+
+static inline void
+record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
+ struct record_entry *entry)
+{
+ switch (entry->type)
+ {
+ case record_reg: /* reg */
+ {
+ gdb_byte reg[MAX_REGISTER_SIZE];
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_reg %s to "
+ "inferior num = %d.\n",
+ host_address_to_string (entry),
+ entry->u.reg.num);
+
+ regcache_cooked_read (regcache, entry->u.reg.num, reg);
+ regcache_cooked_write (regcache, entry->u.reg.num,
+ record_get_loc (entry));
+ memcpy (record_get_loc (entry), reg, entry->u.reg.len);
+ }
+ break;
+
+ case record_mem: /* mem */
+ {
+ /* Nothing to do if the entry is flagged not_accessible. */
+ if (!entry->u.mem.mem_entry_not_accessible)
+ {
+ gdb_byte *mem = alloca (entry->u.mem.len);
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_mem %s to "
+ "inferior addr = %s len = %d.\n",
+ host_address_to_string (entry),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+
+ if (record_read_memory (gdbarch,
+ entry->u.mem.addr, mem, entry->u.mem.len))
+ entry->u.mem.mem_entry_not_accessible = 1;
+ else
+ {
+ if (target_write_memory (entry->u.mem.addr,
+ record_get_loc (entry),
+ entry->u.mem.len))
+ {
+ entry->u.mem.mem_entry_not_accessible = 1;
+ if (record_debug)
+ warning (_("Process record: error writing memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ {
+ memcpy (record_get_loc (entry), mem, entry->u.mem.len);
+
+ /* We've changed memory --- check if a hardware
+ watchpoint should trap. Note that this
+ presently assumes the target beneath supports
+ continuable watchpoints. On non-continuable
+ watchpoints target, we'll want to check this
+ _before_ actually doing the memory change, and
+ not doing the change at all if the watchpoint
+ traps. */
+ if (hardware_watchpoint_inserted_in_range
+ (get_regcache_aspace (regcache),
+ entry->u.mem.addr, entry->u.mem.len))
+ record_hw_watchpoint = 1;
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+ enum gdb_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+ struct target_waitstatus *,
+ int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+ struct regcache *,
+ int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+ enum target_object object,
+ const char *annex,
+ gdb_byte *readbuf,
+ const gdb_byte *writebuf,
+ ULONGEST offset,
+ LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+ struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+ struct bp_target_info *);
+static int (*tmp_to_stopped_by_watchpoint) (void);
+static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *);
+
+static void record_restore (void);
+
+/* Asynchronous signal handle registered as event loop source for when
+ we have pending events ready to be passed to the core. */
+
+static struct async_event_handler *record_async_inferior_event_token;
+
+static void
+record_async_inferior_event_handler (gdb_client_data data)
+{
+ inferior_event_handler (INF_REG_EVENT, NULL);
+}
+
+/* Open the process record target. */
+
+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);
+ record_restore ();
+}
+
+/* "to_open" target method for 'live' processes. */
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
+ /* check exec */
+ if (!target_has_execution)
+ error (_("Process record: the program is not being run."));
+ if (non_stop)
+ error (_("Process record target can't debug inferior in non-stop mode "
+ "(non-stop)."));
+
+ if (!gdbarch_process_record_p (target_gdbarch ()))
+ error (_("Process record: the current architecture doesn't support "
+ "record function."));
+
+ if (!tmp_to_resume)
+ error (_("Could not find 'to_resume' method on the target stack."));
+ if (!tmp_to_wait)
+ error (_("Could not find 'to_wait' method on the target stack."));
+ if (!tmp_to_store_registers)
+ error (_("Could not find 'to_store_registers' "
+ "method on the target stack."));
+ if (!tmp_to_insert_breakpoint)
+ error (_("Could not find 'to_insert_breakpoint' "
+ "method on the target stack."));
+ if (!tmp_to_remove_breakpoint)
+ error (_("Could not find 'to_remove_breakpoint' "
+ "method on the target stack."));
+ if (!tmp_to_stopped_by_watchpoint)
+ error (_("Could not find 'to_stopped_by_watchpoint' "
+ "method on the target stack."));
+ if (!tmp_to_stopped_data_address)
+ error (_("Could not find 'to_stopped_data_address' "
+ "method on the target stack."));
+
+ push_target (&record_ops);
+}
+
+static void record_init_record_breakpoints (void);
+
+/* "to_open" target method. Open the process record target. */
+
+static void
+record_open (char *name, int from_tty)
+{
+ struct target_ops *t;
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
+ /* Check if record target is already running. */
+ if (current_target.to_stratum == record_stratum)
+ error (_("Process record target already running. Use \"record stop\" to "
+ "stop record target first."));
+
+ /* Reset the tmp beneath pointers. */
+ tmp_to_resume_ops = NULL;
+ tmp_to_resume = NULL;
+ tmp_to_wait_ops = NULL;
+ tmp_to_wait = NULL;
+ tmp_to_store_registers_ops = NULL;
+ tmp_to_store_registers = NULL;
+ tmp_to_xfer_partial_ops = NULL;
+ tmp_to_xfer_partial = NULL;
+ tmp_to_insert_breakpoint = NULL;
+ tmp_to_remove_breakpoint = NULL;
+ tmp_to_stopped_by_watchpoint = NULL;
+ tmp_to_stopped_data_address = NULL;
+ tmp_to_async = 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_stopped_by_watchpoint)
+ tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint;
+ if (!tmp_to_stopped_data_address)
+ tmp_to_stopped_data_address = t->to_stopped_data_address;
+ if (!tmp_to_async)
+ tmp_to_async = t->to_async;
+ }
+ if (!tmp_to_xfer_partial)
+ error (_("Could not find 'to_xfer_partial' method on the target stack."));
+
+ /* Reset */
+ record_insn_num = 0;
+ record_insn_count = 0;
+ record_list = &record_first;
+ record_list->next = NULL;
+
+ /* Set the tmp beneath pointers to beneath pointers. */
+ record_beneath_to_resume_ops = tmp_to_resume_ops;
+ record_beneath_to_resume = tmp_to_resume;
+ record_beneath_to_wait_ops = tmp_to_wait_ops;
+ record_beneath_to_wait = tmp_to_wait;
+ record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+ record_beneath_to_store_registers = tmp_to_store_registers;
+ record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+ record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+ record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+ record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+ record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint;
+ record_beneath_to_stopped_data_address = tmp_to_stopped_data_address;
+ record_beneath_to_async = tmp_to_async;
+
+ if (core_bfd)
+ record_core_open_1 (name, from_tty);
+ else
+ record_open_1 (name, from_tty);
+
+ /* Register extra event sources in the event loop. */
+ record_async_inferior_event_token
+ = create_async_event_handler (record_async_inferior_event_handler,
+ NULL);
+
+ record_init_record_breakpoints ();
+
+ observer_notify_record_changed (current_inferior (), 1);
+}
+
+/* "to_close" target method. Close the process record target. */
+
+static void
+record_close (int quitting)
+{
+ struct record_core_buf_entry *entry;
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+ record_list_release (record_list);
+
+ /* Release record_core_regbuf. */
+ if (record_core_regbuf)
+ {
+ xfree (record_core_regbuf);
+ record_core_regbuf = NULL;
+ }
+
+ /* Release record_core_buf_list. */
+ if (record_core_buf_list)
+ {
+ for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+ {
+ xfree (record_core_buf_list);
+ record_core_buf_list = entry;
+ }
+ record_core_buf_list = NULL;
+ }
+
+ if (record_async_inferior_event_token)
+ delete_async_event_handler (&record_async_inferior_event_token);
+}
+
+static int record_resume_step = 0;
+
+/* True if we've been resumed, and so each record_wait call should
+ advance execution. If this is false, record_wait will return a
+ TARGET_WAITKIND_IGNORE. */
+static int record_resumed = 0;
+
+/* The execution direction of the last resume we got. This is
+ necessary for async mode. Vis (order is not strictly accurate):
+
+ 1. user has the global execution direction set to forward
+ 2. user does a reverse-step command
+ 3. record_resume is called with global execution direction
+ temporarily switched to reverse
+ 4. GDB's execution direction is reverted back to forward
+ 5. target record notifies event loop there's an event to handle
+ 6. infrun asks the target which direction was it going, and switches
+ the global execution direction accordingly (to reverse)
+ 7. infrun polls an event out of the record target, and handles it
+ 8. GDB goes back to the event loop, and goto #4.
+*/
+static enum exec_direction_kind record_execution_dir = EXEC_FORWARD;
+
+/* "to_resume" target method. Resume the process record target. */
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+ enum gdb_signal signal)
+{
+ record_resume_step = step;
+ record_resumed = 1;
+ record_execution_dir = execution_direction;
+
+ if (!RECORD_IS_REPLAY)
+ {
+ struct gdbarch *gdbarch = target_thread_architecture (ptid);
+
+ record_message (get_current_regcache (), signal);
+
+ if (!step)
+ {
+ /* This is not hard single step. */
+ if (!gdbarch_software_single_step_p (gdbarch))
+ {
+ /* This is a normal continue. */
+ step = 1;
+ }
+ else
+ {
+ /* This arch support soft sigle step. */
+ if (single_step_breakpoints_inserted ())
+ {
+ /* This is a soft single step. */
+ record_resume_step = 1;
+ }
+ else
+ {
+ /* This is a continue.
+ Try to insert a soft single step breakpoint. */
+ if (!gdbarch_software_single_step (gdbarch,
+ get_current_frame ()))
+ {
+ /* This system don't want use soft single step.
+ Use hard sigle step. */
+ step = 1;
+ }
+ }
+ }
+ }
+
+ /* Make sure the target beneath reports all signals. */
+ target_pass_signals (0, NULL);
+
+ record_beneath_to_resume (record_beneath_to_resume_ops,
+ ptid, step, signal);
+ }
+
+ /* We are about to start executing the inferior (or simulate it),
+ let's register it with the event loop. */
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+ /* Notify the event loop there's an event to wait for. We do
+ most of the work in record_wait. */
+ mark_async_event_handler (record_async_inferior_event_token);
+ }
+}
+
+static int record_get_sig = 0;
+
+/* SIGINT signal handler, registered by "to_wait" method. */
+
+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;
+}
+
+/* "to_wait" target method for process record target.
+
+ In record mode, the target is always run in singlestep mode
+ (even when gdb says to continue). The to_wait method intercepts
+ the stop events and determines which ones are to be passed on to
+ gdb. Most stop events are just singlestep events that gdb is not
+ to know about, so the to_wait method just records them and keeps
+ singlestepping.
+
+ In replay mode, this function emulates the recorded execution log,
+ one instruction at a time (forward or backward), and determines
+ where to stop. */
+
+static ptid_t
+record_wait_1 (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, record_resumed = %d, direction=%s\n",
+ record_resume_step, record_resumed,
+ record_execution_dir == EXEC_FORWARD ? "forward" : "reverse");
+
+ if (!record_resumed)
+ {
+ gdb_assert ((options & TARGET_WNOHANG) != 0);
+
+ /* No interesting event. */
+ status->kind = TARGET_WAITKIND_IGNORE;
+ return minus_one_ptid;
+ }
+
+ record_get_sig = 0;
+ signal (SIGINT, record_sig_handler);
+
+ if (!RECORD_IS_REPLAY && ops != &record_core_ops)
+ {
+ if (record_resume_step)
+ {
+ /* This is a single step. */
+ return record_beneath_to_wait (record_beneath_to_wait_ops,
+ ptid, status, options);
+ }
+ else
+ {
+ /* This is not a single step. */
+ ptid_t ret;
+ CORE_ADDR tmp_pc;
+ struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid);
+
+ while (1)
+ {
+ ret = record_beneath_to_wait (record_beneath_to_wait_ops,
+ ptid, status, options);
+ if (status->kind == TARGET_WAITKIND_IGNORE)
+ {
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_wait "
+ "target beneath not done yet\n");
+ return ret;
+ }
+
+ if (single_step_breakpoints_inserted ())
+ remove_single_step_breakpoints ();
+
+ if (record_resume_step)
+ return ret;
+
+ /* Is this a SIGTRAP? */
+ if (status->kind == TARGET_WAITKIND_STOPPED
+ && status->value.sig == GDB_SIGNAL_TRAP)
+ {
+ struct regcache *regcache;
+ struct address_space *aspace;
+
+ /* Yes -- this is likely our single-step finishing,
+ but check if there's any reason the core would be
+ interested in the event. */
+
+ registers_changed ();
+ regcache = get_current_regcache ();
+ tmp_pc = regcache_read_pc (regcache);
+ aspace = get_regcache_aspace (regcache);
+
+ if (target_stopped_by_watchpoint ())
+ {
+ /* Always interested in watchpoints. */
+ }
+ else if (breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ /* There is a breakpoint here. Let the core
+ handle it. */
+ if (software_breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ struct gdbarch *gdbarch
+ = get_regcache_arch (regcache);
+ CORE_ADDR decr_pc_after_break
+ = gdbarch_decr_pc_after_break (gdbarch);
+ if (decr_pc_after_break)
+ regcache_write_pc (regcache,
+ tmp_pc + decr_pc_after_break);
+ }
+ }
+ else
+ {
+ /* This is a single-step trap. Record the
+ insn and issue another step.
+ FIXME: this part can be a random SIGTRAP too.
+ But GDB cannot handle it. */
+ int step = 1;
+
+ if (!record_message_wrapper_safe (regcache,
+ GDB_SIGNAL_0))
+ {
+ status->kind = TARGET_WAITKIND_STOPPED;
+ status->value.sig = GDB_SIGNAL_0;
+ break;
+ }
+
+ if (gdbarch_software_single_step_p (gdbarch))
+ {
+ /* Try to insert the software single step breakpoint.
+ If insert success, set step to 0. */
+ set_executing (inferior_ptid, 0);
+ reinit_frame_cache ();
+ if (gdbarch_software_single_step (gdbarch,
+ get_current_frame ()))
+ step = 0;
+ set_executing (inferior_ptid, 1);
+ }
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_wait "
+ "issuing one more step in the target beneath\n");
+ record_beneath_to_resume (record_beneath_to_resume_ops,
+ ptid, step,
+ GDB_SIGNAL_0);
+ 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);
+ struct address_space *aspace = get_regcache_aspace (regcache);
+ int continue_flag = 1;
+ int first_record_end = 1;
+ struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+ CORE_ADDR tmp_pc;
+
+ record_hw_watchpoint = 0;
+ status->kind = TARGET_WAITKIND_STOPPED;
+
+ /* Check breakpoint when forward execute. */
+ if (execution_direction == EXEC_FORWARD)
+ {
+ tmp_pc = regcache_read_pc (regcache);
+ if (breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: break at %s.\n",
+ paddress (gdbarch, tmp_pc));
+
+ if (decr_pc_after_break
+ && !record_resume_step
+ && software_breakpoint_inserted_here_p (aspace, tmp_pc))
+ regcache_write_pc (regcache,
+ tmp_pc + decr_pc_after_break);
+ goto replay_out;
+ }
+ }
+
+ /* 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_insn (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 (aspace, tmp_pc))
+ {
+ int decr_pc_after_break
+ = gdbarch_decr_pc_after_break (gdbarch);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: break "
+ "at %s.\n",
+ paddress (gdbarch, tmp_pc));
+ if (decr_pc_after_break
+ && execution_direction == EXEC_FORWARD
+ && !record_resume_step
+ && software_breakpoint_inserted_here_p (aspace,
+ tmp_pc))
+ regcache_write_pc (regcache,
+ tmp_pc + decr_pc_after_break);
+ continue_flag = 0;
+ }
+
+ if (record_hw_watchpoint)
+ {
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: hit hw "
+ "watchpoint.\n");
+ continue_flag = 0;
+ }
+ /* Check target signal */
+ if (record_list->u.end.sigval != GDB_SIGNAL_0)
+ /* FIXME: better way to check */
+ 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);
+
+replay_out:
+ if (record_get_sig)
+ status->value.sig = GDB_SIGNAL_INT;
+ else if (record_list->u.end.sigval != GDB_SIGNAL_0)
+ /* FIXME: better way to check */
+ status->value.sig = record_list->u.end.sigval;
+ else
+ status->value.sig = GDB_SIGNAL_TRAP;
+
+ discard_cleanups (old_cleanups);
+ }
+
+ signal (SIGINT, handle_sigint);
+
+ do_cleanups (set_cleanups);
+ return inferior_ptid;
+}
+
+static ptid_t
+record_wait (struct target_ops *ops,
+ ptid_t ptid, struct target_waitstatus *status,
+ int options)
+{
+ ptid_t return_ptid;
+
+ return_ptid = record_wait_1 (ops, ptid, status, options);
+ if (status->kind != TARGET_WAITKIND_IGNORE)
+ {
+ /* We're reporting a stop. Make sure any spurious
+ target_wait(WNOHANG) doesn't advance the target until the
+ core wants us resumed again. */
+ record_resumed = 0;
+ }
+ return return_ptid;
+}
+
+static int
+record_stopped_by_watchpoint (void)
+{
+ if (RECORD_IS_REPLAY)
+ return record_hw_watchpoint;
+ else
+ return record_beneath_to_stopped_by_watchpoint ();
+}
+
+/* "to_disconnect" method for process record target. */
+
+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);
+}
+
+/* "to_detach" method for process record target. */
+
+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);
+}
+
+/* "to_mourn_inferior" method for process record target. */
+
+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 ();
+}
+
+static int
+record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
+{
+ if (RECORD_IS_REPLAY)
+ return 0;
+ else
+ return record_beneath_to_stopped_data_address (ops, addr_p);
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction. */
+
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+ /* Check record_insn_num. */
+ record_check_insn_num (0);
+
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+
+ if (regnum < 0)
+ {
+ int i;
+
+ for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+ {
+ if (record_arch_list_add_reg (regcache, i))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ }
+ }
+ else
+ {
+ if (record_arch_list_add_reg (regcache, regnum))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ }
+ if (record_arch_list_add_end ())
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ record_list_release_first ();
+ else
+ record_insn_num++;
+}
+
+/* "to_store_registers" method for process record target. */
+
+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 =
+ query (_("Because GDB is in replay mode, changing the "
+ "value of a register will make the execution "
+ "log unusable from this point onward. "
+ "Change all registers?"));
+ else
+ n =
+ query (_("Because GDB is in replay mode, changing the value "
+ "of a register will make the execution log unusable "
+ "from this point onward. Change register %s?"),
+ gdbarch_register_name (get_regcache_arch (regcache),
+ regno));
+
+ 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_following (record_list);
+ }
+
+ record_registers_change (regcache, regno);
+ }
+ record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
+ regcache, regno);
+}
+
+/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY.
+ In replay mode, we cannot write memory unles we are willing to
+ invalidate the record/replay log from this point forward. */
+
+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 (!query (_("Because GDB is in replay mode, writing to memory "
+ "will make the execution log unusable from this "
+ "point onward. Write memory at address %s?"),
+ paddress (target_gdbarch (), offset)))
+ error (_("Process record canceled the operation."));
+
+ /* Destroy the record from here forward. */
+ record_list_release_following (record_list);
+ }
+
+ /* Check record_insn_num */
+ record_check_insn_num (0);
+
+ /* Record registers change to list as an instruction. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ if (record_arch_list_add_mem (offset, len))
+ {
+ record_list_release (record_arch_list_tail);
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: failed to record "
+ "execution log.");
+ return -1;
+ }
+ if (record_arch_list_add_end ())
+ {
+ record_list_release (record_arch_list_tail);
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: failed to record "
+ "execution log.");
+ return -1;
+ }
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ record_list_release_first ();
+ else
+ record_insn_num++;
+ }
+
+ return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+}
+
+/* This structure represents a breakpoint inserted while the record
+ target is active. We use this to know when to install/remove
+ breakpoints in/from the target beneath. For example, a breakpoint
+ may be inserted while recording, but removed when not replaying nor
+ recording. In that case, the breakpoint had not been inserted on
+ the target beneath, so we should not try to remove it there. */
+
+struct record_breakpoint
+{
+ /* The address and address space the breakpoint was set at. */
+ struct address_space *address_space;
+ CORE_ADDR addr;
+
+ /* True when the breakpoint has been also installed in the target
+ beneath. This will be false for breakpoints set during replay or
+ when recording. */
+ int in_target_beneath;
+};
+
+typedef struct record_breakpoint *record_breakpoint_p;
+DEF_VEC_P(record_breakpoint_p);
+
+/* The list of breakpoints inserted while the record target is
+ active. */
+VEC(record_breakpoint_p) *record_breakpoints = NULL;
+
+static void
+record_sync_record_breakpoints (struct bp_location *loc, void *data)
+{
+ if (loc->loc_type != bp_loc_software_breakpoint)
+ return;
+
+ if (loc->inserted)
+ {
+ struct record_breakpoint *bp = XNEW (struct record_breakpoint);
+
+ bp->addr = loc->target_info.placed_address;
+ bp->address_space = loc->target_info.placed_address_space;
+
+ bp->in_target_beneath = 1;
+
+ VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
+ }
+}
+
+/* Sync existing breakpoints to record_breakpoints. */
+
+static void
+record_init_record_breakpoints (void)
+{
+ VEC_free (record_breakpoint_p, record_breakpoints);
+
+ iterate_over_bp_locations (record_sync_record_breakpoints);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY. We will not actually
+ insert or remove breakpoints in the real target when replaying, nor
+ when recording. */
+
+static int
+record_insert_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ struct record_breakpoint *bp;
+ int in_target_beneath = 0;
+
+ if (!RECORD_IS_REPLAY)
+ {
+ /* When recording, we currently always single-step, so we don't
+ really need to install regular breakpoints in the inferior.
+ However, we do have to insert software single-step
+ breakpoints, in case the target can't hardware step. To keep
+ things single, we always insert. */
+ struct cleanup *old_cleanups;
+ int ret;
+
+ old_cleanups = record_gdb_operation_disable_set ();
+ ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
+ do_cleanups (old_cleanups);
+
+ if (ret != 0)
+ return ret;
+
+ in_target_beneath = 1;
+ }
+
+ bp = XNEW (struct record_breakpoint);
+ bp->addr = bp_tgt->placed_address;
+ bp->address_space = bp_tgt->placed_address_space;
+ bp->in_target_beneath = in_target_beneath;
+ VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
+ return 0;
+}
+
+/* "to_remove_breakpoint" method for process record target. */
+
+static int
+record_remove_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ struct record_breakpoint *bp;
+ int ix;
+
+ for (ix = 0;
+ VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp);
+ ++ix)
+ {
+ if (bp->addr == bp_tgt->placed_address
+ && bp->address_space == bp_tgt->placed_address_space)
+ {
+ if (bp->in_target_beneath)
+ {
+ struct cleanup *old_cleanups;
+ int ret;
+
+ old_cleanups = record_gdb_operation_disable_set ();
+ ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
+ do_cleanups (old_cleanups);
+
+ if (ret != 0)
+ return ret;
+ }
+
+ VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix);
+ return 0;
+ }
+ }
+
+ gdb_assert_not_reached ("removing unknown breakpoint");
+}
+
+/* "to_can_execute_reverse" method for process record target. */
+
+static int
+record_can_execute_reverse (void)
+{
+ return 1;
+}
+
+/* "to_get_bookmark" method for process record and prec over core. */
+
+static gdb_byte *
+record_get_bookmark (char *args, int from_tty)
+{
+ gdb_byte *ret = NULL;
+
+ /* Return stringified form of instruction count. */
+ if (record_list && record_list->type == record_end)
+ ret = xstrdup (pulongest (record_list->u.end.insn_num));
+
+ if (record_debug)
+ {
+ if (ret)
+ fprintf_unfiltered (gdb_stdlog,
+ "record_get_bookmark returns %s\n", ret);
+ else
+ fprintf_unfiltered (gdb_stdlog,
+ "record_get_bookmark returns NULL\n");
+ }
+ return ret;
+}
+
+/* "to_goto_bookmark" method for process record and prec over core. */
+
+static void
+record_goto_bookmark (gdb_byte *bookmark, int from_tty)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "record_goto_bookmark receives %s\n", bookmark);
+
+ if (bookmark[0] == '\'' || bookmark[0] == '\"')
+ {
+ if (bookmark[strlen (bookmark) - 1] != bookmark[0])
+ error (_("Unbalanced quotes: %s"), bookmark);
+
+ /* Strip trailing quote. */
+ bookmark[strlen (bookmark) - 1] = '\0';
+ /* Strip leading quote. */
+ bookmark++;
+ /* Pass along to cmd_record_goto. */
+ }
+
+ cmd_record_goto ((char *) bookmark, from_tty);
+ return;
+}
+
+static void
+record_async (void (*callback) (enum inferior_event_type event_type,
+ void *context), void *context)
+{
+ /* If we're on top of a line target (e.g., linux-nat, remote), then
+ set it to async mode as well. Will be NULL if we're sitting on
+ top of the core target, for "record restore". */
+ if (record_beneath_to_async != NULL)
+ record_beneath_to_async (callback, context);
+}
+
+static int
+record_can_async_p (void)
+{
+ /* We only enable async when the user specifically asks for it. */
+ return target_async_permitted;
+}
+
+static int
+record_is_async_p (void)
+{
+ /* We only enable async when the user specifically asks for it. */
+ return target_async_permitted;
+}
+
+static enum exec_direction_kind
+record_execution_direction (void)
+{
+ return record_execution_dir;
+}
+
+static void
+record_info (void)
+{
+ struct record_entry *p;
+
+ if (RECORD_IS_REPLAY)
+ printf_filtered (_("Replay mode:\n"));
+ else
+ printf_filtered (_("Record mode:\n"));
+
+ /* Find entry for first actual instruction in the log. */
+ for (p = record_first.next;
+ p != NULL && p->type != record_end;
+ p = p->next)
+ ;
+
+ /* Do we have a log at all? */
+ if (p != NULL && p->type == record_end)
+ {
+ /* Display instruction number for first instruction in the log. */
+ printf_filtered (_("Lowest recorded instruction number is %s.\n"),
+ pulongest (p->u.end.insn_num));
+
+ /* If in replay mode, display where we are in the log. */
+ if (RECORD_IS_REPLAY)
+ printf_filtered (_("Current instruction number is %s.\n"),
+ pulongest (record_list->u.end.insn_num));
+
+ /* Display instruction number for last instruction in the log. */
+ printf_filtered (_("Highest recorded instruction number is %s.\n"),
+ pulongest (record_insn_count));
+
+ /* Display log count. */
+ printf_filtered (_("Log contains %d instructions.\n"),
+ record_insn_num);
+ }
+ else
+ printf_filtered (_("No instructions have been logged.\n"));
+
+ /* Display max log size. */
+ printf_filtered (_("Max logged instructions is %d.\n"),
+ record_insn_max_num);
+}
+
+/* The "to_record_delete" target method. */
+
+static void
+record_delete (void)
+{
+ record_list_release_following (record_list);
+}
+
+/* The "to_record_is_replaying" target method. */
+
+static int
+record_is_replaying (void)
+{
+ return RECORD_IS_REPLAY;
+}
+
+/* Go to a specific entry. */
+
+static void
+record_goto_entry (struct record_entry *p)
+{
+ if (p == NULL)
+ error (_("Target insn not found."));
+ else if (p == record_list)
+ error (_("Already at target insn."));
+ else if (p->u.end.insn_num > record_list->u.end.insn_num)
+ {
+ printf_filtered (_("Go forward to insn number %s\n"),
+ pulongest (p->u.end.insn_num));
+ record_goto_insn (p, EXEC_FORWARD);
+ }
+ else
+ {
+ printf_filtered (_("Go backward to insn number %s\n"),
+ pulongest (p->u.end.insn_num));
+ record_goto_insn (p, EXEC_REVERSE);
+ }
+
+ registers_changed ();
+ reinit_frame_cache ();
+ print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+/* The "to_goto_record_begin" target method. */
+
+static void
+record_goto_begin (void)
+{
+ struct record_entry *p = NULL;
+
+ for (p = &record_first; p != NULL; p = p->next)
+ if (p->type == record_end)
+ break;
+
+ record_goto_entry (p);
+}
+
+/* The "to_goto_record_end" target method. */
+
+static void
+record_goto_end (void)
+{
+ struct record_entry *p = NULL;
+
+ for (p = record_list; p->next != NULL; p = p->next)
+ ;
+ for (; p!= NULL; p = p->prev)
+ if (p->type == record_end)
+ break;
+
+ record_goto_entry (p);
+}
+
+/* The "to_goto_record" target method. */
+
+static void
+record_goto (ULONGEST target_insn)
+{
+ struct record_entry *p = NULL;
+
+ for (p = &record_first; p != NULL; p = p->next)
+ if (p->type == record_end && p->u.end.insn_num == target_insn)
+ break;
+
+ record_goto_entry (p);
+}
+
+static void
+init_record_ops (void)
+{
+ record_ops.to_shortname = "record-full";
+ 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_stopped_by_watchpoint = record_stopped_by_watchpoint;
+ record_ops.to_stopped_data_address = record_stopped_data_address;
+ record_ops.to_can_execute_reverse = record_can_execute_reverse;
+ record_ops.to_stratum = record_stratum;
+ /* Add bookmark target methods. */
+ record_ops.to_get_bookmark = record_get_bookmark;
+ record_ops.to_goto_bookmark = record_goto_bookmark;
+ record_ops.to_async = record_async;
+ record_ops.to_can_async_p = record_can_async_p;
+ record_ops.to_is_async_p = record_is_async_p;
+ record_ops.to_execution_direction = record_execution_direction;
+ record_ops.to_info_record = record_info;
+ record_ops.to_save_record = record_save;
+ record_ops.to_delete_record = record_delete;
+ record_ops.to_record_is_replaying = record_is_replaying;
+ record_ops.to_goto_record_begin = record_goto_begin;
+ record_ops.to_goto_record_end = record_goto_end;
+ record_ops.to_goto_record = record_goto;
+ record_ops.to_magic = OPS_MAGIC;
+}
+
+/* "to_resume" method for prec over corefile. */
+
+static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+ enum gdb_signal signal)
+{
+ record_resume_step = step;
+ record_resumed = 1;
+ record_execution_dir = execution_direction;
+
+ /* We are about to start executing the inferior (or simulate it),
+ let's register it with the event loop. */
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+
+ /* Notify the event loop there's an event to wait for. */
+ mark_async_event_handler (record_async_inferior_event_token);
+ }
+}
+
+/* "to_kill" method for prec over corefile. */
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+ unpush_target (&record_core_ops);
+}
+
+/* "to_fetch_registers" method for prec over corefile. */
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+ struct regcache *regcache,
+ int regno)
+{
+ if (regno < 0)
+ {
+ int num = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ for (i = 0; i < num; i ++)
+ regcache_raw_supply (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+ }
+ else
+ regcache_raw_supply (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+/* "to_prepare_to_store" method for prec over corefile. */
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+/* "to_store_registers" method for prec over corefile. */
+
+static void
+record_core_store_registers (struct target_ops *ops,
+ struct regcache *regcache,
+ int regno)
+{
+ if (record_gdb_operation_disable)
+ regcache_raw_collect (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+ else
+ error (_("You can't do that without a process to debug."));
+}
+
+/* "to_xfer_partial" method for prec over corefile. */
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+ const char *annex, gdb_byte *readbuf,
+ const gdb_byte *writebuf, ULONGEST offset,
+ LONGEST len)
+{
+ if (object == TARGET_OBJECT_MEMORY)
+ {
+ if (record_gdb_operation_disable || !writebuf)
+ {
+ struct target_section *p;
+
+ for (p = record_core_start; p < record_core_end; p++)
+ {
+ if (offset >= p->addr)
+ {
+ struct record_core_buf_entry *entry;
+ ULONGEST sec_offset;
+
+ if (offset >= p->endaddr)
+ continue;
+
+ if (offset + len > p->endaddr)
+ len = p->endaddr - offset;
+
+ sec_offset = offset - p->addr;
+
+ /* Read readbuf or write writebuf p, offset, len. */
+ /* Check flags. */
+ if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+ || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+ {
+ if (readbuf)
+ memset (readbuf, 0, len);
+ return len;
+ }
+ /* Get record_core_buf_entry. */
+ for (entry = record_core_buf_list; entry;
+ entry = entry->prev)
+ if (entry->p == p)
+ break;
+ if (writebuf)
+ {
+ if (!entry)
+ {
+ /* Add a new entry. */
+ entry = (struct record_core_buf_entry *)
+ xmalloc (sizeof (struct record_core_buf_entry));
+ entry->p = p;
+ if (!bfd_malloc_and_get_section (p->bfd,
+ p->the_bfd_section,
+ &entry->buf))
+ {
+ xfree (entry);
+ return 0;
+ }
+ entry->prev = record_core_buf_list;
+ record_core_buf_list = entry;
+ }
+
+ memcpy (entry->buf + sec_offset, writebuf,
+ (size_t) len);
+ }
+ else
+ {
+ if (!entry)
+ return record_beneath_to_xfer_partial
+ (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+
+ memcpy (readbuf, entry->buf + sec_offset,
+ (size_t) len);
+ }
+
+ return len;
+ }
+ }
+
+ return -1;
+ }
+ else
+ error (_("You can't do that without a process to debug."));
+ }
+
+ return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+}
+
+/* "to_insert_breakpoint" method for prec over corefile. */
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ return 0;
+}
+
+/* "to_remove_breakpoint" method for prec over corefile. */
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ return 0;
+}
+
+/* "to_has_execution" method for prec over corefile. */
+
+static int
+record_core_has_execution (struct target_ops *ops, ptid_t the_ptid)
+{
+ return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+ record_core_ops.to_shortname = "record-core";
+ record_core_ops.to_longname = "Process record and replay target";
+ record_core_ops.to_doc =
+ "Log program while executing and replay execution from log.";
+ record_core_ops.to_open = record_open;
+ record_core_ops.to_close = record_close;
+ record_core_ops.to_resume = record_core_resume;
+ record_core_ops.to_wait = record_wait;
+ record_core_ops.to_kill = record_core_kill;
+ record_core_ops.to_fetch_registers = record_core_fetch_registers;
+ record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+ record_core_ops.to_store_registers = record_core_store_registers;
+ record_core_ops.to_xfer_partial = record_core_xfer_partial;
+ record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+ record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+ record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
+ record_core_ops.to_stopped_data_address = record_stopped_data_address;
+ record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+ record_core_ops.to_has_execution = record_core_has_execution;
+ record_core_ops.to_stratum = record_stratum;
+ /* Add bookmark target methods. */
+ record_core_ops.to_get_bookmark = record_get_bookmark;
+ record_core_ops.to_goto_bookmark = record_goto_bookmark;
+ record_core_ops.to_async = record_async;
+ record_core_ops.to_can_async_p = record_can_async_p;
+ record_core_ops.to_is_async_p = record_is_async_p;
+ record_core_ops.to_execution_direction = record_execution_direction;
+ record_core_ops.to_info_record = record_info;
+ record_core_ops.to_delete_record = record_delete;
+ record_core_ops.to_record_is_replaying = record_is_replaying;
+ record_core_ops.to_goto_record_begin = record_goto_begin;
+ record_core_ops.to_goto_record_end = record_goto_end;
+ record_core_ops.to_goto_record = record_goto;
+ record_core_ops.to_magic = OPS_MAGIC;
+}
+
+/* Record log save-file format
+ Version 1 (never released)
+
+ Header:
+ 4 bytes: magic number htonl(0x20090829).
+ NOTE: be sure to change whenever this file format changes!
+
+ Records:
+ record_end:
+ 1 byte: record type (record_end, see enum record_type).
+ record_reg:
+ 1 byte: record type (record_reg, see enum record_type).
+ 8 bytes: register id (network byte order).
+ MAX_REGISTER_SIZE bytes: register value.
+ record_mem:
+ 1 byte: record type (record_mem, see enum record_type).
+ 8 bytes: memory length (network byte order).
+ 8 bytes: memory address (network byte order).
+ n bytes: memory value (n == memory length).
+
+ Version 2
+ 4 bytes: magic number netorder32(0x20091016).
+ NOTE: be sure to change whenever this file format changes!
+
+ Records:
+ record_end:
+ 1 byte: record type (record_end, see enum record_type).
+ 4 bytes: signal
+ 4 bytes: instruction count
+ record_reg:
+ 1 byte: record type (record_reg, see enum record_type).
+ 4 bytes: register id (network byte order).
+ n bytes: register value (n == actual register size).
+ (eg. 4 bytes for x86 general registers).
+ record_mem:
+ 1 byte: record type (record_mem, see enum record_type).
+ 4 bytes: memory length (network byte order).
+ 8 bytes: memory address (network byte order).
+ n bytes: memory value (n == memory length).
+
+*/
+
+/* bfdcore_read -- read bytes from a core file section. */
+
+static inline void
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+ int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+ if (ret)
+ *offset += len;
+ else
+ error (_("Failed to read %d bytes from core file %s ('%s')."),
+ len, bfd_get_filename (obfd),
+ bfd_errmsg (bfd_get_error ()));
+}
+
+static inline uint64_t
+netorder64 (uint64_t input)
+{
+ uint64_t ret;
+
+ store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
+ BFD_ENDIAN_BIG, input);
+ return ret;
+}
+
+static inline uint32_t
+netorder32 (uint32_t input)
+{
+ uint32_t ret;
+
+ store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
+ BFD_ENDIAN_BIG, input);
+ return ret;
+}
+
+static inline uint16_t
+netorder16 (uint16_t input)
+{
+ uint16_t ret;
+
+ store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
+ BFD_ENDIAN_BIG, input);
+ return ret;
+}
+
+/* Restore the execution log from a core_bfd file. */
+static void
+record_restore (void)
+{
+ uint32_t magic;
+ struct cleanup *old_cleanups;
+ struct record_entry *rec;
+ asection *osec;
+ uint32_t osec_size;
+ int bfd_offset = 0;
+ struct regcache *regcache;
+
+ /* We restore the execution log from the open core bfd,
+ if there is one. */
+ if (core_bfd == NULL)
+ return;
+
+ /* "record_restore" can only be called when record list is empty. */
+ gdb_assert (record_first.next == NULL);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n");
+
+ /* Now need to find our special note section. */
+ osec = bfd_get_section_by_name (core_bfd, "null0");
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n",
+ osec ? "succeeded" : "failed");
+ if (osec == NULL)
+ return;
+ osec_size = bfd_section_size (core_bfd, osec);
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec));
+
+ /* Check the magic code. */
+ bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset);
+ if (magic != RECORD_FILE_MAGIC)
+ error (_("Version mis-match or file format error in core file %s."),
+ bfd_get_filename (core_bfd));
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading 4-byte magic cookie "
+ "RECORD_FILE_MAGIC (0x%s)\n",
+ phex_nz (netorder32 (magic), 4));
+
+ /* Restore the entries in recfd into record_arch_list_head and
+ record_arch_list_tail. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ record_insn_num = 0;
+ old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+ regcache = get_current_regcache ();
+
+ while (1)
+ {
+ uint8_t rectype;
+ uint32_t regnum, len, signal, count;
+ uint64_t addr;
+
+ /* We are finished when offset reaches osec_size. */
+ if (bfd_offset >= osec_size)
+ break;
+ bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset);
+
+ switch (rectype)
+ {
+ case record_reg: /* reg */
+ /* Get register number to regnum. */
+ bfdcore_read (core_bfd, osec, &regnum,
+ sizeof (regnum), &bfd_offset);
+ regnum = netorder32 (regnum);
+
+ rec = record_reg_alloc (regcache, regnum);
+
+ /* Get val. */
+ bfdcore_read (core_bfd, osec, record_get_loc (rec),
+ rec->u.reg.len, &bfd_offset);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading register %d (1 "
+ "plus %lu plus %d bytes)\n",
+ rec->u.reg.num,
+ (unsigned long) sizeof (regnum),
+ rec->u.reg.len);
+ break;
+
+ case record_mem: /* mem */
+ /* Get len. */
+ bfdcore_read (core_bfd, osec, &len,
+ sizeof (len), &bfd_offset);
+ len = netorder32 (len);
+
+ /* Get addr. */
+ bfdcore_read (core_bfd, osec, &addr,
+ sizeof (addr), &bfd_offset);
+ addr = netorder64 (addr);
+
+ rec = record_mem_alloc (addr, len);
+
+ /* Get val. */
+ bfdcore_read (core_bfd, osec, record_get_loc (rec),
+ rec->u.mem.len, &bfd_offset);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading memory %s (1 plus "
+ "%lu plus %lu plus %d bytes)\n",
+ paddress (get_current_arch (),
+ rec->u.mem.addr),
+ (unsigned long) sizeof (addr),
+ (unsigned long) sizeof (len),
+ rec->u.mem.len);
+ break;
+
+ case record_end: /* end */
+ rec = record_end_alloc ();
+ record_insn_num ++;
+
+ /* Get signal value. */
+ bfdcore_read (core_bfd, osec, &signal,
+ sizeof (signal), &bfd_offset);
+ signal = netorder32 (signal);
+ rec->u.end.sigval = signal;
+
+ /* Get insn count. */
+ bfdcore_read (core_bfd, osec, &count,
+ sizeof (count), &bfd_offset);
+ count = netorder32 (count);
+ rec->u.end.insn_num = count;
+ record_insn_count = count + 1;
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading record_end (1 + "
+ "%lu + %lu bytes), offset == %s\n",
+ (unsigned long) sizeof (signal),
+ (unsigned long) sizeof (count),
+ paddress (get_current_arch (),
+ bfd_offset));
+ break;
+
+ default:
+ error (_("Bad entry type in core file %s."),
+ bfd_get_filename (core_bfd));
+ break;
+ }
+
+ /* Add rec to record arch list. */
+ record_arch_list_add (rec);
+ }
+
+ discard_cleanups (old_cleanups);
+
+ /* Add record_arch_list_head to the end of record list. */
+ record_first.next = record_arch_list_head;
+ record_arch_list_head->prev = &record_first;
+ record_arch_list_tail->next = NULL;
+ record_list = &record_first;
+
+ /* Update record_insn_max_num. */
+ if (record_insn_num > record_insn_max_num)
+ {
+ record_insn_max_num = record_insn_num;
+ warning (_("Auto increase record/replay buffer limit to %d."),
+ record_insn_max_num);
+ }
+
+ /* Succeeded. */
+ printf_filtered (_("Restored records from core file %s.\n"),
+ bfd_get_filename (core_bfd));
+
+ print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+/* bfdcore_write -- write bytes into a core file section. */
+
+static inline void
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+ int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+ if (ret)
+ *offset += len;
+ else
+ error (_("Failed to write %d bytes to core file %s ('%s')."),
+ len, bfd_get_filename (obfd),
+ bfd_errmsg (bfd_get_error ()));
+}
+
+/* Restore the execution log from a file. We use a modified elf
+ corefile format, with an extra section for our data. */
+
+static void
+cmd_record_restore (char *args, int from_tty)
+{
+ core_file_command (args, from_tty);
+ record_open (args, from_tty);
+}
+
+static void
+record_save_cleanups (void *data)
+{
+ bfd *obfd = data;
+ char *pathname = xstrdup (bfd_get_filename (obfd));
+
+ gdb_bfd_unref (obfd);
+ unlink (pathname);
+ xfree (pathname);
+}
+
+/* Save the execution log to a file. We use a modified elf corefile
+ format, with an extra section for our data. */
+
+static void
+record_save (char *recfilename)
+{
+ struct record_entry *cur_record_list;
+ uint32_t magic;
+ struct regcache *regcache;
+ struct gdbarch *gdbarch;
+ struct cleanup *old_cleanups;
+ struct cleanup *set_cleanups;
+ bfd *obfd;
+ int save_size = 0;
+ asection *osec = NULL;
+ int bfd_offset = 0;
+
+ /* Open the save file. */
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n",
+ recfilename);
+
+ /* Open the output file. */
+ obfd = create_gcore_bfd (recfilename);
+ old_cleanups = make_cleanup (record_save_cleanups, obfd);
+
+ /* Save the current record entry to "cur_record_list". */
+ cur_record_list = record_list;
+
+ /* Get the values of regcache and gdbarch. */
+ regcache = get_current_regcache ();
+ gdbarch = get_regcache_arch (regcache);
+
+ /* Disable the GDB operation record. */
+ set_cleanups = record_gdb_operation_disable_set ();
+
+ /* Reverse execute to the begin of record list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == &record_first)
+ break;
+
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ /* Compute the size needed for the extra bfd section. */
+ save_size = 4; /* magic cookie */
+ for (record_list = record_first.next; record_list;
+ record_list = record_list->next)
+ switch (record_list->type)
+ {
+ case record_end:
+ save_size += 1 + 4 + 4;
+ break;
+ case record_reg:
+ save_size += 1 + 4 + record_list->u.reg.len;
+ break;
+ case record_mem:
+ save_size += 1 + 4 + 8 + record_list->u.mem.len;
+ break;
+ }
+
+ /* Make the new bfd section. */
+ osec = bfd_make_section_anyway_with_flags (obfd, "precord",
+ SEC_HAS_CONTENTS
+ | SEC_READONLY);
+ if (osec == NULL)
+ error (_("Failed to create 'precord' section for corefile %s: %s"),
+ recfilename,
+ bfd_errmsg (bfd_get_error ()));
+ bfd_set_section_size (obfd, osec, save_size);
+ bfd_set_section_vma (obfd, osec, 0);
+ bfd_set_section_alignment (obfd, osec, 0);
+ bfd_section_lma (obfd, osec) = 0;
+
+ /* Save corefile state. */
+ write_gcore_file (obfd);
+
+ /* Write out the record log. */
+ /* Write the magic code. */
+ magic = RECORD_FILE_MAGIC;
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing 4-byte magic cookie "
+ "RECORD_FILE_MAGIC (0x%s)\n",
+ phex_nz (magic, 4));
+ bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset);
+
+ /* Save the entries to recfd and forward execute to the end of
+ record list. */
+ record_list = &record_first;
+ while (1)
+ {
+ /* Save entry. */
+ if (record_list != &record_first)
+ {
+ uint8_t type;
+ uint32_t regnum, len, signal, count;
+ uint64_t addr;
+
+ type = record_list->type;
+ bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset);
+
+ switch (record_list->type)
+ {
+ case record_reg: /* reg */
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing register %d (1 "
+ "plus %lu plus %d bytes)\n",
+ record_list->u.reg.num,
+ (unsigned long) sizeof (regnum),
+ record_list->u.reg.len);
+
+ /* Write regnum. */
+ regnum = netorder32 (record_list->u.reg.num);
+ bfdcore_write (obfd, osec, &regnum,
+ sizeof (regnum), &bfd_offset);
+
+ /* Write regval. */
+ bfdcore_write (obfd, osec, record_get_loc (record_list),
+ record_list->u.reg.len, &bfd_offset);
+ break;
+
+ case record_mem: /* mem */
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing memory %s (1 plus "
+ "%lu plus %lu plus %d bytes)\n",
+ paddress (gdbarch,
+ record_list->u.mem.addr),
+ (unsigned long) sizeof (addr),
+ (unsigned long) sizeof (len),
+ record_list->u.mem.len);
+
+ /* Write memlen. */
+ len = netorder32 (record_list->u.mem.len);
+ bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset);
+
+ /* Write memaddr. */
+ addr = netorder64 (record_list->u.mem.addr);
+ bfdcore_write (obfd, osec, &addr,
+ sizeof (addr), &bfd_offset);
+
+ /* Write memval. */
+ bfdcore_write (obfd, osec, record_get_loc (record_list),
+ record_list->u.mem.len, &bfd_offset);
+ break;
+
+ case record_end:
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing record_end (1 + "
+ "%lu + %lu bytes)\n",
+ (unsigned long) sizeof (signal),
+ (unsigned long) sizeof (count));
+ /* Write signal value. */
+ signal = netorder32 (record_list->u.end.sigval);
+ bfdcore_write (obfd, osec, &signal,
+ sizeof (signal), &bfd_offset);
+
+ /* Write insn count. */
+ count = netorder32 (record_list->u.end.insn_num);
+ bfdcore_write (obfd, osec, &count,
+ sizeof (count), &bfd_offset);
+ break;
+ }
+ }
+
+ /* Execute entry. */
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->next)
+ record_list = record_list->next;
+ else
+ break;
+ }
+
+ /* Reverse execute to cur_record_list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == cur_record_list)
+ break;
+
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ do_cleanups (set_cleanups);
+ gdb_bfd_unref (obfd);
+ discard_cleanups (old_cleanups);
+
+ /* Succeeded. */
+ printf_filtered (_("Saved core file %s with execution log.\n"),
+ recfilename);
+}
+
+/* record_goto_insn -- rewind the record log (forward or backward,
+ depending on DIR) to the given entry, changing the program state
+ correspondingly. */
+
+static void
+record_goto_insn (struct record_entry *entry,
+ enum exec_direction_kind dir)
+{
+ struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
+ struct regcache *regcache = get_current_regcache ();
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+ /* Assume everything is valid: we will hit the entry,
+ and we will not hit the end of the recording. */
+
+ if (dir == EXEC_FORWARD)
+ record_list = record_list->next;
+
+ do
+ {
+ record_exec_insn (regcache, gdbarch, record_list);
+ if (dir == EXEC_REVERSE)
+ record_list = record_list->prev;
+ else
+ record_list = record_list->next;
+ } while (record_list != entry);
+ do_cleanups (set_cleanups);
+}
+
+/* Alias for "target record-full". */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+ execute_command ("target record-full", from_tty);
+}
+
+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)
+ {
+ /* Count down record_insn_num while releasing records from list. */
+ while (record_insn_num > record_insn_max_num)
+ {
+ record_list_release_first ();
+ record_insn_num--;
+ }
+ }
+}
+
+/* The "set record full" command. */
+
+static void
+set_record_full_command (char *args, int from_tty)
+{
+ printf_unfiltered (_("\"set record full\" must be followed "
+ "by an apporpriate subcommand.\n"));
+ help_list (set_record_full_cmdlist, "set record full ", all_commands,
+ gdb_stdout);
+}
+
+/* The "show record full" command. */
+
+static void
+show_record_full_command (char *args, int from_tty)
+{
+ cmd_show_list (show_record_full_cmdlist, from_tty, "");
+}
+
+/* Provide a prototype to silence -Wmissing-prototypes. */
+extern initialize_file_ftype _initialize_record_full;
+
+void
+_initialize_record_full (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);
+ add_deprecated_target_alias (&record_ops, "record");
+ init_record_core_ops ();
+ add_target (&record_core_ops);
+
+ add_prefix_cmd ("full", class_obscure, cmd_record_start,
+ _("Start full execution recording."), &record_full_cmdlist,
+ "record full ", 0, &record_cmdlist);
+
+ c = add_cmd ("restore", class_obscure, cmd_record_restore,
+ _("Restore the execution log from a file.\n\
+Argument is filename. File must be created with 'record save'."),
+ &record_full_cmdlist);
+ set_cmd_completer (c, filename_completer);
+
+ /* Deprecate the old version without "full" prefix. */
+ c = add_alias_cmd ("restore", "full restore", class_obscure, 1,
+ &record_cmdlist);
+ set_cmd_completer (c, filename_completer);
+ deprecate_cmd (c, "record full restore");
+
+ add_prefix_cmd ("full", class_support, set_record_full_command,
+ _("Set record options"), &set_record_full_cmdlist,
+ "set record full ", 0, &set_record_cmdlist);
+
+ add_prefix_cmd ("full", class_support, show_record_full_command,
+ _("Show record options"), &show_record_full_cmdlist,
+ "show record full ", 0, &show_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_full_cmdlist, &show_record_full_cmdlist);
+
+ c = add_alias_cmd ("stop-at-limit", "full stop-at-limit", no_class, 1,
+ &set_record_cmdlist);
+ deprecate_cmd (c, "set record full stop-at-limit");
+
+ c = add_alias_cmd ("stop-at-limit", "full stop-at-limit", no_class, 1,
+ &show_record_cmdlist);
+ deprecate_cmd (c, "show record full stop-at-limit");
+
+ add_setshow_uinteger_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_full_cmdlist,
+ &show_record_full_cmdlist);
+
+ c = add_alias_cmd ("insn-number-max", "full insn-number-max", no_class, 1,
+ &set_record_cmdlist);
+ deprecate_cmd (c, "set record full insn-number-max");
+
+ c = add_alias_cmd ("insn-number-max", "full insn-number-max", no_class, 1,
+ &show_record_cmdlist);
+ deprecate_cmd (c, "show record full insn-number-max");
+
+ add_setshow_boolean_cmd ("memory-query", no_class, &record_memory_query, _("\
+Set whether query if PREC cannot record memory change of next instruction."),
+ _("\
+Show whether query if PREC cannot record memory change of next instruction."),
+ _("\
+Default is OFF.\n\
+When ON, query if PREC cannot record memory change of next instruction."),
+ NULL, NULL,
+ &set_record_full_cmdlist, &show_record_full_cmdlist);
+
+ c = add_alias_cmd ("memory-query", "full memory-query", no_class, 1,
+ &set_record_cmdlist);
+ deprecate_cmd (c, "set record full memory-query");
+
+ c = add_alias_cmd ("memory-query", "full memory-query", no_class, 1,
+ &show_record_cmdlist);
+ deprecate_cmd (c, "show record full memory-query");
+}
diff --git a/gdb/record-full.h b/gdb/record-full.h
new file mode 100644
index 00000000000..46da3a238b5
--- /dev/null
+++ b/gdb/record-full.h
@@ -0,0 +1,30 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+ Copyright (C) 2013 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef RECORD_FULL_H
+#define RECORD_FULL_H
+
+extern int record_memory_query;
+
+extern int record_arch_list_add_reg (struct regcache *regcache, int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup *record_gdb_operation_disable_set (void);
+
+#endif /* RECORD_FULL_H */
diff --git a/gdb/record.c b/gdb/record.c
index 1a687388100..8b447176e3b 100644
--- a/gdb/record.c
+++ b/gdb/record.c
@@ -19,472 +19,50 @@
#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 "elf-bfd.h"
-#include "gcore.h"
-#include "event-loop.h"
-#include "inf-loop.h"
-#include "gdb_bfd.h"
#include "observer.h"
-
-#include <signal.h>
-
-/* This module implements "target record", also known as "process
- record and replay". This target sits on top of a "normal" target
- (a target that "has execution"), and provides a record and replay
- functionality, including reverse debugging.
-
- Target record has two modes: recording, and replaying.
-
- In record mode, we intercept the to_resume and to_wait methods.
- Whenever gdb resumes the target, we run the target in single step
- mode, and we build up an execution log in which, for each executed
- instruction, we record all changes in memory and register state.
- This is invisible to the user, to whom it just looks like an
- ordinary debugging session (except for performance degredation).
-
- In replay mode, instead of actually letting the inferior run as a
- process, we simulate its execution by playing back the recorded
- execution log. For each instruction in the log, we simulate the
- instruction's side effects by duplicating the changes that it would
- have made on memory and registers. */
-
-#define DEFAULT_RECORD_INSN_MAX_NUM 200000
-
-#define RECORD_IS_REPLAY \
- (record_list->next || execution_direction == EXEC_REVERSE)
-
-#define RECORD_FILE_MAGIC netorder32(0x20091016)
-
-/* These are the core structs of the process record functionality.
-
- A record_entry is a record of the value change of a register
- ("record_reg") or a part of memory ("record_mem"). And each
- instruction must have a struct record_entry ("record_end") that
- indicates that this is the last struct record_entry of this
- instruction.
-
- Each struct record_entry is linked to "record_list" by "prev" and
- "next" pointers. */
-
-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;
- union
- {
- gdb_byte *ptr;
- gdb_byte buf[sizeof (gdb_byte *)];
- } u;
-};
-
-struct record_reg_entry
-{
- unsigned short num;
- unsigned short len;
- union
- {
- gdb_byte *ptr;
- gdb_byte buf[2 * sizeof (gdb_byte *)];
- } u;
-};
-
-struct record_end_entry
-{
- enum gdb_signal sigval;
- ULONGEST insn_num;
-};
-
-enum record_type
-{
- record_end = 0,
- record_reg,
- record_mem
-};
-
-/* This is the data structure that makes up the execution log.
-
- The execution log consists of a single linked list of entries
- of type "struct record_entry". It is doubly linked so that it
- can be traversed in either direction.
-
- The start of the list is anchored by a struct called
- "record_first". The pointer "record_list" either points to the
- last entry that was added to the list (in record mode), or to the
- next entry in the list that will be executed (in replay mode).
-
- Each list element (struct record_entry), in addition to next and
- prev pointers, consists of a union of three entry types: mem, reg,
- and end. A field called "type" determines which entry type is
- represented by a given list element.
-
- Each instruction that is added to the execution log is represented
- by a variable number of list elements ('entries'). The instruction
- will have one "reg" entry for each register that is changed by
- executing the instruction (including the PC in every case). It
- will also have one "mem" entry for each memory change. Finally,
- each instruction will have an "end" entry that separates it from
- the changes associated with the next instruction. */
-
-struct record_entry
-{
- struct record_entry *prev;
- struct record_entry *next;
- enum record_type type;
- union
- {
- /* reg */
- struct record_reg_entry reg;
- /* mem */
- struct record_mem_entry mem;
- /* end */
- struct record_end_entry end;
- } u;
-};
+#include "inferior.h"
+#include "common/common-utils.h"
/* This is the debug switch for process record. */
unsigned int record_debug = 0;
-/* If true, query if PREC cannot record memory
- change of next instruction. */
-int record_memory_query = 0;
-
-struct record_core_buf_entry
-{
- struct record_core_buf_entry *prev;
- struct target_section *p;
- bfd_byte *buf;
-};
-
-/* Record buf with core target. */
-static gdb_byte *record_core_regbuf = NULL;
-static struct target_section *record_core_start;
-static struct target_section *record_core_end;
-static struct record_core_buf_entry *record_core_buf_list = NULL;
-
-/* The following variables are used for managing the linked list that
- represents the execution log.
-
- record_first is the anchor that holds down the beginning of the list.
-
- record_list serves two functions:
- 1) In record mode, it anchors the end of the list.
- 2) In replay mode, it traverses the list and points to
- the next instruction that must be emulated.
-
- record_arch_list_head and record_arch_list_tail are used to manage
- a separate list, which is used to build up the change elements of
- the currently executing instruction during record mode. When this
- instruction has been completely annotated in the "arch list", it
- will be appended to the main execution log. */
-
-static struct record_entry record_first;
-static struct record_entry *record_list = &record_first;
-static struct record_entry *record_arch_list_head = NULL;
-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;
-/* Maximum allowed number of insns in execution log. */
-static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
-/* Actual count of insns presently in execution log. */
-static int record_insn_num = 0;
-/* Count of insns logged so far (may be larger
- than count of insns presently in execution log). */
-static ULONGEST record_insn_count;
-
-/* The target_ops of process record. */
-static struct target_ops record_ops;
-static struct target_ops record_core_ops;
-
-/* The beneath function pointers. */
-static struct target_ops *record_beneath_to_resume_ops;
-static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
- enum gdb_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 int (*record_beneath_to_stopped_by_watchpoint) (void);
-static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
- CORE_ADDR *);
-static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *);
-
-/* Alloc and free functions for record_reg, record_mem, and record_end
- entries. */
-
-/* Alloc a record_reg record entry. */
-
-static inline struct record_entry *
-record_reg_alloc (struct regcache *regcache, int regnum)
-{
- struct record_entry *rec;
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
-
- rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
- rec->type = record_reg;
- rec->u.reg.num = regnum;
- rec->u.reg.len = register_size (gdbarch, regnum);
- if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
- rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
-
- return rec;
-}
-
-/* Free a record_reg record entry. */
-
-static inline void
-record_reg_release (struct record_entry *rec)
-{
- gdb_assert (rec->type == record_reg);
- if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
- xfree (rec->u.reg.u.ptr);
- xfree (rec);
-}
-
-/* Alloc a record_mem record entry. */
-
-static inline struct record_entry *
-record_mem_alloc (CORE_ADDR addr, int len)
-{
- struct record_entry *rec;
-
- rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
- rec->type = record_mem;
- rec->u.mem.addr = addr;
- rec->u.mem.len = len;
- if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
- rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
-
- return rec;
-}
-
-/* Free a record_mem record entry. */
-
-static inline void
-record_mem_release (struct record_entry *rec)
-{
- gdb_assert (rec->type == record_mem);
- if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
- xfree (rec->u.mem.u.ptr);
- xfree (rec);
-}
-
-/* Alloc a record_end record entry. */
-
-static inline struct record_entry *
-record_end_alloc (void)
-{
- struct record_entry *rec;
-
- rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
- rec->type = record_end;
-
- return rec;
-}
-
-/* Free a record_end record entry. */
-
-static inline void
-record_end_release (struct record_entry *rec)
-{
- xfree (rec);
-}
-
-/* Free one record entry, any type.
- Return entry->type, in case caller wants to know. */
-
-static inline enum record_type
-record_entry_release (struct record_entry *rec)
-{
- enum record_type type = rec->type;
-
- switch (type) {
- case record_reg:
- record_reg_release (rec);
- break;
- case record_mem:
- record_mem_release (rec);
- break;
- case record_end:
- record_end_release (rec);
- break;
- }
- return type;
-}
-
-/* Free all record entries in list pointed to by REC. */
-
-static void
-record_list_release (struct record_entry *rec)
-{
- if (!rec)
- return;
-
- while (rec->next)
- rec = rec->next;
-
- while (rec->prev)
- {
- rec = rec->prev;
- record_entry_release (rec->next);
- }
-
- if (rec == &record_first)
- {
- record_insn_num = 0;
- record_first.next = NULL;
- }
- else
- record_entry_release (rec);
-}
-
-/* Free all record entries forward of the given list position. */
-
-static void
-record_list_release_following (struct record_entry *rec)
-{
- struct record_entry *tmp = rec->next;
-
- rec->next = NULL;
- while (tmp)
- {
- rec = tmp->next;
- if (record_entry_release (tmp) == record_end)
- {
- record_insn_num--;
- record_insn_count--;
- }
- tmp = rec;
- }
-}
-
-/* Delete the first instruction from the beginning of the log, to make
- room for adding a new instruction at the end of the log.
+struct cmd_list_element *record_cmdlist = NULL;
+struct cmd_list_element *set_record_cmdlist = NULL;
+struct cmd_list_element *show_record_cmdlist = NULL;
+struct cmd_list_element *info_record_cmdlist = NULL;
- Note -- this function does not modify record_insn_num. */
+/* Find the record target in the target stack. */
-static void
-record_list_release_first (void)
+static struct target_ops *
+find_record_target (void)
{
- struct record_entry *tmp;
-
- if (!record_first.next)
- return;
-
- /* Loop until a record_end. */
- while (1)
- {
- /* Cut record_first.next out of the linked list. */
- tmp = record_first.next;
- record_first.next = tmp->next;
- tmp->next->prev = &record_first;
+ struct target_ops *t;
- /* tmp is now isolated, and can be deleted. */
- if (record_entry_release (tmp) == record_end)
- break; /* End loop at first record_end. */
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_stratum == record_stratum)
+ return t;
- if (!record_first.next)
- {
- gdb_assert (record_insn_num == 1);
- break; /* End loop when list is empty. */
- }
- }
+ return NULL;
}
-/* Add a struct record_entry to record_arch_list. */
+/* Check that recording is active. Throw an error, if it isn't. */
-static void
-record_arch_list_add (struct record_entry *rec)
+static struct target_ops *
+require_record_target (void)
{
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_arch_list_add %s.\n",
- host_address_to_string (rec));
+ struct target_ops *t;
- 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;
- }
-}
+ t = find_record_target ();
+ if (t == NULL)
+ error (_("No record target is currently active.\n"
+ "Use one of the \"target record-<tab><tab>\" commands first."));
-/* Return the value storage location of a record entry. */
-static inline gdb_byte *
-record_get_loc (struct record_entry *rec)
-{
- switch (rec->type) {
- case record_mem:
- if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
- return rec->u.mem.u.ptr;
- else
- return rec->u.mem.u.buf;
- case record_reg:
- if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
- return rec->u.reg.u.ptr;
- else
- return rec->u.reg.u.buf;
- case record_end:
- default:
- gdb_assert_not_reached ("unexpected record_entry type");
- return NULL;
- }
+ return t;
}
-/* Record the value of a register NUM to record_arch_list. */
-
-int
-record_arch_list_add_reg (struct regcache *regcache, int regnum)
-{
- struct record_entry *rec;
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: add register num = %d to "
- "record list.\n",
- regnum);
-
- rec = record_reg_alloc (regcache, regnum);
-
- regcache_raw_read (regcache, regnum, record_get_loc (rec));
-
- record_arch_list_add (rec);
-
- return 0;
-}
+/* See record.h. */
int
record_read_memory (struct gdbarch *gdbarch,
@@ -500,1718 +78,6 @@ record_read_memory (struct gdbarch *gdbarch,
return ret;
}
-/* 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) /* FIXME: Why? Some arch must permit it... */
- return 0;
-
- rec = record_mem_alloc (addr, len);
-
- if (record_read_memory (target_gdbarch (), addr, record_get_loc (rec), len))
- {
- record_mem_release (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 = record_end_alloc ();
- rec->u.end.sigval = GDB_SIGNAL_0;
- rec->u.end.insn_num = ++record_insn_count;
-
- 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: stopped by user."));
- }
- }
- }
-}
-
-static void
-record_arch_list_cleanups (void *ignore)
-{
- record_list_release (record_arch_list_tail);
-}
-
-/* Before inferior step (when GDB record the running message, inferior
- only can step), GDB will call this function to record the values to
- record_list. This function will call gdbarch_process_record to
- record the running message of inferior and set them to
- record_arch_list, and add it to record_list. */
-
-static int
-record_message (struct regcache *regcache, enum gdb_signal signal)
-{
- int ret;
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
- struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
-
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
-
- /* Check record_insn_num. */
- record_check_insn_num (1);
-
- /* If gdb sends a signal value to target_resume,
- save it in the 'end' field of the previous instruction.
-
- Maybe process record should record what really happened,
- rather than what gdb pretends has happened.
-
- So if Linux delivered the signal to the child process during
- the record mode, we will record it and deliver it again in
- the replay mode.
-
- If user says "ignore this signal" during the record mode, then
- it will be ignored again during the replay mode (no matter if
- the user says something different, like "deliver this signal"
- during the replay mode).
-
- User should understand that nothing he does during the replay
- mode will change the behavior of the child. If he tries,
- then that is a user error.
-
- But we should still deliver the signal to gdb during the replay,
- if we delivered it during the recording. Therefore we should
- record the signal during record_wait, not record_resume. */
- if (record_list != &record_first) /* FIXME better way to check */
- {
- gdb_assert (record_list->type == record_end);
- record_list->u.end.sigval = signal;
- }
-
- if (signal == GDB_SIGNAL_0
- || !gdbarch_process_record_signal_p (gdbarch))
- ret = gdbarch_process_record (gdbarch,
- regcache,
- regcache_read_pc (regcache));
- else
- ret = gdbarch_process_record_signal (gdbarch,
- regcache,
- signal);
-
- if (ret > 0)
- error (_("Process record: inferior program stopped."));
- if (ret < 0)
- error (_("Process record: failed to record execution log."));
-
- discard_cleanups (old_cleanups);
-
- record_list->next = record_arch_list_head;
- record_arch_list_head->prev = record_list;
- record_list = record_arch_list_tail;
-
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
-
- return 1;
-}
-
-struct record_message_args {
- struct regcache *regcache;
- enum gdb_signal signal;
-};
-
-static int
-record_message_wrapper (void *args)
-{
- struct record_message_args *record_args = args;
-
- return record_message (record_args->regcache, record_args->signal);
-}
-
-static int
-record_message_wrapper_safe (struct regcache *regcache,
- enum gdb_signal signal)
-{
- struct record_message_args args;
-
- args.regcache = regcache;
- args.signal = signal;
-
- return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL);
-}
-
-/* Set to 1 if record_store_registers and record_xfer_partial
- 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;
-}
-
-/* Flag set to TRUE for target_stopped_by_watchpoint. */
-static int record_hw_watchpoint = 0;
-
-/* Execute one instruction from the record log. Each instruction in
- the log will be represented by an arbitrary sequence of register
- entries and memory entries, followed by an 'end' entry. */
-
-static inline void
-record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
- struct record_entry *entry)
-{
- switch (entry->type)
- {
- case record_reg: /* reg */
- {
- gdb_byte reg[MAX_REGISTER_SIZE];
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_reg %s to "
- "inferior num = %d.\n",
- host_address_to_string (entry),
- entry->u.reg.num);
-
- regcache_cooked_read (regcache, entry->u.reg.num, reg);
- regcache_cooked_write (regcache, entry->u.reg.num,
- record_get_loc (entry));
- memcpy (record_get_loc (entry), reg, entry->u.reg.len);
- }
- break;
-
- case record_mem: /* mem */
- {
- /* Nothing to do if the entry is flagged not_accessible. */
- if (!entry->u.mem.mem_entry_not_accessible)
- {
- gdb_byte *mem = alloca (entry->u.mem.len);
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_mem %s to "
- "inferior addr = %s len = %d.\n",
- host_address_to_string (entry),
- paddress (gdbarch, entry->u.mem.addr),
- entry->u.mem.len);
-
- if (record_read_memory (gdbarch,
- entry->u.mem.addr, mem, entry->u.mem.len))
- entry->u.mem.mem_entry_not_accessible = 1;
- else
- {
- if (target_write_memory (entry->u.mem.addr,
- record_get_loc (entry),
- entry->u.mem.len))
- {
- entry->u.mem.mem_entry_not_accessible = 1;
- if (record_debug)
- warning (_("Process record: error writing memory at "
- "addr = %s len = %d."),
- paddress (gdbarch, entry->u.mem.addr),
- entry->u.mem.len);
- }
- else
- {
- memcpy (record_get_loc (entry), mem, entry->u.mem.len);
-
- /* We've changed memory --- check if a hardware
- watchpoint should trap. Note that this
- presently assumes the target beneath supports
- continuable watchpoints. On non-continuable
- watchpoints target, we'll want to check this
- _before_ actually doing the memory change, and
- not doing the change at all if the watchpoint
- traps. */
- if (hardware_watchpoint_inserted_in_range
- (get_regcache_aspace (regcache),
- entry->u.mem.addr, entry->u.mem.len))
- record_hw_watchpoint = 1;
- }
- }
- }
- }
- break;
- }
-}
-
-static struct target_ops *tmp_to_resume_ops;
-static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
- enum gdb_signal);
-static struct target_ops *tmp_to_wait_ops;
-static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
- struct target_waitstatus *,
- int);
-static struct target_ops *tmp_to_store_registers_ops;
-static void (*tmp_to_store_registers) (struct target_ops *,
- struct regcache *,
- int regno);
-static struct target_ops *tmp_to_xfer_partial_ops;
-static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
- enum target_object object,
- const char *annex,
- gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset,
- LONGEST len);
-static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
- struct bp_target_info *);
-static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
- struct bp_target_info *);
-static int (*tmp_to_stopped_by_watchpoint) (void);
-static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
-static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
-static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *);
-
-static void record_restore (void);
-
-/* Asynchronous signal handle registered as event loop source for when
- we have pending events ready to be passed to the core. */
-
-static struct async_event_handler *record_async_inferior_event_token;
-
-static void
-record_async_inferior_event_handler (gdb_client_data data)
-{
- inferior_event_handler (INF_REG_EVENT, NULL);
-}
-
-/* Open the process record target. */
-
-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);
- record_restore ();
-}
-
-/* "to_open" target method for 'live' processes. */
-
-static void
-record_open_1 (char *name, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
-
- /* check exec */
- if (!target_has_execution)
- error (_("Process record: the program is not being run."));
- if (non_stop)
- error (_("Process record target can't debug inferior in non-stop mode "
- "(non-stop)."));
-
- if (!gdbarch_process_record_p (target_gdbarch ()))
- error (_("Process record: the current architecture doesn't support "
- "record function."));
-
- if (!tmp_to_resume)
- error (_("Could not find 'to_resume' method on the target stack."));
- if (!tmp_to_wait)
- error (_("Could not find 'to_wait' method on the target stack."));
- if (!tmp_to_store_registers)
- error (_("Could not find 'to_store_registers' "
- "method on the target stack."));
- if (!tmp_to_insert_breakpoint)
- error (_("Could not find 'to_insert_breakpoint' "
- "method on the target stack."));
- if (!tmp_to_remove_breakpoint)
- error (_("Could not find 'to_remove_breakpoint' "
- "method on the target stack."));
- if (!tmp_to_stopped_by_watchpoint)
- error (_("Could not find 'to_stopped_by_watchpoint' "
- "method on the target stack."));
- if (!tmp_to_stopped_data_address)
- error (_("Could not find 'to_stopped_data_address' "
- "method on the target stack."));
-
- push_target (&record_ops);
-}
-
-static void record_init_record_breakpoints (void);
-
-/* "to_open" target method. Open the process record target. */
-
-static void
-record_open (char *name, int from_tty)
-{
- struct target_ops *t;
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
-
- /* Check if record target is already running. */
- if (current_target.to_stratum == record_stratum)
- error (_("Process record target already running. Use \"record stop\" to "
- "stop record target first."));
-
- /* Reset the tmp beneath pointers. */
- tmp_to_resume_ops = NULL;
- tmp_to_resume = NULL;
- tmp_to_wait_ops = NULL;
- tmp_to_wait = NULL;
- tmp_to_store_registers_ops = NULL;
- tmp_to_store_registers = NULL;
- tmp_to_xfer_partial_ops = NULL;
- tmp_to_xfer_partial = NULL;
- tmp_to_insert_breakpoint = NULL;
- tmp_to_remove_breakpoint = NULL;
- tmp_to_stopped_by_watchpoint = NULL;
- tmp_to_stopped_data_address = NULL;
- tmp_to_async = 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_stopped_by_watchpoint)
- tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint;
- if (!tmp_to_stopped_data_address)
- tmp_to_stopped_data_address = t->to_stopped_data_address;
- if (!tmp_to_async)
- tmp_to_async = t->to_async;
- }
- if (!tmp_to_xfer_partial)
- error (_("Could not find 'to_xfer_partial' method on the target stack."));
-
- /* Reset */
- record_insn_num = 0;
- record_insn_count = 0;
- record_list = &record_first;
- record_list->next = NULL;
-
- /* Set the tmp beneath pointers to beneath pointers. */
- record_beneath_to_resume_ops = tmp_to_resume_ops;
- record_beneath_to_resume = tmp_to_resume;
- record_beneath_to_wait_ops = tmp_to_wait_ops;
- record_beneath_to_wait = tmp_to_wait;
- record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
- record_beneath_to_store_registers = tmp_to_store_registers;
- record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
- record_beneath_to_xfer_partial = tmp_to_xfer_partial;
- record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
- record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
- record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint;
- record_beneath_to_stopped_data_address = tmp_to_stopped_data_address;
- record_beneath_to_async = tmp_to_async;
-
- if (core_bfd)
- record_core_open_1 (name, from_tty);
- else
- record_open_1 (name, from_tty);
-
- /* Register extra event sources in the event loop. */
- record_async_inferior_event_token
- = create_async_event_handler (record_async_inferior_event_handler,
- NULL);
-
- record_init_record_breakpoints ();
-
- observer_notify_record_changed (current_inferior (), 1);
-}
-
-/* "to_close" target method. Close the process record target. */
-
-static void
-record_close (int quitting)
-{
- struct record_core_buf_entry *entry;
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
-
- record_list_release (record_list);
-
- /* Release record_core_regbuf. */
- if (record_core_regbuf)
- {
- xfree (record_core_regbuf);
- record_core_regbuf = NULL;
- }
-
- /* Release record_core_buf_list. */
- if (record_core_buf_list)
- {
- for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
- {
- xfree (record_core_buf_list);
- record_core_buf_list = entry;
- }
- record_core_buf_list = NULL;
- }
-
- if (record_async_inferior_event_token)
- delete_async_event_handler (&record_async_inferior_event_token);
-}
-
-static int record_resume_step = 0;
-
-/* True if we've been resumed, and so each record_wait call should
- advance execution. If this is false, record_wait will return a
- TARGET_WAITKIND_IGNORE. */
-static int record_resumed = 0;
-
-/* The execution direction of the last resume we got. This is
- necessary for async mode. Vis (order is not strictly accurate):
-
- 1. user has the global execution direction set to forward
- 2. user does a reverse-step command
- 3. record_resume is called with global execution direction
- temporarily switched to reverse
- 4. GDB's execution direction is reverted back to forward
- 5. target record notifies event loop there's an event to handle
- 6. infrun asks the target which direction was it going, and switches
- the global execution direction accordingly (to reverse)
- 7. infrun polls an event out of the record target, and handles it
- 8. GDB goes back to the event loop, and goto #4.
-*/
-static enum exec_direction_kind record_execution_dir = EXEC_FORWARD;
-
-/* "to_resume" target method. Resume the process record target. */
-
-static void
-record_resume (struct target_ops *ops, ptid_t ptid, int step,
- enum gdb_signal signal)
-{
- record_resume_step = step;
- record_resumed = 1;
- record_execution_dir = execution_direction;
-
- if (!RECORD_IS_REPLAY)
- {
- struct gdbarch *gdbarch = target_thread_architecture (ptid);
-
- record_message (get_current_regcache (), signal);
-
- if (!step)
- {
- /* This is not hard single step. */
- if (!gdbarch_software_single_step_p (gdbarch))
- {
- /* This is a normal continue. */
- step = 1;
- }
- else
- {
- /* This arch support soft sigle step. */
- if (single_step_breakpoints_inserted ())
- {
- /* This is a soft single step. */
- record_resume_step = 1;
- }
- else
- {
- /* This is a continue.
- Try to insert a soft single step breakpoint. */
- if (!gdbarch_software_single_step (gdbarch,
- get_current_frame ()))
- {
- /* This system don't want use soft single step.
- Use hard sigle step. */
- step = 1;
- }
- }
- }
- }
-
- /* Make sure the target beneath reports all signals. */
- target_pass_signals (0, NULL);
-
- record_beneath_to_resume (record_beneath_to_resume_ops,
- ptid, step, signal);
- }
-
- /* We are about to start executing the inferior (or simulate it),
- let's register it with the event loop. */
- if (target_can_async_p ())
- {
- target_async (inferior_event_handler, 0);
- /* Notify the event loop there's an event to wait for. We do
- most of the work in record_wait. */
- mark_async_event_handler (record_async_inferior_event_token);
- }
-}
-
-static int record_get_sig = 0;
-
-/* SIGINT signal handler, registered by "to_wait" method. */
-
-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;
-}
-
-/* "to_wait" target method for process record target.
-
- In record mode, the target is always run in singlestep mode
- (even when gdb says to continue). The to_wait method intercepts
- the stop events and determines which ones are to be passed on to
- gdb. Most stop events are just singlestep events that gdb is not
- to know about, so the to_wait method just records them and keeps
- singlestepping.
-
- In replay mode, this function emulates the recorded execution log,
- one instruction at a time (forward or backward), and determines
- where to stop. */
-
-static ptid_t
-record_wait_1 (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, record_resumed = %d, direction=%s\n",
- record_resume_step, record_resumed,
- record_execution_dir == EXEC_FORWARD ? "forward" : "reverse");
-
- if (!record_resumed)
- {
- gdb_assert ((options & TARGET_WNOHANG) != 0);
-
- /* No interesting event. */
- status->kind = TARGET_WAITKIND_IGNORE;
- return minus_one_ptid;
- }
-
- record_get_sig = 0;
- signal (SIGINT, record_sig_handler);
-
- if (!RECORD_IS_REPLAY && ops != &record_core_ops)
- {
- if (record_resume_step)
- {
- /* This is a single step. */
- return record_beneath_to_wait (record_beneath_to_wait_ops,
- ptid, status, options);
- }
- else
- {
- /* This is not a single step. */
- ptid_t ret;
- CORE_ADDR tmp_pc;
- struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid);
-
- while (1)
- {
- ret = record_beneath_to_wait (record_beneath_to_wait_ops,
- ptid, status, options);
- if (status->kind == TARGET_WAITKIND_IGNORE)
- {
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_wait "
- "target beneath not done yet\n");
- return ret;
- }
-
- if (single_step_breakpoints_inserted ())
- remove_single_step_breakpoints ();
-
- if (record_resume_step)
- return ret;
-
- /* Is this a SIGTRAP? */
- if (status->kind == TARGET_WAITKIND_STOPPED
- && status->value.sig == GDB_SIGNAL_TRAP)
- {
- struct regcache *regcache;
- struct address_space *aspace;
-
- /* Yes -- this is likely our single-step finishing,
- but check if there's any reason the core would be
- interested in the event. */
-
- registers_changed ();
- regcache = get_current_regcache ();
- tmp_pc = regcache_read_pc (regcache);
- aspace = get_regcache_aspace (regcache);
-
- if (target_stopped_by_watchpoint ())
- {
- /* Always interested in watchpoints. */
- }
- else if (breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- /* There is a breakpoint here. Let the core
- handle it. */
- if (software_breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- struct gdbarch *gdbarch
- = get_regcache_arch (regcache);
- CORE_ADDR decr_pc_after_break
- = gdbarch_decr_pc_after_break (gdbarch);
- if (decr_pc_after_break)
- regcache_write_pc (regcache,
- tmp_pc + decr_pc_after_break);
- }
- }
- else
- {
- /* This is a single-step trap. Record the
- insn and issue another step.
- FIXME: this part can be a random SIGTRAP too.
- But GDB cannot handle it. */
- int step = 1;
-
- if (!record_message_wrapper_safe (regcache,
- GDB_SIGNAL_0))
- {
- status->kind = TARGET_WAITKIND_STOPPED;
- status->value.sig = GDB_SIGNAL_0;
- break;
- }
-
- if (gdbarch_software_single_step_p (gdbarch))
- {
- /* Try to insert the software single step breakpoint.
- If insert success, set step to 0. */
- set_executing (inferior_ptid, 0);
- reinit_frame_cache ();
- if (gdbarch_software_single_step (gdbarch,
- get_current_frame ()))
- step = 0;
- set_executing (inferior_ptid, 1);
- }
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_wait "
- "issuing one more step in the target beneath\n");
- record_beneath_to_resume (record_beneath_to_resume_ops,
- ptid, step,
- GDB_SIGNAL_0);
- 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);
- struct address_space *aspace = get_regcache_aspace (regcache);
- int continue_flag = 1;
- int first_record_end = 1;
- struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
- CORE_ADDR tmp_pc;
-
- record_hw_watchpoint = 0;
- status->kind = TARGET_WAITKIND_STOPPED;
-
- /* Check breakpoint when forward execute. */
- if (execution_direction == EXEC_FORWARD)
- {
- tmp_pc = regcache_read_pc (regcache);
- if (breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: break at %s.\n",
- paddress (gdbarch, tmp_pc));
-
- if (decr_pc_after_break
- && !record_resume_step
- && software_breakpoint_inserted_here_p (aspace, tmp_pc))
- regcache_write_pc (regcache,
- tmp_pc + decr_pc_after_break);
- goto replay_out;
- }
- }
-
- /* 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_insn (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 (aspace, tmp_pc))
- {
- int decr_pc_after_break
- = gdbarch_decr_pc_after_break (gdbarch);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: break "
- "at %s.\n",
- paddress (gdbarch, tmp_pc));
- if (decr_pc_after_break
- && execution_direction == EXEC_FORWARD
- && !record_resume_step
- && software_breakpoint_inserted_here_p (aspace,
- tmp_pc))
- regcache_write_pc (regcache,
- tmp_pc + decr_pc_after_break);
- continue_flag = 0;
- }
-
- if (record_hw_watchpoint)
- {
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: hit hw "
- "watchpoint.\n");
- continue_flag = 0;
- }
- /* Check target signal */
- if (record_list->u.end.sigval != GDB_SIGNAL_0)
- /* FIXME: better way to check */
- 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);
-
-replay_out:
- if (record_get_sig)
- status->value.sig = GDB_SIGNAL_INT;
- else if (record_list->u.end.sigval != GDB_SIGNAL_0)
- /* FIXME: better way to check */
- status->value.sig = record_list->u.end.sigval;
- else
- status->value.sig = GDB_SIGNAL_TRAP;
-
- discard_cleanups (old_cleanups);
- }
-
- signal (SIGINT, handle_sigint);
-
- do_cleanups (set_cleanups);
- return inferior_ptid;
-}
-
-static ptid_t
-record_wait (struct target_ops *ops,
- ptid_t ptid, struct target_waitstatus *status,
- int options)
-{
- ptid_t return_ptid;
-
- return_ptid = record_wait_1 (ops, ptid, status, options);
- if (status->kind != TARGET_WAITKIND_IGNORE)
- {
- /* We're reporting a stop. Make sure any spurious
- target_wait(WNOHANG) doesn't advance the target until the
- core wants us resumed again. */
- record_resumed = 0;
- }
- return return_ptid;
-}
-
-static int
-record_stopped_by_watchpoint (void)
-{
- if (RECORD_IS_REPLAY)
- return record_hw_watchpoint;
- else
- return record_beneath_to_stopped_by_watchpoint ();
-}
-
-static int
-record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
-{
- if (RECORD_IS_REPLAY)
- return 0;
- else
- return record_beneath_to_stopped_data_address (ops, addr_p);
-}
-
-/* "to_disconnect" method for process record target. */
-
-static void
-record_disconnect (struct target_ops *target, char *args, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n");
-
- unpush_target (&record_ops);
- target_disconnect (args, from_tty);
-}
-
-/* "to_detach" method for process record target. */
-
-static void
-record_detach (struct target_ops *ops, char *args, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n");
-
- unpush_target (&record_ops);
- target_detach (args, from_tty);
-}
-
-/* "to_mourn_inferior" method for process record target. */
-
-static void
-record_mourn_inferior (struct target_ops *ops)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: "
- "record_mourn_inferior\n");
-
- unpush_target (&record_ops);
- target_mourn_inferior ();
-}
-
-/* Close process record target before killing the inferior process. */
-
-static void
-record_kill (struct target_ops *ops)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
-
- unpush_target (&record_ops);
- target_kill ();
-}
-
-/* Record registers change (by user or by GDB) to list as an instruction. */
-
-static void
-record_registers_change (struct regcache *regcache, int regnum)
-{
- /* Check record_insn_num. */
- record_check_insn_num (0);
-
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
-
- if (regnum < 0)
- {
- int i;
-
- for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
- {
- if (record_arch_list_add_reg (regcache, i))
- {
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
- }
- }
- }
- else
- {
- if (record_arch_list_add_reg (regcache, regnum))
- {
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
- }
- }
- if (record_arch_list_add_end ())
- {
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
- }
- record_list->next = record_arch_list_head;
- record_arch_list_head->prev = record_list;
- record_list = record_arch_list_tail;
-
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
-}
-
-/* "to_store_registers" method for process record target. */
-
-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 =
- query (_("Because GDB is in replay mode, changing the "
- "value of a register will make the execution "
- "log unusable from this point onward. "
- "Change all registers?"));
- else
- n =
- query (_("Because GDB is in replay mode, changing the value "
- "of a register will make the execution log unusable "
- "from this point onward. Change register %s?"),
- gdbarch_register_name (get_regcache_arch (regcache),
- regno));
-
- 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_following (record_list);
- }
-
- record_registers_change (regcache, regno);
- }
- record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
- regcache, regno);
-}
-
-/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY.
- In replay mode, we cannot write memory unles we are willing to
- invalidate the record/replay log from this point forward. */
-
-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 (!query (_("Because GDB is in replay mode, writing to memory "
- "will make the execution log unusable from this "
- "point onward. Write memory at address %s?"),
- paddress (target_gdbarch (), offset)))
- error (_("Process record canceled the operation."));
-
- /* Destroy the record from here forward. */
- record_list_release_following (record_list);
- }
-
- /* Check record_insn_num */
- record_check_insn_num (0);
-
- /* Record registers change to list as an instruction. */
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
- if (record_arch_list_add_mem (offset, len))
- {
- record_list_release (record_arch_list_tail);
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: failed to record "
- "execution log.");
- return -1;
- }
- if (record_arch_list_add_end ())
- {
- record_list_release (record_arch_list_tail);
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: failed to record "
- "execution log.");
- return -1;
- }
- record_list->next = record_arch_list_head;
- record_arch_list_head->prev = record_list;
- record_list = record_arch_list_tail;
-
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
- }
-
- return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
- object, annex, readbuf, writebuf,
- offset, len);
-}
-
-/* This structure represents a breakpoint inserted while the record
- target is active. We use this to know when to install/remove
- breakpoints in/from the target beneath. For example, a breakpoint
- may be inserted while recording, but removed when not replaying nor
- recording. In that case, the breakpoint had not been inserted on
- the target beneath, so we should not try to remove it there. */
-
-struct record_breakpoint
-{
- /* The address and address space the breakpoint was set at. */
- struct address_space *address_space;
- CORE_ADDR addr;
-
- /* True when the breakpoint has been also installed in the target
- beneath. This will be false for breakpoints set during replay or
- when recording. */
- int in_target_beneath;
-};
-
-typedef struct record_breakpoint *record_breakpoint_p;
-DEF_VEC_P(record_breakpoint_p);
-
-/* The list of breakpoints inserted while the record target is
- active. */
-VEC(record_breakpoint_p) *record_breakpoints = NULL;
-
-static void
-record_sync_record_breakpoints (struct bp_location *loc, void *data)
-{
- if (loc->loc_type != bp_loc_software_breakpoint)
- return;
-
- if (loc->inserted)
- {
- struct record_breakpoint *bp = XNEW (struct record_breakpoint);
-
- bp->addr = loc->target_info.placed_address;
- bp->address_space = loc->target_info.placed_address_space;
-
- bp->in_target_beneath = 1;
-
- VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
- }
-}
-
-/* Sync existing breakpoints to record_breakpoints. */
-
-static void
-record_init_record_breakpoints (void)
-{
- VEC_free (record_breakpoint_p, record_breakpoints);
-
- iterate_over_bp_locations (record_sync_record_breakpoints);
-}
-
-/* Behavior is conditional on RECORD_IS_REPLAY. We will not actually
- insert or remove breakpoints in the real target when replaying, nor
- when recording. */
-
-static int
-record_insert_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- struct record_breakpoint *bp;
- int in_target_beneath = 0;
-
- if (!RECORD_IS_REPLAY)
- {
- /* When recording, we currently always single-step, so we don't
- really need to install regular breakpoints in the inferior.
- However, we do have to insert software single-step
- breakpoints, in case the target can't hardware step. To keep
- things single, we always insert. */
- struct cleanup *old_cleanups;
- int ret;
-
- old_cleanups = record_gdb_operation_disable_set ();
- ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
- do_cleanups (old_cleanups);
-
- if (ret != 0)
- return ret;
-
- in_target_beneath = 1;
- }
-
- bp = XNEW (struct record_breakpoint);
- bp->addr = bp_tgt->placed_address;
- bp->address_space = bp_tgt->placed_address_space;
- bp->in_target_beneath = in_target_beneath;
- VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
- return 0;
-}
-
-/* "to_remove_breakpoint" method for process record target. */
-
-static int
-record_remove_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- struct record_breakpoint *bp;
- int ix;
-
- for (ix = 0;
- VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp);
- ++ix)
- {
- if (bp->addr == bp_tgt->placed_address
- && bp->address_space == bp_tgt->placed_address_space)
- {
- if (bp->in_target_beneath)
- {
- struct cleanup *old_cleanups;
- int ret;
-
- old_cleanups = record_gdb_operation_disable_set ();
- ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
- do_cleanups (old_cleanups);
-
- if (ret != 0)
- return ret;
- }
-
- VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix);
- return 0;
- }
- }
-
- gdb_assert_not_reached ("removing unknown breakpoint");
-}
-
-/* "to_can_execute_reverse" method for process record target. */
-
-static int
-record_can_execute_reverse (void)
-{
- return 1;
-}
-
-/* "to_get_bookmark" method for process record and prec over core. */
-
-static gdb_byte *
-record_get_bookmark (char *args, int from_tty)
-{
- gdb_byte *ret = NULL;
-
- /* Return stringified form of instruction count. */
- if (record_list && record_list->type == record_end)
- ret = xstrdup (pulongest (record_list->u.end.insn_num));
-
- if (record_debug)
- {
- if (ret)
- fprintf_unfiltered (gdb_stdlog,
- "record_get_bookmark returns %s\n", ret);
- else
- fprintf_unfiltered (gdb_stdlog,
- "record_get_bookmark returns NULL\n");
- }
- return ret;
-}
-
-/* The implementation of the command "record goto". */
-static void cmd_record_goto (char *, int);
-
-/* "to_goto_bookmark" method for process record and prec over core. */
-
-static void
-record_goto_bookmark (gdb_byte *bookmark, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "record_goto_bookmark receives %s\n", bookmark);
-
- if (bookmark[0] == '\'' || bookmark[0] == '\"')
- {
- if (bookmark[strlen (bookmark) - 1] != bookmark[0])
- error (_("Unbalanced quotes: %s"), bookmark);
-
- /* Strip trailing quote. */
- bookmark[strlen (bookmark) - 1] = '\0';
- /* Strip leading quote. */
- bookmark++;
- /* Pass along to cmd_record_goto. */
- }
-
- cmd_record_goto ((char *) bookmark, from_tty);
- return;
-}
-
-static void
-record_async (void (*callback) (enum inferior_event_type event_type,
- void *context), void *context)
-{
- /* If we're on top of a line target (e.g., linux-nat, remote), then
- set it to async mode as well. Will be NULL if we're sitting on
- top of the core target, for "record restore". */
- if (record_beneath_to_async != NULL)
- record_beneath_to_async (callback, context);
-}
-
-static int
-record_can_async_p (void)
-{
- /* We only enable async when the user specifically asks for it. */
- return target_async_permitted;
-}
-
-static int
-record_is_async_p (void)
-{
- /* We only enable async when the user specifically asks for it. */
- return target_async_permitted;
-}
-
-static enum exec_direction_kind
-record_execution_direction (void)
-{
- return record_execution_dir;
-}
-
-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_stopped_by_watchpoint = record_stopped_by_watchpoint;
- record_ops.to_stopped_data_address = record_stopped_data_address;
- record_ops.to_can_execute_reverse = record_can_execute_reverse;
- record_ops.to_stratum = record_stratum;
- /* Add bookmark target methods. */
- record_ops.to_get_bookmark = record_get_bookmark;
- record_ops.to_goto_bookmark = record_goto_bookmark;
- record_ops.to_async = record_async;
- record_ops.to_can_async_p = record_can_async_p;
- record_ops.to_is_async_p = record_is_async_p;
- record_ops.to_execution_direction = record_execution_direction;
- record_ops.to_magic = OPS_MAGIC;
-}
-
-/* "to_resume" method for prec over corefile. */
-
-static void
-record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
- enum gdb_signal signal)
-{
- record_resume_step = step;
- record_resumed = 1;
- record_execution_dir = execution_direction;
-
- /* We are about to start executing the inferior (or simulate it),
- let's register it with the event loop. */
- if (target_can_async_p ())
- {
- target_async (inferior_event_handler, 0);
-
- /* Notify the event loop there's an event to wait for. */
- mark_async_event_handler (record_async_inferior_event_token);
- }
-}
-
-/* "to_kill" method for prec over corefile. */
-
-static void
-record_core_kill (struct target_ops *ops)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
-
- unpush_target (&record_core_ops);
-}
-
-/* "to_fetch_registers" method for prec over corefile. */
-
-static void
-record_core_fetch_registers (struct target_ops *ops,
- struct regcache *regcache,
- int regno)
-{
- if (regno < 0)
- {
- int num = gdbarch_num_regs (get_regcache_arch (regcache));
- int i;
-
- for (i = 0; i < num; i ++)
- regcache_raw_supply (regcache, i,
- record_core_regbuf + MAX_REGISTER_SIZE * i);
- }
- else
- regcache_raw_supply (regcache, regno,
- record_core_regbuf + MAX_REGISTER_SIZE * regno);
-}
-
-/* "to_prepare_to_store" method for prec over corefile. */
-
-static void
-record_core_prepare_to_store (struct regcache *regcache)
-{
-}
-
-/* "to_store_registers" method for prec over corefile. */
-
-static void
-record_core_store_registers (struct target_ops *ops,
- struct regcache *regcache,
- int regno)
-{
- if (record_gdb_operation_disable)
- regcache_raw_collect (regcache, regno,
- record_core_regbuf + MAX_REGISTER_SIZE * regno);
- else
- error (_("You can't do that without a process to debug."));
-}
-
-/* "to_xfer_partial" method for prec over corefile. */
-
-static LONGEST
-record_core_xfer_partial (struct target_ops *ops, enum target_object object,
- const char *annex, gdb_byte *readbuf,
- const gdb_byte *writebuf, ULONGEST offset,
- LONGEST len)
-{
- if (object == TARGET_OBJECT_MEMORY)
- {
- if (record_gdb_operation_disable || !writebuf)
- {
- struct target_section *p;
-
- for (p = record_core_start; p < record_core_end; p++)
- {
- if (offset >= p->addr)
- {
- struct record_core_buf_entry *entry;
- ULONGEST sec_offset;
-
- if (offset >= p->endaddr)
- continue;
-
- if (offset + len > p->endaddr)
- len = p->endaddr - offset;
-
- sec_offset = offset - p->addr;
-
- /* Read readbuf or write writebuf p, offset, len. */
- /* Check flags. */
- if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
- || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
- {
- if (readbuf)
- memset (readbuf, 0, len);
- return len;
- }
- /* Get record_core_buf_entry. */
- for (entry = record_core_buf_list; entry;
- entry = entry->prev)
- if (entry->p == p)
- break;
- if (writebuf)
- {
- if (!entry)
- {
- /* Add a new entry. */
- entry = (struct record_core_buf_entry *)
- xmalloc (sizeof (struct record_core_buf_entry));
- entry->p = p;
- if (!bfd_malloc_and_get_section (p->bfd,
- p->the_bfd_section,
- &entry->buf))
- {
- xfree (entry);
- return 0;
- }
- entry->prev = record_core_buf_list;
- record_core_buf_list = entry;
- }
-
- memcpy (entry->buf + sec_offset, writebuf,
- (size_t) len);
- }
- else
- {
- if (!entry)
- return record_beneath_to_xfer_partial
- (record_beneath_to_xfer_partial_ops,
- object, annex, readbuf, writebuf,
- offset, len);
-
- memcpy (readbuf, entry->buf + sec_offset,
- (size_t) len);
- }
-
- return len;
- }
- }
-
- return -1;
- }
- else
- error (_("You can't do that without a process to debug."));
- }
-
- return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
- object, annex, readbuf, writebuf,
- offset, len);
-}
-
-/* "to_insert_breakpoint" method for prec over corefile. */
-
-static int
-record_core_insert_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- return 0;
-}
-
-/* "to_remove_breakpoint" method for prec over corefile. */
-
-static int
-record_core_remove_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- return 0;
-}
-
-/* "to_has_execution" method for prec over corefile. */
-
-static int
-record_core_has_execution (struct target_ops *ops, ptid_t the_ptid)
-{
- return 1;
-}
-
-static void
-init_record_core_ops (void)
-{
- record_core_ops.to_shortname = "record-core";
- record_core_ops.to_longname = "Process record and replay target";
- record_core_ops.to_doc =
- "Log program while executing and replay execution from log.";
- record_core_ops.to_open = record_open;
- record_core_ops.to_close = record_close;
- record_core_ops.to_resume = record_core_resume;
- record_core_ops.to_wait = record_wait;
- record_core_ops.to_kill = record_core_kill;
- record_core_ops.to_fetch_registers = record_core_fetch_registers;
- record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
- record_core_ops.to_store_registers = record_core_store_registers;
- record_core_ops.to_xfer_partial = record_core_xfer_partial;
- record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
- record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
- record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
- record_core_ops.to_stopped_data_address = record_stopped_data_address;
- record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
- record_core_ops.to_has_execution = record_core_has_execution;
- record_core_ops.to_stratum = record_stratum;
- /* Add bookmark target methods. */
- record_core_ops.to_get_bookmark = record_get_bookmark;
- record_core_ops.to_goto_bookmark = record_goto_bookmark;
- record_core_ops.to_async = record_async;
- record_core_ops.to_can_async_p = record_can_async_p;
- record_core_ops.to_is_async_p = record_is_async_p;
- record_core_ops.to_execution_direction = record_execution_direction;
- record_core_ops.to_magic = OPS_MAGIC;
-}
-
/* Implement "show record debug" command. */
static void
@@ -2227,7 +93,7 @@ show_record_debug (struct ui_file *file, int from_tty,
static void
cmd_record_start (char *args, int from_tty)
{
- execute_command ("target record", from_tty);
+ execute_command ("target record-full", from_tty);
}
/* Truncate the record log from the present point
@@ -2236,21 +102,25 @@ cmd_record_start (char *args, int from_tty)
static void
cmd_record_delete (char *args, int from_tty)
{
- if (current_target.to_stratum == record_stratum)
+ require_record_target ();
+
+ if (!target_record_is_replaying ())
{
- 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_following (record_list);
- }
- else
- printf_unfiltered (_("Already at end of record list.\n"));
+ printf_unfiltered (_("Already at end of record list.\n"));
+ return;
+ }
+ if (!target_supports_delete_record ())
+ {
+ printf_unfiltered (_("The current record target does not support "
+ "this operation.\n"));
+ return;
}
- else
- printf_unfiltered (_("Process record is not started.\n"));
+
+ if (!from_tty || query (_("Delete the log from this point forward "
+ "and begin to record the running message "
+ "at current PC?")))
+ target_delete_record ();
}
/* Implement the "stoprecord" or "record stop" command. */
@@ -2258,36 +128,18 @@ cmd_record_delete (char *args, int from_tty)
static void
cmd_record_stop (char *args, int from_tty)
{
- if (current_target.to_stratum == record_stratum)
- {
- unpush_target (&record_ops);
- printf_unfiltered (_("Process record is stopped and all execution "
- "logs are deleted.\n"));
+ struct target_ops *t;
- observer_notify_record_changed (current_inferior (), 0);
- }
- else
- printf_unfiltered (_("Process record is not started.\n"));
-}
+ t = require_record_target ();
+ unpush_target (t);
-/* Set upper limit of record log size. */
+ printf_unfiltered (_("Process record is stopped and all execution "
+ "logs are deleted.\n"));
-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)
- {
- /* Count down record_insn_num while releasing records from list. */
- while (record_insn_num > record_insn_max_num)
- {
- record_list_release_first ();
- record_insn_num--;
- }
- }
+ observer_notify_record_changed (current_inferior (), 0);
}
-static struct cmd_list_element *record_cmdlist, *set_record_cmdlist,
- *show_record_cmdlist, *info_record_cmdlist;
+/* The "set record" command. */
static void
set_record_command (char *args, int from_tty)
@@ -2297,610 +149,53 @@ set_record_command (char *args, int from_tty)
help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
}
+/* The "show record" command. */
+
static void
show_record_command (char *args, int from_tty)
{
cmd_show_list (show_record_cmdlist, from_tty, "");
}
-/* Display some statistics about the execution log. */
+/* The "info record" command. */
static void
info_record_command (char *args, int from_tty)
{
- struct record_entry *p;
-
- if (current_target.to_stratum == record_stratum)
- {
- if (RECORD_IS_REPLAY)
- printf_filtered (_("Replay mode:\n"));
- else
- printf_filtered (_("Record mode:\n"));
-
- /* Find entry for first actual instruction in the log. */
- for (p = record_first.next;
- p != NULL && p->type != record_end;
- p = p->next)
- ;
-
- /* Do we have a log at all? */
- if (p != NULL && p->type == record_end)
- {
- /* Display instruction number for first instruction in the log. */
- printf_filtered (_("Lowest recorded instruction number is %s.\n"),
- pulongest (p->u.end.insn_num));
-
- /* If in replay mode, display where we are in the log. */
- if (RECORD_IS_REPLAY)
- printf_filtered (_("Current instruction number is %s.\n"),
- pulongest (record_list->u.end.insn_num));
-
- /* Display instruction number for last instruction in the log. */
- printf_filtered (_("Highest recorded instruction number is %s.\n"),
- pulongest (record_insn_count));
-
- /* Display log count. */
- printf_filtered (_("Log contains %d instructions.\n"),
- record_insn_num);
- }
- else
- {
- printf_filtered (_("No instructions have been logged.\n"));
- }
- }
- else
- {
- printf_filtered (_("target record is not active.\n"));
- }
-
- /* Display max log size. */
- printf_filtered (_("Max logged instructions is %d.\n"),
- record_insn_max_num);
-}
-
-/* Record log save-file format
- Version 1 (never released)
-
- Header:
- 4 bytes: magic number htonl(0x20090829).
- NOTE: be sure to change whenever this file format changes!
-
- Records:
- record_end:
- 1 byte: record type (record_end, see enum record_type).
- record_reg:
- 1 byte: record type (record_reg, see enum record_type).
- 8 bytes: register id (network byte order).
- MAX_REGISTER_SIZE bytes: register value.
- record_mem:
- 1 byte: record type (record_mem, see enum record_type).
- 8 bytes: memory length (network byte order).
- 8 bytes: memory address (network byte order).
- n bytes: memory value (n == memory length).
-
- Version 2
- 4 bytes: magic number netorder32(0x20091016).
- NOTE: be sure to change whenever this file format changes!
-
- Records:
- record_end:
- 1 byte: record type (record_end, see enum record_type).
- 4 bytes: signal
- 4 bytes: instruction count
- record_reg:
- 1 byte: record type (record_reg, see enum record_type).
- 4 bytes: register id (network byte order).
- n bytes: register value (n == actual register size).
- (eg. 4 bytes for x86 general registers).
- record_mem:
- 1 byte: record type (record_mem, see enum record_type).
- 4 bytes: memory length (network byte order).
- 8 bytes: memory address (network byte order).
- n bytes: memory value (n == memory length).
-
-*/
-
-/* bfdcore_read -- read bytes from a core file section. */
-
-static inline void
-bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
-{
- int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
-
- if (ret)
- *offset += len;
- else
- error (_("Failed to read %d bytes from core file %s ('%s')."),
- len, bfd_get_filename (obfd),
- bfd_errmsg (bfd_get_error ()));
-}
-
-static inline uint64_t
-netorder64 (uint64_t input)
-{
- uint64_t ret;
-
- store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
- BFD_ENDIAN_BIG, input);
- return ret;
-}
-
-static inline uint32_t
-netorder32 (uint32_t input)
-{
- uint32_t ret;
-
- store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
- BFD_ENDIAN_BIG, input);
- return ret;
-}
-
-static inline uint16_t
-netorder16 (uint16_t input)
-{
- uint16_t ret;
-
- store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
- BFD_ENDIAN_BIG, input);
- return ret;
-}
-
-/* Restore the execution log from a core_bfd file. */
-static void
-record_restore (void)
-{
- uint32_t magic;
- struct cleanup *old_cleanups;
- struct record_entry *rec;
- asection *osec;
- uint32_t osec_size;
- int bfd_offset = 0;
- struct regcache *regcache;
-
- /* We restore the execution log from the open core bfd,
- if there is one. */
- if (core_bfd == NULL)
- return;
-
- /* "record_restore" can only be called when record list is empty. */
- gdb_assert (record_first.next == NULL);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n");
-
- /* Now need to find our special note section. */
- osec = bfd_get_section_by_name (core_bfd, "null0");
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n",
- osec ? "succeeded" : "failed");
- if (osec == NULL)
- return;
- osec_size = bfd_section_size (core_bfd, osec);
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec));
-
- /* Check the magic code. */
- bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset);
- if (magic != RECORD_FILE_MAGIC)
- error (_("Version mis-match or file format error in core file %s."),
- bfd_get_filename (core_bfd));
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading 4-byte magic cookie "
- "RECORD_FILE_MAGIC (0x%s)\n",
- phex_nz (netorder32 (magic), 4));
-
- /* Restore the entries in recfd into record_arch_list_head and
- record_arch_list_tail. */
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
- record_insn_num = 0;
- old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
- regcache = get_current_regcache ();
-
- while (1)
- {
- uint8_t rectype;
- uint32_t regnum, len, signal, count;
- uint64_t addr;
-
- /* We are finished when offset reaches osec_size. */
- if (bfd_offset >= osec_size)
- break;
- bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset);
-
- switch (rectype)
- {
- case record_reg: /* reg */
- /* Get register number to regnum. */
- bfdcore_read (core_bfd, osec, &regnum,
- sizeof (regnum), &bfd_offset);
- regnum = netorder32 (regnum);
-
- rec = record_reg_alloc (regcache, regnum);
-
- /* Get val. */
- bfdcore_read (core_bfd, osec, record_get_loc (rec),
- rec->u.reg.len, &bfd_offset);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading register %d (1 "
- "plus %lu plus %d bytes)\n",
- rec->u.reg.num,
- (unsigned long) sizeof (regnum),
- rec->u.reg.len);
- break;
-
- case record_mem: /* mem */
- /* Get len. */
- bfdcore_read (core_bfd, osec, &len,
- sizeof (len), &bfd_offset);
- len = netorder32 (len);
-
- /* Get addr. */
- bfdcore_read (core_bfd, osec, &addr,
- sizeof (addr), &bfd_offset);
- addr = netorder64 (addr);
-
- rec = record_mem_alloc (addr, len);
-
- /* Get val. */
- bfdcore_read (core_bfd, osec, record_get_loc (rec),
- rec->u.mem.len, &bfd_offset);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading memory %s (1 plus "
- "%lu plus %lu plus %d bytes)\n",
- paddress (get_current_arch (),
- rec->u.mem.addr),
- (unsigned long) sizeof (addr),
- (unsigned long) sizeof (len),
- rec->u.mem.len);
- break;
-
- case record_end: /* end */
- rec = record_end_alloc ();
- record_insn_num ++;
-
- /* Get signal value. */
- bfdcore_read (core_bfd, osec, &signal,
- sizeof (signal), &bfd_offset);
- signal = netorder32 (signal);
- rec->u.end.sigval = signal;
-
- /* Get insn count. */
- bfdcore_read (core_bfd, osec, &count,
- sizeof (count), &bfd_offset);
- count = netorder32 (count);
- rec->u.end.insn_num = count;
- record_insn_count = count + 1;
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading record_end (1 + "
- "%lu + %lu bytes), offset == %s\n",
- (unsigned long) sizeof (signal),
- (unsigned long) sizeof (count),
- paddress (get_current_arch (),
- bfd_offset));
- break;
-
- default:
- error (_("Bad entry type in core file %s."),
- bfd_get_filename (core_bfd));
- break;
- }
-
- /* Add rec to record arch list. */
- record_arch_list_add (rec);
- }
-
- discard_cleanups (old_cleanups);
-
- /* Add record_arch_list_head to the end of record list. */
- record_first.next = record_arch_list_head;
- record_arch_list_head->prev = &record_first;
- record_arch_list_tail->next = NULL;
- record_list = &record_first;
+ struct target_ops *t;
- /* Update record_insn_max_num. */
- if (record_insn_num > record_insn_max_num)
+ t = find_record_target ();
+ if (t == NULL)
{
- record_insn_max_num = record_insn_num;
- warning (_("Auto increase record/replay buffer limit to %d."),
- record_insn_max_num);
+ printf_filtered (_("No record target is currently active.\n"));
+ return;
}
- /* Succeeded. */
- printf_filtered (_("Restored records from core file %s.\n"),
- bfd_get_filename (core_bfd));
-
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
-}
-
-/* bfdcore_write -- write bytes into a core file section. */
-
-static inline void
-bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
-{
- int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
-
- if (ret)
- *offset += len;
- else
- error (_("Failed to write %d bytes to core file %s ('%s')."),
- len, bfd_get_filename (obfd),
- bfd_errmsg (bfd_get_error ()));
+ printf_filtered (_("Active record target: %s\n"), t->to_shortname);
+ if (t->to_info_record != NULL)
+ t->to_info_record ();
}
-/* Restore the execution log from a file. We use a modified elf
- corefile format, with an extra section for our data. */
-
-static void
-cmd_record_restore (char *args, int from_tty)
-{
- core_file_command (args, from_tty);
- record_open (args, from_tty);
-}
-
-static void
-record_save_cleanups (void *data)
-{
- bfd *obfd = data;
- char *pathname = xstrdup (bfd_get_filename (obfd));
-
- gdb_bfd_unref (obfd);
- unlink (pathname);
- xfree (pathname);
-}
-
-/* Save the execution log to a file. We use a modified elf corefile
- format, with an extra section for our data. */
+/* The "record save" command. */
static void
cmd_record_save (char *args, int from_tty)
{
char *recfilename, recfilename_buffer[40];
- struct record_entry *cur_record_list;
- uint32_t magic;
- struct regcache *regcache;
- struct gdbarch *gdbarch;
- struct cleanup *old_cleanups;
- struct cleanup *set_cleanups;
- bfd *obfd;
- int save_size = 0;
- asection *osec = NULL;
- int bfd_offset = 0;
- if (strcmp (current_target.to_shortname, "record") != 0)
- error (_("This command can only be used with target 'record'.\n"
- "Use 'target record' first.\n"));
+ require_record_target ();
- if (args && *args)
+ if (args != NULL && *args != 0)
recfilename = args;
else
{
/* Default recfile name is "gdb_record.PID". */
- snprintf (recfilename_buffer, sizeof (recfilename_buffer),
+ xsnprintf (recfilename_buffer, sizeof (recfilename_buffer),
"gdb_record.%d", PIDGET (inferior_ptid));
recfilename = recfilename_buffer;
}
- /* Open the save file. */
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n",
- recfilename);
-
- /* Open the output file. */
- obfd = create_gcore_bfd (recfilename);
- old_cleanups = make_cleanup (record_save_cleanups, obfd);
-
- /* Save the current record entry to "cur_record_list". */
- cur_record_list = record_list;
-
- /* Get the values of regcache and gdbarch. */
- regcache = get_current_regcache ();
- gdbarch = get_regcache_arch (regcache);
-
- /* Disable the GDB operation record. */
- set_cleanups = record_gdb_operation_disable_set ();
-
- /* Reverse execute to the begin of record list. */
- while (1)
- {
- /* Check for beginning and end of log. */
- if (record_list == &record_first)
- break;
-
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->prev)
- record_list = record_list->prev;
- }
-
- /* Compute the size needed for the extra bfd section. */
- save_size = 4; /* magic cookie */
- for (record_list = record_first.next; record_list;
- record_list = record_list->next)
- switch (record_list->type)
- {
- case record_end:
- save_size += 1 + 4 + 4;
- break;
- case record_reg:
- save_size += 1 + 4 + record_list->u.reg.len;
- break;
- case record_mem:
- save_size += 1 + 4 + 8 + record_list->u.mem.len;
- break;
- }
-
- /* Make the new bfd section. */
- osec = bfd_make_section_anyway_with_flags (obfd, "precord",
- SEC_HAS_CONTENTS
- | SEC_READONLY);
- if (osec == NULL)
- error (_("Failed to create 'precord' section for corefile %s: %s"),
- recfilename,
- bfd_errmsg (bfd_get_error ()));
- bfd_set_section_size (obfd, osec, save_size);
- bfd_set_section_vma (obfd, osec, 0);
- bfd_set_section_alignment (obfd, osec, 0);
- bfd_section_lma (obfd, osec) = 0;
-
- /* Save corefile state. */
- write_gcore_file (obfd);
-
- /* Write out the record log. */
- /* Write the magic code. */
- magic = RECORD_FILE_MAGIC;
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing 4-byte magic cookie "
- "RECORD_FILE_MAGIC (0x%s)\n",
- phex_nz (magic, 4));
- bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset);
-
- /* Save the entries to recfd and forward execute to the end of
- record list. */
- record_list = &record_first;
- while (1)
- {
- /* Save entry. */
- if (record_list != &record_first)
- {
- uint8_t type;
- uint32_t regnum, len, signal, count;
- uint64_t addr;
-
- type = record_list->type;
- bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset);
-
- switch (record_list->type)
- {
- case record_reg: /* reg */
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing register %d (1 "
- "plus %lu plus %d bytes)\n",
- record_list->u.reg.num,
- (unsigned long) sizeof (regnum),
- record_list->u.reg.len);
-
- /* Write regnum. */
- regnum = netorder32 (record_list->u.reg.num);
- bfdcore_write (obfd, osec, &regnum,
- sizeof (regnum), &bfd_offset);
-
- /* Write regval. */
- bfdcore_write (obfd, osec, record_get_loc (record_list),
- record_list->u.reg.len, &bfd_offset);
- break;
-
- case record_mem: /* mem */
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing memory %s (1 plus "
- "%lu plus %lu plus %d bytes)\n",
- paddress (gdbarch,
- record_list->u.mem.addr),
- (unsigned long) sizeof (addr),
- (unsigned long) sizeof (len),
- record_list->u.mem.len);
-
- /* Write memlen. */
- len = netorder32 (record_list->u.mem.len);
- bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset);
-
- /* Write memaddr. */
- addr = netorder64 (record_list->u.mem.addr);
- bfdcore_write (obfd, osec, &addr,
- sizeof (addr), &bfd_offset);
-
- /* Write memval. */
- bfdcore_write (obfd, osec, record_get_loc (record_list),
- record_list->u.mem.len, &bfd_offset);
- break;
-
- case record_end:
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing record_end (1 + "
- "%lu + %lu bytes)\n",
- (unsigned long) sizeof (signal),
- (unsigned long) sizeof (count));
- /* Write signal value. */
- signal = netorder32 (record_list->u.end.sigval);
- bfdcore_write (obfd, osec, &signal,
- sizeof (signal), &bfd_offset);
-
- /* Write insn count. */
- count = netorder32 (record_list->u.end.insn_num);
- bfdcore_write (obfd, osec, &count,
- sizeof (count), &bfd_offset);
- break;
- }
- }
-
- /* Execute entry. */
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->next)
- record_list = record_list->next;
- else
- break;
- }
-
- /* Reverse execute to cur_record_list. */
- while (1)
- {
- /* Check for beginning and end of log. */
- if (record_list == cur_record_list)
- break;
-
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->prev)
- record_list = record_list->prev;
- }
-
- do_cleanups (set_cleanups);
- gdb_bfd_unref (obfd);
- discard_cleanups (old_cleanups);
-
- /* Succeeded. */
- printf_filtered (_("Saved core file %s with execution log.\n"),
- recfilename);
-}
-
-/* record_goto_insn -- rewind the record log (forward or backward,
- depending on DIR) to the given entry, changing the program state
- correspondingly. */
-
-static void
-record_goto_insn (struct record_entry *entry,
- enum exec_direction_kind dir)
-{
- struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
- struct regcache *regcache = get_current_regcache ();
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
-
- /* Assume everything is valid: we will hit the entry,
- and we will not hit the end of the recording. */
-
- if (dir == EXEC_FORWARD)
- record_list = record_list->next;
-
- do
- {
- record_exec_insn (regcache, gdbarch, record_list);
- if (dir == EXEC_REVERSE)
- record_list = record_list->prev;
- else
- record_list = record_list->next;
- } while (record_list != entry);
- do_cleanups (set_cleanups);
+ target_save_record (recfilename);
}
/* "record goto" command. Argument is an instruction number,
@@ -2908,65 +203,26 @@ record_goto_insn (struct record_entry *entry,
Rewinds the recording (forward or backward) to the given instruction. */
-static void
+void
cmd_record_goto (char *arg, int from_tty)
{
- struct record_entry *p = NULL;
- ULONGEST target_insn = 0;
+ require_record_target ();
if (arg == NULL || *arg == '\0')
error (_("Command requires an argument (insn number to go to)."));
if (strncmp (arg, "start", strlen ("start")) == 0
|| strncmp (arg, "begin", strlen ("begin")) == 0)
- {
- /* Special case. Find first insn. */
- for (p = &record_first; p != NULL; p = p->next)
- if (p->type == record_end)
- break;
- if (p)
- target_insn = p->u.end.insn_num;
- }
+ target_goto_record_begin ();
else if (strncmp (arg, "end", strlen ("end")) == 0)
- {
- /* Special case. Find last insn. */
- for (p = record_list; p->next != NULL; p = p->next)
- ;
- for (; p!= NULL; p = p->prev)
- if (p->type == record_end)
- break;
- if (p)
- target_insn = p->u.end.insn_num;
- }
+ target_goto_record_end ();
else
{
- /* General case. Find designated insn. */
- target_insn = parse_and_eval_long (arg);
+ ULONGEST insn;
- for (p = &record_first; p != NULL; p = p->next)
- if (p->type == record_end && p->u.end.insn_num == target_insn)
- break;
- }
-
- if (p == NULL)
- error (_("Target insn '%s' not found."), arg);
- else if (p == record_list)
- error (_("Already at insn '%s'."), arg);
- else if (p->u.end.insn_num > record_list->u.end.insn_num)
- {
- printf_filtered (_("Go forward to insn number %s\n"),
- pulongest (target_insn));
- record_goto_insn (p, EXEC_FORWARD);
+ insn = parse_and_eval_long (arg);
+ target_goto_record (insn);
}
- else
- {
- printf_filtered (_("Go backward to insn number %s\n"),
- pulongest (target_insn));
- record_goto_insn (p, EXEC_REVERSE);
- }
- registers_changed ();
- reinit_frame_cache ();
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
}
/* Provide a prototype to silence -Wmissing-prototypes. */
@@ -2977,16 +233,6 @@ _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_zuinteger_cmd ("record", no_class, &record_debug,
_("Set debugging of record/replay feature."),
_("Show debugging of record/replay feature."),
@@ -2996,7 +242,7 @@ _initialize_record (void)
&showdebuglist);
c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
- _("Abbreviated form of \"target record\" command."),
+ _("Start recording."),
&record_cmdlist, "record ", 0, &cmdlist);
set_cmd_completer (c, filename_completer);
@@ -3021,12 +267,6 @@ Default filename is 'gdb_record.<process_id>'."),
&record_cmdlist);
set_cmd_completer (c, filename_completer);
- c = add_cmd ("restore", class_obscure, cmd_record_restore,
- _("Restore the execution log from a file.\n\
-Argument is filename. File must be created with 'record save'."),
- &record_cmdlist);
- set_cmd_completer (c, filename_completer);
-
add_cmd ("delete", class_obscure, cmd_record_delete,
_("Delete the rest of execution log and start recording it anew."),
&record_cmdlist);
@@ -3038,40 +278,8 @@ Argument is filename. File must be created with 'record save'."),
&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_uinteger_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 ("goto", class_obscure, cmd_record_goto, _("\
Restore the program to its state at instruction number N.\n\
Argument is instruction number, as shown by 'info record'."),
&record_cmdlist);
-
- add_setshow_boolean_cmd ("memory-query", no_class,
- &record_memory_query, _("\
-Set whether query if PREC cannot record memory change of next instruction."),
- _("\
-Show whether query if PREC cannot record memory change of next instruction."),
- _("\
-Default is OFF.\n\
-When ON, query if PREC cannot record memory change of next instruction."),
- NULL, NULL,
- &set_record_cmdlist, &show_record_cmdlist);
-
}
diff --git a/gdb/record.h b/gdb/record.h
index 9cf92237aee..b428eaf91c4 100644
--- a/gdb/record.h
+++ b/gdb/record.h
@@ -20,15 +20,17 @@
#ifndef _RECORD_H_
#define _RECORD_H_
+struct cmd_list_element;
+
#define RECORD_IS_USED (current_target.to_stratum == record_stratum)
extern unsigned int record_debug;
-extern int record_memory_query;
-extern int record_arch_list_add_reg (struct regcache *regcache, int num);
-extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
-extern int record_arch_list_add_end (void);
-extern struct cleanup *record_gdb_operation_disable_set (void);
+/* Allow record targets to add their own sub-commands. */
+extern struct cmd_list_element *record_cmdlist;
+extern struct cmd_list_element *set_record_cmdlist;
+extern struct cmd_list_element *show_record_cmdlist;
+extern struct cmd_list_element *info_record_cmdlist;
/* Wrapper for target_read_memory that prints a debug message if
reading memory fails. */
@@ -36,4 +38,7 @@ extern int record_read_memory (struct gdbarch *gdbarch,
CORE_ADDR memaddr, gdb_byte *myaddr,
ssize_t len);
+/* The "record goto" command. */
+extern void cmd_record_goto (char *arg, int from_tty);
+
#endif /* _RECORD_H_ */
diff --git a/gdb/target.c b/gdb/target.c
index dd201847ac2..efd5f630d53 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -4241,6 +4241,136 @@ target_read_btrace (struct btrace_target_info *btinfo,
return NULL;
}
+/* See target.h. */
+
+void
+target_info_record (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_info_record != NULL)
+ {
+ t->to_info_record ();
+ return;
+ }
+
+ tcomplain ();
+}
+
+/* See target.h. */
+
+void
+target_save_record (char *filename)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_save_record != NULL)
+ {
+ t->to_save_record (filename);
+ return;
+ }
+
+ tcomplain ();
+}
+
+/* See target.h. */
+
+int
+target_supports_delete_record (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_delete_record != NULL)
+ return 1;
+
+ return 0;
+}
+
+/* See target.h. */
+
+void
+target_delete_record (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_delete_record != NULL)
+ {
+ t->to_delete_record ();
+ return;
+ }
+
+ tcomplain ();
+}
+
+/* See target.h. */
+
+int
+target_record_is_replaying (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_record_is_replaying != NULL)
+ return t->to_record_is_replaying ();
+
+ return 0;
+}
+
+/* See target.h. */
+
+void
+target_goto_record_begin (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_goto_record_begin != NULL)
+ {
+ t->to_goto_record_begin ();
+ return;
+ }
+
+ tcomplain ();
+}
+
+/* See target.h. */
+
+void
+target_goto_record_end (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_goto_record_end != NULL)
+ {
+ t->to_goto_record_end ();
+ return;
+ }
+
+ tcomplain ();
+}
+
+/* See target.h. */
+
+void
+target_goto_record (ULONGEST insn)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_goto_record != NULL)
+ {
+ t->to_goto_record (insn);
+ return;
+ }
+
+ tcomplain ();
+}
+
static void
debug_to_prepare_to_store (struct regcache *regcache)
{
diff --git a/gdb/target.h b/gdb/target.h
index e98095e35f8..ba22292e091 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -882,6 +882,27 @@ struct target_ops
VEC (btrace_block_s) *(*to_read_btrace) (struct btrace_target_info *,
enum btrace_read_type);
+ /* Print information about the recording. */
+ void (*to_info_record) (void);
+
+ /* Save the recorded execution trace into a file. */
+ void (*to_save_record) (char *filename);
+
+ /* Delete the recorded execution trace from the current position onwards. */
+ void (*to_delete_record) (void);
+
+ /* Query if the record target is currently replaying. */
+ int (*to_record_is_replaying) (void);
+
+ /* Go to the begin of the execution trace. */
+ void (*to_goto_record_begin) (void);
+
+ /* Go to the end of the execution trace. */
+ void (*to_goto_record_end) (void);
+
+ /* Go to a specific location in the recorded execution trace. */
+ void (*to_goto_record) (ULONGEST insn);
+
int to_magic;
/* Need sub-structure for target machine related rather than comm related?
*/
@@ -1946,5 +1967,28 @@ extern void target_teardown_btrace (struct btrace_target_info *btinfo);
extern VEC (btrace_block_s) *target_read_btrace (struct btrace_target_info *,
enum btrace_read_type);
+/* See to_info_record in struct target_ops. */
+extern void target_info_record (void);
+
+/* See to_save_record in struct target_ops. */
+extern void target_save_record (char *filename);
+
+/* Query if the target supports deleting the execution log. */
+extern int target_supports_delete_record (void);
+
+/* See to_delete_record in struct target_ops. */
+extern void target_delete_record (void);
+
+/* See to_record_is_replaying in struct target_ops. */
+extern int target_record_is_replaying (void);
+
+/* See to_goto_record_begin in struct target_ops. */
+extern void target_goto_record_begin (void);
+
+/* See to_goto_record_end in struct target_ops. */
+extern void target_goto_record_end (void);
+
+/* See to_goto_record in struct target_ops. */
+extern void target_goto_record (ULONGEST insn);
#endif /* !defined (TARGET_H) */