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