X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=gdb%2Frecord.c;h=f7c95153537448f2267e9a6882806b3633318767;hb=213516ef315dc1785e4990ef0fc011abedb38cc0;hp=5cbac007222acc2e56ca502241f0ab7ee2abff93;hpb=ccce17b060f58e5bf38ac70ac972438af2e0f299;p=thirdparty%2Fbinutils-gdb.git diff --git a/gdb/record.c b/gdb/record.c index 5cbac007222..f7c95153537 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-2012 Free Software Foundation, Inc. + Copyright (C) 2008-2023 Free Software Foundation, Inc. This file is part of GDB. @@ -19,2968 +19,750 @@ #include "defs.h" #include "gdbcmd.h" -#include "regcache.h" -#include "gdbthread.h" -#include "event-top.h" -#include "exceptions.h" #include "completer.h" -#include "arch-utils.h" -#include "gdbcore.h" -#include "exec.h" #include "record.h" -#include "elf-bfd.h" -#include "gcore.h" -#include "event-loop.h" -#include "inf-loop.h" -#include "gdb_bfd.h" +#include "observable.h" +#include "inferior.h" +#include "gdbsupport/common-utils.h" +#include "cli/cli-utils.h" +#include "disasm.h" -#include +#include -/* This module implements "target record", also known as "process - record and replay". This target sits on top of a "normal" target - (a target that "has execution"), and provides a record and replay - functionality, including reverse debugging. +/* This is the debug switch for process record. */ +unsigned int record_debug = 0; - Target record has two modes: recording, and replaying. +/* The number of instructions to print in "record instruction-history". */ +static unsigned int record_insn_history_size = 10; - In record mode, we intercept the to_resume and to_wait methods. - Whenever gdb resumes the target, we run the target in single step - mode, and we build up an execution log in which, for each executed - instruction, we record all changes in memory and register state. - This is invisible to the user, to whom it just looks like an - ordinary debugging session (except for performance degredation). +/* The 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; - In replay mode, instead of actually letting the inferior run as a - process, we simulate its execution by playing back the recorded - execution log. For each instruction in the log, we simulate the - instruction's side effects by duplicating the changes that it would - have made on memory and registers. */ +/* The number of functions to print in "record function-call-history". */ +static unsigned int record_call_history_size = 10; -#define DEFAULT_RECORD_INSN_MAX_NUM 200000 +/* 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; -#define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) +struct cmd_list_element *record_cmdlist = NULL; +static 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; -#define RECORD_FILE_MAGIC netorder32(0x20091016) +#define DEBUG(msg, args...) \ + if (record_debug) \ + gdb_printf (gdb_stdlog, "record: " msg "\n", ##args) -/* These are the core structs of the process record functionality. +/* See record.h. */ - 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. +struct target_ops * +find_record_target (void) +{ + return find_target_at (record_stratum); +} - Each struct record_entry is linked to "record_list" by "prev" and - "next" pointers. */ +/* 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; - union - { - gdb_byte *ptr; - gdb_byte buf[sizeof (gdb_byte *)]; - } u; -}; - -struct record_reg_entry + struct target_ops *t; + + t = find_record_target (); + if (t == NULL) + error (_("No recording is currently active.\n" + "Use the \"record full\" or \"record btrace\" command first.")); + + return t; +} + +/* See record.h. */ + +void +record_preopen (void) { - unsigned short num; - unsigned short len; - union - { - gdb_byte *ptr; - gdb_byte buf[2 * sizeof (gdb_byte *)]; - } u; -}; - -struct record_end_entry + /* 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.")); +} + +/* See record.h. */ + +void +record_start (const char *method, const char *format, int from_tty) { - enum gdb_signal sigval; - ULONGEST insn_num; -}; + if (method == NULL) + { + if (format == NULL) + execute_command_to_string ("record", from_tty, false); + else + error (_("Invalid format.")); + } + else if (strcmp (method, "full") == 0) + { + if (format == NULL) + execute_command_to_string ("record full", from_tty, false); + else + error (_("Invalid format.")); + } + else if (strcmp (method, "btrace") == 0) + { + if (format == NULL) + execute_command_to_string ("record btrace", from_tty, false); + else if (strcmp (format, "bts") == 0) + execute_command_to_string ("record btrace bts", from_tty, false); + else if (strcmp (format, "pt") == 0) + execute_command_to_string ("record btrace pt", from_tty, false); + else + error (_("Invalid format.")); + } + else + error (_("Invalid method.")); +} -enum record_type +/* See record.h. */ + +void +record_stop (int from_tty) { - record_end = 0, - record_reg, - record_mem -}; - -/* This is the data structure that makes up the execution log. - - The execution log consists of a single linked list of entries - of type "struct record_entry". It is doubly linked so that it - can be traversed in either direction. - - The start of the list is anchored by a struct called - "record_first". The pointer "record_list" either points to the - last entry that was added to the list (in record mode), or to the - next entry in the list that will be executed (in replay mode). - - Each list element (struct record_entry), in addition to next and - prev pointers, consists of a union of three entry types: mem, reg, - and end. A field called "type" determines which entry type is - represented by a given list element. - - Each instruction that is added to the execution log is represented - by a variable number of list elements ('entries'). The instruction - will have one "reg" entry for each register that is changed by - executing the instruction (including the PC in every case). It - will also have one "mem" entry for each memory change. Finally, - each instruction will have an "end" entry that separates it from - the changes associated with the next instruction. */ - -struct record_entry + execute_command_to_string ("record stop", from_tty, false); +} + +/* 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; - /* end */ - struct record_end_entry end; - } u; -}; + int ret = target_read_memory (memaddr, myaddr, len); -/* This is the debug switch for process record. */ -unsigned int record_debug = 0; + if (ret != 0) + DEBUG ("error reading memory at addr %s len = %ld.\n", + paddress (gdbarch, memaddr), (long) len); + + return ret; +} -/* If true, query if PREC cannot record memory - change of next instruction. */ -int record_memory_query = 0; +/* Stop recording. */ -struct record_core_buf_entry -{ - struct record_core_buf_entry *prev; - struct target_section *p; - bfd_byte *buf; -}; - -/* Record buf with core target. */ -static gdb_byte *record_core_regbuf = NULL; -static struct target_section *record_core_start; -static struct target_section *record_core_end; -static struct record_core_buf_entry *record_core_buf_list = NULL; - -/* The following variables are used for managing the linked list that - represents the execution log. - - record_first is the anchor that holds down the beginning of the list. - - record_list serves two functions: - 1) In record mode, it anchors the end of the list. - 2) In replay mode, it traverses the list and points to - the next instruction that must be emulated. - - record_arch_list_head and record_arch_list_tail are used to manage - a separate list, which is used to build up the change elements of - the currently executing instruction during record mode. When this - instruction has been completely annotated in the "arch list", it - will be appended to the main execution log. */ - -static struct record_entry record_first; -static struct record_entry *record_list = &record_first; -static struct record_entry *record_arch_list_head = NULL; -static struct record_entry *record_arch_list_tail = NULL; - -/* 1 ask user. 0 auto delete the last struct record_entry. */ -static int record_stop_at_limit = 1; -/* Maximum allowed number of insns in execution log. */ -static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; -/* Actual count of insns presently in execution log. */ -static int record_insn_num = 0; -/* Count of insns logged so far (may be larger - than count of insns presently in execution log). */ -static ULONGEST record_insn_count; - -/* The target_ops of process record. */ -static struct target_ops record_ops; -static struct target_ops record_core_ops; - -/* The beneath function pointers. */ -static struct target_ops *record_beneath_to_resume_ops; -static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, - enum gdb_signal); -static struct target_ops *record_beneath_to_wait_ops; -static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, - struct target_waitstatus *, - int); -static struct target_ops *record_beneath_to_store_registers_ops; -static void (*record_beneath_to_store_registers) (struct target_ops *, - struct regcache *, - int regno); -static struct target_ops *record_beneath_to_xfer_partial_ops; -static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops, - enum target_object object, - const char *annex, - gdb_byte *readbuf, - const gdb_byte *writebuf, - ULONGEST offset, - LONGEST len); -static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *, - struct bp_target_info *); -static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, - struct bp_target_info *); -static int (*record_beneath_to_stopped_by_watchpoint) (void); -static int (*record_beneath_to_stopped_data_address) (struct target_ops *, - CORE_ADDR *); -static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *); - -/* Alloc and free functions for record_reg, record_mem, and record_end - entries. */ - -/* Alloc a record_reg record entry. */ - -static inline struct record_entry * -record_reg_alloc (struct regcache *regcache, int regnum) +static void +record_stop (struct target_ops *t) { - struct record_entry *rec; - struct gdbarch *gdbarch = get_regcache_arch (regcache); + DEBUG ("stop %s", t->shortname ()); + + t->stop_recording (); +} + +/* Unpush the record target. */ - 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); +static void +record_unpush (struct target_ops *t) +{ + DEBUG ("unpush %s", t->shortname ()); - return rec; + current_inferior ()->unpush_target (t); } -/* Free a record_reg record entry. */ +/* See record.h. */ -static inline void -record_reg_release (struct record_entry *rec) +void +record_disconnect (struct target_ops *t, const char *args, int from_tty) { - 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); + gdb_assert (t->stratum () == record_stratum); + + DEBUG ("disconnect %s", t->shortname ()); + + record_stop (t); + record_unpush (t); + + target_disconnect (args, from_tty); } -/* Alloc a record_mem record entry. */ +/* See record.h. */ -static inline struct record_entry * -record_mem_alloc (CORE_ADDR addr, int len) +void +record_detach (struct target_ops *t, inferior *inf, int from_tty) { - struct record_entry *rec; + gdb_assert (t->stratum () == record_stratum); - 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); + DEBUG ("detach %s", t->shortname ()); - return rec; + record_stop (t); + record_unpush (t); + + target_detach (inf, from_tty); } -/* Free a record_mem record entry. */ +/* See record.h. */ -static inline void -record_mem_release (struct record_entry *rec) +void +record_mourn_inferior (struct target_ops *t) { - gdb_assert (rec->type == record_mem); - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - xfree (rec->u.mem.u.ptr); - xfree (rec); + gdb_assert (t->stratum () == record_stratum); + + DEBUG ("mourn inferior %s", t->shortname ()); + + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); + + target_mourn_inferior (inferior_ptid); } -/* Alloc a record_end record entry. */ +/* See record.h. */ -static inline struct record_entry * -record_end_alloc (void) +void +record_kill (struct target_ops *t) { - struct record_entry *rec; + gdb_assert (t->stratum () == record_stratum); - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_end; + DEBUG ("kill %s", t->shortname ()); - return rec; + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); + + target_kill (); } -/* Free a record_end record entry. */ +/* See record.h. */ -static inline void -record_end_release (struct record_entry *rec) +int +record_check_stopped_by_breakpoint (const address_space *aspace, + CORE_ADDR pc, + enum target_stop_reason *reason) { - xfree (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; + } + + *reason = TARGET_STOPPED_BY_NO_REASON; + return 0; } -/* Free one record entry, any type. - Return entry->type, in case caller wants to know. */ +/* Implement "show record debug" command. */ -static inline enum record_type -record_entry_release (struct record_entry *rec) +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) { - 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; + gdb_printf (file, _("Debugging of process record target is %s.\n"), + value); } -/* Free all record entries in list pointed to by REC. */ +/* Alias for "target record". */ static void -record_list_release (struct record_entry *rec) +cmd_record_start (const char *args, int from_tty) { - if (!rec) - return; + execute_command ("target record-full", from_tty); +} + +/* Truncate the record log from the present point + of replay until the end. */ - while (rec->next) - rec = rec->next; +static void +cmd_record_delete (const char *args, int from_tty) +{ + require_record_target (); - while (rec->prev) + if (!target_record_is_replaying (inferior_ptid)) { - rec = rec->prev; - record_entry_release (rec->next); + gdb_printf (_("Already at end of record list.\n")); + return; } - if (rec == &record_first) + if (!target_supports_delete_record ()) { - record_insn_num = 0; - record_first.next = NULL; + gdb_printf (_("The current record target does not support " + "this operation.\n")); + return; } - else - record_entry_release (rec); + + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + target_delete_record (); } -/* Free all record entries forward of the given list position. */ +/* Implement the "stoprecord" or "record stop" command. */ static void -record_list_release_following (struct record_entry *rec) +cmd_record_stop (const char *args, int from_tty) { - struct record_entry *tmp = rec->next; + struct target_ops *t; - rec->next = NULL; - while (tmp) - { - rec = tmp->next; - if (record_entry_release (tmp) == record_end) - { - record_insn_num--; - record_insn_count--; - } - tmp = rec; - } + t = require_record_target (); + + record_stop (t); + record_unpush (t); + + gdb_printf (_("Process record is stopped and all execution " + "logs are deleted.\n")); + + gdb::observers::record_changed.notify (current_inferior (), 0, NULL, NULL); } -/* Delete the first instruction from the beginning of the log, to make - room for adding a new instruction at the end of the log. - Note -- this function does not modify record_insn_num. */ +/* The "info record" command. */ static void -record_list_release_first (void) +info_record_command (const char *args, int from_tty) { - struct record_entry *tmp; - - if (!record_first.next) - return; + struct target_ops *t; - /* Loop until a record_end. */ - while (1) + t = find_record_target (); + if (t == NULL) { - /* Cut record_first.next out of the linked list. */ - tmp = record_first.next; - record_first.next = tmp->next; - tmp->next->prev = &record_first; - - /* tmp is now isolated, and can be deleted. */ - if (record_entry_release (tmp) == record_end) - break; /* End loop at first record_end. */ - - if (!record_first.next) - { - gdb_assert (record_insn_num == 1); - break; /* End loop when list is empty. */ - } + gdb_printf (_("No recording is currently active.\n")); + return; } + + gdb_printf (_("Active record target: %s\n"), t->shortname ()); + t->info_record (); } -/* Add a struct record_entry to record_arch_list. */ +/* The "record save" command. */ static void -record_arch_list_add (struct record_entry *rec) +cmd_record_save (const char *args, int from_tty) { - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_arch_list_add %s.\n", - host_address_to_string (rec)); + const char *recfilename; + char recfilename_buffer[40]; - if (record_arch_list_tail) - { - record_arch_list_tail->next = rec; - rec->prev = record_arch_list_tail; - record_arch_list_tail = rec; - } + require_record_target (); + + if (args != NULL && *args != 0) + recfilename = args; else { - record_arch_list_head = rec; - record_arch_list_tail = rec; + /* Default recfile name is "gdb_record.PID". */ + xsnprintf (recfilename_buffer, sizeof (recfilename_buffer), + "gdb_record.%d", inferior_ptid.pid ()); + recfilename = recfilename_buffer; } -} -/* Return the value storage location of a record entry. */ -static inline gdb_byte * -record_get_loc (struct record_entry *rec) -{ - switch (rec->type) { - case record_mem: - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - return rec->u.mem.u.ptr; - else - return rec->u.mem.u.buf; - case record_reg: - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - return rec->u.reg.u.ptr; - else - return rec->u.reg.u.buf; - case record_end: - default: - gdb_assert_not_reached ("unexpected record_entry type"); - return NULL; - } + target_save_record (recfilename); } -/* 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_goto (const char *arg) { - struct record_entry *rec; + ULONGEST insn; + + if (arg == NULL || *arg == '\0') + error (_("Command requires an argument (insn number to go to).")); - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add register num = %d to " - "record list.\n", - regnum); + insn = parse_and_eval_long (arg); - rec = record_reg_alloc (regcache, regnum); + require_record_target (); + target_goto_record (insn); +} - regcache_raw_read (regcache, regnum, record_get_loc (rec)); +/* "record goto" command. Argument is an instruction number, + as given by "info record". - record_arch_list_add (rec); + Rewinds the recording (forward or backward) to the given instruction. */ - return 0; +static void +cmd_record_goto (const char *arg, int from_tty) +{ + record_goto (arg); } -int -record_read_memory (struct gdbarch *gdbarch, - CORE_ADDR memaddr, gdb_byte *myaddr, - ssize_t len) +/* The "record goto begin" command. */ + +static void +cmd_record_goto_begin (const char *arg, int from_tty) { - int ret = target_read_memory (memaddr, myaddr, len); + if (arg != NULL && *arg != '\0') + error (_("Junk after argument: %s."), arg); - if (ret && record_debug) - printf_unfiltered (_("Process record: error reading memory " - "at addr %s len = %ld.\n"), - paddress (gdbarch, memaddr), (long) len); - return ret; + require_record_target (); + target_goto_record_begin (); } -/* Record the value of a region of memory whose address is ADDR and - length is LEN to record_arch_list. */ +/* The "record goto end" command. */ -int -record_arch_list_add_mem (CORE_ADDR addr, int len) +static void +cmd_record_goto_end (const char *arg, int from_tty) { - struct record_entry *rec; + if (arg != NULL && *arg != '\0') + error (_("Junk after argument: %s."), arg); + + require_record_target (); + target_goto_record_end (); +} - 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); +/* Read an instruction number from an argument string. */ - if (!addr) /* FIXME: Why? Some arch must permit it... */ - return 0; +static ULONGEST +get_insn_number (const char **arg) +{ + ULONGEST number; + const char *begin, *end, *pos; - rec = record_mem_alloc (addr, len); + begin = *arg; + pos = skip_spaces (begin); - if (record_read_memory (target_gdbarch, addr, record_get_loc (rec), len)) - { - record_mem_release (rec); - return -1; - } + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); - record_arch_list_add (rec); + number = strtoulst (pos, &end, 10); - return 0; + *arg += (end - begin); + + return number; } -/* Add a record_end type struct record_entry to record_arch_list. */ +/* Read a context size from an argument string. */ -int -record_arch_list_add_end (void) +static int +get_context_size (const char **arg) { - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add end to arch list.\n"); + const char *pos; + char *end; - rec = record_end_alloc (); - rec->u.end.sigval = GDB_SIGNAL_0; - rec->u.end.insn_num = ++record_insn_count; + pos = skip_spaces (*arg); - record_arch_list_add (rec); + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); - return 0; + long result = strtol (pos, &end, 10); + *arg = end; + return result; } -static void -record_check_insn_num (int set_terminal) -{ - if (record_insn_max_num) - { - gdb_assert (record_insn_num <= record_insn_max_num); - if (record_insn_num == record_insn_max_num) - { - /* Ask user what to do. */ - if (record_stop_at_limit) - { - int q; - - if (set_terminal) - target_terminal_ours (); - q = yquery (_("Do you want to auto delete previous execution " - "log entries when record/replay buffer becomes " - "full (record stop-at-limit)?")); - if (set_terminal) - target_terminal_inferior (); - if (q) - record_stop_at_limit = 0; - else - error (_("Process record: stopped by user.")); - } - } - } -} +/* Complain about junk at the end of an argument string. */ static void -record_arch_list_cleanups (void *ignore) +no_chunk (const char *arg) { - record_list_release (record_arch_list_tail); + if (*arg != 0) + error (_("Junk after argument: %s."), arg); } -/* 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. */ +/* Read instruction-history modifiers from an argument string. */ -static int -record_message (struct regcache *regcache, enum gdb_signal signal) +static gdb_disassembly_flags +get_insn_history_modifiers (const char **arg) { - int ret; - struct gdbarch *gdbarch = get_regcache_arch (regcache); - struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); + gdb_disassembly_flags modifiers; + const char *args; - record_arch_list_head = NULL; - record_arch_list_tail = NULL; + modifiers = 0; + args = *arg; - /* Check record_insn_num. */ - record_check_insn_num (1); + if (args == NULL) + return modifiers; - /* If gdb sends a signal value to target_resume, - save it in the 'end' field of the previous instruction. + while (*args == '/') + { + ++args; - Maybe process record should record what really happened, - rather than what gdb pretends has happened. + if (*args == '\0') + error (_("Missing modifier.")); - 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. + for (; *args; ++args) + { + if (isspace (*args)) + break; - 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). + if (*args == '/') + continue; - 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. + switch (*args) + { + case 'm': + case 's': + modifiers |= DISASSEMBLY_SOURCE; + modifiers |= DISASSEMBLY_FILENAME; + break; + case 'r': + modifiers |= DISASSEMBLY_RAW_INSN; + break; + case 'b': + modifiers |= DISASSEMBLY_RAW_BYTES; + break; + case 'f': + modifiers |= DISASSEMBLY_OMIT_FNAME; + break; + case 'p': + modifiers |= DISASSEMBLY_OMIT_PC; + break; + default: + error (_("Invalid modifier: %c."), *args); + } + } - But we should still deliver the signal to gdb during the replay, - if we delivered it during the recording. Therefore we should - record the signal during record_wait, not record_resume. */ - if (record_list != &record_first) /* FIXME better way to check */ - { - gdb_assert (record_list->type == record_end); - record_list->u.end.sigval = signal; + args = skip_spaces (args); } - if (signal == GDB_SIGNAL_0 - || !gdbarch_process_record_signal_p (gdbarch)) - ret = gdbarch_process_record (gdbarch, - regcache, - regcache_read_pc (regcache)); - else - ret = gdbarch_process_record_signal (gdbarch, - regcache, - signal); + /* Update the argument string. */ + *arg = args; - if (ret > 0) - error (_("Process record: inferior program stopped.")); - if (ret < 0) - error (_("Process record: failed to record execution log.")); + return modifiers; +} - discard_cleanups (old_cleanups); +/* 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... */ - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; +static int +command_size_to_target_size (unsigned int size) +{ + gdb_assert (size <= INT_MAX || size == UINT_MAX); - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + if (size == UINT_MAX) + return INT_MAX; else - record_insn_num++; - - return 1; + return size; } -struct record_message_args { - struct regcache *regcache; - enum gdb_signal signal; -}; +/* The "record instruction-history" command. */ -static int -record_message_wrapper (void *args) +static void +cmd_record_insn_history (const char *arg, int from_tty) { - struct record_message_args *record_args = args; + require_record_target (); - return record_message (record_args->regcache, record_args->signal); -} + gdb_disassembly_flags flags = get_insn_history_modifiers (&arg); -static int -record_message_wrapper_safe (struct regcache *regcache, - enum gdb_signal signal) -{ - struct record_message_args args; + int size = command_size_to_target_size (record_insn_history_size); - args.regcache = regcache; - args.signal = signal; + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) + target_insn_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_insn_history (-size, flags); + else + { + ULONGEST begin, end; - return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL); -} + begin = get_insn_number (&arg); -/* Set to 1 if record_store_registers and record_xfer_partial - doesn't need record. */ + if (*arg == ',') + { + arg = skip_spaces (++arg); -static int record_gdb_operation_disable = 0; + if (*arg == '+') + { + arg += 1; + size = get_context_size (&arg); -struct cleanup * -record_gdb_operation_disable_set (void) -{ - struct cleanup *old_cleanups = NULL; + no_chunk (arg); - old_cleanups = - make_cleanup_restore_integer (&record_gdb_operation_disable); - record_gdb_operation_disable = 1; + target_insn_history_from (begin, size, flags); + } + else if (*arg == '-') + { + arg += 1; + size = get_context_size (&arg); - return old_cleanups; -} + no_chunk (arg); -/* Flag set to TRUE for target_stopped_by_watchpoint. */ -static int record_hw_watchpoint = 0; + target_insn_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); -/* Execute one instruction from the record log. Each instruction in - the log will be represented by an arbitrary sequence of register - entries and memory entries, followed by an 'end' entry. */ + no_chunk (arg); -static inline void -record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch, - struct record_entry *entry) -{ - switch (entry->type) - { - case record_reg: /* reg */ - { - gdb_byte reg[MAX_REGISTER_SIZE]; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_reg %s to " - "inferior num = %d.\n", - host_address_to_string (entry), - entry->u.reg.num); - - regcache_cooked_read (regcache, entry->u.reg.num, reg); - regcache_cooked_write (regcache, entry->u.reg.num, - record_get_loc (entry)); - memcpy (record_get_loc (entry), reg, entry->u.reg.len); - } - break; - - case record_mem: /* mem */ - { - /* Nothing to do if the entry is flagged not_accessible. */ - if (!entry->u.mem.mem_entry_not_accessible) - { - gdb_byte *mem = alloca (entry->u.mem.len); - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_mem %s to " - "inferior addr = %s len = %d.\n", - host_address_to_string (entry), - paddress (gdbarch, entry->u.mem.addr), - entry->u.mem.len); - - if (record_read_memory (gdbarch, - entry->u.mem.addr, mem, entry->u.mem.len)) - entry->u.mem.mem_entry_not_accessible = 1; - else - { - if (target_write_memory (entry->u.mem.addr, - record_get_loc (entry), - entry->u.mem.len)) - { - entry->u.mem.mem_entry_not_accessible = 1; - if (record_debug) - warning (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, entry->u.mem.addr), - entry->u.mem.len); - } - else - { - memcpy (record_get_loc (entry), mem, entry->u.mem.len); - - /* We've changed memory --- check if a hardware - watchpoint should trap. Note that this - presently assumes the target beneath supports - continuable watchpoints. On non-continuable - watchpoints target, we'll want to check this - _before_ actually doing the memory change, and - not doing the change at all if the watchpoint - traps. */ - if (hardware_watchpoint_inserted_in_range - (get_regcache_aspace (regcache), - entry->u.mem.addr, entry->u.mem.len)) - record_hw_watchpoint = 1; - } - } - } - } - break; - } -} + target_insn_history_range (begin, end, flags); + } + } + else + { + no_chunk (arg); -static struct target_ops *tmp_to_resume_ops; -static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, - enum gdb_signal); -static struct target_ops *tmp_to_wait_ops; -static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, - struct target_waitstatus *, - int); -static struct target_ops *tmp_to_store_registers_ops; -static void (*tmp_to_store_registers) (struct target_ops *, - struct regcache *, - int regno); -static struct target_ops *tmp_to_xfer_partial_ops; -static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, - enum target_object object, - const char *annex, - gdb_byte *readbuf, - const gdb_byte *writebuf, - ULONGEST offset, - LONGEST len); -static int (*tmp_to_insert_breakpoint) (struct gdbarch *, - struct bp_target_info *); -static int (*tmp_to_remove_breakpoint) (struct gdbarch *, - struct bp_target_info *); -static int (*tmp_to_stopped_by_watchpoint) (void); -static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *); -static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *); -static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *); - -static void record_restore (void); - -/* Asynchronous signal handle registered as event loop source for when - we have pending events ready to be passed to the core. */ - -static struct async_event_handler *record_async_inferior_event_token; + target_insn_history_from (begin, size, flags); + } -static void -record_async_inferior_event_handler (gdb_client_data data) -{ - inferior_event_handler (INF_REG_EVENT, NULL); + dont_repeat (); + } } -/* Open the process record target. */ +/* Read function-call-history modifiers from an argument string. */ -static void -record_core_open_1 (char *name, int from_tty) +static record_print_flags +get_call_history_modifiers (const char **arg) { - struct regcache *regcache = get_current_regcache (); - int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); - int i; - - /* Get record_core_regbuf. */ - target_fetch_registers (regcache, -1); - record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); - for (i = 0; i < regnum; i ++) - regcache_raw_collect (regcache, i, - record_core_regbuf + MAX_REGISTER_SIZE * i); - - /* Get record_core_start and record_core_end. */ - if (build_section_table (core_bfd, &record_core_start, &record_core_end)) - { - xfree (record_core_regbuf); - record_core_regbuf = NULL; - error (_("\"%s\": Can't find sections: %s"), - bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); - } + record_print_flags modifiers = 0; + const char *args = *arg; - push_target (&record_core_ops); - record_restore (); -} + if (args == NULL) + return modifiers; -/* "to_open" target method for 'live' processes. */ + while (*args == '/') + { + ++args; -static void -record_open_1 (char *name, int from_tty) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); - - /* check exec */ - if (!target_has_execution) - error (_("Process record: the program is not being run.")); - if (non_stop) - error (_("Process record target can't debug inferior in non-stop mode " - "(non-stop).")); - - if (!gdbarch_process_record_p (target_gdbarch)) - error (_("Process record: the current architecture doesn't support " - "record function.")); - - if (!tmp_to_resume) - error (_("Could not find 'to_resume' method on the target stack.")); - if (!tmp_to_wait) - error (_("Could not find 'to_wait' method on the target stack.")); - if (!tmp_to_store_registers) - error (_("Could not find 'to_store_registers' " - "method on the target stack.")); - if (!tmp_to_insert_breakpoint) - error (_("Could not find 'to_insert_breakpoint' " - "method on the target stack.")); - if (!tmp_to_remove_breakpoint) - error (_("Could not find 'to_remove_breakpoint' " - "method on the target stack.")); - if (!tmp_to_stopped_by_watchpoint) - error (_("Could not find 'to_stopped_by_watchpoint' " - "method on the target stack.")); - if (!tmp_to_stopped_data_address) - error (_("Could not find 'to_stopped_data_address' " - "method on the target stack.")); - - push_target (&record_ops); -} + if (*args == '\0') + error (_("Missing modifier.")); -static void record_init_record_breakpoints (void); + for (; *args; ++args) + { + if (isspace (*args)) + break; -/* "to_open" target method. Open the process record target. */ + if (*args == '/') + continue; -static void -record_open (char *name, int from_tty) -{ - struct target_ops *t; + 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); + } + } - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); - - /* Check if record target is already running. */ - if (current_target.to_stratum == record_stratum) - error (_("Process record target already running. Use \"record stop\" to " - "stop record target first.")); - - /* Reset the tmp beneath pointers. */ - tmp_to_resume_ops = NULL; - tmp_to_resume = NULL; - tmp_to_wait_ops = NULL; - tmp_to_wait = NULL; - tmp_to_store_registers_ops = NULL; - tmp_to_store_registers = NULL; - tmp_to_xfer_partial_ops = NULL; - tmp_to_xfer_partial = NULL; - tmp_to_insert_breakpoint = NULL; - tmp_to_remove_breakpoint = NULL; - tmp_to_stopped_by_watchpoint = NULL; - tmp_to_stopped_data_address = NULL; - tmp_to_async = NULL; - - /* Set the beneath function pointers. */ - for (t = current_target.beneath; t != NULL; t = t->beneath) - { - if (!tmp_to_resume) - { - tmp_to_resume = t->to_resume; - tmp_to_resume_ops = t; - } - if (!tmp_to_wait) - { - tmp_to_wait = t->to_wait; - tmp_to_wait_ops = t; - } - if (!tmp_to_store_registers) - { - tmp_to_store_registers = t->to_store_registers; - tmp_to_store_registers_ops = t; - } - if (!tmp_to_xfer_partial) - { - tmp_to_xfer_partial = t->to_xfer_partial; - tmp_to_xfer_partial_ops = t; - } - if (!tmp_to_insert_breakpoint) - tmp_to_insert_breakpoint = t->to_insert_breakpoint; - if (!tmp_to_remove_breakpoint) - tmp_to_remove_breakpoint = t->to_remove_breakpoint; - if (!tmp_to_stopped_by_watchpoint) - tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint; - if (!tmp_to_stopped_data_address) - tmp_to_stopped_data_address = t->to_stopped_data_address; - if (!tmp_to_async) - tmp_to_async = t->to_async; + args = skip_spaces (args); } - if (!tmp_to_xfer_partial) - error (_("Could not find 'to_xfer_partial' method on the target stack.")); - - /* Reset */ - record_insn_num = 0; - record_insn_count = 0; - record_list = &record_first; - record_list->next = NULL; - - /* Set the tmp beneath pointers to beneath pointers. */ - record_beneath_to_resume_ops = tmp_to_resume_ops; - record_beneath_to_resume = tmp_to_resume; - record_beneath_to_wait_ops = tmp_to_wait_ops; - record_beneath_to_wait = tmp_to_wait; - record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; - record_beneath_to_store_registers = tmp_to_store_registers; - record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; - record_beneath_to_xfer_partial = tmp_to_xfer_partial; - record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; - record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; - record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint; - record_beneath_to_stopped_data_address = tmp_to_stopped_data_address; - record_beneath_to_async = tmp_to_async; - - if (core_bfd) - record_core_open_1 (name, from_tty); - else - record_open_1 (name, from_tty); - /* Register extra event sources in the event loop. */ - record_async_inferior_event_token - = create_async_event_handler (record_async_inferior_event_handler, - NULL); + /* Update the argument string. */ + *arg = args; - record_init_record_breakpoints (); + return modifiers; } -/* "to_close" target method. Close the process record target. */ +/* The "record function-call-history" command. */ static void -record_close (int quitting) +cmd_record_call_history (const char *arg, int from_tty) { - struct record_core_buf_entry *entry; + require_record_target (); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + record_print_flags flags = get_call_history_modifiers (&arg); - record_list_release (record_list); + int size = command_size_to_target_size (record_call_history_size); - /* Release record_core_regbuf. */ - if (record_core_regbuf) + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) + target_call_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_call_history (-size, flags); + else { - xfree (record_core_regbuf); - record_core_regbuf = NULL; - } + ULONGEST begin, end; - /* Release record_core_buf_list. */ - if (record_core_buf_list) - { - for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + begin = get_insn_number (&arg); + + if (*arg == ',') { - xfree (record_core_buf_list); - record_core_buf_list = entry; - } - record_core_buf_list = NULL; - } + arg = skip_spaces (++arg); - if (record_async_inferior_event_token) - delete_async_event_handler (&record_async_inferior_event_token); -} + if (*arg == '+') + { + arg += 1; + size = get_context_size (&arg); -static int record_resume_step = 0; + no_chunk (arg); -/* True if we've been resumed, and so each record_wait call should - advance execution. If this is false, record_wait will return a - TARGET_WAITKIND_IGNORE. */ -static int record_resumed = 0; + target_call_history_from (begin, size, flags); + } + else if (*arg == '-') + { + arg += 1; + size = get_context_size (&arg); -/* The execution direction of the last resume we got. This is - necessary for async mode. Vis (order is not strictly accurate): + no_chunk (arg); - 1. user has the global execution direction set to forward - 2. user does a reverse-step command - 3. record_resume is called with global execution direction - temporarily switched to reverse - 4. GDB's execution direction is reverted back to forward - 5. target record notifies event loop there's an event to handle - 6. infrun asks the target which direction was it going, and switches - the global execution direction accordingly (to reverse) - 7. infrun polls an event out of the record target, and handles it - 8. GDB goes back to the event loop, and goto #4. -*/ -static enum exec_direction_kind record_execution_dir = EXEC_FORWARD; + target_call_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); -/* "to_resume" target method. Resume the process record target. */ + no_chunk (arg); -static void -record_resume (struct target_ops *ops, ptid_t ptid, int step, - enum gdb_signal signal) -{ - record_resume_step = step; - record_resumed = 1; - record_execution_dir = execution_direction; + target_call_history_range (begin, end, flags); + } + } + else + { + no_chunk (arg); - if (!RECORD_IS_REPLAY) - { - struct gdbarch *gdbarch = target_thread_architecture (ptid); - - record_message (get_current_regcache (), signal); - - if (!step) - { - /* This is not hard single step. */ - if (!gdbarch_software_single_step_p (gdbarch)) - { - /* This is a normal continue. */ - step = 1; - } - else - { - /* This arch support soft sigle step. */ - if (single_step_breakpoints_inserted ()) - { - /* This is a soft single step. */ - record_resume_step = 1; - } - else - { - /* This is a continue. - Try to insert a soft single step breakpoint. */ - if (!gdbarch_software_single_step (gdbarch, - get_current_frame ())) - { - /* This system don't want use soft single step. - Use hard sigle step. */ - step = 1; - } - } - } - } - - /* Make sure the target beneath reports all signals. */ - target_pass_signals (0, NULL); - - record_beneath_to_resume (record_beneath_to_resume_ops, - ptid, step, signal); - } + target_call_history_from (begin, size, flags); + } - /* We are about to start executing the inferior (or simulate it), - let's register it with the event loop. */ - if (target_can_async_p ()) - { - target_async (inferior_event_handler, 0); - /* Notify the event loop there's an event to wait for. We do - most of the work in record_wait. */ - mark_async_event_handler (record_async_inferior_event_token); + dont_repeat (); } } -static int record_get_sig = 0; - -/* SIGINT signal handler, registered by "to_wait" method. */ +/* 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 -record_sig_handler (int signo) +validate_history_size (unsigned int *command_var, unsigned int *setting) { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + if (*command_var != UINT_MAX && *command_var > INT_MAX) + { + unsigned int new_value = *command_var; - /* It will break the running inferior in replay mode. */ - record_resume_step = 1; + /* Restore previous value. */ + *command_var = *setting; + error (_("integer %u out of range"), new_value); + } - /* It will let record_wait set inferior status to get the signal - SIGINT. */ - record_get_sig = 1; + /* Commit new value. */ + *setting = *command_var; } +/* Called by do_setshow_command. We only want values in the + [0..INT_MAX] range, while the command's machinery accepts + [0..UINT_MAX]. See command_size_to_target_size. */ + static void -record_wait_cleanups (void *ignore) +set_record_insn_history_size (const char *args, int from_tty, + struct cmd_list_element *c) { - if (execution_direction == EXEC_REVERSE) - { - if (record_list->next) - record_list = record_list->next; - } - else - record_list = record_list->prev; + validate_history_size (&record_insn_history_size_setshow_var, + &record_insn_history_size); } -/* "to_wait" target method for process record target. - - In record mode, the target is always run in singlestep mode - (even when gdb says to continue). The to_wait method intercepts - the stop events and determines which ones are to be passed on to - gdb. Most stop events are just singlestep events that gdb is not - to know about, so the to_wait method just records them and keeps - singlestepping. - - In replay mode, this function emulates the recorded execution log, - one instruction at a time (forward or backward), and determines - where to stop. */ +/* 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 ptid_t -record_wait_1 (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *status, - int options) +static void +set_record_call_history_size (const char *args, int from_tty, + struct cmd_list_element *c) { - struct cleanup *set_cleanups = record_gdb_operation_disable_set (); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "record_resume_step = %d, record_resumed = %d, direction=%s\n", - record_resume_step, record_resumed, - record_execution_dir == EXEC_FORWARD ? "forward" : "reverse"); + validate_history_size (&record_call_history_size_setshow_var, + &record_call_history_size); +} - if (!record_resumed) - { - gdb_assert ((options & TARGET_WNOHANG) != 0); - - /* No interesting event. */ - status->kind = TARGET_WAITKIND_IGNORE; - return minus_one_ptid; - } - - record_get_sig = 0; - signal (SIGINT, record_sig_handler); - - if (!RECORD_IS_REPLAY && ops != &record_core_ops) - { - if (record_resume_step) - { - /* This is a single step. */ - return record_beneath_to_wait (record_beneath_to_wait_ops, - ptid, status, options); - } - else - { - /* This is not a single step. */ - ptid_t ret; - CORE_ADDR tmp_pc; - struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid); - - while (1) - { - ret = record_beneath_to_wait (record_beneath_to_wait_ops, - ptid, status, options); - if (status->kind == TARGET_WAITKIND_IGNORE) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "target beneath not done yet\n"); - return ret; - } - - if (single_step_breakpoints_inserted ()) - remove_single_step_breakpoints (); - - if (record_resume_step) - return ret; - - /* Is this a SIGTRAP? */ - if (status->kind == TARGET_WAITKIND_STOPPED - && status->value.sig == GDB_SIGNAL_TRAP) - { - struct regcache *regcache; - struct address_space *aspace; - - /* Yes -- this is likely our single-step finishing, - but check if there's any reason the core would be - interested in the event. */ - - registers_changed (); - regcache = get_current_regcache (); - tmp_pc = regcache_read_pc (regcache); - aspace = get_regcache_aspace (regcache); - - if (target_stopped_by_watchpoint ()) - { - /* Always interested in watchpoints. */ - } - else if (breakpoint_inserted_here_p (aspace, tmp_pc)) - { - /* There is a breakpoint here. Let the core - handle it. */ - if (software_breakpoint_inserted_here_p (aspace, tmp_pc)) - { - struct gdbarch *gdbarch - = get_regcache_arch (regcache); - CORE_ADDR decr_pc_after_break - = gdbarch_decr_pc_after_break (gdbarch); - if (decr_pc_after_break) - regcache_write_pc (regcache, - tmp_pc + decr_pc_after_break); - } - } - else - { - /* This is a single-step trap. Record the - insn and issue another step. - FIXME: this part can be a random SIGTRAP too. - But GDB cannot handle it. */ - int step = 1; - - if (!record_message_wrapper_safe (regcache, - GDB_SIGNAL_0)) - { - status->kind = TARGET_WAITKIND_STOPPED; - status->value.sig = GDB_SIGNAL_0; - break; - } - - if (gdbarch_software_single_step_p (gdbarch)) - { - /* Try to insert the software single step breakpoint. - If insert success, set step to 0. */ - set_executing (inferior_ptid, 0); - reinit_frame_cache (); - if (gdbarch_software_single_step (gdbarch, - get_current_frame ())) - step = 0; - set_executing (inferior_ptid, 1); - } - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "issuing one more step in the target beneath\n"); - record_beneath_to_resume (record_beneath_to_resume_ops, - ptid, step, - GDB_SIGNAL_0); - continue; - } - } - - /* The inferior is broken by a breakpoint or a signal. */ - break; - } - - return ret; - } - } - else - { - struct regcache *regcache = get_current_regcache (); - struct gdbarch *gdbarch = get_regcache_arch (regcache); - struct address_space *aspace = get_regcache_aspace (regcache); - int continue_flag = 1; - int first_record_end = 1; - struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); - CORE_ADDR tmp_pc; - - record_hw_watchpoint = 0; - status->kind = TARGET_WAITKIND_STOPPED; - - /* Check breakpoint when forward execute. */ - if (execution_direction == EXEC_FORWARD) - { - tmp_pc = regcache_read_pc (regcache); - if (breakpoint_inserted_here_p (aspace, tmp_pc)) - { - int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: break at %s.\n", - paddress (gdbarch, tmp_pc)); - - if (decr_pc_after_break - && !record_resume_step - && software_breakpoint_inserted_here_p (aspace, tmp_pc)) - regcache_write_pc (regcache, - tmp_pc + decr_pc_after_break); - goto replay_out; - } - } - - /* If GDB is in terminal_inferior mode, it will not get the signal. - And in GDB replay mode, GDB doesn't need to be in terminal_inferior - mode, because inferior will not executed. - Then set it to terminal_ours to make GDB get the signal. */ - target_terminal_ours (); - - /* In EXEC_FORWARD mode, record_list points to the tail of prev - instruction. */ - if (execution_direction == EXEC_FORWARD && record_list->next) - record_list = record_list->next; - - /* Loop over the record_list, looking for the next place to - stop. */ - do - { - /* Check for beginning and end of log. */ - if (execution_direction == EXEC_REVERSE - && record_list == &record_first) - { - /* Hit beginning of record log in reverse. */ - status->kind = TARGET_WAITKIND_NO_HISTORY; - break; - } - if (execution_direction != EXEC_REVERSE && !record_list->next) - { - /* Hit end of record log going forward. */ - status->kind = TARGET_WAITKIND_NO_HISTORY; - break; - } - - record_exec_insn (regcache, gdbarch, record_list); - - if (record_list->type == record_end) - { - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_end %s to " - "inferior.\n", - host_address_to_string (record_list)); - - if (first_record_end && execution_direction == EXEC_REVERSE) - { - /* When reverse excute, the first record_end is the part of - current instruction. */ - first_record_end = 0; - } - else - { - /* In EXEC_REVERSE mode, this is the record_end of prev - instruction. - In EXEC_FORWARD mode, this is the record_end of current - instruction. */ - /* step */ - if (record_resume_step) - { - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: step.\n"); - continue_flag = 0; - } - - /* check breakpoint */ - tmp_pc = regcache_read_pc (regcache); - if (breakpoint_inserted_here_p (aspace, tmp_pc)) - { - int decr_pc_after_break - = gdbarch_decr_pc_after_break (gdbarch); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: break " - "at %s.\n", - paddress (gdbarch, tmp_pc)); - if (decr_pc_after_break - && execution_direction == EXEC_FORWARD - && !record_resume_step - && software_breakpoint_inserted_here_p (aspace, - tmp_pc)) - regcache_write_pc (regcache, - tmp_pc + decr_pc_after_break); - continue_flag = 0; - } - - if (record_hw_watchpoint) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: hit hw " - "watchpoint.\n"); - continue_flag = 0; - } - /* Check target signal */ - if (record_list->u.end.sigval != GDB_SIGNAL_0) - /* FIXME: better way to check */ - continue_flag = 0; - } - } - - if (continue_flag) - { - if (execution_direction == EXEC_REVERSE) - { - if (record_list->prev) - record_list = record_list->prev; - } - else - { - if (record_list->next) - record_list = record_list->next; - } - } - } - while (continue_flag); - -replay_out: - if (record_get_sig) - status->value.sig = GDB_SIGNAL_INT; - else if (record_list->u.end.sigval != GDB_SIGNAL_0) - /* FIXME: better way to check */ - status->value.sig = record_list->u.end.sigval; - else - status->value.sig = GDB_SIGNAL_TRAP; - - discard_cleanups (old_cleanups); - } - - signal (SIGINT, handle_sigint); - - do_cleanups (set_cleanups); - return inferior_ptid; -} - -static ptid_t -record_wait (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *status, - int options) -{ - ptid_t return_ptid; - - return_ptid = record_wait_1 (ops, ptid, status, options); - if (status->kind != TARGET_WAITKIND_IGNORE) - { - /* We're reporting a stop. Make sure any spurious - target_wait(WNOHANG) doesn't advance the target until the - core wants us resumed again. */ - record_resumed = 0; - } - return return_ptid; -} - -static int -record_stopped_by_watchpoint (void) -{ - if (RECORD_IS_REPLAY) - return record_hw_watchpoint; - else - return record_beneath_to_stopped_by_watchpoint (); -} - -static int -record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p) -{ - if (RECORD_IS_REPLAY) - return 0; - else - return record_beneath_to_stopped_data_address (ops, addr_p); -} - -/* "to_disconnect" method for process record target. */ - -static void -record_disconnect (struct target_ops *target, char *args, int from_tty) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); - - unpush_target (&record_ops); - target_disconnect (args, from_tty); -} - -/* "to_detach" method for process record target. */ - -static void -record_detach (struct target_ops *ops, char *args, int from_tty) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); - - unpush_target (&record_ops); - target_detach (args, from_tty); -} - -/* "to_mourn_inferior" method for process record target. */ - -static void -record_mourn_inferior (struct target_ops *ops) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: " - "record_mourn_inferior\n"); - - unpush_target (&record_ops); - target_mourn_inferior (); -} - -/* Close process record target before killing the inferior process. */ - -static void -record_kill (struct target_ops *ops) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); - - unpush_target (&record_ops); - target_kill (); -} - -/* Record registers change (by user or by GDB) to list as an instruction. */ - -static void -record_registers_change (struct regcache *regcache, int regnum) -{ - /* Check record_insn_num. */ - record_check_insn_num (0); - - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - - if (regnum < 0) - { - int i; - - for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) - { - if (record_arch_list_add_reg (regcache, i)) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - } - } - else - { - if (record_arch_list_add_reg (regcache, regnum)) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - } - if (record_arch_list_add_end ()) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; - - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; -} - -/* "to_store_registers" method for process record target. */ - -static void -record_store_registers (struct target_ops *ops, struct regcache *regcache, - int regno) -{ - if (!record_gdb_operation_disable) - { - if (RECORD_IS_REPLAY) - { - int n; - - /* Let user choose if he wants to write register or not. */ - if (regno < 0) - n = - query (_("Because GDB is in replay mode, changing the " - "value of a register will make the execution " - "log unusable from this point onward. " - "Change all registers?")); - else - n = - query (_("Because GDB is in replay mode, changing the value " - "of a register will make the execution log unusable " - "from this point onward. Change register %s?"), - gdbarch_register_name (get_regcache_arch (regcache), - regno)); - - if (!n) - { - /* Invalidate the value of regcache that was set in function - "regcache_raw_write". */ - if (regno < 0) - { - int i; - - for (i = 0; - i < gdbarch_num_regs (get_regcache_arch (regcache)); - i++) - regcache_invalidate (regcache, i); - } - else - regcache_invalidate (regcache, regno); - - error (_("Process record canceled the operation.")); - } - - /* Destroy the record from here forward. */ - record_list_release_following (record_list); - } - - record_registers_change (regcache, regno); - } - record_beneath_to_store_registers (record_beneath_to_store_registers_ops, - regcache, regno); -} - -/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY. - In replay mode, we cannot write memory unles we are willing to - invalidate the record/replay log from this point forward. */ - -static LONGEST -record_xfer_partial (struct target_ops *ops, enum target_object object, - const char *annex, gdb_byte *readbuf, - const gdb_byte *writebuf, ULONGEST offset, LONGEST len) -{ - if (!record_gdb_operation_disable - && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) - { - if (RECORD_IS_REPLAY) - { - /* Let user choose if he wants to write memory or not. */ - if (!query (_("Because GDB is in replay mode, writing to memory " - "will make the execution log unusable from this " - "point onward. Write memory at address %s?"), - paddress (target_gdbarch, offset))) - error (_("Process record canceled the operation.")); - - /* Destroy the record from here forward. */ - record_list_release_following (record_list); - } - - /* Check record_insn_num */ - record_check_insn_num (0); - - /* Record registers change to list as an instruction. */ - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - if (record_arch_list_add_mem (offset, len)) - { - record_list_release (record_arch_list_tail); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: failed to record " - "execution log."); - return -1; - } - if (record_arch_list_add_end ()) - { - record_list_release (record_arch_list_tail); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: failed to record " - "execution log."); - return -1; - } - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; - - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; - } - - return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, - object, annex, readbuf, writebuf, - offset, len); -} - -/* This structure represents a breakpoint inserted while the record - target is active. We use this to know when to install/remove - breakpoints in/from the target beneath. For example, a breakpoint - may be inserted while recording, but removed when not replaying nor - recording. In that case, the breakpoint had not been inserted on - the target beneath, so we should not try to remove it there. */ - -struct record_breakpoint -{ - /* The address and address space the breakpoint was set at. */ - struct address_space *address_space; - CORE_ADDR addr; - - /* True when the breakpoint has been also installed in the target - beneath. This will be false for breakpoints set during replay or - when recording. */ - int in_target_beneath; -}; - -typedef struct record_breakpoint *record_breakpoint_p; -DEF_VEC_P(record_breakpoint_p); - -/* The list of breakpoints inserted while the record target is - active. */ -VEC(record_breakpoint_p) *record_breakpoints = NULL; - -static void -record_sync_record_breakpoints (struct bp_location *loc, void *data) -{ - if (loc->loc_type != bp_loc_software_breakpoint) - return; - - if (loc->inserted) - { - struct record_breakpoint *bp = XNEW (struct record_breakpoint); - - bp->addr = loc->target_info.placed_address; - bp->address_space = loc->target_info.placed_address_space; - - bp->in_target_beneath = 1; - - VEC_safe_push (record_breakpoint_p, record_breakpoints, bp); - } -} - -/* Sync existing breakpoints to record_breakpoints. */ - -static void -record_init_record_breakpoints (void) -{ - VEC_free (record_breakpoint_p, record_breakpoints); - - iterate_over_bp_locations (record_sync_record_breakpoints); -} - -/* Behavior is conditional on RECORD_IS_REPLAY. We will not actually - insert or remove breakpoints in the real target when replaying, nor - when recording. */ - -static int -record_insert_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - struct record_breakpoint *bp; - int in_target_beneath = 0; - - if (!RECORD_IS_REPLAY) - { - /* When recording, we currently always single-step, so we don't - really need to install regular breakpoints in the inferior. - However, we do have to insert software single-step - breakpoints, in case the target can't hardware step. To keep - things single, we always insert. */ - struct cleanup *old_cleanups; - int ret; - - old_cleanups = record_gdb_operation_disable_set (); - ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt); - do_cleanups (old_cleanups); - - if (ret != 0) - return ret; - - in_target_beneath = 1; - } - - bp = XNEW (struct record_breakpoint); - bp->addr = bp_tgt->placed_address; - bp->address_space = bp_tgt->placed_address_space; - bp->in_target_beneath = in_target_beneath; - VEC_safe_push (record_breakpoint_p, record_breakpoints, bp); - return 0; -} - -/* "to_remove_breakpoint" method for process record target. */ - -static int -record_remove_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - struct record_breakpoint *bp; - int ix; - - for (ix = 0; - VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp); - ++ix) - { - if (bp->addr == bp_tgt->placed_address - && bp->address_space == bp_tgt->placed_address_space) - { - if (bp->in_target_beneath) - { - struct cleanup *old_cleanups; - int ret; - - old_cleanups = record_gdb_operation_disable_set (); - ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt); - do_cleanups (old_cleanups); - - if (ret != 0) - return ret; - } - - VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix); - return 0; - } - } - - gdb_assert_not_reached ("removing unknown breakpoint"); -} - -/* "to_can_execute_reverse" method for process record target. */ - -static int -record_can_execute_reverse (void) -{ - return 1; -} - -/* "to_get_bookmark" method for process record and prec over core. */ - -static gdb_byte * -record_get_bookmark (char *args, int from_tty) -{ - gdb_byte *ret = NULL; - - /* Return stringified form of instruction count. */ - if (record_list && record_list->type == record_end) - ret = xstrdup (pulongest (record_list->u.end.insn_num)); - - if (record_debug) - { - if (ret) - fprintf_unfiltered (gdb_stdlog, - "record_get_bookmark returns %s\n", ret); - else - fprintf_unfiltered (gdb_stdlog, - "record_get_bookmark returns NULL\n"); - } - return ret; -} - -/* The implementation of the command "record goto". */ -static void cmd_record_goto (char *, int); - -/* "to_goto_bookmark" method for process record and prec over core. */ - -static void -record_goto_bookmark (gdb_byte *bookmark, int from_tty) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "record_goto_bookmark receives %s\n", bookmark); - - if (bookmark[0] == '\'' || bookmark[0] == '\"') - { - if (bookmark[strlen (bookmark) - 1] != bookmark[0]) - error (_("Unbalanced quotes: %s"), bookmark); - - /* Strip trailing quote. */ - bookmark[strlen (bookmark) - 1] = '\0'; - /* Strip leading quote. */ - bookmark++; - /* Pass along to cmd_record_goto. */ - } - - cmd_record_goto ((char *) bookmark, from_tty); - return; -} - -static void -record_async (void (*callback) (enum inferior_event_type event_type, - void *context), void *context) -{ - /* If we're on top of a line target (e.g., linux-nat, remote), then - set it to async mode as well. Will be NULL if we're sitting on - top of the core target, for "record restore". */ - if (record_beneath_to_async != NULL) - record_beneath_to_async (callback, context); -} - -static int -record_can_async_p (void) -{ - /* We only enable async when the user specifically asks for it. */ - return target_async_permitted; -} - -static int -record_is_async_p (void) -{ - /* We only enable async when the user specifically asks for it. */ - return target_async_permitted; -} - -static enum exec_direction_kind -record_execution_direction (void) -{ - return record_execution_dir; -} - -static void -init_record_ops (void) -{ - record_ops.to_shortname = "record"; - record_ops.to_longname = "Process record and replay target"; - record_ops.to_doc = - "Log program while executing and replay execution from log."; - record_ops.to_open = record_open; - record_ops.to_close = record_close; - record_ops.to_resume = record_resume; - record_ops.to_wait = record_wait; - record_ops.to_disconnect = record_disconnect; - record_ops.to_detach = record_detach; - record_ops.to_mourn_inferior = record_mourn_inferior; - record_ops.to_kill = record_kill; - record_ops.to_create_inferior = find_default_create_inferior; - record_ops.to_store_registers = record_store_registers; - record_ops.to_xfer_partial = record_xfer_partial; - record_ops.to_insert_breakpoint = record_insert_breakpoint; - record_ops.to_remove_breakpoint = record_remove_breakpoint; - record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; - record_ops.to_stopped_data_address = record_stopped_data_address; - record_ops.to_can_execute_reverse = record_can_execute_reverse; - record_ops.to_stratum = record_stratum; - /* Add bookmark target methods. */ - record_ops.to_get_bookmark = record_get_bookmark; - record_ops.to_goto_bookmark = record_goto_bookmark; - record_ops.to_async = record_async; - record_ops.to_can_async_p = record_can_async_p; - record_ops.to_is_async_p = record_is_async_p; - record_ops.to_execution_direction = record_execution_direction; - record_ops.to_magic = OPS_MAGIC; -} - -/* "to_resume" method for prec over corefile. */ - -static void -record_core_resume (struct target_ops *ops, ptid_t ptid, int step, - enum gdb_signal signal) -{ - record_resume_step = step; - record_resumed = 1; - record_execution_dir = execution_direction; - - /* We are about to start executing the inferior (or simulate it), - let's register it with the event loop. */ - if (target_can_async_p ()) - { - target_async (inferior_event_handler, 0); - - /* Notify the event loop there's an event to wait for. */ - mark_async_event_handler (record_async_inferior_event_token); - } -} - -/* "to_kill" method for prec over corefile. */ - -static void -record_core_kill (struct target_ops *ops) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); - - unpush_target (&record_core_ops); -} - -/* "to_fetch_registers" method for prec over corefile. */ - -static void -record_core_fetch_registers (struct target_ops *ops, - struct regcache *regcache, - int regno) -{ - if (regno < 0) - { - int num = gdbarch_num_regs (get_regcache_arch (regcache)); - int i; - - for (i = 0; i < num; i ++) - regcache_raw_supply (regcache, i, - record_core_regbuf + MAX_REGISTER_SIZE * i); - } - else - regcache_raw_supply (regcache, regno, - record_core_regbuf + MAX_REGISTER_SIZE * regno); -} - -/* "to_prepare_to_store" method for prec over corefile. */ - -static void -record_core_prepare_to_store (struct regcache *regcache) -{ -} - -/* "to_store_registers" method for prec over corefile. */ - -static void -record_core_store_registers (struct target_ops *ops, - struct regcache *regcache, - int regno) -{ - if (record_gdb_operation_disable) - regcache_raw_collect (regcache, regno, - record_core_regbuf + MAX_REGISTER_SIZE * regno); - else - error (_("You can't do that without a process to debug.")); -} - -/* "to_xfer_partial" method for prec over corefile. */ - -static LONGEST -record_core_xfer_partial (struct target_ops *ops, enum target_object object, - const char *annex, gdb_byte *readbuf, - const gdb_byte *writebuf, ULONGEST offset, - LONGEST len) -{ - if (object == TARGET_OBJECT_MEMORY) - { - if (record_gdb_operation_disable || !writebuf) - { - struct target_section *p; - - for (p = record_core_start; p < record_core_end; p++) - { - if (offset >= p->addr) - { - struct record_core_buf_entry *entry; - ULONGEST sec_offset; - - if (offset >= p->endaddr) - continue; - - if (offset + len > p->endaddr) - len = p->endaddr - offset; - - sec_offset = offset - p->addr; - - /* Read readbuf or write writebuf p, offset, len. */ - /* Check flags. */ - if (p->the_bfd_section->flags & SEC_CONSTRUCTOR - || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) - { - if (readbuf) - memset (readbuf, 0, len); - return len; - } - /* Get record_core_buf_entry. */ - for (entry = record_core_buf_list; entry; - entry = entry->prev) - if (entry->p == p) - break; - if (writebuf) - { - if (!entry) - { - /* Add a new entry. */ - entry = (struct record_core_buf_entry *) - xmalloc (sizeof (struct record_core_buf_entry)); - entry->p = p; - if (!bfd_malloc_and_get_section (p->bfd, - p->the_bfd_section, - &entry->buf)) - { - xfree (entry); - return 0; - } - entry->prev = record_core_buf_list; - record_core_buf_list = entry; - } - - memcpy (entry->buf + sec_offset, writebuf, - (size_t) len); - } - else - { - if (!entry) - return record_beneath_to_xfer_partial - (record_beneath_to_xfer_partial_ops, - object, annex, readbuf, writebuf, - offset, len); - - memcpy (readbuf, entry->buf + sec_offset, - (size_t) len); - } - - return len; - } - } - - return -1; - } - else - error (_("You can't do that without a process to debug.")); - } - - return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, - object, annex, readbuf, writebuf, - offset, len); -} - -/* "to_insert_breakpoint" method for prec over corefile. */ - -static int -record_core_insert_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - return 0; -} - -/* "to_remove_breakpoint" method for prec over corefile. */ - -static int -record_core_remove_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - return 0; -} - -/* "to_has_execution" method for prec over corefile. */ - -static int -record_core_has_execution (struct target_ops *ops, ptid_t the_ptid) -{ - return 1; -} - -static void -init_record_core_ops (void) -{ - record_core_ops.to_shortname = "record-core"; - record_core_ops.to_longname = "Process record and replay target"; - record_core_ops.to_doc = - "Log program while executing and replay execution from log."; - record_core_ops.to_open = record_open; - record_core_ops.to_close = record_close; - record_core_ops.to_resume = record_core_resume; - record_core_ops.to_wait = record_wait; - record_core_ops.to_kill = record_core_kill; - record_core_ops.to_fetch_registers = record_core_fetch_registers; - record_core_ops.to_prepare_to_store = record_core_prepare_to_store; - record_core_ops.to_store_registers = record_core_store_registers; - record_core_ops.to_xfer_partial = record_core_xfer_partial; - record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; - record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; - record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; - record_core_ops.to_stopped_data_address = record_stopped_data_address; - record_core_ops.to_can_execute_reverse = record_can_execute_reverse; - record_core_ops.to_has_execution = record_core_has_execution; - record_core_ops.to_stratum = record_stratum; - /* Add bookmark target methods. */ - record_core_ops.to_get_bookmark = record_get_bookmark; - record_core_ops.to_goto_bookmark = record_goto_bookmark; - record_core_ops.to_async = record_async; - record_core_ops.to_can_async_p = record_can_async_p; - record_core_ops.to_is_async_p = record_is_async_p; - record_core_ops.to_execution_direction = record_execution_direction; - record_core_ops.to_magic = OPS_MAGIC; -} - -/* Implement "show record debug" command. */ - -static void -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); -} - -/* Alias for "target record". */ - -static void -cmd_record_start (char *args, int from_tty) -{ - execute_command ("target record", from_tty); -} - -/* Truncate the record log from the present point - of replay until the end. */ - -static void -cmd_record_delete (char *args, int from_tty) -{ - 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); - } - else - printf_unfiltered (_("Already at end of record list.\n")); - - } - else - printf_unfiltered (_("Process record is not started.\n")); -} - -/* Implement the "stoprecord" or "record stop" command. */ - -static void -cmd_record_stop (char *args, int from_tty) -{ - if (current_target.to_stratum == record_stratum) - { - unpush_target (&record_ops); - printf_unfiltered (_("Process record is stopped and all execution " - "logs are deleted.\n")); - } - else - printf_unfiltered (_("Process record is not started.\n")); -} - -/* Set upper limit of record log size. */ - -static void -set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) -{ - if (record_insn_num > record_insn_max_num && record_insn_max_num) - { - /* Count down record_insn_num while releasing records from list. */ - while (record_insn_num > record_insn_max_num) - { - record_list_release_first (); - record_insn_num--; - } - } -} - -static struct cmd_list_element *record_cmdlist, *set_record_cmdlist, - *show_record_cmdlist, *info_record_cmdlist; - -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, ""); -} - -/* Display some statistics about the execution log. */ - -static void -info_record_command (char *args, int from_tty) -{ - struct record_entry *p; - - if (current_target.to_stratum == record_stratum) - { - if (RECORD_IS_REPLAY) - printf_filtered (_("Replay mode:\n")); - else - printf_filtered (_("Record mode:\n")); - - /* Find entry for first actual instruction in the log. */ - for (p = record_first.next; - p != NULL && p->type != record_end; - p = p->next) - ; - - /* Do we have a log at all? */ - if (p != NULL && p->type == record_end) - { - /* Display instruction number for first instruction in the log. */ - printf_filtered (_("Lowest recorded instruction number is %s.\n"), - pulongest (p->u.end.insn_num)); - - /* If in replay mode, display where we are in the log. */ - if (RECORD_IS_REPLAY) - printf_filtered (_("Current instruction number is %s.\n"), - pulongest (record_list->u.end.insn_num)); - - /* Display instruction number for last instruction in the log. */ - printf_filtered (_("Highest recorded instruction number is %s.\n"), - pulongest (record_insn_count)); - - /* Display log count. */ - printf_filtered (_("Log contains %d instructions.\n"), - record_insn_num); - } - else - { - printf_filtered (_("No instructions have been logged.\n")); - } - } - else - { - printf_filtered (_("target record is not active.\n")); - } - - /* Display max log size. */ - printf_filtered (_("Max logged instructions is %d.\n"), - record_insn_max_num); -} - -/* Record log save-file format - Version 1 (never released) - - Header: - 4 bytes: magic number htonl(0x20090829). - NOTE: be sure to change whenever this file format changes! - - Records: - record_end: - 1 byte: record type (record_end, see enum record_type). - record_reg: - 1 byte: record type (record_reg, see enum record_type). - 8 bytes: register id (network byte order). - MAX_REGISTER_SIZE bytes: register value. - record_mem: - 1 byte: record type (record_mem, see enum record_type). - 8 bytes: memory length (network byte order). - 8 bytes: memory address (network byte order). - n bytes: memory value (n == memory length). - - Version 2 - 4 bytes: magic number netorder32(0x20091016). - NOTE: be sure to change whenever this file format changes! - - Records: - record_end: - 1 byte: record type (record_end, see enum record_type). - 4 bytes: signal - 4 bytes: instruction count - record_reg: - 1 byte: record type (record_reg, see enum record_type). - 4 bytes: register id (network byte order). - n bytes: register value (n == actual register size). - (eg. 4 bytes for x86 general registers). - record_mem: - 1 byte: record type (record_mem, see enum record_type). - 4 bytes: memory length (network byte order). - 8 bytes: memory address (network byte order). - n bytes: memory value (n == memory length). - -*/ - -/* bfdcore_read -- read bytes from a core file section. */ - -static inline void -bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset) -{ - int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); - - if (ret) - *offset += len; - else - error (_("Failed to read %d bytes from core file %s ('%s')."), - len, bfd_get_filename (obfd), - bfd_errmsg (bfd_get_error ())); -} - -static inline uint64_t -netorder64 (uint64_t input) -{ - uint64_t ret; - - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; -} - -static inline uint32_t -netorder32 (uint32_t input) -{ - uint32_t ret; - - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; -} - -static inline uint16_t -netorder16 (uint16_t input) -{ - uint16_t ret; - - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; -} - -/* Restore the execution log from a core_bfd file. */ -static void -record_restore (void) -{ - uint32_t magic; - struct cleanup *old_cleanups; - struct record_entry *rec; - asection *osec; - uint32_t osec_size; - int bfd_offset = 0; - struct regcache *regcache; - - /* We restore the execution log from the open core bfd, - if there is one. */ - if (core_bfd == NULL) - return; - - /* "record_restore" can only be called when record list is empty. */ - gdb_assert (record_first.next == NULL); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n"); - - /* Now need to find our special note section. */ - osec = bfd_get_section_by_name (core_bfd, "null0"); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n", - osec ? "succeeded" : "failed"); - if (osec == NULL) - return; - osec_size = bfd_section_size (core_bfd, osec); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec)); - - /* Check the magic code. */ - bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset); - if (magic != RECORD_FILE_MAGIC) - error (_("Version mis-match or file format error in core file %s."), - bfd_get_filename (core_bfd)); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading 4-byte magic cookie " - "RECORD_FILE_MAGIC (0x%s)\n", - phex_nz (netorder32 (magic), 4)); - - /* Restore the entries in recfd into record_arch_list_head and - record_arch_list_tail. */ - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - record_insn_num = 0; - old_cleanups = make_cleanup (record_arch_list_cleanups, 0); - regcache = get_current_regcache (); - - while (1) - { - uint8_t rectype; - uint32_t regnum, len, signal, count; - uint64_t addr; - - /* We are finished when offset reaches osec_size. */ - if (bfd_offset >= osec_size) - break; - bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset); - - switch (rectype) - { - case record_reg: /* reg */ - /* Get register number to regnum. */ - bfdcore_read (core_bfd, osec, ®num, - sizeof (regnum), &bfd_offset); - regnum = netorder32 (regnum); - - rec = record_reg_alloc (regcache, regnum); - - /* Get val. */ - bfdcore_read (core_bfd, osec, record_get_loc (rec), - rec->u.reg.len, &bfd_offset); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading register %d (1 " - "plus %lu plus %d bytes)\n", - rec->u.reg.num, - (unsigned long) sizeof (regnum), - rec->u.reg.len); - break; - - case record_mem: /* mem */ - /* Get len. */ - bfdcore_read (core_bfd, osec, &len, - sizeof (len), &bfd_offset); - len = netorder32 (len); - - /* Get addr. */ - bfdcore_read (core_bfd, osec, &addr, - sizeof (addr), &bfd_offset); - addr = netorder64 (addr); - - rec = record_mem_alloc (addr, len); - - /* Get val. */ - bfdcore_read (core_bfd, osec, record_get_loc (rec), - rec->u.mem.len, &bfd_offset); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading memory %s (1 plus " - "%lu plus %lu plus %d bytes)\n", - paddress (get_current_arch (), - rec->u.mem.addr), - (unsigned long) sizeof (addr), - (unsigned long) sizeof (len), - rec->u.mem.len); - break; - - case record_end: /* end */ - rec = record_end_alloc (); - record_insn_num ++; - - /* Get signal value. */ - bfdcore_read (core_bfd, osec, &signal, - sizeof (signal), &bfd_offset); - signal = netorder32 (signal); - rec->u.end.sigval = signal; - - /* Get insn count. */ - bfdcore_read (core_bfd, osec, &count, - sizeof (count), &bfd_offset); - count = netorder32 (count); - rec->u.end.insn_num = count; - record_insn_count = count + 1; - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading record_end (1 + " - "%lu + %lu bytes), offset == %s\n", - (unsigned long) sizeof (signal), - (unsigned long) sizeof (count), - paddress (get_current_arch (), - bfd_offset)); - break; - - default: - error (_("Bad entry type in core file %s."), - bfd_get_filename (core_bfd)); - break; - } - - /* Add rec to record arch list. */ - record_arch_list_add (rec); - } - - discard_cleanups (old_cleanups); - - /* Add record_arch_list_head to the end of record list. */ - record_first.next = record_arch_list_head; - record_arch_list_head->prev = &record_first; - record_arch_list_tail->next = NULL; - record_list = &record_first; - - /* Update record_insn_max_num. */ - if (record_insn_num > record_insn_max_num) - { - record_insn_max_num = record_insn_num; - warning (_("Auto increase record/replay buffer limit to %d."), - record_insn_max_num); - } - - /* Succeeded. */ - printf_filtered (_("Restored records from core file %s.\n"), - bfd_get_filename (core_bfd)); - - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); -} - -/* bfdcore_write -- write bytes into a core file section. */ - -static inline void -bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset) -{ - int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len); - - if (ret) - *offset += len; - else - error (_("Failed to write %d bytes to core file %s ('%s')."), - len, bfd_get_filename (obfd), - bfd_errmsg (bfd_get_error ())); -} - -/* Restore the execution log from a file. We use a modified elf - corefile format, with an extra section for our data. */ - -static void -cmd_record_restore (char *args, int from_tty) -{ - core_file_command (args, from_tty); - record_open (args, from_tty); -} - -static void -record_save_cleanups (void *data) -{ - bfd *obfd = data; - char *pathname = xstrdup (bfd_get_filename (obfd)); - - gdb_bfd_unref (obfd); - unlink (pathname); - xfree (pathname); -} - -/* Save the execution log to a file. We use a modified elf corefile - format, with an extra section for our data. */ - -static void -cmd_record_save (char *args, int from_tty) -{ - char *recfilename, recfilename_buffer[40]; - struct record_entry *cur_record_list; - uint32_t magic; - struct regcache *regcache; - struct gdbarch *gdbarch; - struct cleanup *old_cleanups; - struct cleanup *set_cleanups; - bfd *obfd; - int save_size = 0; - asection *osec = NULL; - int bfd_offset = 0; - - if (strcmp (current_target.to_shortname, "record") != 0) - error (_("This command can only be used with target 'record'.\n" - "Use 'target record' first.\n")); - - if (args && *args) - recfilename = args; - else - { - /* Default recfile name is "gdb_record.PID". */ - snprintf (recfilename_buffer, sizeof (recfilename_buffer), - "gdb_record.%d", PIDGET (inferior_ptid)); - recfilename = recfilename_buffer; - } - - /* Open the save file. */ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n", - recfilename); - - /* Open the output file. */ - obfd = create_gcore_bfd (recfilename); - old_cleanups = make_cleanup (record_save_cleanups, obfd); - - /* Save the current record entry to "cur_record_list". */ - cur_record_list = record_list; - - /* Get the values of regcache and gdbarch. */ - regcache = get_current_regcache (); - gdbarch = get_regcache_arch (regcache); - - /* Disable the GDB operation record. */ - set_cleanups = record_gdb_operation_disable_set (); - - /* Reverse execute to the begin of record list. */ - while (1) - { - /* Check for beginning and end of log. */ - if (record_list == &record_first) - break; - - record_exec_insn (regcache, gdbarch, record_list); - - if (record_list->prev) - record_list = record_list->prev; - } - - /* Compute the size needed for the extra bfd section. */ - save_size = 4; /* magic cookie */ - for (record_list = record_first.next; record_list; - record_list = record_list->next) - switch (record_list->type) - { - case record_end: - save_size += 1 + 4 + 4; - break; - case record_reg: - save_size += 1 + 4 + record_list->u.reg.len; - break; - case record_mem: - save_size += 1 + 4 + 8 + record_list->u.mem.len; - break; - } - - /* Make the new bfd section. */ - osec = bfd_make_section_anyway_with_flags (obfd, "precord", - SEC_HAS_CONTENTS - | SEC_READONLY); - if (osec == NULL) - error (_("Failed to create 'precord' section for corefile %s: %s"), - recfilename, - bfd_errmsg (bfd_get_error ())); - bfd_set_section_size (obfd, osec, save_size); - bfd_set_section_vma (obfd, osec, 0); - bfd_set_section_alignment (obfd, osec, 0); - bfd_section_lma (obfd, osec) = 0; - - /* Save corefile state. */ - write_gcore_file (obfd); - - /* Write out the record log. */ - /* Write the magic code. */ - magic = RECORD_FILE_MAGIC; - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Writing 4-byte magic cookie " - "RECORD_FILE_MAGIC (0x%s)\n", - phex_nz (magic, 4)); - bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset); - - /* Save the entries to recfd and forward execute to the end of - record list. */ - record_list = &record_first; - while (1) - { - /* Save entry. */ - if (record_list != &record_first) - { - uint8_t type; - uint32_t regnum, len, signal, count; - uint64_t addr; - - type = record_list->type; - bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset); - - switch (record_list->type) - { - case record_reg: /* reg */ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Writing register %d (1 " - "plus %lu plus %d bytes)\n", - record_list->u.reg.num, - (unsigned long) sizeof (regnum), - record_list->u.reg.len); - - /* Write regnum. */ - regnum = netorder32 (record_list->u.reg.num); - bfdcore_write (obfd, osec, ®num, - sizeof (regnum), &bfd_offset); - - /* Write regval. */ - bfdcore_write (obfd, osec, record_get_loc (record_list), - record_list->u.reg.len, &bfd_offset); - break; - - case record_mem: /* mem */ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Writing memory %s (1 plus " - "%lu plus %lu plus %d bytes)\n", - paddress (gdbarch, - record_list->u.mem.addr), - (unsigned long) sizeof (addr), - (unsigned long) sizeof (len), - record_list->u.mem.len); - - /* Write memlen. */ - len = netorder32 (record_list->u.mem.len); - bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset); - - /* Write memaddr. */ - addr = netorder64 (record_list->u.mem.addr); - bfdcore_write (obfd, osec, &addr, - sizeof (addr), &bfd_offset); - - /* Write memval. */ - bfdcore_write (obfd, osec, record_get_loc (record_list), - record_list->u.mem.len, &bfd_offset); - break; - - case record_end: - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Writing record_end (1 + " - "%lu + %lu bytes)\n", - (unsigned long) sizeof (signal), - (unsigned long) sizeof (count)); - /* Write signal value. */ - signal = netorder32 (record_list->u.end.sigval); - bfdcore_write (obfd, osec, &signal, - sizeof (signal), &bfd_offset); - - /* Write insn count. */ - count = netorder32 (record_list->u.end.insn_num); - bfdcore_write (obfd, osec, &count, - sizeof (count), &bfd_offset); - break; - } - } - - /* Execute entry. */ - record_exec_insn (regcache, gdbarch, record_list); - - if (record_list->next) - record_list = record_list->next; - else - break; - } - - /* Reverse execute to cur_record_list. */ - while (1) - { - /* Check for beginning and end of log. */ - if (record_list == cur_record_list) - break; - - record_exec_insn (regcache, gdbarch, record_list); - - if (record_list->prev) - record_list = record_list->prev; - } - - do_cleanups (set_cleanups); - gdb_bfd_unref (obfd); - discard_cleanups (old_cleanups); - - /* Succeeded. */ - printf_filtered (_("Saved core file %s with execution log.\n"), - recfilename); -} - -/* record_goto_insn -- rewind the record log (forward or backward, - depending on DIR) to the given entry, changing the program state - correspondingly. */ - -static void -record_goto_insn (struct record_entry *entry, - enum exec_direction_kind dir) -{ - struct cleanup *set_cleanups = record_gdb_operation_disable_set (); - struct regcache *regcache = get_current_regcache (); - struct gdbarch *gdbarch = get_regcache_arch (regcache); - - /* Assume everything is valid: we will hit the entry, - and we will not hit the end of the recording. */ - - if (dir == EXEC_FORWARD) - record_list = record_list->next; - - do - { - record_exec_insn (regcache, gdbarch, record_list); - if (dir == EXEC_REVERSE) - record_list = record_list->prev; - else - record_list = record_list->next; - } while (record_list != entry); - do_cleanups (set_cleanups); -} - -/* "record goto" command. Argument is an instruction number, - as given by "info record". - - Rewinds the recording (forward or backward) to the given instruction. */ - -static void -cmd_record_goto (char *arg, int from_tty) -{ - struct record_entry *p = NULL; - ULONGEST target_insn = 0; - - if (arg == NULL || *arg == '\0') - error (_("Command requires an argument (insn number to go to).")); - - if (strncmp (arg, "start", strlen ("start")) == 0 - || strncmp (arg, "begin", strlen ("begin")) == 0) - { - /* Special case. Find first insn. */ - for (p = &record_first; p != NULL; p = p->next) - if (p->type == record_end) - break; - if (p) - target_insn = p->u.end.insn_num; - } - else if (strncmp (arg, "end", strlen ("end")) == 0) - { - /* Special case. Find last insn. */ - for (p = record_list; p->next != NULL; p = p->next) - ; - for (; p!= NULL; p = p->prev) - if (p->type == record_end) - break; - if (p) - target_insn = p->u.end.insn_num; - } - else - { - /* General case. Find designated insn. */ - target_insn = parse_and_eval_long (arg); - - for (p = &record_first; p != NULL; p = p->next) - if (p->type == record_end && p->u.end.insn_num == target_insn) - break; - } - - if (p == NULL) - error (_("Target insn '%s' not found."), arg); - else if (p == record_list) - error (_("Already at insn '%s'."), arg); - else if (p->u.end.insn_num > record_list->u.end.insn_num) - { - printf_filtered (_("Go forward to insn number %s\n"), - pulongest (target_insn)); - record_goto_insn (p, EXEC_FORWARD); - } - else - { - printf_filtered (_("Go backward to insn number %s\n"), - pulongest (target_insn)); - record_goto_insn (p, EXEC_REVERSE); - } - registers_changed (); - reinit_frame_cache (); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); -} - -/* Provide a prototype to silence -Wmissing-prototypes. */ -extern initialize_file_ftype _initialize_record; - -void -_initialize_record (void) -{ - struct cmd_list_element *c; - - /* Init record_first. */ - record_first.prev = NULL; - record_first.next = NULL; - record_first.type = record_end; - - init_record_ops (); - add_target (&record_ops); - init_record_core_ops (); - add_target (&record_core_ops); +void _initialize_record (); +void +_initialize_record () +{ + struct cmd_list_element *c; add_setshow_zuinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -2990,83 +772,125 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - c = add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &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, - "set record ", 0, &setlist); - add_alias_cmd ("rec", "record", class_obscure, 1, &setlist); - add_prefix_cmd ("record", class_support, show_record_command, - _("Show record options"), &show_record_cmdlist, - "show record ", 0, &showlist); - add_alias_cmd ("rec", "record", class_obscure, 1, &showlist); - add_prefix_cmd ("record", class_support, info_record_command, - _("Info record options"), &info_record_cmdlist, - "info record ", 0, &infolist); - add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + 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); + + cmd_list_element *record_cmd + = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Start recording."), + &record_cmdlist, 0, &cmdlist); + set_cmd_completer (record_cmd, filename_completer); + + add_com_alias ("rec", record_cmd, class_obscure, 1); + + set_show_commands setshow_record_cmds + = add_setshow_prefix_cmd ("record", class_support, + _("Set record options."), + _("Show record options."), + &set_record_cmdlist, &show_record_cmdlist, + &setlist, &showlist); + + + add_alias_cmd ("rec", setshow_record_cmds.set, class_obscure, 1, &setlist); + add_alias_cmd ("rec", setshow_record_cmds.show, class_obscure, 1, &showlist); + + cmd_list_element *info_record_cmd + = add_prefix_cmd ("record", class_support, info_record_command, + _("Info record options."), &info_record_cmdlist, + 0, &infolist); + add_alias_cmd ("rec", info_record_cmd, 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.'."), +Usage: record save [FILENAME]\n\ +Default filename is 'gdb_record.PROCESS_ID'."), &record_cmdlist); set_cmd_completer (c, filename_completer); - c = add_cmd ("restore", class_obscure, cmd_record_restore, - _("Restore the execution log from a file.\n\ -Argument is filename. File must be created with 'record save'."), + cmd_list_element *record_delete_cmd + = add_cmd ("delete", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it \ +anew."), + &record_cmdlist); + add_alias_cmd ("d", record_delete_cmd, class_obscure, 1, &record_cmdlist); + add_alias_cmd ("del", record_delete_cmd, class_obscure, 1, &record_cmdlist); + + cmd_list_element *record_stop_cmd + = add_cmd ("stop", class_obscure, cmd_record_stop, + _("Stop the record/replay target."), &record_cmdlist); - set_cmd_completer (c, filename_completer); + add_alias_cmd ("s", record_stop_cmd, class_obscure, 1, &record_cmdlist); - add_cmd ("delete", class_obscure, cmd_record_delete, - _("Delete the rest of execution log and start recording it anew."), - &record_cmdlist); - add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist); - add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist); - - add_cmd ("stop", class_obscure, cmd_record_stop, - _("Stop the record/replay target."), - &record_cmdlist); - add_alias_cmd ("s", "stop", class_obscure, 1, &record_cmdlist); - - /* Record instructions number limit command. */ - add_setshow_boolean_cmd ("stop-at-limit", no_class, - &record_stop_at_limit, _("\ -Set whether record/replay stops when record/replay buffer becomes full."), _("\ -Show whether record/replay stops when record/replay buffer becomes full."), - _("Default is ON.\n\ -When ON, if the record/replay buffer becomes full, ask user what to do.\n\ -When OFF, if the record/replay buffer becomes full,\n\ -delete the oldest recorded instruction to make room for each new one."), - NULL, NULL, - &set_record_cmdlist, &show_record_cmdlist); - add_setshow_uinteger_cmd ("insn-number-max", no_class, - &record_insn_max_num, - _("Set record/replay buffer limit."), - _("Show record/replay buffer limit."), _("\ -Set the maximum number of instructions to be stored in the\n\ -record/replay buffer. Zero means unlimited. Default is 200000."), - set_record_insn_max_num, - NULL, &set_record_cmdlist, &show_record_cmdlist); - - add_cmd ("goto", class_obscure, cmd_record_goto, _("\ + 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, 1, &record_cmdlist); + + cmd_list_element *record_goto_begin_cmd + = add_cmd ("begin", class_obscure, cmd_record_goto_begin, + _("Go to the beginning of the execution log."), + &record_goto_cmdlist); + add_alias_cmd ("start", record_goto_begin_cmd, 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_setshow_boolean_cmd ("memory-query", no_class, - &record_memory_query, _("\ -Set whether query if PREC cannot record memory change of next instruction."), - _("\ -Show whether query if PREC cannot record memory change of next instruction."), - _("\ -Default is OFF.\n\ -When ON, query if PREC cannot record memory change of next instruction."), - NULL, NULL, - &set_record_cmdlist, &show_record_cmdlist); + 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; }