X-Git-Url: http://git.efficios.com/?a=blobdiff_plain;f=gdb%2Frecord.c;h=ea8e7df3a78dded73e5e09988d697fed3d669679;hb=67c86d068313124e6c73e6ec0294866fc0471268;hp=c9d325154e51a23fad74262df719f1ff648fbfd0;hpb=6c95b8df7fef5273da71c34775918c554aae0ea8;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/record.c b/gdb/record.c index c9d325154e..ea8e7df3a7 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-2013 Free Software Foundation, Inc. This file is part of GDB. @@ -19,1427 +19,506 @@ #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 - -#define DEFAULT_RECORD_INSN_MAX_NUM 200000 - -#define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) - -/* These are the core structs of the process record functionality. - - A record_entry is a record of the value change of a register - ("record_reg") or a part of memory ("record_mem"). And each - instruction must have a struct record_entry ("record_end") that - indicates that this is the last struct record_entry of this - instruction. - - Each struct record_entry is linked to "record_list" by "prev" and - "next" pointers. */ - -struct record_mem_entry -{ - CORE_ADDR addr; - int len; - /* Set this flag if target memory for this entry - can no longer be accessed. */ - int mem_entry_not_accessible; - union - { - gdb_byte *ptr; - gdb_byte buf[sizeof (gdb_byte *)]; - } u; -}; - -struct record_reg_entry -{ - unsigned short num; - unsigned short len; - union - { - gdb_byte *ptr; - gdb_byte buf[2 * sizeof (gdb_byte *)]; - } u; -}; - -struct record_end_entry -{ - enum target_signal sigval; -}; - -enum record_type -{ - record_end = 0, - record_reg, - record_mem -}; - -struct record_entry -{ - struct record_entry *prev; - struct record_entry *next; - enum record_type type; - union - { - /* reg */ - struct record_reg_entry reg; - /* mem */ - struct record_mem_entry mem; - /* end */ - struct record_end_entry end; - } u; -}; +#include /* 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 unsigned 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 *); - -/* Alloc and free functions for record_reg, record_mem, and record_end - entries. */ - -/* Alloc a record_reg record entry. */ - -static inline struct record_entry * -record_reg_alloc (struct regcache *regcache, int regnum) -{ - struct record_entry *rec; - struct gdbarch *gdbarch = get_regcache_arch (regcache); +unsigned int record_debug = 0; - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_reg; - rec->u.reg.num = regnum; - rec->u.reg.len = register_size (gdbarch, regnum); - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len); - - return rec; -} +/* The number of instructions to print in "record instruction-history". */ +static unsigned int record_insn_history_size = 10; -/* Free a record_reg record entry. */ +struct cmd_list_element *record_cmdlist = NULL; +struct cmd_list_element *set_record_cmdlist = NULL; +struct cmd_list_element *show_record_cmdlist = NULL; +struct cmd_list_element *info_record_cmdlist = NULL; -static inline void -record_reg_release (struct record_entry *rec) -{ - gdb_assert (rec->type == record_reg); - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - xfree (rec->u.reg.u.ptr); - xfree (rec); -} +#define DEBUG(msg, args...) \ + if (record_debug) \ + fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args) -/* Alloc a record_mem record entry. */ +/* Find the record target in the target stack. */ -static inline struct record_entry * -record_mem_alloc (CORE_ADDR addr, int len) +static struct target_ops * +find_record_target (void) { - struct record_entry *rec; - - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_mem; - rec->u.mem.addr = addr; - rec->u.mem.len = len; - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len); - - return rec; -} + struct target_ops *t; -/* Free a record_mem record entry. */ + for (t = current_target.beneath; t != NULL; t = t->beneath) + if (t->to_stratum == record_stratum) + return t; -static inline void -record_mem_release (struct record_entry *rec) -{ - gdb_assert (rec->type == record_mem); - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - xfree (rec->u.mem.u.ptr); - xfree (rec); + return NULL; } -/* Alloc a record_end record entry. */ +/* Check that recording is active. Throw an error, if it isn't. */ -static inline struct record_entry * -record_end_alloc (void) +static struct target_ops * +require_record_target (void) { - struct record_entry *rec; + struct target_ops *t; - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_end; + t = find_record_target (); + if (t == NULL) + error (_("No record target is currently active.\n" + "Use one of the \"target record-\" commands first.")); - return rec; + return t; } -/* Free a record_end record entry. */ +/* See record.h. */ -static inline void -record_end_release (struct record_entry *rec) +int +record_read_memory (struct gdbarch *gdbarch, + CORE_ADDR memaddr, gdb_byte *myaddr, + ssize_t len) { - xfree (rec); -} + int ret = target_read_memory (memaddr, myaddr, len); -/* Free one record entry, any type. - Return entry->type, in case caller wants to know. */ + if (ret != 0) + DEBUG ("error reading memory at addr %s len = %ld.\n", + paddress (gdbarch, memaddr), (long) len); -static inline enum record_type -record_entry_release (struct record_entry *rec) -{ - enum record_type type = rec->type; - - switch (type) { - case record_reg: - record_reg_release (rec); - break; - case record_mem: - record_mem_release (rec); - break; - case record_end: - record_end_release (rec); - break; - } - return type; + return ret; } -/* Free all record entries in list pointed to by REC. */ +/* Stop recording. */ static void -record_list_release (struct record_entry *rec) +record_stop (struct target_ops *t) { - if (!rec) - return; - - while (rec->next) - rec = rec->next; - - while (rec->prev) - { - rec = rec->prev; - record_entry_release (rec->next); - } + DEBUG ("stop %s", t->to_shortname); - if (rec == &record_first) - { - record_insn_num = 0; - record_first.next = NULL; - } - else - record_entry_release (rec); + if (t->to_stop_recording != NULL) + t->to_stop_recording (); } -/* Free all record entries forward of the given list position. */ +/* Unpush the record target. */ static void -record_list_release_following (struct record_entry *rec) +record_unpush (struct target_ops *t) { - struct record_entry *tmp = rec->next; + DEBUG ("unpush %s", t->to_shortname); - rec->next = NULL; - while (tmp) - { - rec = tmp->next; - if (record_entry_release (tmp) == record_end) - record_insn_num--; - tmp = rec; - } + unpush_target (t); } -/* Delete the first instruction from the beginning of the log, to make - room for adding a new instruction at the end of the log. +/* See record.h. */ - Note -- this function does not modify record_insn_num. */ - -static void -record_list_release_first (void) +void +record_disconnect (struct target_ops *t, char *args, int from_tty) { - struct record_entry *tmp; + gdb_assert (t->to_stratum == record_stratum); - if (!record_first.next) - return; + DEBUG ("disconnect %s", t->to_shortname); - /* Loop until a record_end. */ - while (1) - { - /* Cut record_first.next out of the linked list. */ - tmp = record_first.next; - record_first.next = tmp->next; - tmp->next->prev = &record_first; + record_stop (t); + record_unpush (t); - /* tmp is now isolated, and can be deleted. */ - if (record_entry_release (tmp) == record_end) - { - record_insn_num--; - break; /* End loop at first record_end. */ - } - - if (!record_first.next) - { - gdb_assert (record_insn_num == 1); - break; /* End loop when list is empty. */ - } - } -} - -/* Add a struct record_entry to record_arch_list. */ - -static void -record_arch_list_add (struct record_entry *rec) -{ - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_arch_list_add %s.\n", - host_address_to_string (rec)); - - if (record_arch_list_tail) - { - record_arch_list_tail->next = rec; - rec->prev = record_arch_list_tail; - record_arch_list_tail = rec; - } - else - { - record_arch_list_head = rec; - record_arch_list_tail = rec; - } -} - -/* Return the value storage location of a record entry. */ -static inline gdb_byte * -record_get_loc (struct record_entry *rec) -{ - switch (rec->type) { - case record_mem: - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - return rec->u.mem.u.ptr; - else - return rec->u.mem.u.buf; - case record_reg: - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - return rec->u.reg.u.ptr; - else - return rec->u.reg.u.buf; - case record_end: - default: - gdb_assert (0); - return NULL; - } + target_disconnect (args, from_tty); } -/* Record the value of a register NUM to record_arch_list. */ +/* See record.h. */ -int -record_arch_list_add_reg (struct regcache *regcache, int regnum) +void +record_detach (struct target_ops *t, char *args, int from_tty) { - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add register num = %d to " - "record list.\n", - regnum); - - rec = record_reg_alloc (regcache, regnum); + gdb_assert (t->to_stratum == record_stratum); - regcache_raw_read (regcache, regnum, record_get_loc (rec)); + DEBUG ("detach %s", t->to_shortname); - record_arch_list_add (rec); + record_stop (t); + record_unpush (t); - return 0; + target_detach (args, from_tty); } -/* Record the value of a region of memory whose address is ADDR and - length is LEN to record_arch_list. */ +/* See record.h. */ -int -record_arch_list_add_mem (CORE_ADDR addr, int len) +void +record_mourn_inferior (struct target_ops *t) { - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add mem addr = %s len = %d to " - "record list.\n", - paddress (target_gdbarch, addr), len); - - if (!addr) /* FIXME: Why? Some arch must permit it... */ - return 0; + gdb_assert (t->to_stratum == record_stratum); - rec = record_mem_alloc (addr, len); - - if (target_read_memory (addr, record_get_loc (rec), len)) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: error reading memory at " - "addr = %s len = %d.\n", - paddress (target_gdbarch, addr), len); - record_mem_release (rec); - return -1; - } + DEBUG ("mourn inferior %s", t->to_shortname); - record_arch_list_add (rec); + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); - return 0; + target_mourn_inferior (); } -/* Add a record_end type struct record_entry to record_arch_list. */ +/* See record.h. */ -int -record_arch_list_add_end (void) +void +record_kill (struct target_ops *t) { - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add end to arch list.\n"); + gdb_assert (t->to_stratum == record_stratum); - rec = record_end_alloc (); - rec->u.end.sigval = TARGET_SIGNAL_0; + DEBUG ("kill %s", t->to_shortname); - record_arch_list_add (rec); + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); - return 0; + target_kill (); } +/* Implement "show record debug" command. */ + static void -record_check_insn_num (int set_terminal) +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) { - 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.")); - } - } - } + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); } -/* 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. */ +/* Alias for "target record". */ static void -record_message_cleanups (void *ignore) +cmd_record_start (char *args, int from_tty) { - record_list_release (record_arch_list_tail); + execute_command ("target record-full", from_tty); } -struct record_message_args { - struct regcache *regcache; - enum target_signal signal; -}; +/* Truncate the record log from the present point + of replay until the end. */ -static int -record_message (void *args) +static void +cmd_record_delete (char *args, int from_tty) { - int ret; - struct record_message_args *myargs = args; - struct gdbarch *gdbarch = get_regcache_arch (myargs->regcache); - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); - - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - - /* Check record_insn_num. */ - record_check_insn_num (1); - - /* If gdb sends a signal value to target_resume, - save it in the 'end' field of the previous instruction. - - Maybe process record should record what really happened, - rather than what gdb pretends has happened. - - So if Linux delivered the signal to the child process during - the record mode, we will record it and deliver it again in - the replay mode. - - If user says "ignore this signal" during the record mode, then - it will be ignored again during the replay mode (no matter if - the user says something different, like "deliver this signal" - during the replay mode). - - User should understand that nothing he does during the replay - mode will change the behavior of the child. If he tries, - then that is a user error. + require_record_target (); - But we should still deliver the signal to gdb during the replay, - if we delivered it during the recording. Therefore we should - record the signal during record_wait, not record_resume. */ - if (record_list != &record_first) /* FIXME better way to check */ + if (!target_record_is_replaying ()) { - gdb_assert (record_list->type == record_end); - record_list->u.end.sigval = myargs->signal; + printf_unfiltered (_("Already at end of record list.\n")); + return; } - if (myargs->signal == TARGET_SIGNAL_0 - || !gdbarch_process_record_signal_p (gdbarch)) - ret = gdbarch_process_record (gdbarch, - myargs->regcache, - regcache_read_pc (myargs->regcache)); - else - ret = gdbarch_process_record_signal (gdbarch, - myargs->regcache, - myargs->signal); - - if (ret > 0) - error (_("Process record: inferior program stopped.")); - if (ret < 0) - error (_("Process record: failed to record execution log.")); - - discard_cleanups (old_cleanups); - - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; - - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; + if (!target_supports_delete_record ()) + { + printf_unfiltered (_("The current record target does not support " + "this operation.\n")); + return; + } - return 1; + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + target_delete_record (); } -static int -do_record_message (struct regcache *regcache, - enum target_signal signal) -{ - struct record_message_args args; +/* Implement the "stoprecord" or "record stop" command. */ - args.regcache = regcache; - args.signal = signal; - return catch_errors (record_message, &args, NULL, RETURN_MASK_ALL); -} - -/* Set to 1 if record_store_registers and record_xfer_partial - doesn't need record. */ +static void +cmd_record_stop (char *args, int from_tty) +{ + struct target_ops *t; -static int record_gdb_operation_disable = 0; + t = require_record_target (); -struct cleanup * -record_gdb_operation_disable_set (void) -{ - struct cleanup *old_cleanups = NULL; + record_stop (t); + record_unpush (t); - old_cleanups = - make_cleanup_restore_integer (&record_gdb_operation_disable); - record_gdb_operation_disable = 1; + printf_unfiltered (_("Process record is stopped and all execution " + "logs are deleted.\n")); - return old_cleanups; + observer_notify_record_changed (current_inferior (), 0); } +/* The "set record" command. */ + static void -record_open (char *name, int from_tty) +set_record_command (char *args, 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) - error (_("Process record target already running. Use \"record stop\" to " - "stop record target first.")); - - /*Reset the beneath function pointers. */ - record_beneath_to_resume = NULL; - record_beneath_to_wait = NULL; - record_beneath_to_store_registers = NULL; - record_beneath_to_xfer_partial = NULL; - record_beneath_to_insert_breakpoint = NULL; - record_beneath_to_remove_breakpoint = NULL; - - /* Set the beneath function pointers. */ - for (t = current_target.beneath; t != NULL; t = t->beneath) - { - if (!record_beneath_to_resume) - { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; - } - if (!record_beneath_to_wait) - { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; - } - if (!record_beneath_to_store_registers) - { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; - } - if (!record_beneath_to_xfer_partial) - { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; - } - if (!record_beneath_to_insert_breakpoint) - record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; - if (!record_beneath_to_remove_breakpoint) - record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; - } - if (!record_beneath_to_resume) - error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) - error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) - error (_("Process record can't get to_store_registers.")); - if (!record_beneath_to_xfer_partial) - error (_("Process record can't get to_xfer_partial.")); - if (!record_beneath_to_insert_breakpoint) - error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) - error (_("Process record can't get to_remove_breakpoint.")); - - push_target (&record_ops); - - /* Reset */ - record_insn_num = 0; - record_list = &record_first; - record_list->next = NULL; + printf_unfiltered (_("\"set record\" must be followed " + "by an apporpriate subcommand.\n")); + help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout); } +/* The "show record" command. */ + static void -record_close (int quitting) +show_record_command (char *args, int from_tty) { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); - - record_list_release (record_list); + cmd_show_list (show_record_cmdlist, from_tty, ""); } -static int record_resume_step = 0; -static int record_resume_error; +/* The "info record" command. */ static void -record_resume (struct target_ops *ops, ptid_t ptid, int step, - enum target_signal signal) +info_record_command (char *args, int from_tty) { - record_resume_step = step; + struct target_ops *t; - if (!RECORD_IS_REPLAY) + t = find_record_target (); + if (t == NULL) { - if (do_record_message (get_current_regcache (), signal)) - { - record_resume_error = 0; - } - else - { - record_resume_error = 1; - return; - } - record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, - signal); + printf_filtered (_("No record target is currently active.\n")); + return; } + + printf_filtered (_("Active record target: %s\n"), t->to_shortname); + if (t->to_info_record != NULL) + t->to_info_record (); } -static int record_get_sig = 0; +/* The "record save" command. */ static void -record_sig_handler (int signo) +cmd_record_save (char *args, int from_tty) { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); - - /* It will break the running inferior in replay mode. */ - record_resume_step = 1; + char *recfilename, recfilename_buffer[40]; - /* It will let record_wait set inferior status to get the signal - SIGINT. */ - record_get_sig = 1; -} + require_record_target (); -static void -record_wait_cleanups (void *ignore) -{ - if (execution_direction == EXEC_REVERSE) + if (args != NULL && *args != 0) + recfilename = args; + else { - if (record_list->next) - record_list = record_list->next; + /* Default recfile name is "gdb_record.PID". */ + xsnprintf (recfilename_buffer, sizeof (recfilename_buffer), + "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; } - else - record_list = record_list->prev; -} -/* In replay mode, this function examines the recorded log and - determines where to stop. */ - -static ptid_t -record_wait (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *status, - int options) -{ - struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + target_save_record (recfilename); +} - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "record_resume_step = %d\n", - record_resume_step); +/* "record goto" command. Argument is an instruction number, + as given by "info record". - 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; - } + Rewinds the recording (forward or backward) to the given instruction. */ - 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; +void +cmd_record_goto (char *arg, int from_tty) +{ + require_record_target (); - while (1) - { - ret = record_beneath_to_wait (record_beneath_to_wait_ops, - ptid, status, options); - - /* Is this a SIGTRAP? */ - if (status->kind == TARGET_WAITKIND_STOPPED - && status->value.sig == TARGET_SIGNAL_TRAP) - { - struct regcache *regcache; - - /* Yes -- check if there is a breakpoint. */ - registers_changed (); - regcache = get_current_regcache (); - tmp_pc = regcache_read_pc (regcache); - if (breakpoint_inserted_here_p (get_regcache_aspace (regcache), - tmp_pc)) - { - /* There is a breakpoint. GDB will want to stop. */ - struct gdbarch *gdbarch = get_regcache_arch (regcache); - CORE_ADDR decr_pc_after_break - = gdbarch_decr_pc_after_break (gdbarch); - if (decr_pc_after_break) - regcache_write_pc (regcache, - tmp_pc + decr_pc_after_break); - } - else - { - /* There is not a breakpoint, and gdb is not - stepping, therefore gdb will not stop. - Therefore we will not return to gdb. - Record the insn and resume. */ - if (!do_record_message (regcache, TARGET_SIGNAL_0)) - break; - - record_beneath_to_resume (record_beneath_to_resume_ops, - ptid, 1, - TARGET_SIGNAL_0); - continue; - } - } - - /* The inferior is broken by a breakpoint or a signal. */ - break; - } + if (arg == NULL || *arg == '\0') + error (_("Command requires an argument (insn number to go to).")); - return ret; - } - } + if (strncmp (arg, "start", strlen ("start")) == 0 + || strncmp (arg, "begin", strlen ("begin")) == 0) + target_goto_record_begin (); + else if (strncmp (arg, "end", strlen ("end")) == 0) + target_goto_record_end (); else { - 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; + ULONGEST insn; - status->kind = TARGET_WAITKIND_STOPPED; - - /* Check breakpoint when forward execute. */ - if (execution_direction == EXEC_FORWARD) - { - tmp_pc = regcache_read_pc (regcache); - if (breakpoint_inserted_here_p (get_regcache_aspace (regcache), - tmp_pc)) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: break at %s.\n", - paddress (gdbarch, tmp_pc)); - if (gdbarch_decr_pc_after_break (gdbarch) - && !record_resume_step) - regcache_write_pc (regcache, - tmp_pc + - gdbarch_decr_pc_after_break (gdbarch)); - goto replay_out; - } - } + insn = parse_and_eval_long (arg); + target_goto_record (insn); + } +} - 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) - { - /* Hit beginning of record log in reverse. */ - status->kind = TARGET_WAITKIND_NO_HISTORY; - break; - } - if (execution_direction != EXEC_REVERSE && !record_list->next) - { - /* Hit end of record log going forward. */ - status->kind = TARGET_WAITKIND_NO_HISTORY; - break; - } +/* Read an instruction number from an argument string. */ - /* 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_get_loc (record_list)); - memcpy (record_get_loc (record_list), reg, - record_list->u.reg.len); - } - 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_get_loc (record_list), - 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_get_loc (record_list), 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 (get_regcache_aspace (regcache), - tmp_pc)) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: break " - "at %s.\n", - paddress (gdbarch, tmp_pc)); - if (gdbarch_decr_pc_after_break (gdbarch) - && execution_direction == EXEC_FORWARD - && !record_resume_step) - regcache_write_pc (regcache, - tmp_pc + - gdbarch_decr_pc_after_break (gdbarch)); - continue_flag = 0; - } - /* Check target signal */ - if (record_list->u.end.sigval != TARGET_SIGNAL_0) - /* FIXME: better way to check */ - continue_flag = 0; - } - } +static ULONGEST +get_insn_number (char **arg) +{ + ULONGEST number; + const char *begin, *end, *pos; - if (continue_flag) - { - if (execution_direction == EXEC_REVERSE) - { - if (record_list->prev) - record_list = record_list->prev; - } - else - { - if (record_list->next) - record_list = record_list->next; - } - } - } - while (continue_flag); + begin = *arg; + pos = skip_spaces_const (begin); - signal (SIGINT, handle_sigint); + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); -replay_out: - if (record_get_sig) - status->value.sig = TARGET_SIGNAL_INT; - else if (record_list->u.end.sigval != TARGET_SIGNAL_0) - /* FIXME: better way to check */ - status->value.sig = record_list->u.end.sigval; - else - status->value.sig = TARGET_SIGNAL_TRAP; + number = strtoulst (pos, &end, 10); - discard_cleanups (old_cleanups); - } + *arg += (end - begin); - do_cleanups (set_cleanups); - return inferior_ptid; + return number; } -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); -} +/* Read a context size from an argument string. */ -static void -record_detach (struct target_ops *ops, char *args, int from_tty) +static int +get_context_size (char **arg) { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + char *pos; + int number; - unpush_target (&record_ops); - target_detach (args, from_tty); -} + pos = skip_spaces (*arg); -static void -record_mourn_inferior (struct target_ops *ops) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: " - "record_mourn_inferior\n"); + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); - unpush_target (&record_ops); - target_mourn_inferior (); + return strtol (pos, arg, 10); } -/* Close process record target before killing the inferior process. */ +/* Complain about junk at the end of an argument string. */ static void -record_kill (struct target_ops *ops) +no_chunk (char *arg) { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); - - unpush_target (&record_ops); - target_kill (); + if (*arg != 0) + error (_("Junk after argument: %s."), arg); } -/* Record registers change (by user or by GDB) to list as an instruction. */ +/* Read instruction-history modifiers from an argument string. */ -static void -record_registers_change (struct regcache *regcache, int regnum) +static int +get_insn_history_modifiers (char **arg) { - /* Check record_insn_num. */ - record_check_insn_num (0); + int modifiers; + char *args; - record_arch_list_head = NULL; - record_arch_list_tail = NULL; + modifiers = 0; + args = *arg; - if (regnum < 0) - { - int i; - for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) - { - if (record_arch_list_add_reg (regcache, i)) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - } - } - else - { - if (record_arch_list_add_reg (regcache, regnum)) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - } - if (record_arch_list_add_end ()) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; + if (args == NULL) + return modifiers; - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; -} - -static void -record_store_registers (struct target_ops *ops, struct regcache *regcache, - int regno) -{ - if (!record_gdb_operation_disable) + while (*args == '/') { - if (RECORD_IS_REPLAY) - { - int n; - - /* Let user choose if he wants to write register or not. */ - if (regno < 0) - n = - query (_("Because GDB is in replay mode, changing the " - "value of a register will make the execution " - "log unusable from this point onward. " - "Change all registers?")); - else - n = - query (_("Because GDB is in replay mode, changing the value " - "of a register will make the execution log unusable " - "from this point onward. Change register %s?"), - gdbarch_register_name (get_regcache_arch (regcache), - regno)); - - if (!n) - { - /* Invalidate the value of regcache that was set in function - "regcache_raw_write". */ - if (regno < 0) - { - int i; - for (i = 0; - i < gdbarch_num_regs (get_regcache_arch (regcache)); - i++) - regcache_invalidate (regcache, i); - } - else - regcache_invalidate (regcache, regno); - - error (_("Process record canceled the operation.")); - } + ++args; - /* Destroy the record from here forward. */ - record_list_release_following (record_list); - } + if (*args == '\0') + error (_("Missing modifier.")); - record_registers_change (regcache, regno); - } - record_beneath_to_store_registers (record_beneath_to_store_registers_ops, - regcache, regno); -} - -/* Behavior is conditional on RECORD_IS_REPLAY. - In replay mode, we cannot write memory unles we are willing to - invalidate the record/replay log from this point forward. */ - -static LONGEST -record_xfer_partial (struct target_ops *ops, enum target_object object, - const char *annex, gdb_byte *readbuf, - const gdb_byte *writebuf, ULONGEST offset, LONGEST len) -{ - if (!record_gdb_operation_disable - && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) - { - if (RECORD_IS_REPLAY) + for (; *args; ++args) { - /* Let user choose if he wants to write memory or not. */ - if (!query (_("Because GDB is in replay mode, writing to memory " - "will make the execution log unusable from this " - "point onward. Write memory at address %s?"), - paddress (target_gdbarch, offset))) - error (_("Process record canceled the operation.")); - - /* Destroy the record from here forward. */ - record_list_release_following (record_list); - } + if (isspace (*args)) + break; - /* Check record_insn_num */ - record_check_insn_num (0); + if (*args == '/') + continue; - /* Record registers change to list as an instruction. */ - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - if (record_arch_list_add_mem (offset, len)) - { - record_list_release (record_arch_list_tail); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - _("Process record: failed to record " - "execution log.")); - return -1; - } - if (record_arch_list_add_end ()) - { - record_list_release (record_arch_list_tail); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - _("Process record: failed to record " - "execution log.")); - return -1; + switch (*args) + { + case 'm': + modifiers |= DISASSEMBLY_SOURCE; + modifiers |= DISASSEMBLY_FILENAME; + break; + case 'r': + modifiers |= DISASSEMBLY_RAW_INSN; + break; + case 'f': + modifiers |= DISASSEMBLY_OMIT_FNAME; + 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 instruction-history" command. */ -static int -record_insert_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) +static void +cmd_record_insn_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_insn_history_modifiers (&arg); - return 0; -} + /* We use a signed size to also indicate the direction. Make sure that + unlimited remains unlimited. */ + size = (int) record_insn_history_size; + if (size < 0) + size = INT_MAX; -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_insn_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_insn_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_insn_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_insn_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); -/* Truncate the record log from the present point - of replay until the end. */ + no_chunk (arg); -static void -cmd_record_delete (char *args, int from_tty) -{ - if (current_target.to_stratum == record_stratum) - { - if (RECORD_IS_REPLAY) - { - if (!from_tty || query (_("Delete the log from this point forward " - "and begin to record the running message " - "at current PC?"))) - record_list_release_following (record_list); + target_insn_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_insn_history_from (begin, size, flags); + } -static void -cmd_record_stop (char *args, int from_tty) -{ - if (current_target.to_stratum == record_stratum) - { - unpush_target (&record_ops); - printf_unfiltered (_("Process record is stoped and all execution " - "log is deleted.\n")); + dont_repeat (); } - else - printf_unfiltered (_("Process record is not started.\n")); } -/* Set upper limit of record log size. */ +/* Provide a prototype to silence -Wmissing-prototypes. */ +extern initialize_file_ftype _initialize_record; -static void -set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +void +_initialize_record (void) { - if (record_insn_num > record_insn_max_num && record_insn_max_num) - { - /* Count down record_insn_num while releasing records from list. */ - while (record_insn_num > record_insn_max_num) - { - record_list_release_first (); - record_insn_num--; - } - } -} + struct cmd_list_element *c; -/* Print the current index into the record log (number of insns recorded - so far). */ + 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); -static void -show_record_insn_number (char *ignore, int from_tty) -{ - printf_unfiltered (_("Record instruction number is %d.\n"), - record_insn_num); -} + add_setshow_uinteger_cmd ("instruction-history-size", no_class, + &record_insn_history_size, _("\ +Set number of instructions to print in \"record instruction-history\"."), _("\ +Show number of instructions to print in \"record instruction-history\"."), + NULL, NULL, NULL, &set_record_cmdlist, + &show_record_cmdlist); -static struct cmd_list_element *record_cmdlist, *set_record_cmdlist, - *show_record_cmdlist, *info_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); -static void -set_record_command (char *args, int from_tty) -{ - printf_unfiltered (_("\ -\"set record\" must be followed by an apporpriate subcommand.\n")); - help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout); -} - -static void -show_record_command (char *args, int from_tty) -{ - cmd_show_list (show_record_cmdlist, from_tty, ""); -} - -static void -info_record_command (char *args, int from_tty) -{ - cmd_show_list (info_record_cmdlist, from_tty, ""); -} - -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); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1454,6 +533,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."), @@ -1466,26 +551,27 @@ _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_uinteger_cmd ("insn-number-max", no_class, - &record_insn_max_num, - _("Set record/replay buffer limit."), - _("Show record/replay buffer limit."), _("\ -Set the maximum number of instructions to be stored in the\n\ -record/replay buffer. Zero means unlimited. Default is 200000."), - set_record_insn_max_num, - NULL, &set_record_cmdlist, &show_record_cmdlist); - add_cmd ("insn-number", class_obscure, show_record_insn_number, - _("Show the current number of instructions in the " - "record/replay buffer."), &info_record_cmdlist); + add_cmd ("goto", class_obscure, cmd_record_goto, _("\ +Restore the program to its state at instruction number N.\n\ +Argument is instruction number, as shown by 'info record'."), + &record_cmdlist); + + add_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 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); }