/* Process record and replay target for GDB, the GNU debugger.
- Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+ Copyright (C) 2008-2013 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>
-#define DEFAULT_RECORD_INSN_MAX_NUM 200000
+/* This is the debug switch for process record. */
+unsigned int record_debug = 0;
-#define RECORD_IS_REPLAY \
- (record_list->next || execution_direction == EXEC_REVERSE)
+/* The number of instructions to print in "record instruction-history". */
+static unsigned int record_insn_history_size = 10;
-/* These are the core struct of record function.
+/* 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;
- An record_entry is a record of the value change of a register
- ("record_reg") or a part of memory ("record_mem"). And each
- instruction must has a struct record_entry ("record_end") that points out this
- is the last struct record_entry of this instruction.
+/* The number of functions to print in "record function-call-history". */
+static unsigned int record_call_history_size = 10;
- Each struct record_entry is linked to "record_list" by "prev" and "next". */
+/* 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;
-struct record_reg_entry
-{
- int num;
- gdb_byte *val;
-};
+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;
-struct record_mem_entry
-{
- CORE_ADDR addr;
- int len;
- /* Set this flag if target memory for this entry
- can no longer be accessed. */
- int mem_entry_not_accessible;
- gdb_byte *val;
-};
-
-enum record_type
-{
- record_end = 0,
- record_reg,
- record_mem
-};
+#define DEBUG(msg, args...) \
+ if (record_debug) \
+ fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args)
+
+/* Find the record target in the target stack. */
-struct record_entry
+static struct target_ops *
+find_record_target (void)
{
- struct record_entry *prev;
- struct record_entry *next;
- enum record_type type;
- union
- {
- /* reg */
- struct record_reg_entry reg;
- /* mem */
- struct record_mem_entry mem;
- } u;
-};
+ struct target_ops *t;
-/* This is the debug switch for process record. */
-int record_debug = 0;
-
-/* These list is for execution log. */
-static struct record_entry record_first;
-static struct record_entry *record_list = &record_first;
-static struct record_entry *record_arch_list_head = NULL;
-static struct record_entry *record_arch_list_tail = NULL;
-
-/* 1 ask user. 0 auto delete the last struct record_entry. */
-static int record_stop_at_limit = 1;
-static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
-static int record_insn_num = 0;
-
-/* The target_ops of process record. */
-static struct target_ops record_ops;
-
-/* The beneath function pointers. */
-static struct target_ops *record_beneath_to_resume_ops;
-static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
- enum target_signal);
-static struct target_ops *record_beneath_to_wait_ops;
-static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
- struct target_waitstatus *,
- 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 *);
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_stratum == record_stratum)
+ return t;
-static void
-record_list_release (struct record_entry *rec)
+ return NULL;
+}
+
+/* Check that recording is active. Throw an error, if it isn't. */
+
+static struct target_ops *
+require_record_target (void)
{
- struct record_entry *tmp;
+ struct target_ops *t;
- if (!rec)
- return;
+ 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."));
- while (rec->next)
- {
- rec = rec->next;
- }
+ return t;
+}
- while (rec->prev)
- {
- tmp = rec;
- rec = rec->prev;
- if (tmp->type == record_reg)
- xfree (tmp->u.reg.val);
- else if (tmp->type == record_mem)
- xfree (tmp->u.mem.val);
- xfree (tmp);
- }
+/* See record.h. */
- if (rec != &record_first)
- xfree (rec);
+int
+record_read_memory (struct gdbarch *gdbarch,
+ CORE_ADDR memaddr, gdb_byte *myaddr,
+ ssize_t len)
+{
+ int ret = target_read_memory (memaddr, myaddr, len);
+
+ if (ret != 0)
+ DEBUG ("error reading memory at addr %s len = %ld.\n",
+ paddress (gdbarch, memaddr), (long) len);
+
+ return ret;
}
+/* Stop recording. */
+
static void
-record_list_release_next (void)
+record_stop (struct target_ops *t)
{
- struct record_entry *rec = record_list;
- struct record_entry *tmp = rec->next;
- rec->next = NULL;
- while (tmp)
- {
- rec = tmp->next;
- if (tmp->type == record_end)
- record_insn_num--;
- else if (tmp->type == record_reg)
- xfree (tmp->u.reg.val);
- else if (tmp->type == record_mem)
- xfree (tmp->u.mem.val);
- xfree (tmp);
- tmp = rec;
- }
+ DEBUG ("stop %s", t->to_shortname);
+
+ if (t->to_stop_recording != NULL)
+ t->to_stop_recording ();
}
+/* Unpush the record target. */
+
static void
-record_list_release_first (void)
+record_unpush (struct target_ops *t)
{
- struct record_entry *tmp = NULL;
- enum record_type type;
+ DEBUG ("unpush %s", t->to_shortname);
- if (!record_first.next)
- return;
+ unpush_target (t);
+}
- while (1)
- {
- type = record_first.next->type;
+/* See record.h. */
- if (type == record_reg)
- xfree (record_first.next->u.reg.val);
- else if (type == record_mem)
- xfree (record_first.next->u.mem.val);
- tmp = record_first.next;
- record_first.next = tmp->next;
- xfree (tmp);
+void
+record_disconnect (struct target_ops *t, char *args, int from_tty)
+{
+ gdb_assert (t->to_stratum == record_stratum);
- if (!record_first.next)
- {
- gdb_assert (record_insn_num == 1);
- break;
- }
+ DEBUG ("disconnect %s", t->to_shortname);
- record_first.next->prev = &record_first;
+ record_stop (t);
+ record_unpush (t);
- if (type == record_end)
- break;
- }
+ target_disconnect (args, from_tty);
+}
+
+/* See record.h. */
+
+void
+record_detach (struct target_ops *t, char *args, int from_tty)
+{
+ gdb_assert (t->to_stratum == record_stratum);
+
+ DEBUG ("detach %s", t->to_shortname);
+
+ record_stop (t);
+ record_unpush (t);
- record_insn_num--;
+ target_detach (args, from_tty);
}
-/* Add a struct record_entry to record_arch_list. */
+/* See record.h. */
-static void
-record_arch_list_add (struct record_entry *rec)
+void
+record_mourn_inferior (struct target_ops *t)
{
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_arch_list_add %s.\n",
- host_address_to_string (rec));
+ gdb_assert (t->to_stratum == record_stratum);
- 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;
- }
+ DEBUG ("mourn inferior %s", t->to_shortname);
+
+ /* It is safer to not stop recording. Resources will be freed when
+ threads are discarded. */
+ record_unpush (t);
+
+ target_mourn_inferior ();
}
-/* Record the value of a register NUM to record_arch_list. */
+/* See record.h. */
-int
-record_arch_list_add_reg (struct regcache *regcache, int num)
+void
+record_kill (struct target_ops *t)
{
- struct record_entry *rec;
+ gdb_assert (t->to_stratum == record_stratum);
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: add register num = %d to "
- "record list.\n",
- num);
+ DEBUG ("kill %s", t->to_shortname);
- rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
- rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
- rec->prev = NULL;
- rec->next = NULL;
- rec->type = record_reg;
- rec->u.reg.num = num;
+ /* It is safer to not stop recording. Resources will be freed when
+ threads are discarded. */
+ record_unpush (t);
- regcache_raw_read (regcache, num, rec->u.reg.val);
+ target_kill ();
+}
- record_arch_list_add (rec);
+/* Implement "show record debug" command. */
- return 0;
+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);
}
-/* 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);
-
- if (!addr)
- return 0;
-
- rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
- rec->u.mem.val = (gdb_byte *) xmalloc (len);
- rec->prev = NULL;
- rec->next = NULL;
- rec->type = record_mem;
- rec->u.mem.addr = addr;
- rec->u.mem.len = len;
- rec->u.mem.mem_entry_not_accessible = 0;
-
- if (target_read_memory (addr, rec->u.mem.val, len))
+ execute_command ("target record-full", from_tty);
+}
+
+/* Truncate the record log from the present point
+ of replay until the end. */
+
+static void
+cmd_record_delete (char *args, int from_tty)
+{
+ require_record_target ();
+
+ 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);
- xfree (rec->u.mem.val);
- xfree (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 = (struct record_entry *) xmalloc (sizeof (struct record_entry));
- rec->prev = NULL;
- rec->next = NULL;
- rec->type = record_end;
+ 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, "");
}
-static int
-record_message (void *args)
+/* The "info record" command. */
+
+static void
+info_record_command (char *args, int from_tty)
{
- int ret;
- struct regcache *regcache = args;
- struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+ struct target_ops *t;
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
+ t = find_record_target ();
+ if (t == NULL)
+ {
+ printf_filtered (_("No record target is currently active.\n"));
+ return;
+ }
- /* Check record_insn_num. */
- record_check_insn_num (1);
+ printf_filtered (_("Active record target: %s\n"), t->to_shortname);
+ if (t->to_info_record != NULL)
+ t->to_info_record ();
+}
- ret = gdbarch_process_record (get_regcache_arch (regcache),
- regcache,
- regcache_read_pc (regcache));
- if (ret > 0)
- error (_("Process record: inferior program stopped."));
- if (ret < 0)
- error (_("Process record: failed to record execution log."));
+/* 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", PIDGET (inferior_ptid));
+ recfilename = recfilename_buffer;
+ }
- return 1;
+ target_save_record (recfilename);
}
-static int
-do_record_message (struct regcache *regcache)
-{
- return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL);
-}
+/* "record goto" command. Argument is an instruction number,
+ as given by "info record".
-/* Set to 1 if record_store_registers and record_xfer_partial
- doesn't need record. */
+ Rewinds the recording (forward or backward) to the given instruction. */
-static int record_gdb_operation_disable = 0;
-
-struct cleanup *
-record_gdb_operation_disable_set (void)
+void
+cmd_record_goto (char *arg, int from_tty)
{
- struct cleanup *old_cleanups = NULL;
+ ULONGEST insn;
+
+ if (arg == NULL || *arg == '\0')
+ error (_("Command requires an argument (insn number to go to)."));
- old_cleanups =
- make_cleanup_restore_integer (&record_gdb_operation_disable);
- record_gdb_operation_disable = 1;
+ insn = parse_and_eval_long (arg);
- return old_cleanups;
+ require_record_target ();
+ target_goto_record (insn);
}
+/* The "record goto begin" command. */
+
static void
-record_open (char *name, int from_tty)
+cmd_record_goto_begin (char *arg, int from_tty)
{
- struct target_ops *t;
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
-
- /* check exec */
- if (!target_has_execution)
- error (_("Process record: the program is not being run."));
- if (non_stop)
- error (_("Process record target can't debug inferior in non-stop mode "
- "(non-stop)."));
- if (target_async_permitted)
- error (_("Process record target can't debug inferior in asynchronous "
- "mode (target-async)."));
-
- if (!gdbarch_process_record_p (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)
- {
- if (!nquery
- (_("Process record target already running, do you want to delete "
- "the old record log?")))
- return;
- }
-
- /*Reset the beneath function pointers. */
- record_beneath_to_resume = NULL;
- record_beneath_to_wait = NULL;
- record_beneath_to_store_registers = NULL;
- record_beneath_to_xfer_partial = NULL;
- record_beneath_to_insert_breakpoint = NULL;
- record_beneath_to_remove_breakpoint = NULL;
+ if (arg != NULL && *arg != '\0')
+ error (_("Junk after argument: %s."), arg);
- /* Set the beneath function pointers. */
- for (t = current_target.beneath; t != NULL; t = t->beneath)
- {
- if (!record_beneath_to_resume)
- {
- record_beneath_to_resume = t->to_resume;
- record_beneath_to_resume_ops = t;
- }
- if (!record_beneath_to_wait)
- {
- record_beneath_to_wait = t->to_wait;
- record_beneath_to_wait_ops = t;
- }
- if (!record_beneath_to_store_registers)
- {
- record_beneath_to_store_registers = t->to_store_registers;
- record_beneath_to_store_registers_ops = t;
- }
- if (!record_beneath_to_xfer_partial)
- {
- record_beneath_to_xfer_partial = t->to_xfer_partial;
- record_beneath_to_xfer_partial_ops = t;
- }
- if (!record_beneath_to_insert_breakpoint)
- record_beneath_to_insert_breakpoint = t->to_insert_breakpoint;
- if (!record_beneath_to_remove_breakpoint)
- record_beneath_to_remove_breakpoint = t->to_remove_breakpoint;
- }
- if (!record_beneath_to_resume)
- error (_("Process record can't get to_resume."));
- if (!record_beneath_to_wait)
- error (_("Process record can't get to_wait."));
- if (!record_beneath_to_store_registers)
- error (_("Process record can't get to_store_registers."));
- if (!record_beneath_to_xfer_partial)
- error (_("Process record can't get to_xfer_partial."));
- if (!record_beneath_to_insert_breakpoint)
- error (_("Process record can't get to_insert_breakpoint."));
- if (!record_beneath_to_remove_breakpoint)
- error (_("Process record can't get to_remove_breakpoint."));
-
- push_target (&record_ops);
-
- /* Reset */
- record_insn_num = 0;
- record_list = &record_first;
- record_list->next = NULL;
+ require_record_target ();
+ target_goto_record_begin ();
}
+/* The "record goto end" command. */
+
static void
-record_close (int quitting)
+cmd_record_goto_end (char *arg, int from_tty)
{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+ if (arg != NULL && *arg != '\0')
+ error (_("Junk after argument: %s."), arg);
- record_list_release (record_list);
+ require_record_target ();
+ target_goto_record_end ();
}
-static int record_resume_step = 0;
-static int record_resume_error;
+/* Read an instruction number from an argument string. */
-static void
-record_resume (struct target_ops *ops, ptid_t ptid, int step,
- enum target_signal siggnal)
+static ULONGEST
+get_insn_number (char **arg)
{
- record_resume_step = step;
+ ULONGEST number;
+ const char *begin, *end, *pos;
- if (!RECORD_IS_REPLAY)
- {
- if (do_record_message (get_current_regcache ()))
- {
- record_resume_error = 0;
- }
- else
- {
- record_resume_error = 1;
- return;
- }
- record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
- siggnal);
- }
+ begin = *arg;
+ pos = skip_spaces_const (begin);
+
+ if (!isdigit (*pos))
+ error (_("Expected positive number, got: %s."), pos);
+
+ number = strtoulst (pos, &end, 10);
+
+ *arg += (end - begin);
+
+ return number;
}
-static int record_get_sig = 0;
+/* 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);
}
-/* In replay mode, this function examines the recorded log 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;
+ if (args == NULL)
+ return modifiers;
- while (1)
- {
- ret = record_beneath_to_wait (record_beneath_to_wait_ops,
- ptid, status, options);
-
- if (status->kind == TARGET_WAITKIND_STOPPED
- && status->value.sig == TARGET_SIGNAL_TRAP)
- {
- /* Check if there is a breakpoint. */
- registers_changed ();
- tmp_pc = regcache_read_pc (get_current_regcache ());
- if (breakpoint_inserted_here_p (tmp_pc))
- {
- /* There is a breakpoint. */
- CORE_ADDR decr_pc_after_break =
- gdbarch_decr_pc_after_break
- (get_regcache_arch (get_current_regcache ()));
- if (decr_pc_after_break)
- {
- regcache_write_pc (get_thread_regcache (ret),
- tmp_pc + decr_pc_after_break);
- }
- }
- else
- {
- /* There is not a breakpoint. */
- if (!do_record_message (get_current_regcache ()))
- {
- break;
- }
- record_beneath_to_resume (record_beneath_to_resume_ops,
- ptid, 1,
- TARGET_SIGNAL_0);
- continue;
- }
- }
-
- /* The inferior is broken by a breakpoint or a signal. */
- break;
- }
-
- 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 (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;
- }
-
- /* Set ptid, register and memory according to record_list. */
- if (record_list->type == record_reg)
- {
- /* reg */
- gdb_byte reg[MAX_REGISTER_SIZE];
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_reg %s to "
- "inferior num = %d.\n",
- host_address_to_string (record_list),
- record_list->u.reg.num);
- regcache_cooked_read (regcache, record_list->u.reg.num, reg);
- regcache_cooked_write (regcache, record_list->u.reg.num,
- record_list->u.reg.val);
- memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
- }
- else if (record_list->type == record_mem)
- {
- /* mem */
- /* Nothing to do if the entry is flagged not_accessible. */
- if (!record_list->u.mem.mem_entry_not_accessible)
- {
- gdb_byte *mem = alloca (record_list->u.mem.len);
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_mem %s to "
- "inferior addr = %s len = %d.\n",
- host_address_to_string (record_list),
- paddress (gdbarch,
- record_list->u.mem.addr),
- record_list->u.mem.len);
-
- if (target_read_memory (record_list->u.mem.addr, mem,
- record_list->u.mem.len))
- {
- if (execution_direction != EXEC_REVERSE)
- error (_("Process record: error reading memory at "
- "addr = %s len = %d."),
- paddress (gdbarch, record_list->u.mem.addr),
- record_list->u.mem.len);
- else
- /* Read failed --
- flag entry as not_accessible. */
- record_list->u.mem.mem_entry_not_accessible = 1;
- }
- else
- {
- if (target_write_memory (record_list->u.mem.addr,
- record_list->u.mem.val,
- record_list->u.mem.len))
- {
- if (execution_direction != EXEC_REVERSE)
- error (_("Process record: error writing memory at "
- "addr = %s len = %d."),
- paddress (gdbarch, record_list->u.mem.addr),
- record_list->u.mem.len);
- else
- /* Write failed --
- flag entry as not_accessible. */
- record_list->u.mem.mem_entry_not_accessible = 1;
- }
- else
- {
- memcpy (record_list->u.mem.val, mem,
- record_list->u.mem.len);
- }
- }
- }
- }
- else
- {
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_end %s to "
- "inferior.\n",
- host_address_to_string (record_list));
-
- if (first_record_end && execution_direction == EXEC_REVERSE)
- {
- /* When reverse excute, the first record_end is the part of
- current instruction. */
- first_record_end = 0;
- }
- else
- {
- /* In EXEC_REVERSE mode, this is the record_end of prev
- instruction.
- In EXEC_FORWARD mode, this is the record_end of current
- instruction. */
- /* step */
- if (record_resume_step)
- {
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: step.\n");
- continue_flag = 0;
- }
-
- /* check breakpoint */
- tmp_pc = regcache_read_pc (regcache);
- if (breakpoint_inserted_here_p (tmp_pc))
- {
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: break "
- "at %s.\n",
- paddress (gdbarch, tmp_pc));
- if (gdbarch_decr_pc_after_break (gdbarch)
- && execution_direction == EXEC_FORWARD
- && !record_resume_step)
- regcache_write_pc (regcache,
- tmp_pc +
- gdbarch_decr_pc_after_break (gdbarch));
- continue_flag = 0;
- }
- }
- }
-
- if (continue_flag)
- {
- if (execution_direction == EXEC_REVERSE)
- {
- if (record_list->prev)
- record_list = record_list->prev;
- }
- else
- {
- if (record_list->next)
- record_list = record_list->next;
- }
+ 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
- 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;
-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);
+ return modifiers;
}
-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);
-}
+/* 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_mourn_inferior (struct target_ops *ops)
+static int
+command_size_to_target_size (unsigned int size)
{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: "
- "record_mourn_inferior\n");
+ gdb_assert (size <= INT_MAX || size == UINT_MAX);
- unpush_target (&record_ops);
- target_mourn_inferior ();
+ if (size == UINT_MAX)
+ return INT_MAX;
+ else
+ return size;
}
-/* Close process record target before killing the inferior process. */
+/* The "record instruction-history" command. */
static void
-record_kill (struct target_ops *ops)
+cmd_record_insn_history (char *arg, int from_tty)
{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+ int flags, size;
- unpush_target (&record_ops);
- target_kill ();
-}
+ require_record_target ();
-/* Record registers change (by user or by GDB) to list as an instruction. */
+ flags = get_insn_history_modifiers (&arg);
-static void
-record_registers_change (struct regcache *regcache, int regnum)
-{
- /* Check record_insn_num. */
- record_check_insn_num (0);
+ size = command_size_to_target_size (record_insn_history_size);
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
-
- if (regnum < 0)
+ if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0)
+ target_insn_history (size, flags);
+ else if (strcmp (arg, "-") == 0)
+ target_insn_history (-size, flags);
+ else
{
- int i;
- for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+ ULONGEST begin, end;
+
+ begin = get_insn_number (&arg);
+
+ if (*arg == ',')
{
- if (record_arch_list_add_reg (regcache, i))
+ arg = skip_spaces (++arg);
+
+ if (*arg == '+')
{
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
+ arg += 1;
+ size = get_context_size (&arg);
+
+ no_chunk (arg);
+
+ target_insn_history_from (begin, size, flags);
}
- }
- }
- 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;
+ else if (*arg == '-')
+ {
+ arg += 1;
+ size = get_context_size (&arg);
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
-}
+ no_chunk (arg);
-static void
-record_store_registers (struct target_ops *ops, struct regcache *regcache,
- int regno)
-{
- if (!record_gdb_operation_disable)
- {
- if (RECORD_IS_REPLAY)
- {
- int n;
-
- /* Let user choose if he wants to write register or not. */
- if (regno < 0)
- n =
- nquery (_("Because GDB is in replay mode, changing the "
- "value of a register will make the execution "
- "log unusable from this point onward. "
- "Change all registers?"));
+ target_insn_history_from (begin, -size, flags);
+ }
else
- n =
- nquery (_("Because GDB is in replay mode, changing the value "
- "of a register will make the execution log unusable "
- "from this point onward. Change register %s?"),
- gdbarch_register_name (get_regcache_arch (regcache),
- regno));
-
- if (!n)
{
- /* Invalidate the value of regcache that was set in function
- "regcache_raw_write". */
- if (regno < 0)
- {
- int i;
- for (i = 0;
- i < gdbarch_num_regs (get_regcache_arch (regcache));
- i++)
- regcache_invalidate (regcache, i);
- }
- else
- regcache_invalidate (regcache, regno);
-
- error (_("Process record canceled the operation."));
+ end = get_insn_number (&arg);
+
+ no_chunk (arg);
+
+ target_insn_history_range (begin, end, flags);
}
+ }
+ else
+ {
+ no_chunk (arg);
- /* Destroy the record from here forward. */
- record_list_release_next ();
+ target_insn_history_from (begin, size, flags);
}
- record_registers_change (regcache, regno);
+ dont_repeat ();
}
- 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. */
+/* Read function-call-history modifiers from an argument string. */
-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)
+static int
+get_call_history_modifiers (char **arg)
{
- if (!record_gdb_operation_disable
- && (object == TARGET_OBJECT_MEMORY
- || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+ int modifiers;
+ char *args;
+
+ modifiers = 0;
+ args = *arg;
+
+ if (args == NULL)
+ return modifiers;
+
+ while (*args == '/')
{
- if (RECORD_IS_REPLAY)
- {
- /* Let user choose if he wants to write memory or not. */
- if (!nquery (_("Because GDB is in replay mode, writing to memory "
- "will make the execution log unusable from this "
- "point onward. Write memory at address %s?"),
- paddress (target_gdbarch, offset)))
- error (_("Process record canceled the operation."));
-
- /* Destroy the record from here forward. */
- record_list_release_next ();
- }
+ ++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))
- {
- 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 ())
+ 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 (isspace (*args))
+ break;
+
+ if (*args == '/')
+ continue;
+
+ switch (*args)
+ {
+ case 'l':
+ modifiers |= record_print_src_line;
+ break;
+ case 'i':
+ modifiers |= record_print_insn_range;
+ 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);
+ require_record_target ();
- return ret;
- }
+ flags = get_call_history_modifiers (&arg);
- return 0;
-}
+ 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;
- }
+ if (*arg == ',')
+ {
+ arg = skip_spaces (++arg);
- return 0;
-}
+ if (*arg == '+')
+ {
+ arg += 1;
+ size = get_context_size (&arg);
-static int
-record_can_execute_reverse (void)
-{
- return 1;
-}
+ no_chunk (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;
-}
-
-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_next ();
+ 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" 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)
- {
- if (!record_list || !from_tty || query (_("Delete recorded log and "
- "stop recording?")))
- unpush_target (&record_ops);
+ 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)
{
- printf_unfiltered (_("Record instructions number is bigger than "
- "record instructions max number. Auto delete "
- "the first ones?\n"));
+ unsigned int new_value = *command_var;
- while (record_insn_num > record_insn_max_num)
- record_list_release_first ();
+ /* Restore previous value. */
+ *command_var = *setting;
+ error (_("integer %u out of range"), new_value);
}
-}
-
-/* Print the current index into the record log (number of insns recorded
- so far). */
-static void
-show_record_insn_number (char *ignore, int from_tty)
-{
- printf_unfiltered (_("Record instruction number is %d.\n"),
- record_insn_num);
+ /* Commit new value. */
+ *setting = *command_var;
}
-static struct cmd_list_element *record_cmdlist, *set_record_cmdlist,
- *show_record_cmdlist, *info_record_cmdlist;
+/* 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
-set_record_command (char *args, int from_tty)
+set_record_insn_history_size (char *args, int from_tty,
+ struct cmd_list_element *c)
{
- printf_unfiltered (_("\
-\"set record\" must be followed by an apporpriate subcommand.\n"));
- help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
+ validate_history_size (&record_insn_history_size_setshow_var,
+ &record_insn_history_size);
}
-static void
-show_record_command (char *args, int from_tty)
-{
- cmd_show_list (show_record_cmdlist, from_tty, "");
-}
+/* 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)
{
- cmd_show_list (info_record_cmdlist, from_tty, "");
+ 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_zinteger_cmd ("insn-number-max", no_class,
- &record_insn_max_num,
- _("Set record/replay buffer limit."),
- _("Show record/replay buffer limit."), _("\
-Set the maximum number of instructions to be stored in the\n\
-record/replay buffer. Zero means unlimited. Default is 200000."),
- set_record_insn_max_num,
- NULL, &set_record_cmdlist, &show_record_cmdlist);
- add_cmd ("insn-number", class_obscure, show_record_insn_number,
- _("Show the current number of instructions in the "
- "record/replay buffer."), &info_record_cmdlist);
+ 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 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;
}