diff options
author | Markus Metzger <mmetzger@sourceware.org> | 2013-03-11 08:42:55 +0000 |
---|---|---|
committer | Markus Metzger <mmetzger@sourceware.org> | 2013-03-11 08:42:55 +0000 |
commit | d02ed0bbfa8046dc0a059583747822b6f92b6235 (patch) | |
tree | 9cc7021800059008d287141d40b0503defa9e86e /gdb | |
parent | b48d48ebed2efed429a1e6b92603d784b703a4ab (diff) | |
download | gdb-d02ed0bbfa8046dc0a059583747822b6f92b6235.zip gdb-d02ed0bbfa8046dc0a059583747822b6f92b6235.tar.gz gdb-d02ed0bbfa8046dc0a059583747822b6f92b6235.tar.bz2 |
Split record.h into record.h and record-full.h.
Split record.c into record.c and record-full.c.
The split leaves the command part in record.c and moves the target part into
record-full.c.
gdb/
* 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.
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 93 | ||||
-rw-r--r-- | gdb/Makefile.in | 5 | ||||
-rw-r--r-- | gdb/amd64-linux-tdep.c | 2 | ||||
-rw-r--r-- | gdb/arm-tdep.c | 1 | ||||
-rw-r--r-- | gdb/i386-linux-tdep.c | 2 | ||||
-rw-r--r-- | gdb/i386-tdep.c | 1 | ||||
-rw-r--r-- | gdb/infrun.c | 1 | ||||
-rw-r--r-- | gdb/linux-record.c | 1 | ||||
-rw-r--r-- | gdb/moxie-tdep.c | 1 | ||||
-rw-r--r-- | gdb/record-full.c | 3019 | ||||
-rw-r--r-- | gdb/record-full.h | 30 | ||||
-rw-r--r-- | gdb/record.c | 2936 | ||||
-rw-r--r-- | gdb/record.h | 15 | ||||
-rw-r--r-- | gdb/target.c | 130 | ||||
-rw-r--r-- | gdb/target.h | 44 |
15 files changed, 3408 insertions, 2873 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 8655973..21e2c2d 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 e11e3d1..a36d576 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 e262c19..4f383db 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 d890dcb..33b8d5d 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 15a1247..f96fc81 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 637c44d..a36a83d 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 1cf30fe..1e2addc 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 c769700..2b62219 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 4b250f8..fc0f85c 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 0000000..1cbd724 --- /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, ®num, + 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, ®num, + 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 0000000..46da3a2 --- /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 1a68738..8b44717 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, ®num, - 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, ®num, - 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 9cf9223..b428eaf 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 dd20184..efd5f63 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 e98095e..ba22292 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) */ |