X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdb%2Frecord.c;h=ff54a8bd0da659e1360c8b8d9b68051b4cd195fb;hb=5aa03310cef09e8c41aaa05152d9570dfe62ba81;hp=24438132c5f117feac891dd1c0ffe20fd88dd02d;hpb=a480d2f6c8f7b90b797f240063b58f07e43ee011;p=platform%2Fupstream%2Fbinutils.git diff --git a/gdb/record.c b/gdb/record.c index 2443813..ff54a8b 100644 --- a/gdb/record.c +++ b/gdb/record.c @@ -1,6 +1,6 @@ /* Process record and replay target for GDB, the GNU debugger. - Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc. + Copyright (C) 2008-2014 Free Software Foundation, Inc. This file is part of GDB. @@ -19,1882 +19,174 @@ #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 "observer.h" +#include "inferior.h" +#include "common/common-utils.h" +#include "cli/cli-utils.h" +#include "disasm.h" -#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. - - Target record has two modes: recording, and replaying. - - 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). - - 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. */ - -#define DEFAULT_RECORD_INSN_MAX_NUM 200000 - -#define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) - -#define RECORD_FILE_MAGIC netorder32(0x20091016) - -/* These are the core structs of the process record functionality. - - A record_entry is a record of the value change of a register - ("record_reg") or a part of memory ("record_mem"). And each - instruction must have a struct record_entry ("record_end") that - indicates that this is the last struct record_entry of this - instruction. - - Each struct record_entry is linked to "record_list" by "prev" and - "next" pointers. */ - -struct record_mem_entry -{ - CORE_ADDR addr; - int len; - /* Set this flag if target memory for this entry - can no longer be accessed. */ - int mem_entry_not_accessible; - union - { - gdb_byte *ptr; - gdb_byte buf[sizeof (gdb_byte *)]; - } u; -}; - -struct record_reg_entry -{ - unsigned short num; - unsigned short len; - union - { - gdb_byte *ptr; - gdb_byte buf[2 * sizeof (gdb_byte *)]; - } u; -}; - -struct record_end_entry -{ - enum target_signal sigval; - ULONGEST insn_num; -}; - -enum record_type -{ - record_end = 0, - record_reg, - record_mem -}; - -/* This is the data structure that makes up the execution log. - - The execution log consists of a single linked list of entries - of type "struct record_entry". It is doubly linked so that it - can be traversed in either direction. - - The start of the list is anchored by a struct called - "record_first". The pointer "record_list" either points to the - last entry that was added to the list (in record mode), or to the - next entry in the list that will be executed (in replay mode). - - Each list element (struct record_entry), in addition to next and - prev pointers, consists of a union of three entry types: mem, reg, - and end. A field called "type" determines which entry type is - represented by a given list element. - - Each instruction that is added to the execution log is represented - by a variable number of list elements ('entries'). The instruction - will have one "reg" entry for each register that is changed by - executing the instruction (including the PC in every case). It - will also have one "mem" entry for each memory change. Finally, - each instruction will have an "end" entry that separates it from - the changes associated with the next instruction. */ - -struct record_entry -{ - struct record_entry *prev; - struct record_entry *next; - enum record_type type; - union - { - /* reg */ - struct record_reg_entry reg; - /* mem */ - struct record_mem_entry mem; - /* end */ - struct record_end_entry end; - } u; -}; +#include /* This is the debug switch for process record. */ -int record_debug = 0; - -/* If true, query if PREC cannot record memory - change of next instruction. */ -int record_memory_query = 0; - -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 target_signal); -static struct target_ops *record_beneath_to_wait_ops; -static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, - struct target_waitstatus *, - int); -static struct target_ops *record_beneath_to_store_registers_ops; -static void (*record_beneath_to_store_registers) (struct target_ops *, - struct regcache *, - int regno); -static struct target_ops *record_beneath_to_xfer_partial_ops; -static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops, - enum target_object object, - const char *annex, - gdb_byte *readbuf, - const gdb_byte *writebuf, - ULONGEST offset, - LONGEST len); -static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *, - struct bp_target_info *); -static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, - struct bp_target_info *); -static int (*record_beneath_to_stopped_by_watchpoint) (void); -static int (*record_beneath_to_stopped_data_address) (struct target_ops *, - CORE_ADDR *); - -/* Alloc and free functions for record_reg, record_mem, and record_end - entries. */ - -/* Alloc a record_reg record entry. */ - -static inline struct record_entry * -record_reg_alloc (struct regcache *regcache, int regnum) -{ - struct record_entry *rec; - struct gdbarch *gdbarch = get_regcache_arch (regcache); - - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_reg; - rec->u.reg.num = regnum; - rec->u.reg.len = register_size (gdbarch, regnum); - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len); - - return rec; -} - -/* Free a record_reg record entry. */ - -static inline void -record_reg_release (struct record_entry *rec) -{ - gdb_assert (rec->type == record_reg); - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - xfree (rec->u.reg.u.ptr); - xfree (rec); -} +unsigned int record_debug = 0; -/* Alloc a record_mem record entry. */ +/* The number of instructions to print in "record instruction-history". */ +static unsigned int record_insn_history_size = 10; -static inline struct record_entry * -record_mem_alloc (CORE_ADDR addr, int len) -{ - struct record_entry *rec; - - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_mem; - rec->u.mem.addr = addr; - rec->u.mem.len = len; - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len); - - return rec; -} - -/* Free a record_mem record entry. */ - -static inline void -record_mem_release (struct record_entry *rec) -{ - gdb_assert (rec->type == record_mem); - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - xfree (rec->u.mem.u.ptr); - xfree (rec); -} - -/* Alloc a record_end record entry. */ - -static inline struct record_entry * -record_end_alloc (void) -{ - struct record_entry *rec; - - rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry)); - rec->type = record_end; - - return rec; -} +/* 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; -/* Free a record_end record entry. */ +/* The number of functions to print in "record function-call-history". */ +static unsigned int record_call_history_size = 10; -static inline void -record_end_release (struct record_entry *rec) -{ - xfree (rec); -} +/* 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; -/* Free one record entry, any type. - Return entry->type, in case caller wants to know. */ +struct cmd_list_element *record_cmdlist = NULL; +struct cmd_list_element *record_goto_cmdlist = NULL; +struct cmd_list_element *set_record_cmdlist = NULL; +struct cmd_list_element *show_record_cmdlist = NULL; +struct cmd_list_element *info_record_cmdlist = NULL; -static inline enum record_type -record_entry_release (struct record_entry *rec) -{ - enum record_type type = rec->type; - - switch (type) { - case record_reg: - record_reg_release (rec); - break; - case record_mem: - record_mem_release (rec); - break; - case record_end: - record_end_release (rec); - break; - } - return type; -} +#define DEBUG(msg, args...) \ + if (record_debug) \ + fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args) -/* Free all record entries in list pointed to by REC. */ +/* See record.h. */ -static void -record_list_release (struct record_entry *rec) +struct target_ops * +find_record_target (void) { - if (!rec) - return; - - while (rec->next) - rec = rec->next; - - while (rec->prev) - { - rec = rec->prev; - record_entry_release (rec->next); - } - - if (rec == &record_first) - { - record_insn_num = 0; - record_first.next = NULL; - } - else - record_entry_release (rec); + return find_target_at (record_stratum); } -/* Free all record entries forward of the given list position. */ +/* Check that recording is active. Throw an error, if it isn't. */ -static void -record_list_release_following (struct record_entry *rec) +static struct target_ops * +require_record_target (void) { - struct record_entry *tmp = rec->next; - - rec->next = NULL; - while (tmp) - { - rec = tmp->next; - if (record_entry_release (tmp) == record_end) - { - record_insn_num--; - record_insn_count--; - } - tmp = rec; - } -} - -/* 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. */ - -static void -record_list_release_first (void) -{ - struct record_entry *tmp; - - if (!record_first.next) - return; - - /* Loop until a record_end. */ - while (1) - { - /* Cut record_first.next out of the linked list. */ - tmp = record_first.next; - record_first.next = tmp->next; - tmp->next->prev = &record_first; - - /* 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. */ - } - } -} - -/* Add a struct record_entry to record_arch_list. */ - -static void -record_arch_list_add (struct record_entry *rec) -{ - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_arch_list_add %s.\n", - host_address_to_string (rec)); - - if (record_arch_list_tail) - { - record_arch_list_tail->next = rec; - rec->prev = record_arch_list_tail; - record_arch_list_tail = rec; - } - else - { - record_arch_list_head = rec; - record_arch_list_tail = rec; - } -} - -/* Return the value storage location of a record entry. */ -static inline gdb_byte * -record_get_loc (struct record_entry *rec) -{ - switch (rec->type) { - case record_mem: - if (rec->u.mem.len > sizeof (rec->u.mem.u.buf)) - return rec->u.mem.u.ptr; - else - return rec->u.mem.u.buf; - case record_reg: - if (rec->u.reg.len > sizeof (rec->u.reg.u.buf)) - return rec->u.reg.u.ptr; - else - return rec->u.reg.u.buf; - case record_end: - default: - gdb_assert (0); - return NULL; - } -} - -/* Record the value of a register NUM to record_arch_list. */ - -int -record_arch_list_add_reg (struct regcache *regcache, int regnum) -{ - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add register num = %d to " - "record list.\n", - regnum); - - rec = record_reg_alloc (regcache, regnum); - - regcache_raw_read (regcache, regnum, record_get_loc (rec)); + struct target_ops *t; - record_arch_list_add (rec); + t = find_record_target (); + if (t == NULL) + error (_("No record target is currently active.\n" + "Use one of the \"target record-\" commands first.")); - return 0; + return t; } -/* Record the value of a region of memory whose address is ADDR and - length is LEN to record_arch_list. */ +/* See record.h. */ -int -record_arch_list_add_mem (CORE_ADDR addr, int len) +void +record_preopen (void) { - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add mem addr = %s len = %d to " - "record list.\n", - paddress (target_gdbarch, addr), len); - - if (!addr) /* FIXME: Why? Some arch must permit it... */ - return 0; - - rec = record_mem_alloc (addr, len); - - if (target_read_memory (addr, record_get_loc (rec), len)) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: error reading memory at " - "addr = %s len = %d.\n", - paddress (target_gdbarch, addr), len); - record_mem_release (rec); - return -1; - } - - record_arch_list_add (rec); - - return 0; + /* 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.")); } -/* Add a record_end type struct record_entry to record_arch_list. */ +/* See record.h. */ int -record_arch_list_add_end (void) -{ - struct record_entry *rec; - - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: add end to arch list.\n"); - - rec = record_end_alloc (); - rec->u.end.sigval = TARGET_SIGNAL_0; - rec->u.end.insn_num = ++record_insn_count; - - record_arch_list_add (rec); - - return 0; -} - -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.")); - } - } - } -} - -static void -record_arch_list_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - -/* 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. */ - -static int -record_message (struct regcache *regcache, enum target_signal signal) -{ - int ret; - struct gdbarch *gdbarch = get_regcache_arch (regcache); - struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); - - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - - /* Check record_insn_num. */ - record_check_insn_num (1); - - /* If gdb sends a signal value to target_resume, - save it in the 'end' field of the previous instruction. - - Maybe process record should record what really happened, - rather than what gdb pretends has happened. - - So if Linux delivered the signal to the child process during - the record mode, we will record it and deliver it again in - the replay mode. - - If user says "ignore this signal" during the record mode, then - it will be ignored again during the replay mode (no matter if - the user says something different, like "deliver this signal" - during the replay mode). - - User should understand that nothing he does during the replay - mode will change the behavior of the child. If he tries, - then that is a user error. - - 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; - } - - if (signal == TARGET_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); - - if (ret > 0) - error (_("Process record: inferior program stopped.")); - if (ret < 0) - error (_("Process record: failed to record execution log.")); - - discard_cleanups (old_cleanups); - - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; - - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; - - return 1; -} - -struct record_message_args { - struct regcache *regcache; - enum target_signal signal; -}; - -static int -record_message_wrapper (void *args) -{ - struct record_message_args *record_args = args; - - return record_message (record_args->regcache, record_args->signal); -} - -static int -record_message_wrapper_safe (struct regcache *regcache, - enum target_signal signal) -{ - struct record_message_args args; - - args.regcache = regcache; - args.signal = signal; - - return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL); -} - -/* Set to 1 if record_store_registers and record_xfer_partial - doesn't need record. */ - -static int record_gdb_operation_disable = 0; - -struct cleanup * -record_gdb_operation_disable_set (void) +record_read_memory (struct gdbarch *gdbarch, + CORE_ADDR memaddr, gdb_byte *myaddr, + ssize_t len) { - struct cleanup *old_cleanups = NULL; + int ret = target_read_memory (memaddr, myaddr, len); - old_cleanups = - make_cleanup_restore_integer (&record_gdb_operation_disable); - record_gdb_operation_disable = 1; + if (ret != 0) + DEBUG ("error reading memory at addr %s len = %ld.\n", + paddress (gdbarch, memaddr), (long) len); - return old_cleanups; -} - -/* Flag set to TRUE for target_stopped_by_watchpoint. */ -static int record_hw_watchpoint = 0; - -/* 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. */ - -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 (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) - { - entry->u.mem.mem_entry_not_accessible = 1; - if (record_debug) - warning ("Process record: error reading memory at " - "addr = %s len = %d.", - paddress (gdbarch, entry->u.mem.addr), - entry->u.mem.len); - } - else - { - if (target_write_memory (entry->u.mem.addr, - record_get_loc (entry), - entry->u.mem.len)) - { - entry->u.mem.mem_entry_not_accessible = 1; - if (record_debug) - warning ("Process record: error writing memory at " - "addr = %s len = %d.", - paddress (gdbarch, entry->u.mem.addr), - entry->u.mem.len); - } - else - { - memcpy (record_get_loc (entry), mem, entry->u.mem.len); - - /* 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; - } -} - -static struct target_ops *tmp_to_resume_ops; -static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, - enum target_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 void record_restore (void); - -/* Open the process record target. */ - -static void -record_core_open_1 (char *name, int from_tty) -{ - 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 ())); - } - - push_target (&record_core_ops); - record_restore (); -} - -/* "to_open" target method for 'live' processes. */ - -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 (target_async_permitted) - error (_("Process record target can't debug inferior in asynchronous " - "mode (target-async).")); - - if (!gdbarch_process_record_p (target_gdbarch)) - error (_("Process record: the current architecture doesn't support " - "record function.")); - - 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); -} - -/* "to_open" target method. Open the process record target. */ - -static void -record_open (char *name, int from_tty) -{ - struct target_ops *t; - - 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; - - /* 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_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; - - if (current_target.to_stratum == core_stratum) - record_core_open_1 (name, from_tty); - else - record_open_1 (name, from_tty); -} - -/* "to_close" target method. Close the process record target. */ - -static void -record_close (int quitting) -{ - struct record_core_buf_entry *entry; - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); - - record_list_release (record_list); - - /* Release record_core_regbuf. */ - if (record_core_regbuf) - { - xfree (record_core_regbuf); - record_core_regbuf = NULL; - } - - /* Release record_core_buf_list. */ - if (record_core_buf_list) - { - for (entry = record_core_buf_list->prev; entry; entry = entry->prev) - { - xfree (record_core_buf_list); - record_core_buf_list = entry; - } - record_core_buf_list = NULL; - } + return ret; } -static int record_resume_step = 0; - -/* "to_resume" target method. Resume the process record target. */ +/* Stop recording. */ static void -record_resume (struct target_ops *ops, ptid_t ptid, int step, - enum target_signal signal) +record_stop (struct target_ops *t) { - record_resume_step = step; + DEBUG ("stop %s", t->to_shortname); - if (!RECORD_IS_REPLAY) - { - record_message (get_current_regcache (), signal); - record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, - signal); - } + t->to_stop_recording (t); } -static int record_get_sig = 0; - -/* SIGINT signal handler, registered by "to_wait" method. */ - -static void -record_sig_handler (int signo) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); - - /* It will break the running inferior in replay mode. */ - record_resume_step = 1; - - /* It will let record_wait set inferior status to get the signal - SIGINT. */ - record_get_sig = 1; -} +/* Unpush the record target. */ static void -record_wait_cleanups (void *ignore) +record_unpush (struct target_ops *t) { - if (execution_direction == EXEC_REVERSE) - { - if (record_list->next) - record_list = record_list->next; - } - else - record_list = record_list->prev; -} - -/* "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. */ - -static ptid_t -record_wait (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *status, - int options) -{ - struct cleanup *set_cleanups = record_gdb_operation_disable_set (); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "record_resume_step = %d\n", - record_resume_step); - - 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; - - while (1) - { - ret = record_beneath_to_wait (record_beneath_to_wait_ops, - ptid, status, options); - - if (record_resume_step) - return ret; - - /* Is this a SIGTRAP? */ - if (status->kind == TARGET_WAITKIND_STOPPED - && status->value.sig == TARGET_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 must be a single-step trap. Record the - insn and issue another step. */ - if (!record_message_wrapper_safe (regcache, - TARGET_SIGNAL_0)) - { - status->kind = TARGET_WAITKIND_STOPPED; - status->value.sig = TARGET_SIGNAL_0; - break; - } - - record_beneath_to_resume (record_beneath_to_resume_ops, - ptid, 1, - TARGET_SIGNAL_0); - continue; - } - } - - /* The inferior is broken by a breakpoint or a signal. */ - break; - } - - 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 != TARGET_SIGNAL_0) - /* FIXME: better way to check */ - continue_flag = 0; - } - } - - if (continue_flag) - { - if (execution_direction == EXEC_REVERSE) - { - if (record_list->prev) - record_list = record_list->prev; - } - else - { - if (record_list->next) - record_list = record_list->next; - } - } - } - while (continue_flag); - -replay_out: - if (record_get_sig) - status->value.sig = TARGET_SIGNAL_INT; - else if (record_list->u.end.sigval != TARGET_SIGNAL_0) - /* FIXME: better way to check */ - status->value.sig = record_list->u.end.sigval; - else - status->value.sig = TARGET_SIGNAL_TRAP; + DEBUG ("unpush %s", t->to_shortname); - discard_cleanups (old_cleanups); - } - - signal (SIGINT, handle_sigint); - - do_cleanups (set_cleanups); - return inferior_ptid; + unpush_target (t); } -static int -record_stopped_by_watchpoint (void) -{ - if (RECORD_IS_REPLAY) - return record_hw_watchpoint; - else - return record_beneath_to_stopped_by_watchpoint (); -} +/* See record.h. */ -static int -record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p) +void +record_disconnect (struct target_ops *t, const char *args, int from_tty) { - if (RECORD_IS_REPLAY) - return 0; - else - return record_beneath_to_stopped_data_address (ops, addr_p); -} + gdb_assert (t->to_stratum == record_stratum); -/* "to_disconnect" method for process record target. */ + DEBUG ("disconnect %s", t->to_shortname); -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"); + record_stop (t); + record_unpush (t); - 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); -} - -/* Behavior is conditional on RECORD_IS_REPLAY. - We will not actually insert or remove breakpoints when replaying, - nor when recording. */ - -static int -record_insert_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - if (!RECORD_IS_REPLAY) - { - struct cleanup *old_cleanups = record_gdb_operation_disable_set (); - int ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt); - - do_cleanups (old_cleanups); - - return ret; - } - - return 0; -} - -/* "to_remove_breakpoint" method for process record target. */ - -static int -record_remove_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - if (!RECORD_IS_REPLAY) - { - struct cleanup *old_cleanups = record_gdb_operation_disable_set (); - int ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt); - - do_cleanups (old_cleanups); - - return ret; - } - - return 0; -} - -/* "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; +/* See record.h. */ - /* 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 -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_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 target_signal signal) -{ - record_resume_step = step; -} - -/* "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) +void +record_detach (struct target_ops *t, const char *args, int from_tty) { - 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); -} + gdb_assert (t->to_stratum == record_stratum); -/* "to_prepare_to_store" method for prec over corefile. */ + DEBUG ("detach %s", t->to_shortname); -static void -record_core_prepare_to_store (struct regcache *regcache) -{ -} - -/* "to_store_registers" method for prec over corefile. */ + record_stop (t); + record_unpush (t); -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.")); + target_detach (args, from_tty); } -/* "to_xfer_partial" method for prec over corefile. */ +/* See record.h. */ -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) +void +record_mourn_inferior (struct target_ops *t) { - 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.")); - } + gdb_assert (t->to_stratum == record_stratum); - return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, - object, annex, readbuf, writebuf, - offset, len); -} + DEBUG ("mourn inferior %s", t->to_shortname); -/* "to_insert_breakpoint" method for prec over corefile. */ + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); -static int -record_core_insert_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - return 0; + target_mourn_inferior (); } -/* "to_remove_breakpoint" method for prec over corefile. */ +/* See record.h. */ -static int -record_core_remove_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) +void +record_kill (struct target_ops *t) { - return 0; -} + gdb_assert (t->to_stratum == record_stratum); -/* "to_has_execution" method for prec over corefile. */ + DEBUG ("kill %s", t->to_shortname); -int -record_core_has_execution (struct target_ops *ops) -{ - return 1; -} + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); -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_magic = OPS_MAGIC; + target_kill (); } /* Implement "show record debug" command. */ @@ -1912,7 +204,7 @@ show_record_debug (struct ui_file *file, int from_tty, static void cmd_record_start (char *args, int from_tty) { - execute_command ("target record", from_tty); + execute_command ("target record-full", from_tty); } /* Truncate the record log from the present point @@ -1921,21 +213,25 @@ cmd_record_start (char *args, int from_tty) static void cmd_record_delete (char *args, int from_tty) { - if (current_target.to_stratum == record_stratum) + require_record_target (); + + if (!target_record_is_replaying ()) { - 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")); + printf_unfiltered (_("Already at end of record list.\n")); + return; + } + if (!target_supports_delete_record ()) + { + printf_unfiltered (_("The current record target does not support " + "this operation.\n")); + return; } - else - printf_unfiltered (_("Process record is not started.\n")); + + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + target_delete_record (); } /* Implement the "stoprecord" or "record stop" command. */ @@ -1943,1281 +239,514 @@ cmd_record_delete (char *args, int from_tty) 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")); -} + struct target_ops *t; -/* Set upper limit of record log size. */ + t = require_record_target (); -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--; - } - } + record_stop (t); + record_unpush (t); + + printf_unfiltered (_("Process record is stopped and all execution " + "logs are deleted.\n")); + + observer_notify_record_changed (current_inferior (), 0); } -static struct cmd_list_element *record_cmdlist, *set_record_cmdlist, - *show_record_cmdlist, *info_record_cmdlist; +/* The "set record" command. */ static void set_record_command (char *args, int from_tty) { - printf_unfiltered (_("\ -\"set record\" must be followed by an apporpriate subcommand.\n")); + printf_unfiltered (_("\"set record\" must be followed " + "by an apporpriate subcommand.\n")); help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout); } +/* The "show record" command. */ + static void show_record_command (char *args, int from_tty) { cmd_show_list (show_record_cmdlist, from_tty, ""); } -/* Display some statistics about the execution log. */ +/* The "info record" command. */ static void info_record_command (char *args, int from_tty) { - struct record_entry *p; + struct target_ops *t; - if (current_target.to_stratum == record_stratum) + t = find_record_target (); + if (t == NULL) { - if (RECORD_IS_REPLAY) - printf_filtered (_("Replay mode:\n")); - else - printf_filtered (_("Record mode:\n")); + printf_filtered (_("No record target is currently active.\n")); + return; + } - /* Find entry for first actual instruction in the log. */ - for (p = record_first.next; - p != NULL && p->type != record_end; - p = p->next) - ; + printf_filtered (_("Active record target: %s\n"), t->to_shortname); + t->to_info_record (t); +} - /* 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")); - } - } +/* The "record save" command. */ + +static void +cmd_record_save (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + + require_record_target (); + + if (args != NULL && *args != 0) + recfilename = args; else { - printf_filtered (_("target record is not active.\n")); + /* Default recfile name is "gdb_record.PID". */ + xsnprintf (recfilename_buffer, sizeof (recfilename_buffer), + "gdb_record.%d", ptid_get_pid (inferior_ptid)); + recfilename = recfilename_buffer; } - /* Display max log size. */ - printf_filtered (_("Max logged instructions is %d.\n"), - record_insn_max_num); + target_save_record (recfilename); } -/* 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) +/* See record.h. */ + +void +record_goto (const char *arg) { - int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); + ULONGEST insn; - if (ret) - *offset += len; - else - error (_("Failed to read %d bytes from core file %s ('%s').\n"), - len, bfd_get_filename (obfd), - bfd_errmsg (bfd_get_error ())); -} + if (arg == NULL || *arg == '\0') + error (_("Command requires an argument (insn number to go to).")); -static inline uint64_t -netorder64 (uint64_t input) -{ - uint64_t ret; + insn = parse_and_eval_long (arg); - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; + require_record_target (); + target_goto_record (insn); } -static inline uint32_t -netorder32 (uint32_t input) -{ - uint32_t ret; +/* "record goto" command. Argument is an instruction number, + as given by "info record". - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; + Rewinds the recording (forward or backward) to the given instruction. */ + +static void +cmd_record_goto (char *arg, int from_tty) +{ + record_goto (arg); } -static inline uint16_t -netorder16 (uint16_t input) +/* The "record goto begin" command. */ + +static void +cmd_record_goto_begin (char *arg, int from_tty) { - uint16_t ret; + if (arg != NULL && *arg != '\0') + error (_("Junk after argument: %s."), arg); - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; + require_record_target (); + target_goto_record_begin (); } -/* Restore the execution log from a core_bfd file. */ +/* The "record goto end" command. */ + static void -record_restore (void) +cmd_record_goto_end (char *arg, int from_tty) { - 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"); - osec_size = bfd_section_size (core_bfd, osec); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n", - osec ? "succeeded" : "failed"); - if (osec == NULL) - return; - 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); - } + if (arg != NULL && *arg != '\0') + error (_("Junk after argument: %s."), arg); - discard_cleanups (old_cleanups); + require_record_target (); + target_goto_record_end (); +} - /* 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; +/* Read an instruction number from an argument string. */ - /* 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); - } +static ULONGEST +get_insn_number (char **arg) +{ + ULONGEST number; + const char *begin, *end, *pos; - /* Succeeded. */ - printf_filtered (_("Restored records from core file %s.\n"), - bfd_get_filename (core_bfd)); + begin = *arg; + pos = skip_spaces_const (begin); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); -} + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); -/* bfdcore_write -- write bytes into a core file section. */ + number = strtoulst (pos, &end, 10); -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); + *arg += (end - begin); - if (ret) - *offset += len; - else - error (_("Failed to write %d bytes to core file %s ('%s').\n"), - len, bfd_get_filename (obfd), - bfd_errmsg (bfd_get_error ())); + return number; } -/* Restore the execution log from a file. We use a modified elf - corefile format, with an extra section for our data. */ +/* Read a context size from an argument string. */ -static void -cmd_record_restore (char *args, int from_tty) +static int +get_context_size (char **arg) { - core_file_command (args, from_tty); - record_open (args, from_tty); -} + char *pos; + int number; -static void -record_save_cleanups (void *data) -{ - bfd *obfd = data; - char *pathname = xstrdup (bfd_get_filename (obfd)); + pos = skip_spaces (*arg); - bfd_close (obfd); - unlink (pathname); - xfree (pathname); + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); + + return strtol (pos, arg, 10); } -/* Save the execution log to a file. We use a modified elf corefile - format, with an extra section for our data. */ +/* Complain about junk at the end of an argument string. */ static void -cmd_record_save (char *args, int from_tty) +no_chunk (char *arg) { - 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); + if (*arg != 0) + error (_("Junk after argument: %s."), arg); +} - /* Open the output file. */ - obfd = create_gcore_bfd (recfilename); - old_cleanups = make_cleanup (record_save_cleanups, obfd); +/* Read instruction-history modifiers from an argument string. */ - /* Save the current record entry to "cur_record_list". */ - cur_record_list = record_list; +static int +get_insn_history_modifiers (char **arg) +{ + int modifiers; + char *args; - /* Get the values of regcache and gdbarch. */ - regcache = get_current_regcache (); - gdbarch = get_regcache_arch (regcache); + modifiers = 0; + args = *arg; - /* Disable the GDB operation record. */ - set_cleanups = record_gdb_operation_disable_set (); + if (args == NULL) + return modifiers; - /* Reverse execute to the begin of record list. */ - while (1) + while (*args == '/') { - /* Check for beginning and end of log. */ - if (record_list == &record_first) - break; - - record_exec_insn (regcache, gdbarch, record_list); + ++args; - if (record_list->prev) - record_list = record_list->prev; - } + if (*args == '\0') + error (_("Missing modifier.")); - /* 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; - } + for (; *args; ++args) + { + if (isspace (*args)) + break; - /* Reverse execute to cur_record_list. */ - while (1) - { - /* Check for beginning and end of log. */ - if (record_list == cur_record_list) - break; + if (*args == '/') + continue; - record_exec_insn (regcache, gdbarch, record_list); + switch (*args) + { + case 'm': + modifiers |= DISASSEMBLY_SOURCE; + modifiers |= DISASSEMBLY_FILENAME; + break; + case 'r': + modifiers |= DISASSEMBLY_RAW_INSN; + break; + case 'f': + modifiers |= DISASSEMBLY_OMIT_FNAME; + break; + case 'p': + modifiers |= DISASSEMBLY_OMIT_PC; + break; + default: + error (_("Invalid modifier: %c."), *args); + } + } - if (record_list->prev) - record_list = record_list->prev; + args = skip_spaces (args); } - do_cleanups (set_cleanups); - bfd_close (obfd); - discard_cleanups (old_cleanups); + /* Update the argument string. */ + *arg = args; - /* Succeeded. */ - printf_filtered (_("Saved core file %s with execution log.\n"), - recfilename); + return modifiers; } -/* For "record pic" command. */ - -static struct cmd_list_element *set_record_pic_cmdlist, - *show_record_pic_cmdlist; +/* The "set record instruction-history-size / set record + function-call-history-size" commands are unsigned, with UINT_MAX + meaning unlimited. The target interfaces works with signed int + though, to indicate direction, so map "unlimited" to INT_MAX, which + is about the same as unlimited in practice. If the user does have + a log that huge, she can fetch it in chunks across several requests, + but she'll likely have other problems first... */ -static void -set_record_pic_command (char *args, int from_tty) +static int +command_size_to_target_size (unsigned int size) { - printf_unfiltered (_("\ -\"set record pic\" must be followed by an apporpriate subcommand.\n")); - help_list (set_record_cmdlist, "set record pic ", all_commands, gdb_stdout); -} + gdb_assert (size <= INT_MAX || size == UINT_MAX); -static void -show_record_pic_command (char *args, int from_tty) -{ - cmd_show_list (show_record_pic_cmdlist, from_tty, ""); + if (size == UINT_MAX) + return INT_MAX; + else + return size; } -static const char record_pic_function[] = "function"; -static const char record_pic_line[] = "line"; -static const char *record_pic_enum[] = -{ - record_pic_function, - record_pic_line, - NULL, -}; -static const char *set_record_pic_type = record_pic_line; - -static int record_pic_hide_nofunction = 1; -static int record_pic_hide_nosource = 1; -static int record_pic_hide_same = 1; +/* The "record instruction-history" command. */ static void -record_pic_fputs (FILE *fp, const char *buf) +cmd_record_insn_history (char *arg, int from_tty) { - if (fputs (buf, fp) == EOF) - error (_("Write to file error.")); -} + int flags, size; -struct function_list -{ - struct function_list *next; - CORE_ADDR addr; - int fid; -}; -struct node_list -{ - struct node_list *next; - int count; - CORE_ADDR addr; - int showall; - struct symtab *symtab; - int line; - struct minimal_symbol *function; - int fid; -}; -struct edge_list -{ - struct edge_list *next; - int count; - struct node_list *s; - struct node_list *t; - int frame_diff; - int is_return; -}; -struct function_list *function_list = NULL; -struct node_list *node_list = NULL; -struct edge_list *edge_list = NULL; + require_record_target (); -static void -record_pic_cleanups (void *data) -{ - FILE *fp = data; - struct function_list *fl, *fl2; - struct node_list *nl, *nl2; - struct edge_list *el, *el2; + flags = get_insn_history_modifiers (&arg); - fl = function_list; - while (fl) - { - fl2 = fl; - fl = fl->next; - xfree (fl2); - } - function_list = NULL; + size = command_size_to_target_size (record_insn_history_size); - nl = node_list; - while (nl) + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) + target_insn_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_insn_history (-size, flags); + else { - nl2 = nl; - nl = nl->next; - xfree (nl2); - } - node_list = NULL; + ULONGEST begin, end; - el = edge_list; - while (el) - { - el2 = el; - el = el->next; - xfree (el2); - } - edge_list = NULL; + begin = get_insn_number (&arg); - fclose (fp); -} + if (*arg == ',') + { + arg = skip_spaces (++arg); -static void -record_pic_node (char *buf, int buf_max, struct gdbarch *gdbarch, - const char *type, struct node_list *nlp) -{ - if (type == record_pic_function) - { - snprintf (buf, buf_max, "%s %s %s", - (nlp->symtab) ? nlp->symtab->filename : "", - (nlp->function) ? SYMBOL_LINKAGE_NAME (nlp->function) : "", - (!nlp->function) ? paddress (gdbarch, nlp->addr) : ""); - } - else - { - if (nlp->showall) - { - snprintf (buf, buf_max, "%s:%d %s %s", nlp->symtab->filename, - nlp->line, - (nlp->function) ? SYMBOL_LINKAGE_NAME (nlp->function) : "", - paddress (gdbarch, nlp->addr)); - } + if (*arg == '+') + { + arg += 1; + size = get_context_size (&arg); + + no_chunk (arg); + + target_insn_history_from (begin, size, flags); + } + else if (*arg == '-') + { + arg += 1; + size = get_context_size (&arg); + + no_chunk (arg); + + target_insn_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); + + no_chunk (arg); + + target_insn_history_range (begin, end, flags); + } + } else - { - if (nlp->symtab) - snprintf (buf, buf_max, "%s %d %s", - (nlp->function) ? SYMBOL_LINKAGE_NAME (nlp->function) : "", - nlp->line, paddress (gdbarch, nlp->addr)); - else - snprintf (buf, buf_max, "%s %s", - (nlp->function) ? SYMBOL_LINKAGE_NAME (nlp->function) : "", - paddress (gdbarch, nlp->addr)); - } + { + no_chunk (arg); + + target_insn_history_from (begin, size, flags); + } + + dont_repeat (); } } -static void -record_pic_edge (char *buf, int buf_max, struct edge_list *elp, - char *node, char *prev_node) +/* Read function-call-history modifiers from an argument string. */ + +static int +get_call_history_modifiers (char **arg) { - if (elp->frame_diff) + int modifiers; + char *args; + + modifiers = 0; + args = *arg; + + if (args == NULL) + return modifiers; + + while (*args == '/') { - if (elp->is_return) - snprintf (buf, buf_max, "edge: {color:blue sourcename: \"%s\" " - "targetname: \"%s\"", - prev_node, node); - else - snprintf (buf, buf_max, "edge: {color:red sourcename: \"%s\" " - "targetname: \"%s\"", - prev_node, node); + ++args; + + if (*args == '\0') + error (_("Missing modifier.")); + + for (; *args; ++args) + { + if (isspace (*args)) + break; + + if (*args == '/') + continue; + + switch (*args) + { + case 'l': + modifiers |= RECORD_PRINT_SRC_LINE; + break; + case 'i': + modifiers |= RECORD_PRINT_INSN_RANGE; + break; + case 'c': + modifiers |= RECORD_PRINT_INDENT_CALLS; + break; + default: + error (_("Invalid modifier: %c."), *args); + } + } + + args = skip_spaces (args); } - else - snprintf (buf, buf_max, - "edge: {sourcename: \"%s\" targetname: \"%s\"", - prev_node, node); + + /* Update the argument string. */ + *arg = args; + + return modifiers; } -/* Save the execution log to a vcg file. */ +/* The "record function-call-history" command. */ static void -cmd_record_pic (char *args, int from_tty) +cmd_record_call_history (char *arg, int from_tty) { - char *recfilename, recfilename_buffer[40]; - FILE *fp; - struct cleanup *old_cleanups, *set_cleanups; - struct regcache *regcache; - struct gdbarch *gdbarch; - struct record_entry *cur_record_list; - char prev_node[256], line[256]; - CORE_ADDR prev_addr; - struct frame_id fi, caller_fi, prev_fi, prev_caller_fi; - struct function_list *function_list_tail, *function_list_prev; - struct edge_list *edge_list_tail = NULL; - struct node_list *node_list_tail = NULL; - struct symtab_and_line sal, prev_sal; - struct node_list *prev_nlp; - struct node_list prev_nlp_real; - int fid_count = 1; - - /* Check if record target is running. */ - if (current_target.to_stratum != record_stratum) - error (_("This command can only be used with target 'record' \ -or target 'record-core'.")); - - if (args && *args) - recfilename = args; + int flags, size; + + require_record_target (); + + flags = get_call_history_modifiers (&arg); + + size = command_size_to_target_size (record_call_history_size); + + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) + target_call_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_call_history (-size, flags); else { - /* Default recfile name is "gdb_record_PID.vcg". */ - snprintf (recfilename_buffer, sizeof (recfilename_buffer), - "gdb_record_%d.vcg", PIDGET (inferior_ptid)); - recfilename = recfilename_buffer; - } + ULONGEST begin, end; - /* Open the output file. */ - fp = fopen (recfilename, "wb"); - if (!fp) - error (_("Unable to open file '%s'"), recfilename); + begin = get_insn_number (&arg); - old_cleanups = make_cleanup (record_pic_cleanups, fp); + if (*arg == ',') + { + arg = skip_spaces (++arg); - /* Save the current record entry to "cur_record_list". */ - cur_record_list = record_list; + if (*arg == '+') + { + arg += 1; + size = get_context_size (&arg); - /* Get the values of regcache and gdbarch. */ - regcache = get_current_regcache (); - gdbarch = get_regcache_arch (regcache); + no_chunk (arg); - /* Disable the GDB operation record. */ - set_cleanups = record_gdb_operation_disable_set (); + target_call_history_from (begin, size, flags); + } + else if (*arg == '-') + { + arg += 1; + size = get_context_size (&arg); - /* Reverse execute to the begin of record list. */ - while (1) - { - /* Check for beginning and end of log. */ - if (record_list == &record_first) - break; + no_chunk (arg); - record_exec_insn (regcache, gdbarch, record_list); + target_call_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); - if (record_list->prev) - record_list = record_list->prev; - } + no_chunk (arg); - /* Write out the record log. */ - /* Write the head. */ - record_pic_fputs (fp, "graph: {title: \"GDB process record\"\n"); - - /* Write the first node. */ - record_pic_fputs (fp, "node: {title: \"[BEGIN]\" vertical_order:0}\n"); - - /* Initialization. */ - snprintf (prev_node, 256, "[BEGIN]"); - prev_fi = null_frame_id; - prev_caller_fi = null_frame_id; - prev_addr = 0; - prev_sal.symtab = NULL; - prev_sal.pc = 0; - prev_sal.end = 0; - prev_nlp_real.addr = 0; - prev_nlp = &prev_nlp_real; - - /* Create first entry for function_list. */ - function_list = xmalloc (sizeof (struct function_list)); - function_list->next = NULL; - function_list->addr = 0; - function_list->fid = -1; - function_list_tail = function_list; - function_list_prev = function_list; - - /* Save the entries to fp and forward execute to the end of - record list. */ - record_list = &record_first; - while (1) - { - if (record_list->type == record_end) - { - int frame_diff = 0; - CORE_ADDR addr = regcache_read_pc (regcache); - - /* Check if the ADDR is stil in the same line with the - prev cycle. */ - if (prev_sal.symtab - && addr >= prev_sal.pc && addr < prev_sal.end) - goto exec; - sal = find_pc_line (addr, 0); - - if (record_pic_hide_nosource && !sal.symtab) - goto exec; - - /* Check if the inferior is in same frame with prev cycle. - Check both the current fi and caller fi because the last - addr of function is different with current function. */ - reinit_frame_cache (); - fi = get_frame_id (get_current_frame ()); - caller_fi = frame_unwind_caller_id (get_current_frame ()); - if (!frame_id_eq (prev_fi, fi) - && !frame_id_eq (prev_caller_fi, caller_fi)) - frame_diff = 1; - - if (set_record_pic_type == record_pic_line || frame_diff) - { - int is_return = 0; - struct node_list *nlp = NULL; - struct edge_list *elp = NULL; - char node[256]; - struct minimal_symbol *function; - - /* Get the node addr. */ - if (set_record_pic_type == record_pic_function) - { - /* Get the start addr of function. */ - addr = get_pc_function_start (addr); - if (addr == 0) - { - if (record_pic_hide_nofunction) - goto exec; - addr = regcache_read_pc (regcache); - } - } - else - { - /* Get the start addr of line. */ - if (sal.symtab) - addr = sal.pc; - } - - function = lookup_minimal_symbol_by_pc (addr); - if (!function && record_pic_hide_nofunction) - goto exec; - - if (frame_id_eq (fi, prev_caller_fi)) - is_return = 1; - - if (record_pic_hide_same) - { - /* Check if addr in node_list. */ - for (nlp = node_list; nlp; nlp = nlp->next) - { - if (nlp->addr == addr) - { - if (!is_return - || set_record_pic_type != record_pic_function) - nlp->count ++; - break; - } - } - - /* Check if prev_addr and addr in edge_list. */ - if (nlp) - { - for (elp = edge_list; elp; elp = elp->next) - { - if (elp->s->addr == prev_addr && elp->t->addr == addr) - { - elp->count ++; - break; - } - } - } - } - - if (!nlp) - { - struct node_list nl; - CORE_ADDR function_addr; - struct function_list *flp; - - nl.addr = addr; - if (frame_diff && sal.symtab) - nl.showall = 1; - else - nl.showall = 0; - nl.symtab = sal.symtab; - nl.line = sal.line; - nl.function = function; - - /* Get the fid of the nl. */ - if (set_record_pic_type != record_pic_function) - function_addr = get_pc_function_start (addr); - else - function_addr = addr; - if (function_list_prev->addr == function_addr) - nl.fid = function_list_prev->fid; - else - { - for (flp = function_list; flp; flp = flp->next) - { - if (flp->addr == function_addr) - { - nl.fid = flp->fid; - break; - } - } - if (flp == NULL) - { - /* Creat a new entry to function_list. */ - nl.fid = fid_count ++; - flp = xmalloc (sizeof (struct function_list)); - flp->addr = function_addr; - flp->fid = nl.fid; - flp->next = NULL; - function_list_tail->next = flp; - function_list_tail = flp; - } - function_list_prev = flp; - } - - if (record_pic_hide_same) - { - nlp = xmalloc (sizeof (struct node_list)); - *nlp = nl; - nlp->count = 1; - - /* Add node to node_list. */ - nlp->next = NULL; - if (node_list_tail) - node_list_tail->next = nlp; - if (node_list == NULL) - node_list = nlp; - node_list_tail = nlp; - } - else - { - /* Draw the node. */ - record_pic_node (node, 256, gdbarch, - set_record_pic_type, &nl); - snprintf (line, 256, "%s i:%s", node, - pulongest (record_list->u.end.insn_num)); - strcpy (node, line); - snprintf (line, 256, "node: {title: \"%s\" " - "vertical_order: %d}\n", - node, nl.fid); - record_pic_fputs (fp, line); - } - } - - if (!elp) - { - struct edge_list el; - - el.is_return = is_return; - el.frame_diff = frame_diff; - - if (record_pic_hide_same) - { - elp = xmalloc (sizeof (struct edge_list)); - *elp = el; - elp->s = prev_nlp; - elp->t = nlp; - elp->count = 1; - - /* Add edge to edge_list. */ - elp->next = NULL; - if (edge_list_tail) - edge_list_tail->next = elp; - if (edge_list == NULL) - edge_list = elp; - edge_list_tail = elp; - } - else - { - /* Draw the edge. */ - record_pic_edge (line, 256, &el, node, prev_node); - record_pic_fputs (fp, line); - record_pic_fputs (fp, " }\n"); - } - } - - if (record_pic_hide_same) - prev_nlp = nlp; - else - snprintf (prev_node, 256, "%s", node); - prev_addr = addr; - } - - prev_sal = sal; - prev_fi = fi; - prev_caller_fi = caller_fi; - } - -exec: - /* Execute entry. */ - record_exec_insn (regcache, gdbarch, record_list); - - if (record_list->next) - record_list = record_list->next; + target_call_history_range (begin, end, flags); + } + } else - break; - } + { + no_chunk (arg); - if (record_pic_hide_same) - { - struct node_list *nlp = NULL; - struct edge_list *elp = NULL; - char node[256]; - - for (nlp = node_list; nlp; nlp = nlp->next) - { - /* Draw the node. */ - record_pic_node (node, 256, gdbarch, set_record_pic_type, nlp); - snprintf (line, 256, "node: {title: \"%s c:%d\" " - "vertical_order: %d}\n", node, - nlp->count, nlp->fid); - record_pic_fputs (fp, line); + target_call_history_from (begin, size, flags); } - record_pic_node (node, 256, gdbarch, set_record_pic_type, edge_list->t); - snprintf (line, 256, - "edge: {color:red sourcename: \"[BEGIN]\" targetname: \"%s c:%d\"}\n", - node, edge_list->count); - record_pic_fputs (fp, line); - for (elp = edge_list->next; elp; elp = elp->next) - { - /* Draw the edge. */ - record_pic_node (prev_node, 256, gdbarch, set_record_pic_type, - elp->s); - snprintf (line, 256, "%s c:%d", prev_node, elp->s->count); - strcpy (prev_node, line); - record_pic_node (node, 256, gdbarch, set_record_pic_type, - elp->t); - snprintf (line, 256, "%s c:%d", node, elp->t->count); - strcpy (node, line); - record_pic_edge (line, 256, elp, node, prev_node); - record_pic_fputs (fp, line); - snprintf (line, 256, " label: \"c:%d\"}\n", elp->count); - record_pic_fputs (fp, line); - } + dont_repeat (); } +} - /* Write the last node. */ - snprintf (line, 256, "node: {title: \"[END]\" vertical_order: %d}\n", - fid_count); - record_pic_fputs (fp, line); - snprintf (line, 256, - "edge: {color:red sourcename: \"%s\" targetname: \"[END]\" }\n", - prev_node); - record_pic_fputs (fp, line); - - /* Write the tail. */ - record_pic_fputs (fp, "}\n"); +/* 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. */ - /* Reverse execute to cur_record_list. */ - while (1) +static void +validate_history_size (unsigned int *command_var, unsigned int *setting) +{ + if (*command_var != UINT_MAX && *command_var > INT_MAX) { - /* Check for beginning and end of log. */ - if (record_list == cur_record_list) - break; - - record_exec_insn (regcache, gdbarch, record_list); + unsigned int new_value = *command_var; - if (record_list->prev) - record_list = record_list->prev; + /* Restore previous value. */ + *command_var = *setting; + error (_("integer %u out of range"), new_value); } - do_cleanups (set_cleanups); - do_cleanups (old_cleanups); - - /* Succeeded. */ - printf_filtered (_("Saved file %s with execution log.\n"), - recfilename); + /* Commit new value. */ + *setting = *command_var; } -/* record_goto_insn -- rewind the record log (forward or backward, - depending on DIR) to the given entry, changing the program state - correspondingly. */ +/* 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_goto_insn (struct record_entry *entry, - enum exec_direction_kind dir) +set_record_insn_history_size (char *args, int from_tty, + struct cmd_list_element *c) { - 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); + validate_history_size (&record_insn_history_size_setshow_var, + &record_insn_history_size); } -/* "record goto" command. Argument is an instruction number, - as given by "info record". - - Rewinds the recording (forward or backward) to the given instruction. */ +/* 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 -cmd_record_goto (char *arg, int from_tty) +set_record_call_history_size (char *args, int from_tty, + struct cmd_list_element *c) { - 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); + validate_history_size (&record_call_history_size_setshow_var, + &record_call_history_size); } +/* Provide a prototype to silence -Wmissing-prototypes. */ +extern initialize_file_ftype _initialize_record; + void _initialize_record (void) { 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); - - add_setshow_zinteger_cmd ("record", no_class, &record_debug, - _("Set debugging of record/replay feature."), - _("Show debugging of record/replay feature."), - _("When enabled, debugging output for " - "record/replay feature is displayed."), - NULL, show_record_debug, &setdebuglist, - &showdebuglist); + add_setshow_zuinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _("When enabled, debugging output for " + "record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_setshow_uinteger_cmd ("instruction-history-size", no_class, + &record_insn_history_size_setshow_var, _("\ +Set number of instructions to print in \"record instruction-history\"."), _("\ +Show number of instructions to print in \"record instruction-history\"."), _("\ +A size of \"unlimited\" means unlimited instructions. The default is 10."), + set_record_insn_history_size, NULL, + &set_record_cmdlist, &show_record_cmdlist); + + add_setshow_uinteger_cmd ("function-call-history-size", no_class, + &record_call_history_size_setshow_var, _("\ +Set number of function to print in \"record function-call-history\"."), _("\ +Show number of functions to print in \"record function-call-history\"."), _("\ +A size of \"unlimited\" means unlimited lines. The default is 10."), + set_record_call_history_size, NULL, + &set_record_cmdlist, &show_record_cmdlist); c = add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), + _("Start recording."), &record_cmdlist, "record ", 0, &cmdlist); set_cmd_completer (c, filename_completer); @@ -3242,12 +771,6 @@ Default filename is 'gdb_record.'."), &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'."), - &record_cmdlist); - set_cmd_completer (c, filename_completer); - add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), &record_cmdlist); @@ -3259,100 +782,61 @@ Argument is filename. File must be created with 'record save'."), &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_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); - - /* For "record pic" command. */ - c = add_cmd ("pic", class_obscure, cmd_record_pic, - _("Save the execution log to a vcg file.\n\ -Argument is optional filename.\n\ -Default filename is 'gdb_record_.vcg'."), - &record_cmdlist); - set_cmd_completer (c, filename_completer); - add_prefix_cmd ("pic", class_support, set_record_pic_command, - _("Set record pic options"), &set_record_pic_cmdlist, - "set record pic ", 0, &set_record_cmdlist); - add_prefix_cmd ("pic", class_support, show_record_pic_command, - _("Show record pic options"), &show_record_pic_cmdlist, - "show record pic ", 0, &show_record_cmdlist); - add_setshow_enum_cmd ("type", no_class, - record_pic_enum, &set_record_pic_type, _("\ -Set the type of the nodes that record pic command saved."), _("\ -Show the type of the nodes that record pic command saved."), _("\ -When LINE, each node of vcg file that command record pic saved\n\ -will be a line of the inferior.\n\ -When FUNCTION, each node of vcg file that command record pic saved\n\ -will be a function of the inferior."), - NULL, NULL, - &set_record_pic_cmdlist, &show_record_pic_cmdlist); - add_setshow_boolean_cmd ("hide-nofunction", no_class, - &record_pic_hide_nofunction, _("\ -Set whether record pic command hide the nodes that don't have the function name."), _("\ -Show whether record pic command hide the nodes that don't have the function name."), _("\ -Default is ON.\n\ -When ON, record pic command will hide the nodes that don't have\n\ -the function name.\n\ -When OFF, record pic command will show the nodes that don't have\n\ -the function name."), - NULL, NULL, - &set_record_pic_cmdlist, &show_record_pic_cmdlist); - add_setshow_boolean_cmd ("hide-nosource", no_class, - &record_pic_hide_nosource, _("\ -Set whether record pic command hide the nodes that don't have the source message."), _("\ -Show whether record pic command hide the nodes that don't have the source message."), _("\ -Default is ON.\n\ -When ON, record pic command will hide the nodes that don't have\n\ -the source message.\n\ -When OFF, record pic command will show the nodes that don't have\n\ -the source message."), - NULL, NULL, - &set_record_pic_cmdlist, &show_record_pic_cmdlist); - add_setshow_boolean_cmd ("hide-sameaddr", no_class, - &record_pic_hide_same, _("\ -Set whether record pic command hide the nodes that have the same address node in vcg file."), _("\ -Show whether record pic command hide the nodes that have the same address node in vcg file."), _("\ -Default is ON.\n\ -When ON, record pic command will hide the nodes that have\n\ -the same address node in vcg file.\n\ -And record pic will show the execute count number of this line\n\ -in format \"c:number\".\n\ -When OFF, record pic command will show the nodes that have\n\ -the same address node in vcg file.\n\ -And record pic show the instruction number in format \"i:number\"\n\ -that \"record goto\" support."), - NULL, NULL, - &set_record_pic_cmdlist, &show_record_pic_cmdlist); + &record_goto_cmdlist, "record goto ", 1, &record_cmdlist); + + add_cmd ("begin", class_obscure, cmd_record_goto_begin, + _("Go to the beginning of the execution log."), + &record_goto_cmdlist); + add_alias_cmd ("start", "begin", class_obscure, 1, &record_goto_cmdlist); + + add_cmd ("end", class_obscure, cmd_record_goto_end, + _("Go to the end of the execution log."), + &record_goto_cmdlist); + + add_cmd ("instruction-history", class_obscure, cmd_record_insn_history, _("\ +Print disassembled instructions stored in the execution log.\n\ +With a /m modifier, source lines are included (if available).\n\ +With a /r modifier, raw instructions in hex are included.\n\ +With a /f modifier, function names are omitted.\n\ +With a /p modifier, current position markers are omitted.\n\ +With no argument, disassembles ten more instructions after the previous \ +disassembly.\n\ +\"record instruction-history -\" disassembles ten instructions before a \ +previous disassembly.\n\ +One argument specifies an instruction number as shown by 'info record', and \ +ten instructions are disassembled after that instruction.\n\ +Two arguments with comma between them specify starting and ending instruction \ +numbers to disassemble.\n\ +If the second argument is preceded by '+' or '-', it specifies the distance \ +from the first argument.\n\ +The number of instructions to disassemble can be defined with \"set record \ +instruction-history-size\"."), + &record_cmdlist); + + add_cmd ("function-call-history", class_obscure, cmd_record_call_history, _("\ +Prints the execution history at function granularity.\n\ +It prints one line for each sequence of instructions that belong to the same \ +function.\n\ +Without modifiers, it prints the function name.\n\ +With a /l modifier, the source file and line number range is included.\n\ +With a /i modifier, the instruction number range is included.\n\ +With a /c modifier, the output is indented based on the call stack depth.\n\ +With no argument, prints ten more lines after the previous ten-line print.\n\ +\"record function-call-history -\" prints ten lines before a previous ten-line \ +print.\n\ +One argument specifies a function number as shown by 'info record', and \ +ten lines are printed after that function.\n\ +Two arguments with comma between them specify a range of functions to print.\n\ +If the second argument is preceded by '+' or '-', it specifies the distance \ +from the first argument.\n\ +The number of functions to print can be defined with \"set record \ +function-call-history-size\"."), + &record_cmdlist); + + /* Sync command control variables. */ + record_insn_history_size_setshow_var = record_insn_history_size; + record_call_history_size_setshow_var = record_call_history_size; }