X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=gdb%2Frecord.c;h=6190794492fd73f2b2ba67ab22a08e006b8cf154;hb=618f726fcb851883a0094aa7fa17003889b7189f;hp=8afca6bcf7521c5479bb4e1bbcaa447ed725999c;hpb=90092760f359c6438a856a0d001e254c80e7b5c1;p=thirdparty%2Fbinutils-gdb.git diff --git a/gdb/record.c b/gdb/record.c index 8afca6bcf75..6190794492f 100644 --- a/gdb/record.c +++ b/gdb/record.c @@ -1,6 +1,6 @@ /* Process record and replay target for GDB, the GNU debugger. - Copyright (C) 2008, 2009 Free Software Foundation, Inc. + Copyright (C) 2008-2016 Free Software Foundation, Inc. This file is part of GDB. @@ -19,1249 +19,757 @@ #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 +#include -#define DEFAULT_RECORD_INSN_MAX_NUM 200000 +/* This is the debug switch for process record. */ +unsigned int record_debug = 0; + +/* The number of instructions to print in "record instruction-history". */ +static unsigned int record_insn_history_size = 10; + +/* 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 RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) +/* The number of functions to print in "record function-call-history". */ +static unsigned int record_call_history_size = 10; -/* These are the core struct of record function. +/* 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; - 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. +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; - Each struct record_entry is linked to "record_list" by "prev" and "next". */ +#define DEBUG(msg, args...) \ + if (record_debug) \ + fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args) -struct record_reg_entry +/* See record.h. */ + +struct target_ops * +find_record_target (void) { - int num; - gdb_byte *val; -}; + return find_target_at (record_stratum); +} + +/* Check that recording is active. Throw an error, if it isn't. */ -struct record_mem_entry +static struct target_ops * +require_record_target (void) { - 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 + struct target_ops *t; + + t = find_record_target (); + if (t == NULL) + error (_("No record target is currently active.\n" + "Use one of the \"target record-\" commands first.")); + + return t; +} + +/* See record.h. */ + +void +record_preopen (void) { - record_end = 0, - record_reg, - record_mem -}; + /* 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.")); +} -struct record_entry +/* See record.h. */ + +int +record_read_memory (struct gdbarch *gdbarch, + CORE_ADDR memaddr, gdb_byte *myaddr, + ssize_t len) { - 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; -}; + int ret = target_read_memory (memaddr, myaddr, len); -/* 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 *); + 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 (struct record_entry *rec) +record_stop (struct target_ops *t) { - struct record_entry *tmp; + DEBUG ("stop %s", t->to_shortname); - if (!rec) - return; + t->to_stop_recording (t); +} - while (rec->next) - { - rec = rec->next; - } +/* Unpush the record target. */ - 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); - } +static void +record_unpush (struct target_ops *t) +{ + DEBUG ("unpush %s", t->to_shortname); - if (rec != &record_first) - xfree (rec); + unpush_target (t); } -static void -record_list_release_next (void) +/* See record.h. */ + +void +record_disconnect (struct target_ops *t, const char *args, int from_tty) { - struct record_entry *rec = record_list; - struct record_entry *tmp = rec->next; - rec->next = NULL; - while (tmp) - { - rec = tmp->next; - if (tmp->type == record_reg) - record_insn_num--; - else if (tmp->type == record_reg) - xfree (tmp->u.reg.val); - else if (tmp->type == record_mem) - xfree (tmp->u.mem.val); - xfree (tmp); - tmp = rec; - } + gdb_assert (t->to_stratum == record_stratum); + + DEBUG ("disconnect %s", t->to_shortname); + + record_stop (t); + record_unpush (t); + + target_disconnect (args, from_tty); } -static void -record_list_release_first (void) +/* See record.h. */ + +void +record_detach (struct target_ops *t, const char *args, int from_tty) { - struct record_entry *tmp = NULL; - enum record_type type; + gdb_assert (t->to_stratum == record_stratum); - if (!record_first.next) - return; + DEBUG ("detach %s", t->to_shortname); - while (1) - { - type = record_first.next->type; + record_stop (t); + record_unpush (t); - 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); + target_detach (args, from_tty); +} - if (!record_first.next) - { - gdb_assert (record_insn_num == 1); - break; - } +/* See record.h. */ + +void +record_mourn_inferior (struct target_ops *t) +{ + gdb_assert (t->to_stratum == record_stratum); - record_first.next->prev = &record_first; + DEBUG ("mourn inferior %s", t->to_shortname); - if (type == record_end) - break; - } + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); - record_insn_num--; + target_mourn_inferior (); } -/* Add a struct record_entry to record_arch_list. */ +/* See record.h. */ -static void -record_arch_list_add (struct record_entry *rec) +void +record_kill (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 ("kill %s", t->to_shortname); + + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); + + target_kill (); } -/* 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) +record_check_stopped_by_breakpoint (struct address_space *aspace, CORE_ADDR pc, + enum target_stop_reason *reason) { - struct record_entry *rec; + if (breakpoint_inserted_here_p (aspace, pc)) + { + if (hardware_breakpoint_inserted_here_p (aspace, pc)) + *reason = TARGET_STOPPED_BY_HW_BREAKPOINT; + else + *reason = TARGET_STOPPED_BY_SW_BREAKPOINT; + return 1; + } - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add register num = %d to " - "record list.\n", - num); + *reason = TARGET_STOPPED_BY_NO_REASON; + return 0; +} - 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; +/* Implement "show record debug" command. */ - regcache_raw_read (regcache, num, rec->u.reg.val); +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_arch_list_add (rec); +/* Alias for "target record". */ - return 0; +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record-full", from_tty); } -/* Record the value of a region of memory whose address is ADDR and - length is LEN to record_arch_list. */ +/* Truncate the record log from the present point + of replay until the end. */ -int -record_arch_list_add_mem (CORE_ADDR addr, int len) +static void +cmd_record_delete (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)) + require_record_target (); + + if (!target_record_is_replaying (inferior_ptid)) { - 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); + t->to_info_record (t); +} - 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", ptid_get_pid (inferior_ptid)); + recfilename = recfilename_buffer; + } - return 1; + target_save_record (recfilename); } -static int -do_record_message (struct regcache *regcache) +/* See record.h. */ + +void +record_goto (const char *arg) { - return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL); -} + ULONGEST insn; -/* Set to 1 if record_store_registers and record_xfer_partial - doesn't need record. */ + if (arg == NULL || *arg == '\0') + error (_("Command requires an argument (insn number to go to).")); -static int record_gdb_operation_disable = 0; + insn = parse_and_eval_long (arg); -struct cleanup * -record_gdb_operation_disable_set (void) -{ - struct cleanup *old_cleanups = NULL; + require_record_target (); + target_goto_record (insn); +} - old_cleanups = - make_cleanup_restore_integer (&record_gdb_operation_disable); - record_gdb_operation_disable = 1; +/* "record goto" command. Argument is an instruction number, + as given by "info record". - return old_cleanups; -} + Rewinds the recording (forward or backward) to the given instruction. */ static void -record_open (char *name, int from_tty) +cmd_record_goto (char *arg, int from_tty) { - struct target_ops *t; + record_goto (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) - { - if (!nquery - (_("Process record target already running, do you want to delete " - "the old record log?"))) - return; - } +/* The "record goto begin" command. */ - /*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; +static void +cmd_record_goto_begin (char *arg, int from_tty) +{ + 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 enum target_signal record_resume_siggnal; -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; - record_resume_siggnal = siggnal; + 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; - /* It will break the running inferior in replay mode. */ - record_resume_step = 1; + pos = skip_spaces (*arg); - /* It will let record_wait set inferior status to get the signal - SIGINT. */ - record_get_sig = 1; + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); + + 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 (); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "record_resume_step = %d\n", - record_resume_step); + int modifiers; + char *args; - 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; + modifiers = 0; + args = *arg; - 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, - record_resume_siggnal); - 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 (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': + case 's': + 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; -} - -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"); + /* Update the argument string. */ + *arg = args; - 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 (); -} - -/* Record registers change (by user or by GDB) to list as an instruction. */ + require_record_target (); -static void -record_registers_change (struct regcache *regcache, int regnum) -{ - /* Check record_insn_num. */ - record_check_insn_num (0); + flags = get_insn_history_modifiers (&arg); - record_arch_list_head = NULL; - record_arch_list_tail = NULL; + size = command_size_to_target_size (record_insn_history_size); - 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; + 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); + 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); - - do_cleanups (old_cleanups); + ULONGEST begin, end; - return ret; - } + begin = get_insn_number (&arg); - return 0; -} + if (*arg == ',') + { + arg = skip_spaces (++arg); -static int -record_can_execute_reverse (void) -{ - return 1; -} + if (*arg == '+') + { + arg += 1; + size = get_context_size (&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; -} + 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_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, @@ -1276,6 +784,12 @@ _initialize_record (void) "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.'."), + &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."), @@ -1288,26 +802,61 @@ _initialize_record (void) &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 or /s 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; }